Oscarli commited on
Commit
e7aaeda
Β·
verified Β·
1 Parent(s): a7829b6

Upload 8 files

Browse files
Files changed (2) hide show
  1. app.py +136 -28
  2. static/index.html +75 -541
app.py CHANGED
@@ -1,11 +1,11 @@
1
  import os
2
  import httpx
3
  from fastapi import FastAPI, Request, HTTPException
4
- from fastapi.responses import HTMLResponse
5
  from fastapi.staticfiles import StaticFiles
 
6
 
7
  # --- Configuration ---
8
- # Get the API key from Hugging Face Secrets
9
  DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
10
  DEEPSEEK_ENDPOINT = "https://api.deepseek.com/v1/chat/completions"
11
 
@@ -16,24 +16,146 @@ app = FastAPI()
16
  client = httpx.AsyncClient()
17
 
18
  # --- Absolute Path Configuration ---
19
- # Get the absolute path of the directory where this file is located
20
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
21
- # Define the path to the static directory
22
  STATIC_DIR = os.path.join(BASE_DIR, "static")
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- # --- API Endpoint ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  @app.post("/call-deepseek")
27
  async def proxy_deepseek(request: Request):
28
  """
29
  This endpoint receives the request from our frontend (index.html),
30
  adds the secret API key, and forwards it to the DeepSeek API.
 
31
  """
32
- # Check if the API key is configured in HF Secrets
33
- print(f"[DEBUG] DEEPSEEK_API_KEY exists: {bool(DEEPSEEK_API_KEY)}")
34
- if DEEPSEEK_API_KEY:
35
- print(f"[DEBUG] API Key starts with: {DEEPSEEK_API_KEY[:10]}...")
36
-
37
  if not DEEPSEEK_API_KEY:
38
  print("[ERROR] DEEPSEEK_API_KEY is not set!")
39
  raise HTTPException(
@@ -42,31 +164,23 @@ async def proxy_deepseek(request: Request):
42
  )
43
 
44
  try:
45
- # Get the original JSON body from the frontend
46
  body = await request.json()
47
-
48
- # Prepare the authorization headers for DeepSeek
49
  headers = {
50
  "Content-Type": "application/json",
51
  "Authorization": f"Bearer {DEEPSEEK_API_KEY}"
52
  }
53
 
54
- # Make the asynchronous request to DeepSeek
55
  response = await client.post(
56
  DEEPSEEK_ENDPOINT,
57
- json=body, # Forward the body from the frontend
58
  headers=headers,
59
- timeout=300.0 # Set a 300-second timeout (5 minutes)
60
  )
61
 
62
- # Check if DeepSeek returned an error
63
  response.raise_for_status()
64
-
65
- # Return DeepSeek's successful response directly to the frontend
66
  return response.json()
67
 
68
  except httpx.HTTPStatusError as e:
69
- # Handle errors from the DeepSeek API
70
  error_msg = f"DeepSeek API Error: {e.response.status_code} - {e.response.text}"
71
  print(f"[ERROR] {error_msg}")
72
  raise HTTPException(
@@ -74,20 +188,15 @@ async def proxy_deepseek(request: Request):
74
  detail=error_msg
75
  )
76
  except Exception as e:
77
- # Handle any other unexpected errors
78
  error_msg = f"Internal server error: {type(e).__name__} - {str(e)}"
79
  print(f"[ERROR] {error_msg}")
80
- import traceback
81
  print(f"[TRACEBACK] {traceback.format_exc()}")
82
  raise HTTPException(
83
  status_code=500,
84
  detail=error_msg
85
  )
86
 
87
-
88
  # --- Static File Serving ---
89
- # Mount the 'static' directory to serve index.html and other assets
90
- # Use the absolute path to be safe
91
  app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
92
 
93
  @app.get("/")
@@ -96,11 +205,10 @@ async def read_root():
96
  Serves the main index.html file from the 'static' directory.
97
  """
98
  try:
99
- # Use the absolute path to be safe
100
  with open(os.path.join(STATIC_DIR, "index.html")) as f:
101
  return HTMLResponse(content=f.read(), status_code=200)
102
  except FileNotFoundError:
103
  return HTMLResponse(
104
- content="<h1>Error: index.html not found</h1><p>Ensure index.html is in a 'static' folder.</p>",
105
  status_code=404
106
- )
 
1
  import os
2
  import httpx
3
  from fastapi import FastAPI, Request, HTTPException
4
+ from fastapi.responses import HTMLResponse, JSONResponse
5
  from fastapi.staticfiles import StaticFiles
6
+ import traceback
7
 
8
  # --- Configuration ---
 
9
  DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
10
  DEEPSEEK_ENDPOINT = "https://api.deepseek.com/v1/chat/completions"
11
 
 
16
  client = httpx.AsyncClient()
17
 
18
  # --- Absolute Path Configuration ---
 
19
  BASE_DIR = os.path.dirname(os.path.abspath(__file__))
 
20
  STATIC_DIR = os.path.join(BASE_DIR, "static")
21
 
22
+ # --- Helper Function for DeepSeek API Call ---
23
+ async def call_deepseek_api(messages: list, model: str = "deepseek-chat", temperature: float = 0.7):
24
+ """
25
+ A helper function to make calls to the DeepSeek API.
26
+ """
27
+ if not DEEPSEEK_API_KEY:
28
+ print("[ERROR] DEEPSEEK_API_KEY is not set!")
29
+ raise HTTPException(
30
+ status_code=500,
31
+ detail="DEEPSEEK_API_KEY is not set on the server."
32
+ )
33
+
34
+ headers = {
35
+ "Content-Type": "application/json",
36
+ "Authorization": f"Bearer {DEEPSEEK_API_KEY}"
37
+ }
38
+
39
+ body = {
40
+ "model": model,
41
+ "messages": messages,
42
+ "temperature": temperature,
43
+ "stream": False
44
+ }
45
+
46
+ try:
47
+ response = await client.post(
48
+ DEEPSEEK_ENDPOINT,
49
+ json=body,
50
+ headers=headers,
51
+ timeout=300.0
52
+ )
53
+ response.raise_for_status()
54
+ data = response.json()
55
+ return data['choices'][0]['message']['content']
56
+ except httpx.HTTPStatusError as e:
57
+ error_msg = f"DeepSeek API Error: {e.response.status_code} - {e.response.text}"
58
+ print(f"[ERROR] {error_msg}")
59
+ raise HTTPException(
60
+ status_code=e.response.status_code,
61
+ detail=error_msg
62
+ )
63
+ except Exception as e:
64
+ error_msg = f"Internal server error in API call: {type(e).__name__} - {str(e)}"
65
+ print(f"[ERROR] {error_msg}")
66
+ print(f"[TRACEBACK] {traceback.format_exc()}")
67
+ raise HTTPException(
68
+ status_code=500,
69
+ detail=error_msg
70
+ )
71
 
72
+ # --- New AI Agent Endpoint ---
73
+ @app.post("/generate")
74
+ async def generate_content(request: Request):
75
+ """
76
+ This endpoint uses a two-step LLM process:
77
+ 1. Generate a high-quality prompt based on user data.
78
+ 2. Use that prompt to generate the final content.
79
+ """
80
+ try:
81
+ body = await request.json()
82
+ task = body.get("task")
83
+ data = body.get("data")
84
+
85
+ if not task or not data:
86
+ raise HTTPException(status_code=400, detail="Missing 'task' or 'data' in request body")
87
+
88
+ # Step 1: Generate a high-quality prompt for the main task
89
+ meta_system_prompt = "You are an expert prompt engineer. Your task is to create a detailed and effective 'user' prompt for another AI model, which is an expert career consultant. The generated prompt must guide the second AI to produce a comprehensive, high-quality response in the required format based on the user's raw data and task."
90
+
91
+ meta_user_prompt = f"""
92
+ I have the following task and user data. Create the perfect prompt for a career consultant AI to handle this.
93
+
94
+ **Task:** '{task}'
95
+
96
+ **User's Raw Data:**
97
+ ```json
98
+ {data}
99
+ ```
100
+
101
+ **Instructions for the prompt you will generate:**
102
+ - The prompt must be self-contained and include all necessary user data.
103
+ - It must clearly state the desired output format.
104
+ - For the 'resume' task, the format MUST be a single JSON object with two keys: "resume" and "analysis", both containing well-formed HTML.
105
+ - For all other tasks ('interview', 'learning_path', 'cover_letter', 'linkedin', 'salary'), the format MUST be well-formed HTML content directly.
106
+ - The tone of the prompt should be as if a user is asking an expert for help.
107
+ - Incorporate all the details from the user's data into the prompt naturally.
108
+ """
109
+
110
+ print(f"[INFO] Generating prompt for task: {task}")
111
+ generated_prompt = await call_deepseek_api(
112
+ messages=[
113
+ {"role": "system", "content": meta_system_prompt},
114
+ {"role": "user", "content": meta_user_prompt}
115
+ ],
116
+ temperature=0.3 # Lower temperature for more deterministic prompt generation
117
+ )
118
+ print(f"[DEBUG] Generated Prompt for 2nd LLM call:\n{generated_prompt}")
119
+
120
+ # Step 2: Use the generated prompt to get the final content
121
+ final_system_prompts = {
122
+ "resume": "You are a professional career consultant and resume expert. Please strictly follow the JSON format for the response, ensuring the HTML is well-formed and professional.",
123
+ "interview": "You are an experienced interviewer and career mentor. Provide practical, professional interview preparation materials in well-formed HTML.",
124
+ "learning_path": "You are an experienced career mentor and learning planner. Create a personalized, actionable learning path in well-formed HTML.",
125
+ "cover_letter": "You are an expert cover letter writer. Write a professional, persuasive, and personalized cover letter in well-formed HTML.",
126
+ "linkedin": "You are a LinkedIn optimization expert. Create a professional and attractive LinkedIn profile optimization plan in well-formed HTML.",
127
+ "salary": "You are a salary negotiation expert and market analyst. Provide an accurate, practical salary analysis and negotiation advice in well-formed HTML."
128
+ }
129
+ final_system_prompt = final_system_prompts.get(task, "You are a helpful AI career assistant.")
130
+
131
+ print(f"[INFO] Generating final content for task: {task}")
132
+ final_content = await call_deepseek_api(
133
+ messages=[
134
+ {"role": "system", "content": final_system_prompt},
135
+ {"role": "user", "content": generated_prompt}
136
+ ]
137
+ )
138
+
139
+ return JSONResponse(content={"content": final_content})
140
+
141
+ except Exception as e:
142
+ error_msg = f"Error in /generate endpoint: {type(e).__name__} - {str(e)}"
143
+ print(f"[ERROR] {error_msg}")
144
+ print(f"[TRACEBACK] {traceback.format_exc()}")
145
+ raise HTTPException(
146
+ status_code=500,
147
+ detail=error_msg
148
+ )
149
+
150
+
151
+ # --- Original API Endpoint (Proxy) ---
152
  @app.post("/call-deepseek")
153
  async def proxy_deepseek(request: Request):
154
  """
155
  This endpoint receives the request from our frontend (index.html),
156
  adds the secret API key, and forwards it to the DeepSeek API.
157
+ This is the original proxy endpoint and will be replaced by the /generate endpoint logic.
158
  """
 
 
 
 
 
159
  if not DEEPSEEK_API_KEY:
160
  print("[ERROR] DEEPSEEK_API_KEY is not set!")
161
  raise HTTPException(
 
164
  )
165
 
166
  try:
 
167
  body = await request.json()
 
 
168
  headers = {
169
  "Content-Type": "application/json",
170
  "Authorization": f"Bearer {DEEPSEEK_API_KEY}"
171
  }
172
 
 
173
  response = await client.post(
174
  DEEPSEEK_ENDPOINT,
175
+ json=body,
176
  headers=headers,
177
+ timeout=300.0
178
  )
179
 
 
180
  response.raise_for_status()
 
 
181
  return response.json()
182
 
183
  except httpx.HTTPStatusError as e:
 
184
  error_msg = f"DeepSeek API Error: {e.response.status_code} - {e.response.text}"
185
  print(f"[ERROR] {error_msg}")
186
  raise HTTPException(
 
188
  detail=error_msg
189
  )
190
  except Exception as e:
 
191
  error_msg = f"Internal server error: {type(e).__name__} - {str(e)}"
192
  print(f"[ERROR] {error_msg}")
 
193
  print(f"[TRACEBACK] {traceback.format_exc()}")
194
  raise HTTPException(
195
  status_code=500,
196
  detail=error_msg
197
  )
198
 
 
199
  # --- Static File Serving ---
 
 
200
  app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
201
 
202
  @app.get("/")
 
205
  Serves the main index.html file from the 'static' directory.
206
  """
207
  try:
 
208
  with open(os.path.join(STATIC_DIR, "index.html")) as f:
209
  return HTMLResponse(content=f.read(), status_code=200)
210
  except FileNotFoundError:
211
  return HTMLResponse(
212
+ content="<h1>Error: index.html not found</h1><p>Ensure index.html is in a 'static' folder.",
213
  status_code=404
214
+ )
static/index.html CHANGED
@@ -522,102 +522,6 @@
522
  </div>
523
 
524
  <script>
525
- // Career Database - Real data for all functionalities
526
- const careerDatabase = {
527
- skillsRoadmaps: {
528
- 'AI Engineer': {
529
- foundation: ['Python Programming', 'Linear Algebra', 'Statistics & Probability', 'Data Structures'],
530
- intermediate: ['Machine Learning Fundamentals', 'Deep Learning', 'Data Preprocessing', 'Model Evaluation'],
531
- advanced: ['Neural Networks', 'Natural Language Processing', 'Computer Vision', 'Reinforcement Learning'],
532
- tools: ['TensorFlow', 'PyTorch', 'scikit-learn', 'Keras', 'OpenCV'],
533
- projects: ['Sentiment Analysis Model', 'Image Classification System', 'Recommendation Engine']
534
- },
535
- 'Full Stack Developer': {
536
- foundation: ['HTML/CSS/JavaScript', 'Git Version Control', 'Basic Algorithms', 'Responsive Design'],
537
- intermediate: ['React/Vue.js', 'Node.js/Express', 'Database Design', 'RESTful APIs'],
538
- advanced: ['System Design', 'DevOps & CI/CD', 'Cloud Services (AWS/Azure)', 'Microservices'],
539
- tools: ['Docker', 'MongoDB/PostgreSQL', 'AWS Services', 'Jenkins'],
540
- projects: ['E-commerce Platform', 'Social Media App', 'Real-time Chat Application']
541
- },
542
- 'Data Scientist': {
543
- foundation: ['Python/R Programming', 'SQL Databases', 'Statistics', 'Data Visualization'],
544
- intermediate: ['Machine Learning', 'Experimental Design', 'Feature Engineering', 'Data Wrangling'],
545
- advanced: ['Big Data Technologies', 'Advanced ML Models', 'Business Intelligence', 'MLOps'],
546
- tools: ['Pandas/NumPy', 'Tableau/PowerBI', 'Apache Spark', 'Jupyter Notebooks'],
547
- projects: ['Predictive Analytics Model', 'Customer Segmentation', 'Sales Forecasting']
548
- },
549
- 'Product Manager': {
550
- foundation: ['Market Research', 'User Stories', 'Agile Methodology', 'Basic Analytics'],
551
- intermediate: ['Product Strategy', 'Roadmap Planning', 'Stakeholder Management', 'A/B Testing'],
552
- advanced: ['Go-to-Market Strategy', 'Product Metrics', 'Team Leadership', 'Business Case Development'],
553
- tools: ['Jira/Asana', 'Figma', 'Google Analytics', 'SQL for PMs'],
554
- projects: ['Product Launch Plan', 'Feature Prioritization Framework', 'User Research Report']
555
- }
556
- },
557
-
558
- interviewQuestions: {
559
- technical: {
560
- entry: [
561
- "Explain the difference between let, const, and var in JavaScript",
562
- "What is a REST API and how does it work?",
563
- "Describe your experience with version control systems like Git",
564
- "How would you approach debugging a software issue?"
565
- ],
566
- mid: [
567
- "Explain system design principles for a scalable application",
568
- "How do you ensure code quality and maintainability?",
569
- "Describe your experience with database optimization",
570
- "What's your approach to testing and test-driven development?"
571
- ],
572
- senior: [
573
- "Design a system that handles 1 million concurrent users",
574
- "How do you mentor junior developers and improve team processes?",
575
- "Explain your strategy for technical debt management",
576
- "Describe a complex technical challenge you solved and your approach"
577
- ],
578
- lead: [
579
- "How do you align technical strategy with business goals?",
580
- "Describe your experience building and leading engineering teams",
581
- "What's your approach to incident management and post-mortems?",
582
- "How do you handle technical disagreements within your team?"
583
- ]
584
- },
585
- behavioral: [
586
- "Tell me about a time you faced a significant challenge and how you overcame it",
587
- "Describe a situation where you had to work with a difficult team member",
588
- "How do you handle tight deadlines and competing priorities?",
589
- "Share an example of a project failure and what you learned from it"
590
- ]
591
- },
592
-
593
- salaryData: {
594
- 'Software Engineer': { base: 120000, bonus: 0.15, equity: 0.10 },
595
- 'Senior Software Engineer': { base: 150000, bonus: 0.20, equity: 0.15 },
596
- 'Data Scientist': { base: 130000, bonus: 0.15, equity: 0.12 },
597
- 'AI Engineer': { base: 160000, bonus: 0.20, equity: 0.18 },
598
- 'Product Manager': { base: 140000, bonus: 0.25, equity: 0.15 },
599
- 'Full Stack Developer': { base: 125000, bonus: 0.15, equity: 0.10 }
600
- },
601
-
602
- locationMultipliers: {
603
- 'San Francisco, CA': 1.4,
604
- 'New York, NY': 1.35,
605
- 'Seattle, WA': 1.25,
606
- 'Boston, MA': 1.2,
607
- 'Austin, TX': 1.1,
608
- 'Remote': 0.95,
609
- 'Default': 1.0
610
- },
611
-
612
- companyMultipliers: {
613
- 'startup': { base: 0.9, equity: 2.0 },
614
- 'small': { base: 1.0, equity: 1.5 },
615
- 'medium': { base: 1.1, equity: 1.2 },
616
- 'large': { base: 1.2, equity: 1.0 },
617
- 'enterprise': { base: 1.3, equity: 0.8 }
618
- }
619
- };
620
-
621
  // Tab navigation
622
  function openTab(tabName) {
623
  const tabContents = document.getElementsByClassName('tab-content');
@@ -632,456 +536,133 @@
632
  event.currentTarget.classList.add('active');
633
  }
634
 
635
- // 1. RESUME OPTIMIZER - ACTUAL FUNCTIONALITY
636
  async function generateResume() {
637
- const name = document.getElementById('name').value || 'Your Name';
638
- const currentRole = document.getElementById('current-role').value || 'Professional';
639
- const experience = document.getElementById('experience').value || '3';
640
- const skills = document.getElementById('skills').value || 'Various skills';
641
- const jobTitle = document.getElementById('job-title').value || 'Target Role';
642
- const company = document.getElementById('company-name').value || 'Target Company';
643
- const jobDescription = document.getElementById('job-description').value;
644
-
645
- // Show loading state
 
646
  const resumeOutput = document.getElementById('resume-output');
647
  const analysisOutput = document.getElementById('analysis-output');
648
- resumeOutput.innerHTML = '<p>πŸš€ Generating AI-Optimized Resume...</p>';
649
  analysisOutput.innerHTML = '<p>πŸ“Š Analyzing your profile against the job...</p>';
650
 
651
- // Construct the prompt for the LLM
652
- const prompt = `Based on the following information, generate an optimized resume and a matching analysis:
653
-
654
- **User Profile:**
655
- - Name: ${name}
656
- - Current Position: ${currentRole}
657
- - Years of Experience: ${experience} years
658
- - Skills: ${skills}
659
-
660
- **Target Position:**
661
- - Job Title: ${jobTitle}
662
- - Company Name: ${company}
663
- - Job Description: ${jobDescription}
664
-
665
- Please return a JSON object strictly containing two keys:
666
- 1. "resume" - The complete resume content (in HTML format), including:
667
- - Professional Summary (highlighting the match with the position)
668
- - Core Skills (sorted by importance)
669
- - Work Experience (can generate examples based on common experience, emphasizing achievements relevant to the target position)
670
- - Education
671
-
672
- 2. "analysis" - The matching analysis (in HTML format), including:
673
- - A match score (0-100%) displayed in a styled div
674
- - A list of matched skills
675
- - 3-5 specific suggestions for improvement
676
-
677
- JSON format example:
678
- {
679
- "resume": "<h1>Name</h1><h2>Professional Summary</h2><p>...</p>",
680
- "analysis": "<div class='match-score' style='...'>85%</div><h3>Matched Skills</h3>..."
681
- }`;
682
-
683
- const systemPrompt = 'You are a professional career consultant and resume expert. Please strictly follow the JSON format for the response, ensuring the HTML is well-formed and professional.';
684
-
685
  try {
686
- // Call the LLM API
687
- const aiReply = await callLLM(prompt, systemPrompt);
688
  const llmResponse = parseAIResponse(aiReply);
689
 
690
  if (llmResponse && llmResponse.resume && llmResponse.analysis) {
691
  resumeOutput.innerHTML = llmResponse.resume;
692
  analysisOutput.innerHTML = llmResponse.analysis;
693
  } else {
694
- // ε¦‚ζžœθ§£ζžε€±θ΄₯οΌŒη›΄ζŽ₯显瀺 AI ηš„ε›žε€
695
  resumeOutput.innerHTML = `<div style="line-height: 1.6;">${aiReply.replace(/\n/g, '<br>')}</div>`;
696
  analysisOutput.innerHTML = '<p>βœ… Resume generated (format may need adjustment)</p>';
697
  }
698
  } catch (error) {
699
- resumeOutput.innerHTML = '<p style="color: red;">❌ Generation failed, please check API configuration</p>';
700
  analysisOutput.innerHTML = '<p style="color: red;">❌ Generation failed</p>';
701
  }
702
  }
703
 
704
- // 2. INTERVIEW COACH - AI-POWERED FUNCTIONALITY
705
  async function generateInterviewQuestions() {
706
- const role = document.getElementById('interview-role').value || 'Developer';
707
- const level = document.getElementById('interview-level').value;
708
- const skills = document.getElementById('interview-skills').value || 'Relevant Skills';
709
-
710
- const levelText = {
711
- 'entry': 'Entry Level',
712
- 'mid': 'Mid Level',
713
- 'senior': 'Senior Level',
714
- 'lead': 'Lead/Manager'
715
- }[level];
716
-
717
  const outputDiv = document.getElementById('interview-output');
718
- outputDiv.innerHTML = '<p>πŸ€” AI is generating customized interview questions for you...</p>';
719
-
720
- const prompt = `As a senior interviewer and career consultant, generate comprehensive interview preparation materials for the following position:
721
-
722
- **Position Information:**
723
- - Position: ${role}
724
- - Level: ${levelText}
725
- - Key Skills: ${skills}
726
-
727
- Please generate the following content (using HTML format):
728
-
729
- 1. **Technical Interview Questions** (8-10 questions)
730
- - Technical depth appropriate for the position level
731
- - Covering the listed key skills
732
- - Including system design, algorithms, best practices, etc.
733
-
734
- 2. **Behavioral Interview Questions** (5-6 questions)
735
- - Questions suitable for the STAR method
736
- - Targeting leadership/collaboration skills for this level
737
- - Scenarios for conflict resolution and challenges
738
-
739
- 3. **For each question, provide:**
740
- - The question itself
741
- - The competencies the interviewer wants to assess
742
- - Answering strategy/key points (brief)
743
-
744
- 4. **Interview Preparation Advice**
745
- - Key preparation points for this position
746
- - Explanation of the STAR method
747
- - 3-5 practical tips
748
-
749
- Please use the following HTML structure:
750
- - Use <div class="skill-category"> to wrap each major category
751
- - Use <div class="question-item"> to wrap each question
752
- - Use <div class="tips"> to wrap preparation advice`;
753
-
754
- const systemPrompt = 'You are an experienced interviewer and career mentor, skilled at designing in-depth interview questions. Please provide practical, professional interview preparation materials.';
755
 
756
  try {
757
- const aiReply = await callLLM(prompt, systemPrompt);
758
- // η›΄ζŽ₯使用 AI θΏ”ε›žηš„ HTML
759
  outputDiv.innerHTML = aiReply;
760
  } catch (error) {
761
- outputDiv.innerHTML = '<p style="color: red;">❌ Generation failed, please check API configuration</p>';
762
  }
763
  }
764
 
765
- // 3. LEARNING PATH - AI-POWERED FUNCTIONALITY
766
  async function generateLearningPath() {
767
- const currentSkills = document.getElementById('current-skills').value || 'Basic Skills';
768
- const targetRole = document.getElementById('target-role-learning').value || 'Full Stack Developer';
769
- const timeline = parseInt(document.getElementById('timeline').value);
770
-
 
771
  const outputDiv = document.getElementById('learning-output');
772
- outputDiv.innerHTML = '<p>πŸ“š AI is planning a personalized learning path for you...</p>';
773
-
774
- const prompt = `As a senior career mentor and skill development expert, create a detailed learning path for the following situation:
775
-
776
- **Current Status:**
777
- - Existing Skills: ${currentSkills}
778
- - Target Position: ${targetRole}
779
- - Learning Timeline: ${timeline} months
780
-
781
- Please generate a detailed learning path plan (in HTML format), including:
782
-
783
- 1. **Skill Gap Analysis**
784
- - Skills already possessed (mark with βœ…)
785
- - Skills that need to be learned (mark with πŸ“š)
786
- - Prioritization of skills
787
-
788
- 2. **Phased Learning Plan**
789
- Divided into 3 phases over ${timeline} months:
790
- - **Foundation Phase** (first ${Math.floor(timeline/3)} months): Essential basic knowledge and tools
791
- - **Intermediate Phase** (middle ${Math.floor(timeline/3)} months): Deepening core skills
792
- - **Advanced Phase** (last ${Math.ceil(timeline/3)} months): Advanced skills and project practice
793
-
794
- Each phase includes:
795
- - Specific learning content
796
- - Recommended types of learning resources
797
- - Time allocation suggestions
798
-
799
- 3. **Practical Project Suggestions**
800
- - 3-5 progressive projects
801
- - Skill application points for each project
802
- - Increasing project difficulty
803
-
804
- 4. **Learning Resource Recommendations**
805
- - Online course platforms
806
- - Book recommendations
807
- - Practice platforms
808
-
809
- 5. **Weekly Learning Plan**
810
- - Suggested weekly study hours
811
- - Learning methods (theory vs. practice ratio)
812
- - Milestone checkpoints
813
-
814
- Please use the following HTML structure:
815
- - <div class="skill-category"> to wrap each learning phase
816
- - Use colored divs to mark skill status: #d1fae5 for mastered, #fef3c7 for to-learn
817
- - <div class="tips"> to wrap learning advice and resources`;
818
-
819
- const systemPrompt = 'You are an experienced career mentor and learning planner, skilled at creating personalized, actionable learning paths. Please ensure the advice is specific and actionable.';
820
 
821
  try {
822
- const aiReply = await callLLM(prompt, systemPrompt);
823
  outputDiv.innerHTML = aiReply;
824
  } catch (error) {
825
- outputDiv.innerHTML = '<p style="color: red;">❌ Generation failed, please check API configuration</p>';
826
  }
827
  }
828
 
829
- // 4. COVER LETTER GENERATOR - AI-POWERED FUNCTIONALITY
830
  async function generateCoverLetter() {
831
- const company = document.getElementById('cl-company').value || 'Target Company';
832
- const role = document.getElementById('cl-role').value || 'Target Role';
833
- const achievement = document.getElementById('cl-highlight').value || 'My work experience and achievements';
834
- const tone = document.getElementById('cl-tone').value;
835
-
836
- const toneText = {
837
- 'professional': 'Professional',
838
- 'enthusiastic': 'Enthusiastic',
839
- 'formal': 'Formal'
840
- }[tone];
841
-
842
  const outputDiv = document.getElementById('coverletter-output');
843
- outputDiv.innerHTML = '<p>✍️ AI is writing a personalized cover letter for you...</p>';
844
-
845
- const prompt = `As a professional cover letter writing expert, write a high-quality cover letter for the following job application scenario:
846
-
847
- **Application Information:**
848
- - Target Company: ${company}
849
- - Position Applied For: ${role}
850
- - Core Achievement: ${achievement}
851
- - Writing Style: ${toneText}
852
-
853
- Please generate a complete cover letter (in HTML format), including:
854
-
855
- 1. **Salutation** (choose an appropriate opening based on the style)
856
-
857
- 2. **Opening Paragraph**
858
- - Express interest in the position
859
- - Briefly explain why you are applying
860
- - Reflect the ${toneText} style
861
-
862
- 3. **Body Paragraphs** (2-3 paragraphs)
863
- - First paragraph: Elaborate on the core achievement, supported by specific data and results
864
- - Second paragraph: Explain why you are a good fit for ${company} and ${role}
865
- - Third paragraph (optional): Show your knowledge and appreciation of the company
866
-
867
- 4. **Closing Paragraph**
868
- - Reiterate your interest and value
869
- - Politely request an interview opportunity
870
- - Thank the reader
871
-
872
- 5. **Signature**
873
- - Include a polite closing
874
- - Placeholders for [Name], [Phone], [Email]
875
-
876
- 6. **Customization Suggestions**
877
- After the cover letter, add a <div class="tips"> containing 3-5 specific optimization suggestions
878
-
879
- Note:
880
- - Use <p> tags for paragraphs
881
- - Line height 1.8, font Arial
882
- - Maintain a consistent ${toneText} tone
883
- - Avoid being too generic, be as specific as possible
884
- - Keep the length moderate (300-400 words)`;
885
-
886
- const systemPrompt = 'You are an experienced career consultant and expert cover letter writer. Please write a professional, persuasive, and personalized cover letter.';
887
 
888
  try {
889
- const aiReply = await callLLM(prompt, systemPrompt);
890
  outputDiv.innerHTML = aiReply;
891
  } catch (error) {
892
- outputDiv.innerHTML = '<p style="color: red;">❌ Generation failed, please check API configuration</p>';
893
  }
894
  }
895
 
896
- // 5. LINKEDIN OPTIMIZER - AI-POWERED FUNCTIONALITY
897
  async function optimizeLinkedIn() {
898
- const headline = document.getElementById('li-headline').value || 'Current Professional';
899
- const about = document.getElementById('li-about').value || 'Experienced professional seeking new opportunities';
900
- const target = document.getElementById('li-target').value || 'Technology field';
901
-
 
902
  const outputDiv = document.getElementById('linkedin-output');
903
- outputDiv.innerHTML = '<p>πŸ’Ό AI is optimizing your LinkedIn profile...</p>';
904
-
905
- const prompt = `As a LinkedIn profile optimization expert and personal branding consultant, optimize the following LinkedIn profile:
906
-
907
- **Current Profile:**
908
- - Current Headline: ${headline}
909
- - Current About Section: ${about}
910
- - Target Industry/Roles: ${target}
911
-
912
- Please generate an optimized LinkedIn profile (in HTML format), including:
913
-
914
- 1. **Optimized Headline**
915
- - Include job title, professional field, key skills
916
- - Use | or β€’ as separators
917
- - Make full use of the 220-character limit
918
- - Include search keywords
919
- - Display in <div class="question-item" style="font-size: 1.1em; font-weight: bold;">
920
-
921
- 2. **Optimized About Section**
922
- - Opening: An engaging self-introduction
923
- - Core Competencies: 3-5 professional areas
924
- - Skills List: Categorized (core competencies, technical skills, etc.)
925
- - Career Highlights: 2-3 quantifiable achievements
926
- - Closing: Open to opportunities and contact information
927
- - Use <p> and <strong> tags
928
- - Line height 1.6
929
-
930
- 3. **Recommended Hashtags**
931
- - 8-10 relevant industry hashtags
932
- - Format: #hashtag
933
-
934
- 4. **Optimization Suggestions**
935
- Use <div class="tips"> to include:
936
- - Suggestions for optimizing the skills section
937
- - Content publishing strategy
938
- - Networking expansion advice
939
- - 5-7 practical tips
940
-
941
- Note:
942
- - Emphasize keywords related to ${target}
943
- - Use industry-specific professional terminology
944
- - Reflect personal brand and value
945
- - SEO-friendly (to be easily found by search)
946
- `;
947
-
948
- const systemPrompt = 'You are a LinkedIn optimization expert and personal branding consultant, skilled at creating professional and attractive LinkedIn profiles.';
949
 
950
  try {
951
- const aiReply = await callLLM(prompt, systemPrompt);
952
  outputDiv.innerHTML = aiReply;
953
  } catch (error) {
954
- outputDiv.innerHTML = '<p style="color: red;">❌ Generation failed, please check API configuration</p>';
955
  }
956
  }
957
 
958
- // 6. SALARY INTELLIGENCE - AI-ENHANCED FUNCTIONALITY
959
  async function analyzeSalary() {
960
- const role = document.getElementById('salary-role').value || 'Software Engineer';
961
- const location = document.getElementById('salary-location').value || 'San Francisco, CA';
962
- const experience = parseInt(document.getElementById('salary-experience').value) || 3;
963
- const companySize = document.getElementById('salary-company').value;
964
-
 
965
  const outputDiv = document.getElementById('salary-output');
966
- outputDiv.innerHTML = '<p>πŸ’° AI is analyzing market salary data...</p>';
967
-
968
- const companySizeText = {
969
- 'startup': 'Startup (1-50 employees)',
970
- 'small': 'Small (51-200 employees)',
971
- 'medium': 'Medium (201-1000 employees)',
972
- 'large': 'Large (1001-5000 employees)',
973
- 'enterprise': 'Enterprise (5000+ employees)'
974
- }[companySize];
975
-
976
- const prompt = `As a salary negotiation expert and market analyst, analyze the salary for the following position:
977
-
978
- **Position Information:**
979
- - Job Title: ${role}
980
- - Location: ${location}
981
- - Years of Experience: ${experience} years
982
- - Company Size: ${companySizeText}
983
-
984
- Please provide a detailed salary analysis report (in HTML format), including:
985
-
986
- 1. **Salary Estimation**
987
- Create 3 <div class="salary-item"> cards showing:
988
- - Base Salary (annual range: low-average-high)
989
- - Annual Bonus (as a percentage of base salary)
990
- - Equity/RSUs (estimated value vesting over 4 years)
991
-
992
- Each card format:
993
- \
994
- <div class="salary-item">
995
- <div class="salary-value">$XXX,XXX</div>
996
- <div>Item Name</div>
997
- <small>Additional Info</small>
998
- </div>
999
- \
1000
-
1001
- 2. **Total Compensation**
1002
- Display using the following style:
1003
- \
1004
- <div class="match-score" style="background: linear-gradient(135deg, #f59e0b, #fbbf24);">
1005
- Total Compensation: $XXX,XXX/year
1006
- </div>
1007
- \
1008
-
1009
- 3. **Market Positioning Analysis**
1010
- Include in a <div class="skill-category">:
1011
- - Geographic Impact (${location} compared to national average)
1012
- - Experience Premium (${experience} years vs. new graduate)
1013
- - Company Size Impact (trade-off between base salary vs. equity)
1014
- - Industry Comparison
1015
-
1016
- 4. **Negotiation Strategy**
1017
- Provide in a <div class="tips">:
1018
- - Target salary range (considering a 10-15% negotiation buffer)
1019
- - Equity negotiation advice
1020
- - Benefits considerations
1021
- - 5-7 practical negotiation tips
1022
- - Market trend insights
1023
-
1024
- Notes:
1025
- - All amounts should use USD ($) and thousand separators
1026
- - Salary data should be current for 2024-2025 market conditions
1027
- - Consider the cost of living in ${location}
1028
- - Differentiate salary structures for different company sizes
1029
- - The advice given should be specific and actionable
1030
-
1031
- Add a title before the salary cards:
1032
- <h4>πŸ’° Salary Analysis: ${role}</h4>
1033
- <div class="salary-breakdown">
1034
- [three salary-item cards]
1035
- </div>`;
1036
-
1037
- const systemPrompt = 'You are a salary negotiation expert and market analyst, specializing in the tech industry compensation structure. Please provide accurate, practical salary analysis and negotiation advice.';
1038
-
1039
 
1040
  try {
1041
- const aiReply = await callLLM(prompt, systemPrompt);
1042
  outputDiv.innerHTML = aiReply;
1043
  } catch (error) {
1044
- outputDiv.innerHTML = '<p style="color: red;">❌ Generation failed, please check API configuration</p>';
1045
  }
1046
  }
1047
 
1048
  // UTILITY FUNCTIONS
1049
- function extractKeywords(text) {
1050
- if (!text) return ['various', 'skills', 'experience'];
1051
- const commonWords = ['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'];
1052
- return text.toLowerCase()
1053
- .replace(/[^\w\s]/g, '')
1054
- .split(/\s+/)
1055
- .filter(word => word.length > 3 && !commonWords.includes(word))
1056
- .slice(0, 15);
1057
- }
1058
-
1059
- function generateProfessionalSummary(name, currentRole, experience, jobTitle, company, matchedSkills) {
1060
- return `Accomplished ${currentRole} with ${experience} years of experience seeking ${jobTitle} position at ${company}. Proven expertise in ${matchedSkills.slice(0,3).join(', ')} with track record of delivering innovative solutions and driving business growth. Strong background in full project lifecycle management and cross-functional collaboration.`;
1061
- }
1062
-
1063
- function getSkillDescription(skill) {
1064
- const descriptions = {
1065
- 'python': 'Python programming and development',
1066
- 'javascript': 'JavaScript and modern frameworks',
1067
- 'react': 'React.js and frontend development',
1068
- 'node': 'Node.js and backend services',
1069
- 'aws': 'Amazon Web Services cloud platform',
1070
- 'docker': 'Containerization and DevOps',
1071
- 'machine learning': 'ML algorithms and model development',
1072
- 'data analysis': 'Data processing and insights generation',
1073
- 'project management': 'Project planning and execution',
1074
- 'leadership': 'Team leadership and mentorship'
1075
- };
1076
- return descriptions[skill.toLowerCase()] || 'relevant professional skill';
1077
- }
1078
-
1079
- function getScoreColor(score) {
1080
- if (score >= 80) return 'linear-gradient(135deg, #10b981, #34d399)';
1081
- if (score >= 60) return 'linear-gradient(135deg, #f59e0b, #fbbf24)';
1082
- return 'linear-gradient(135deg, #ef4444, #f87171)';
1083
- }
1084
-
1085
  function exportToPDF() {
1086
  const element = document.getElementById('resume-output');
1087
  const opt = {
@@ -1102,87 +683,46 @@ Add a title before the salary cards:
1102
  });
1103
  }
1104
 
1105
- // API CONFIGURATION - DeepSeek only
1106
- const API_CONFIG = {
1107
- model: 'deepseek-chat'
1108
- };
1109
-
1110
- // LLM API CALL - REAL IMPLEMENTATION
1111
- async function callLLM(prompt, systemPrompt = 'You are a professional career consultant and AI assistant. Please provide valuable and practical advice.') {
1112
- console.log("Sending request to HF Space backend...");
1113
-
1114
  const statusIcon = document.getElementById('api-status');
1115
-
1116
- // Set status to loading
1117
  if (statusIcon) statusIcon.textContent = 'πŸ”„';
1118
 
1119
  try {
1120
- // This is the request body DeepSeek expects.
1121
- // We build it here and send it to our backend proxy.
1122
- const requestBody = {
1123
- model: API_CONFIG.model,
1124
- messages: [
1125
- {
1126
- role: 'system',
1127
- content: systemPrompt
1128
- },
1129
- {
1130
- role: 'user',
1131
- content: prompt
1132
- }
1133
- ],
1134
- temperature: 0.7,
1135
- stream: false
1136
- };
1137
-
1138
- // Send the request to OUR backend endpoint '/call-deepseek'
1139
- const response = await fetch('/call-deepseek', {
1140
  method: 'POST',
1141
- headers: {
1142
- 'Content-Type': 'application/json',
1143
- },
1144
- body: JSON.stringify(requestBody) // Send the JSON payload
1145
  });
1146
 
1147
  if (!response.ok) {
1148
- // Get error detail from our FastAPI backend
1149
- const errorData = await response.json().catch(() => ({}));
1150
  if (statusIcon) statusIcon.textContent = '❌';
1151
- throw new Error(errorData.detail || `Server Error ${response.status}: ${response.statusText}`);
1152
  }
1153
 
1154
- // The 'data' is the full response from DeepSeek,
1155
- // proxied through our backend
1156
- const data = await response.json();
1157
-
1158
  if (statusIcon) statusIcon.textContent = 'βœ…';
1159
 
1160
- // Extract the AI reply as before
1161
- let aiReply = data.choices[0].message.content;
1162
- console.log("AI reply received, length:", aiReply.length);
1163
-
1164
- return aiReply;
1165
 
1166
  } catch (error) {
1167
  if (statusIcon) statusIcon.textContent = '❌';
1168
- console.error("API call error:", error);
1169
-
1170
- alert(`❌ API call failed: ${error.message}\n\nPlease check the server status. If this persists, the app owner may need to check the Hugging Face logs.`);
1171
-
1172
  throw error;
1173
  }
1174
  }
1175
 
1176
- // 解析 JSON ε“εΊ”ηš„θΎ…εŠ©ε‡½ζ•°
1177
  function parseAIResponse(aiReply) {
1178
  try {
1179
- // Clean up possible markdown code block markers
1180
  let cleaned = aiReply.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
1181
  return JSON.parse(cleaned);
1182
  } catch (parseError) {
1183
  console.warn("JSON parsing failed, trying to extract:", parseError);
1184
-
1185
- // Try to extract the JSON object
1186
  const jsonMatch = aiReply.match(/\{[\s\S]*\}/);
1187
  if (jsonMatch) {
1188
  try {
@@ -1191,20 +731,15 @@ Add a title before the salary cards:
1191
  console.error("Secondary parsing failed:", e);
1192
  }
1193
  }
1194
-
1195
- // Return the original text
1196
  return null;
1197
  }
1198
  }
1199
 
1200
  // Initialize with sample data
1201
  document.addEventListener('DOMContentLoaded', function() {
1202
-
1203
- // Set status to 'Ready'
1204
  const statusIcon = document.getElementById('api-status');
1205
  if (statusIcon) statusIcon.textContent = 'βœ…';
1206
 
1207
- // Set sample data for demonstration
1208
  document.getElementById('name').value = 'Alexandra Chen';
1209
  document.getElementById('current-role').value = 'Senior Frontend Developer';
1210
  document.getElementById('experience').value = '6';
@@ -1245,7 +780,6 @@ Requirements:
1245
  document.getElementById('salary-location').value = 'San Francisco, CA';
1246
  document.getElementById('salary-experience').value = '6';
1247
  });
1248
-
1249
  </script>
1250
  </body>
1251
  </html>
 
522
  </div>
523
 
524
  <script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
  // Tab navigation
526
  function openTab(tabName) {
527
  const tabContents = document.getElementsByClassName('tab-content');
 
536
  event.currentTarget.classList.add('active');
537
  }
538
 
539
+ // 1. RESUME OPTIMIZER
540
  async function generateResume() {
541
+ const resumeData = {
542
+ name: document.getElementById('name').value || 'Your Name',
543
+ currentRole: document.getElementById('current-role').value || 'Professional',
544
+ experience: document.getElementById('experience').value || '3',
545
+ skills: document.getElementById('skills').value || 'Various skills',
546
+ jobTitle: document.getElementById('job-title').value || 'Target Role',
547
+ company: document.getElementById('company-name').value || 'Target Company',
548
+ jobDescription: document.getElementById('job-description').value
549
+ };
550
+
551
  const resumeOutput = document.getElementById('resume-output');
552
  const analysisOutput = document.getElementById('analysis-output');
553
+ resumeOutput.innerHTML = '<p>πŸš€ Contacting AI Agent to generate content...</p>';
554
  analysisOutput.innerHTML = '<p>πŸ“Š Analyzing your profile against the job...</p>';
555
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
556
  try {
557
+ const aiReply = await callAIAgent('resume', resumeData);
 
558
  const llmResponse = parseAIResponse(aiReply);
559
 
560
  if (llmResponse && llmResponse.resume && llmResponse.analysis) {
561
  resumeOutput.innerHTML = llmResponse.resume;
562
  analysisOutput.innerHTML = llmResponse.analysis;
563
  } else {
 
564
  resumeOutput.innerHTML = `<div style="line-height: 1.6;">${aiReply.replace(/\n/g, '<br>')}</div>`;
565
  analysisOutput.innerHTML = '<p>βœ… Resume generated (format may need adjustment)</p>';
566
  }
567
  } catch (error) {
568
+ resumeOutput.innerHTML = '<p style="color: red;">❌ Generation failed, please check server logs.</p>';
569
  analysisOutput.innerHTML = '<p style="color: red;">❌ Generation failed</p>';
570
  }
571
  }
572
 
573
+ // 2. INTERVIEW COACH
574
  async function generateInterviewQuestions() {
575
+ const interviewData = {
576
+ role: document.getElementById('interview-role').value || 'Developer',
577
+ level: document.getElementById('interview-level').value,
578
+ skills: document.getElementById('interview-skills').value || 'Relevant Skills'
579
+ };
 
 
 
 
 
 
580
  const outputDiv = document.getElementById('interview-output');
581
+ outputDiv.innerHTML = '<p>πŸ€” AI Agent is generating customized interview questions...</p>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
582
 
583
  try {
584
+ const aiReply = await callAIAgent('interview', interviewData);
 
585
  outputDiv.innerHTML = aiReply;
586
  } catch (error) {
587
+ outputDiv.innerHTML = '<p style="color: red;">❌ Generation failed, please check server logs.</p>';
588
  }
589
  }
590
 
591
+ // 3. LEARNING PATH
592
  async function generateLearningPath() {
593
+ const learningData = {
594
+ currentSkills: document.getElementById('current-skills').value || 'Basic Skills',
595
+ targetRole: document.getElementById('target-role-learning').value || 'Full Stack Developer',
596
+ timeline: parseInt(document.getElementById('timeline').value)
597
+ };
598
  const outputDiv = document.getElementById('learning-output');
599
+ outputDiv.innerHTML = '<p>πŸ“š AI Agent is planning a personalized learning path...</p>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
600
 
601
  try {
602
+ const aiReply = await callAIAgent('learning_path', learningData);
603
  outputDiv.innerHTML = aiReply;
604
  } catch (error) {
605
+ outputDiv.innerHTML = '<p style="color: red;">❌ Generation failed, please check server logs.</p>';
606
  }
607
  }
608
 
609
+ // 4. COVER LETTER GENERATOR
610
  async function generateCoverLetter() {
611
+ const letterData = {
612
+ company: document.getElementById('cl-company').value || 'Target Company',
613
+ role: document.getElementById('cl-role').value || 'Target Role',
614
+ achievement: document.getElementById('cl-highlight').value || 'My work experience and achievements',
615
+ tone: document.getElementById('cl-tone').value
616
+ };
 
 
 
 
 
617
  const outputDiv = document.getElementById('coverletter-output');
618
+ outputDiv.innerHTML = '<p>✍️ AI Agent is writing a personalized cover letter...</p>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
619
 
620
  try {
621
+ const aiReply = await callAIAgent('cover_letter', letterData);
622
  outputDiv.innerHTML = aiReply;
623
  } catch (error) {
624
+ outputDiv.innerHTML = '<p style="color: red;">❌ Generation failed, please check server logs.</p>';
625
  }
626
  }
627
 
628
+ // 5. LINKEDIN OPTIMIZER
629
  async function optimizeLinkedIn() {
630
+ const linkedinData = {
631
+ headline: document.getElementById('li-headline').value || 'Current Professional',
632
+ about: document.getElementById('li-about').value || 'Experienced professional seeking new opportunities',
633
+ target: document.getElementById('li-target').value || 'Technology field'
634
+ };
635
  const outputDiv = document.getElementById('linkedin-output');
636
+ outputDiv.innerHTML = '<p>πŸ’Ό AI Agent is optimizing your LinkedIn profile...</p>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
637
 
638
  try {
639
+ const aiReply = await callAIAgent('linkedin', linkedinData);
640
  outputDiv.innerHTML = aiReply;
641
  } catch (error) {
642
+ outputDiv.innerHTML = '<p style="color: red;">❌ Generation failed, please check server logs.</p>';
643
  }
644
  }
645
 
646
+ // 6. SALARY INTELLIGENCE
647
  async function analyzeSalary() {
648
+ const salaryData = {
649
+ role: document.getElementById('salary-role').value || 'Software Engineer',
650
+ location: document.getElementById('salary-location').value || 'San Francisco, CA',
651
+ experience: parseInt(document.getElementById('salary-experience').value) || 3,
652
+ companySize: document.getElementById('salary-company').value
653
+ };
654
  const outputDiv = document.getElementById('salary-output');
655
+ outputDiv.innerHTML = '<p>πŸ’° AI Agent is analyzing market salary data...</p>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
656
 
657
  try {
658
+ const aiReply = await callAIAgent('salary', salaryData);
659
  outputDiv.innerHTML = aiReply;
660
  } catch (error) {
661
+ outputDiv.innerHTML = '<p style="color: red;">❌ Generation failed, please check server logs.</p>';
662
  }
663
  }
664
 
665
  // UTILITY FUNCTIONS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
  function exportToPDF() {
667
  const element = document.getElementById('resume-output');
668
  const opt = {
 
683
  });
684
  }
685
 
686
+ // LLM API CALL - NEW AGENT IMPLEMENTATION
687
+ async function callAIAgent(task, data) {
688
+ console.log(`Sending task '${task}' to AI agent backend...`);
 
 
 
 
 
 
689
  const statusIcon = document.getElementById('api-status');
 
 
690
  if (statusIcon) statusIcon.textContent = 'πŸ”„';
691
 
692
  try {
693
+ const response = await fetch('/generate', {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
694
  method: 'POST',
695
+ headers: { 'Content-Type': 'application/json' },
696
+ body: JSON.stringify({ task, data })
 
 
697
  });
698
 
699
  if (!response.ok) {
700
+ const errorData = await response.json().catch(() => ({ detail: `Server Error ${response.status}: ${response.statusText}` }));
 
701
  if (statusIcon) statusIcon.textContent = '❌';
702
+ throw new Error(errorData.detail);
703
  }
704
 
705
+ const responseData = await response.json();
 
 
 
706
  if (statusIcon) statusIcon.textContent = 'βœ…';
707
 
708
+ console.log("AI Agent reply received, length:", responseData.content.length);
709
+ return responseData.content;
 
 
 
710
 
711
  } catch (error) {
712
  if (statusIcon) statusIcon.textContent = '❌';
713
+ console.error("Agent call error:", error);
714
+ alert(`❌ Agent call failed: ${error.message}\n\nPlease check the server status and logs.`);
 
 
715
  throw error;
716
  }
717
  }
718
 
719
+ // Parse JSON response from AI
720
  function parseAIResponse(aiReply) {
721
  try {
 
722
  let cleaned = aiReply.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
723
  return JSON.parse(cleaned);
724
  } catch (parseError) {
725
  console.warn("JSON parsing failed, trying to extract:", parseError);
 
 
726
  const jsonMatch = aiReply.match(/\{[\s\S]*\}/);
727
  if (jsonMatch) {
728
  try {
 
731
  console.error("Secondary parsing failed:", e);
732
  }
733
  }
 
 
734
  return null;
735
  }
736
  }
737
 
738
  // Initialize with sample data
739
  document.addEventListener('DOMContentLoaded', function() {
 
 
740
  const statusIcon = document.getElementById('api-status');
741
  if (statusIcon) statusIcon.textContent = 'βœ…';
742
 
 
743
  document.getElementById('name').value = 'Alexandra Chen';
744
  document.getElementById('current-role').value = 'Senior Frontend Developer';
745
  document.getElementById('experience').value = '6';
 
780
  document.getElementById('salary-location').value = 'San Francisco, CA';
781
  document.getElementById('salary-experience').value = '6';
782
  });
 
783
  </script>
784
  </body>
785
  </html>