Nikolay Angelov commited on
Commit
459ee0b
·
1 Parent(s): 4d396f0

adding send_feedback feature and refactor code

Browse files
.dockerignore CHANGED
@@ -11,4 +11,4 @@ venv/
11
  .venv/
12
  node_modules/
13
  npm-debug.log
14
- logs/
 
11
  .venv/
12
  node_modules/
13
  npm-debug.log
14
+ data/
.gitignore CHANGED
@@ -11,4 +11,4 @@ venv/
11
  .venv/
12
  node_modules/
13
  npm-debug.log
14
- logs/
 
11
  .venv/
12
  node_modules/
13
  npm-debug.log
14
+ data/
Dockerfile CHANGED
@@ -9,12 +9,13 @@ RUN apt-get update && \
9
  apt-get install -y nodejs && \
10
  rm -rf /var/lib/apt/lists/*
11
 
12
- # Create logs directory as root and set permissions
13
- RUN mkdir -p logs
14
 
15
  # Create a non-root user and set permissions
16
  RUN useradd -m -u 1000 user && \
17
- chown -R user:user /app
 
18
 
19
  # Switch to non-root user
20
  USER user
 
9
  apt-get install -y nodejs && \
10
  rm -rf /var/lib/apt/lists/*
11
 
12
+ # Create logs directory as root
13
+ RUN mkdir -p /app/data
14
 
15
  # Create a non-root user and set permissions
16
  RUN useradd -m -u 1000 user && \
17
+ chown -R user:user /app \
18
+ && chown -R user:user /app/data
19
 
20
  # Switch to non-root user
21
  USER user
README.md CHANGED
@@ -15,34 +15,39 @@ tags:
15
  - coaching
16
  ---
17
 
18
- # Career Coach AI Assistant
19
 
20
- A FastAPI-based AI assistant that helps users with career development, powered by Llama-3.3-70B-Instruct and LangChain.
21
-
22
- ## Features
23
-
24
- - Interactive chat interface
25
- - Career-focused AI assistant using Llama-3.3-70B-Instruct model
26
- - FastAPI backend with RESTful endpoints
27
- - LangChain integration for advanced prompt handling and tool usage
28
- - System prompts and templates managed through YAML configuration
29
 
30
  ## Project Structure
31
 
32
  ```
33
  career-coach-agent/
34
  ├── app.py # Main FastAPI application with agent setup
35
- ├── prompts.yaml # System prompts and templates
 
 
 
 
 
 
 
36
  ├── tools/ # Tool implementations
37
  │ ├── visit_webpage.py # Web page content fetcher
38
  │ ├── wikipedia_tool.py # Wikipedia search tool
39
  │ ├── python_repl.py # Python code execution tool
40
- └── internet_search.py # Internet search tool
 
 
 
 
 
41
  └── README.md # This file
42
  ```
43
 
44
  ## Requirements
45
 
 
46
  - Python 3.12+
47
  - FastAPI
48
  - LangChain
@@ -52,6 +57,19 @@ career-coach-agent/
52
  - markdownify
53
  - wikipedia
54
  - duckduckgo-search
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
  ## Environment Variables
57
 
@@ -68,24 +86,51 @@ git clone <repository-url>
68
  cd career-coach-agent
69
  ```
70
 
71
- 2. Install dependencies:
72
  ```bash
73
- pip install fastapi langchain langchain_huggingface pydantic python-multipart uvicorn requests markdownify wikipedia duckduckgo-search
74
  ```
75
 
76
- 3. Set up your Hugging Face API token as an environment variable.
 
 
 
 
 
 
77
 
78
  ## Running the Application
79
 
80
- Start the application:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  ```bash
82
- uvicorn app:app --reload
83
  ```
84
 
85
  The application will be available at:
86
- - Web Interface: http://localhost:8000
87
- - API Documentation: http://localhost:8000/docs
88
- - Alternative API Documentation: http://localhost:8000/redoc
89
 
90
  ## API Endpoints
91
 
@@ -101,6 +146,30 @@ Request body:
101
  }
102
  ```
103
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  ## Available Tools
105
 
106
  The AI assistant has access to the following tools:
@@ -124,9 +193,29 @@ The AI assistant has access to the following tools:
124
  - Returns relevant search results
125
  - Useful for finding current information
126
 
127
- ## Technical Features
 
 
 
 
 
 
 
 
 
 
128
 
129
- - Real-time chat interface
 
 
 
 
 
 
 
 
 
 
130
  - Conversation memory and context management
131
  - Error handling and graceful degradation
132
  - CORS support
@@ -136,11 +225,18 @@ The AI assistant has access to the following tools:
136
 
137
  ## Architecture
138
 
139
- - **FastAPI**: Handles HTTP requests and API endpoints
140
- - **LangChain**: Manages the AI agent and tools
141
- - **Llama-3.3-70B-Instruct**: Powers the AI responses
142
- - **YAML Configuration**: Manages system prompts and templates
143
- - **Tools Integration**: Wikipedia, Web, Python, and Search capabilities
 
 
 
 
 
 
 
144
 
145
  ## Contributing
146
 
 
15
  - coaching
16
  ---
17
 
18
+ # Career Coach Agent
19
 
20
+ An AI-powered career coaching assistant that helps users create personalized development plans and provides career guidance.
 
 
 
 
 
 
 
 
21
 
22
  ## Project Structure
23
 
24
  ```
25
  career-coach-agent/
26
  ├── app.py # Main FastAPI application with agent setup
27
+ ├── main.py # Application entry point
28
+ ├── prompts.yaml # System prompts and templates
29
+ ├── output_parser.py # Response parsing and validation
30
+ ├── frontend/ # React frontend application
31
+ │ ├── src/
32
+ │ │ ├── components/ # React components
33
+ │ │ ├── types/ # TypeScript type definitions
34
+ │ │ └── App.tsx # Main application component
35
  ├── tools/ # Tool implementations
36
  │ ├── visit_webpage.py # Web page content fetcher
37
  │ ├── wikipedia_tool.py # Wikipedia search tool
38
  │ ├── python_repl.py # Python code execution tool
39
+ ├── internet_search.py # Internet search tool
40
+ │ ├── google_jobs_search.py # Google Jobs search tool
41
+ │ └── date_and_time.py # Date and time utilities
42
+ ├── helpers/ # Helper functions
43
+ │ ├── helper.py # General helper functions
44
+ │ └── feedback_handler.py # Feedback processing
45
  └── README.md # This file
46
  ```
47
 
48
  ## Requirements
49
 
50
+ ### Backend
51
  - Python 3.12+
52
  - FastAPI
53
  - LangChain
 
57
  - markdownify
58
  - wikipedia
59
  - duckduckgo-search
60
+ - pytz
61
+ - python-multipart
62
+ - uvicorn
63
+
64
+ ### Frontend
65
+ - Node.js 16+
66
+ - React
67
+ - TypeScript
68
+ - Additional dependencies:
69
+ - styled-components
70
+ - axios
71
+ - @types/react
72
+ - @types/styled-components
73
 
74
  ## Environment Variables
75
 
 
86
  cd career-coach-agent
87
  ```
88
 
89
+ 2. Install backend dependencies:
90
  ```bash
91
+ pip install -r requirements.txt
92
  ```
93
 
94
+ 3. Install frontend dependencies:
95
+ ```bash
96
+ cd frontend
97
+ npm install
98
+ ```
99
+
100
+ 4. Set up your Hugging Face API token as an environment variable.
101
 
102
  ## Running the Application
103
 
104
+ ### Development Mode
105
+
106
+ 1. Start the backend:
107
+ ```bash
108
+ uvicorn main:app --reload
109
+ ```
110
+
111
+ 2. Start the frontend development server:
112
+ ```bash
113
+ cd frontend
114
+ npm start
115
+ ```
116
+
117
+ ### Production Mode
118
+
119
+ 1. Build the frontend:
120
+ ```bash
121
+ cd frontend
122
+ npm run build
123
+ ```
124
+
125
+ 2. Start the application:
126
  ```bash
127
+ uvicorn main:app
128
  ```
129
 
130
  The application will be available at:
131
+ - Web Interface: http://localhost:7860
132
+ - API Documentation: http://localhost:7860/docs
133
+ - Alternative API Documentation: http://localhost:7860/redoc
134
 
135
  ## API Endpoints
136
 
 
146
  }
147
  ```
148
 
149
+ ### `/agent/feedback` (POST)
150
+ Submit user feedback.
151
+
152
+ Request body:
153
+ ```json
154
+ {
155
+ "contact": "string",
156
+ "feedback": "string"
157
+ }
158
+ ```
159
+
160
+ ### `/pdp-generator` (POST)
161
+ Generate a Personal Development Plan.
162
+
163
+ Request body:
164
+ ```json
165
+ {
166
+ "file": "PDF file",
167
+ "career_goal": "string",
168
+ "additional_context": "string (optional)",
169
+ "target_date": "string"
170
+ }
171
+ ```
172
+
173
  ## Available Tools
174
 
175
  The AI assistant has access to the following tools:
 
193
  - Returns relevant search results
194
  - Useful for finding current information
195
 
196
+ 5. **Google Jobs Search**
197
+ - Searches for job listings on Google Jobs
198
+ - Returns relevant job opportunities
199
+ - Helps with career research
200
+
201
+ 6. **Date and Time**
202
+ - Provides current date and time information
203
+ - Supports multiple timezones
204
+ - Useful for scheduling and planning
205
+
206
+ ## Features
207
 
208
+ ### Frontend
209
+ - Modern, responsive UI built with React and TypeScript
210
+ - Real-time chat interface with message history
211
+ - Personal Development Plan (PDP) generation
212
+ - PDF upload and processing
213
+ - User feedback system
214
+ - Mobile-friendly design
215
+
216
+ ### Backend
217
+ - FastAPI-based REST API
218
+ - LangChain-powered AI agent
219
  - Conversation memory and context management
220
  - Error handling and graceful degradation
221
  - CORS support
 
225
 
226
  ## Architecture
227
 
228
+ - **Frontend**:
229
+ - React with TypeScript
230
+ - Styled Components for styling
231
+ - Axios for API communication
232
+ - Responsive design for all devices
233
+
234
+ - **Backend**:
235
+ - FastAPI: Handles HTTP requests and API endpoints
236
+ - LangChain: Manages the AI agent and tools
237
+ - Llama-3.3-70B-Instruct: Powers the AI responses
238
+ - YAML Configuration: Manages system prompts and templates
239
+ - Tools Integration: Wikipedia, Web, Python, Search, and more
240
 
241
  ## Contributing
242
 
app.py CHANGED
@@ -7,14 +7,10 @@ from langchain_community.document_loaders import PyPDFLoader
7
  from langchain_core.tools.render import render_text_description
8
  from langchain.text_splitter import RecursiveCharacterTextSplitter
9
 
10
- from tools.visit_webpage import visit_webpage
11
- from tools.wikipedia_tool import wikipedia_search
12
- from tools.python_repl import run_python_code
13
- from tools.internet_search import internet_search
14
- from tools.google_jobs_search import google_job_search
15
- from tools.date_and_time import current_date_and_time
16
  from output_parser import FlexibleOutputParser, clean_llm_response, validate_pdp_response
17
- from tools import create_pdp_pdf
 
 
18
 
19
  import os
20
  import yaml
@@ -100,12 +96,11 @@ chat_model_with_stop = llm.bind(
100
  stop=["\nHuman:", "Human:", "\n\nHuman", "\nUser:"]
101
  )
102
 
103
- # Update the agent to include chat_history
104
  agent = (
105
  {
106
  "input": lambda x: x["input"],
107
  "agent_scratchpad": lambda x: format_log_to_str(x["intermediate_steps"]) if x["intermediate_steps"] else "",
108
- "chat_history": lambda x: x.get("chat_history", []) # Add this line!
109
  }
110
  | prompt
111
  | chat_model_with_stop
@@ -152,6 +147,18 @@ class PDPRequest(BaseModel):
152
  cv_content: str = "" # To store extracted CV text
153
 
154
  # API Routes
 
 
 
 
 
 
 
 
 
 
 
 
155
  @app.post("/agent/cancel/{thread_id}")
156
  async def cancel_request(thread_id: str):
157
  if thread_id in active_requests:
@@ -224,6 +231,7 @@ async def pdp_generator(
224
  # Generate PDP using the agent
225
  pdp_query = f"""
226
  Create a comprehensive Personal Development Plan for transitioning to {pdp_request.career_goal} by {pdp_request.target_date}.
 
227
 
228
  Based on this CV content: {pdp_request.cv_content}
229
  Additional context: {pdp_request.additional_context}
@@ -265,6 +273,7 @@ async def pdp_generator(
265
  # Validate the response
266
  if validate_pdp_response(pdp_response):
267
  # Create PDF only if validation passes
 
268
  try:
269
  pdf_buffer = create_pdp_pdf(
270
  pdp_content=pdp_response,
@@ -278,6 +287,14 @@ async def pdp_generator(
278
  safe_career_goal = re.sub(r'[-\s]+', '-', safe_career_goal)
279
  pdf_filename = f"PDP_{safe_career_goal}_{datetime.now().strftime('%Y%m%d')}.pdf"
280
 
 
 
 
 
 
 
 
 
281
  return StreamingResponse(
282
  BytesIO(pdf_buffer.read()),
283
  media_type="application/pdf",
 
7
  from langchain_core.tools.render import render_text_description
8
  from langchain.text_splitter import RecursiveCharacterTextSplitter
9
 
 
 
 
 
 
 
10
  from output_parser import FlexibleOutputParser, clean_llm_response, validate_pdp_response
11
+ from tools import *
12
+ from helpers import *
13
+
14
 
15
  import os
16
  import yaml
 
96
  stop=["\nHuman:", "Human:", "\n\nHuman", "\nUser:"]
97
  )
98
 
 
99
  agent = (
100
  {
101
  "input": lambda x: x["input"],
102
  "agent_scratchpad": lambda x: format_log_to_str(x["intermediate_steps"]) if x["intermediate_steps"] else "",
103
+ "chat_history": lambda x: x.get("chat_history", [])
104
  }
105
  | prompt
106
  | chat_model_with_stop
 
147
  cv_content: str = "" # To store extracted CV text
148
 
149
  # API Routes
150
+ @app.post("/agent/feedback")
151
+ async def feedback(contact: str, feedback: str):
152
+ """
153
+ Receive and store user feedback
154
+ """
155
+ try:
156
+ print("Feedback: ",contact, feedback)
157
+ result = store_feedback(contact, feedback)
158
+ return result
159
+ except Exception as e:
160
+ raise HTTPException(status_code=500, detail=str(e))
161
+
162
  @app.post("/agent/cancel/{thread_id}")
163
  async def cancel_request(thread_id: str):
164
  if thread_id in active_requests:
 
231
  # Generate PDP using the agent
232
  pdp_query = f"""
233
  Create a comprehensive Personal Development Plan for transitioning to {pdp_request.career_goal} by {pdp_request.target_date}.
234
+ DO NOT ASK FOR CONFIRMATION OF THIS REQUEST!
235
 
236
  Based on this CV content: {pdp_request.cv_content}
237
  Additional context: {pdp_request.additional_context}
 
273
  # Validate the response
274
  if validate_pdp_response(pdp_response):
275
  # Create PDF only if validation passes
276
+ print("raw-pdp_response: ",pdp_response)
277
  try:
278
  pdf_buffer = create_pdp_pdf(
279
  pdp_content=pdp_response,
 
287
  safe_career_goal = re.sub(r'[-\s]+', '-', safe_career_goal)
288
  pdf_filename = f"PDP_{safe_career_goal}_{datetime.now().strftime('%Y%m%d')}.pdf"
289
 
290
+ # Add PDP request to chat history memory
291
+ user_pdp_message = f"User requested a Personal Development Plan.\nCareer Goal: {pdp_request.career_goal}\nTarget Date: {pdp_request.target_date}\nAdditional Context: {pdp_request.additional_context or 'None'}\nCV Provided: {'Yes' if cv_content.strip() else 'No'}"
292
+ assistant_pdp_ack = "Okay, I 've successfully generated you PDP PDF file"
293
+ memory.save_context(
294
+ {"input": user_pdp_message},
295
+ {"output": assistant_pdp_ack}
296
+ )
297
+
298
  return StreamingResponse(
299
  BytesIO(pdf_buffer.read()),
300
  media_type="application/pdf",
frontend/package.json CHANGED
@@ -38,5 +38,11 @@
38
  "last 1 safari version"
39
  ]
40
  },
41
- "proxy": "http://localhost:7860"
 
 
 
 
 
 
42
  }
 
38
  "last 1 safari version"
39
  ]
40
  },
41
+ "proxy": "http://localhost:7860",
42
+ "devDependencies": {
43
+ "@types/react": "^18.2.66",
44
+ "@types/react-dom": "^18.2.22",
45
+ "@types/styled-components": "^5.1.34",
46
+ "@types/axios": "^0.14.0"
47
+ }
48
  }
frontend/src/components/ChatBot.tsx CHANGED
@@ -5,7 +5,8 @@ import ChatHeader from './ChatHeader';
5
  import ChatWindow from './ChatWindow';
6
  import ChatInput from './ChatInput';
7
  import PDPDialog, { PDPFormData } from './PDPDialog';
8
- import { Message, ChatResponse } from '../types';
 
9
 
10
  const ChatContainer = styled.div`
11
  display: flex;
@@ -22,6 +23,7 @@ const ChatBot: React.FC = () => {
22
  const [threadId, setThreadId] = useState<string | null>(null);
23
  const [isLoading, setIsLoading] = useState(false);
24
  const [isPDPDialogOpen, setIsPDPDialogOpen] = useState(false);
 
25
  const cancelTokenRef = useRef<AbortController | null>(null);
26
 
27
  // Add welcome message when component mounts
@@ -221,12 +223,38 @@ const ChatBot: React.FC = () => {
221
  setIsLoading(false);
222
  }
223
  };
224
-
225
  // The pdpDialog function as requested
226
  const pdpDialog = () => {
227
  openPDPDialog();
228
  };
229
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  return (
231
  <ChatContainer>
232
  <ChatHeader
@@ -240,7 +268,13 @@ const ChatBot: React.FC = () => {
240
  <ChatInput
241
  onSendMessage={sendMessage}
242
  onCancelRequest={cancelRequest}
243
- isLoading={isLoading}
 
 
 
 
 
 
244
  />
245
  <PDPDialog
246
  isOpen={isPDPDialogOpen}
 
5
  import ChatWindow from './ChatWindow';
6
  import ChatInput from './ChatInput';
7
  import PDPDialog, { PDPFormData } from './PDPDialog';
8
+ import SendFeedback from './SendFeedback';
9
+ import { Message, ChatResponse, FeedbackFormData } from '../types';
10
 
11
  const ChatContainer = styled.div`
12
  display: flex;
 
23
  const [threadId, setThreadId] = useState<string | null>(null);
24
  const [isLoading, setIsLoading] = useState(false);
25
  const [isPDPDialogOpen, setIsPDPDialogOpen] = useState(false);
26
+ const [isSendFeedbackDialogOpen, setIsSendFeedbackDialogOpen] = useState(false);
27
  const cancelTokenRef = useRef<AbortController | null>(null);
28
 
29
  // Add welcome message when component mounts
 
223
  setIsLoading(false);
224
  }
225
  };
226
+
227
  // The pdpDialog function as requested
228
  const pdpDialog = () => {
229
  openPDPDialog();
230
  };
231
 
232
+ const closeSendFeedbackDialog = () => {
233
+ setIsSendFeedbackDialogOpen(false);
234
+ };
235
+
236
+ const handleSendFeedbackSubmit = async (data: FeedbackFormData) => {
237
+ try {
238
+ console.log('Sending feedback data:', JSON.stringify(data));
239
+ const url = new URL('/agent/feedback', window.location.origin);
240
+ url.searchParams.append('contact', data.contact);
241
+ url.searchParams.append('feedback', data.feedback);
242
+
243
+ const response = await fetch(url.toString(), {
244
+ method: 'POST'
245
+ });
246
+
247
+ if (!response.ok) {
248
+ throw new Error('Failed to submit feedback');
249
+ }
250
+
251
+ alert('Thank you for your feedback!');
252
+ } catch (error) {
253
+ console.error('Error submitting feedback:', error);
254
+ alert('Failed to submit feedback. Please try again later.');
255
+ }
256
+ };
257
+
258
  return (
259
  <ChatContainer>
260
  <ChatHeader
 
268
  <ChatInput
269
  onSendMessage={sendMessage}
270
  onCancelRequest={cancelRequest}
271
+ isLoading={isLoading}
272
+ onOpenFeedback={() => setIsSendFeedbackDialogOpen(true)}
273
+ />
274
+ <SendFeedback
275
+ isOpen={isSendFeedbackDialogOpen}
276
+ onClose={closeSendFeedbackDialog}
277
+ onSubmit={handleSendFeedbackSubmit}
278
  />
279
  <PDPDialog
280
  isOpen={isPDPDialogOpen}
frontend/src/components/ChatInput.tsx CHANGED
@@ -1,5 +1,7 @@
1
- import React, { useState } from 'react';
2
  import styled from 'styled-components';
 
 
3
 
4
  const InputContainer = styled.div`
5
  display: flex;
@@ -52,16 +54,32 @@ const StopButton = styled.button`
52
  }
53
  `;
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  interface ChatInputProps {
56
  onSendMessage: (message: string) => void;
57
  onCancelRequest: () => void;
58
  isLoading: boolean;
 
59
  }
60
 
61
- const ChatInput: React.FC<ChatInputProps> = ({ onSendMessage, onCancelRequest, isLoading }) => {
62
  const [message, setMessage] = useState('');
63
 
64
- const handleSubmit = (e: React.FormEvent) => {
65
  e.preventDefault();
66
  if (message.trim() && !isLoading) {
67
  onSendMessage(message);
@@ -72,10 +90,13 @@ const ChatInput: React.FC<ChatInputProps> = ({ onSendMessage, onCancelRequest, i
72
  return (
73
  <InputContainer>
74
  <form onSubmit={handleSubmit} style={{ display: 'flex', width: '100%' }}>
 
 
 
75
  <Input
76
  type="text"
77
  value={message}
78
- onChange={(e) => setMessage(e.target.value)}
79
  placeholder="Type your message..."
80
  disabled={isLoading}
81
  />
@@ -87,7 +108,7 @@ const ChatInput: React.FC<ChatInputProps> = ({ onSendMessage, onCancelRequest, i
87
  <StopButton type="button" onClick={onCancelRequest}>
88
  Stop
89
  </StopButton>
90
- )}
91
  </form>
92
  </InputContainer>
93
  );
 
1
+ import React, { useState, FormEvent, ChangeEvent } from 'react';
2
  import styled from 'styled-components';
3
+ import SendFeedback from './SendFeedback';
4
+ import { FeedbackFormData, FeedbackResponse } from '../types';
5
 
6
  const InputContainer = styled.div`
7
  display: flex;
 
54
  }
55
  `;
56
 
57
+ const FeedbackButton = styled.button`
58
+ margin-left: 10px;
59
+ padding: 10px 15px;
60
+ background-color:rgb(131, 138, 145);
61
+ color: white;
62
+ border: none;
63
+ border-radius: 20px;
64
+ cursor: pointer;
65
+ font-size: 16px;
66
+
67
+ &:hover {
68
+ background-color: #5a6268;
69
+ }
70
+ `;
71
+
72
  interface ChatInputProps {
73
  onSendMessage: (message: string) => void;
74
  onCancelRequest: () => void;
75
  isLoading: boolean;
76
+ onOpenFeedback: () => void;
77
  }
78
 
79
+ const ChatInput: React.FC<ChatInputProps> = ({ onSendMessage, onCancelRequest, isLoading, onOpenFeedback }) => {
80
  const [message, setMessage] = useState('');
81
 
82
+ const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
83
  e.preventDefault();
84
  if (message.trim() && !isLoading) {
85
  onSendMessage(message);
 
90
  return (
91
  <InputContainer>
92
  <form onSubmit={handleSubmit} style={{ display: 'flex', width: '100%' }}>
93
+ <FeedbackButton type="button" onClick={onOpenFeedback}>
94
+ Send Feedback
95
+ </FeedbackButton>
96
  <Input
97
  type="text"
98
  value={message}
99
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setMessage(e.target.value)}
100
  placeholder="Type your message..."
101
  disabled={isLoading}
102
  />
 
108
  <StopButton type="button" onClick={onCancelRequest}>
109
  Stop
110
  </StopButton>
111
+ )}
112
  </form>
113
  </InputContainer>
114
  );
frontend/src/components/SendFeedback.tsx ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, FormEvent, ChangeEvent } from 'react';
2
+ import styled from 'styled-components';
3
+ import { FeedbackFormData } from '../types';
4
+
5
+ const Overlay = styled.div`
6
+ position: fixed;
7
+ top: 0;
8
+ left: 0;
9
+ right: 0;
10
+ bottom: 0;
11
+ background-color: rgba(0, 0, 0, 0.5);
12
+ display: flex;
13
+ justify-content: center;
14
+ align-items: center;
15
+ z-index: 1000;
16
+ `;
17
+
18
+ const Modal = styled.div`
19
+ background: white;
20
+ padding: 30px;
21
+ border-radius: 8px;
22
+ width: 90%;
23
+ max-width: 500px;
24
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
25
+
26
+ @media screen and (max-width: 768px) {
27
+ padding: 20px;
28
+ width: 95%;
29
+ }
30
+
31
+ @media screen and (max-width: 480px) {
32
+ padding: 15px;
33
+ width: 100%;
34
+ margin: 10px;
35
+ }
36
+ `;
37
+
38
+ const Title = styled.h2`
39
+ margin: 0 0 20px 0;
40
+ color: #333;
41
+ font-size: 24px;
42
+
43
+ @media screen and (max-width: 768px) {
44
+ font-size: 20px;
45
+ margin-bottom: 15px;
46
+ }
47
+
48
+ @media screen and (max-width: 480px) {
49
+ font-size: 18px;
50
+ margin-bottom: 12px;
51
+ }
52
+ `;
53
+
54
+ const Form = styled.form`
55
+ display: flex;
56
+ flex-direction: column;
57
+ gap: 15px;
58
+
59
+ @media screen and (max-width: 768px) {
60
+ gap: 12px;
61
+ }
62
+
63
+ @media screen and (max-width: 480px) {
64
+ gap: 10px;
65
+ }
66
+ `;
67
+
68
+ const Label = styled.label`
69
+ font-weight: bold;
70
+ color: #555;
71
+ font-size: 16px;
72
+
73
+ @media screen and (max-width: 768px) {
74
+ font-size: 14px;
75
+ }
76
+
77
+ @media screen and (max-width: 480px) {
78
+ font-size: 13px;
79
+ }
80
+ `;
81
+
82
+ const Input = styled.input`
83
+ padding: 10px;
84
+ border: 1px solid #ddd;
85
+ border-radius: 4px;
86
+ font-size: 16px;
87
+
88
+ @media screen and (max-width: 768px) {
89
+ padding: 8px;
90
+ font-size: 14px;
91
+ }
92
+
93
+ @media screen and (max-width: 480px) {
94
+ padding: 6px;
95
+ font-size: 13px;
96
+ }
97
+ `;
98
+
99
+ const TextArea = styled.textarea`
100
+ padding: 10px;
101
+ border: 1px solid #ddd;
102
+ border-radius: 4px;
103
+ font-size: 16px;
104
+ min-height: 100px;
105
+ resize: vertical;
106
+
107
+ @media screen and (max-width: 768px) {
108
+ padding: 8px;
109
+ font-size: 14px;
110
+ min-height: 80px;
111
+ }
112
+
113
+ @media screen and (max-width: 480px) {
114
+ padding: 6px;
115
+ font-size: 13px;
116
+ min-height: 70px;
117
+ }
118
+ `;
119
+
120
+ const ButtonGroup = styled.div`
121
+ display: flex;
122
+ gap: 10px;
123
+ justify-content: flex-end;
124
+ margin-top: 20px;
125
+
126
+ @media screen and (max-width: 768px) {
127
+ gap: 8px;
128
+ margin-top: 15px;
129
+ }
130
+
131
+ @media screen and (max-width: 480px) {
132
+ gap: 6px;
133
+ margin-top: 12px;
134
+ }
135
+ `;
136
+
137
+ const Button = styled.button`
138
+ padding: 10px 20px;
139
+ border: none;
140
+ border-radius: 4px;
141
+ cursor: pointer;
142
+ font-size: 16px;
143
+
144
+ @media screen and (max-width: 768px) {
145
+ padding: 8px 16px;
146
+ font-size: 14px;
147
+ }
148
+
149
+ @media screen and (max-width: 480px) {
150
+ padding: 6px 12px;
151
+ font-size: 13px;
152
+ }
153
+ `;
154
+
155
+ const SubmitButton = styled(Button)`
156
+ background-color: #0084ff;
157
+ color: white;
158
+
159
+ &:hover {
160
+ background-color: #0073e6;
161
+ }
162
+
163
+ &:disabled {
164
+ background-color: #ccc;
165
+ cursor: not-allowed;
166
+ }
167
+
168
+ @media (hover: none) {
169
+ &:hover {
170
+ background-color: #0084ff;
171
+ }
172
+ }
173
+ `;
174
+
175
+ const CancelButton = styled(Button)`
176
+ background-color: #6c757d;
177
+ color: white;
178
+
179
+ &:hover {
180
+ background-color: #5a6268;
181
+ }
182
+
183
+ @media (hover: none) {
184
+ &:hover {
185
+ background-color: #6c757d;
186
+ }
187
+ }
188
+ `;
189
+
190
+ interface SendFeedbackProps {
191
+ isOpen: boolean;
192
+ onClose: () => void;
193
+ onSubmit: (data: FeedbackFormData) => void;
194
+ }
195
+
196
+ const SendFeedback: React.FC<SendFeedbackProps> = ({ isOpen, onClose, onSubmit }) => {
197
+ const [contact, setContact] = useState('');
198
+ const [feedbackContent, setFeedbackContent] = useState('');
199
+
200
+ const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
201
+ e.preventDefault();
202
+ onSubmit({
203
+ contact: contact,
204
+ feedback: feedbackContent
205
+ });
206
+ setContact('');
207
+ setFeedbackContent('');
208
+ onClose();
209
+ };
210
+
211
+ if (!isOpen) return null;
212
+
213
+ return (
214
+ <Overlay onClick={onClose}>
215
+ <Modal onClick={(e: React.MouseEvent) => e.stopPropagation()}>
216
+ <Title>Send Feedback</Title>
217
+ <Form onSubmit={handleSubmit}>
218
+ <div>
219
+ <Label htmlFor="contact">Contact Information (Email/Name):</Label>
220
+ <Input
221
+ id="contact"
222
+ type="text"
223
+ value={contact}
224
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setContact(e.target.value)}
225
+ placeholder="your email or name"
226
+ required
227
+ />
228
+ </div>
229
+
230
+ <div>
231
+ <Label htmlFor="feedback">Feedback:</Label>
232
+ <TextArea
233
+ id="feedback"
234
+ value={feedbackContent}
235
+ onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setFeedbackContent(e.target.value)}
236
+ placeholder="Please share your feedback, suggestions, or report any issues..."
237
+ required
238
+ />
239
+ </div>
240
+
241
+ <ButtonGroup>
242
+ <CancelButton type="button" onClick={onClose}>
243
+ Cancel
244
+ </CancelButton>
245
+ <SubmitButton type="submit">
246
+ Send Feedback
247
+ </SubmitButton>
248
+ </ButtonGroup>
249
+ </Form>
250
+ </Modal>
251
+ </Overlay>
252
+ );
253
+ };
254
+
255
+ export default SendFeedback;
frontend/src/types.ts CHANGED
@@ -18,4 +18,14 @@ export interface PDPResponse {
18
  filename: string;
19
  career_goal: string;
20
  target_date: string;
 
 
 
 
 
 
 
 
 
 
21
  }
 
18
  filename: string;
19
  career_goal: string;
20
  target_date: string;
21
+ }
22
+
23
+ export interface FeedbackFormData {
24
+ contact: string;
25
+ feedback: string;
26
+ }
27
+
28
+ export interface FeedbackResponse {
29
+ status: string;
30
+ message: string;
31
  }
helpers/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from .helper import create_pdp_pdf, clean_input
2
+ from .feedback_handler import store_feedback
3
+
4
+ __all__ = ["create_pdp_pdf", "store_feedback", "clean_input"]
helpers/feedback_handler.py ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from datetime import datetime
4
+ from typing import Dict, Any, List
5
+ import shutil
6
+
7
+ def store_feedback(contact: str, feedback: str) -> Dict[str, Any]:
8
+ """
9
+ Store feedback to a file in /data directory for Hugging Face Spaces
10
+
11
+ Args:
12
+ contact (str): Contact information (email/name)
13
+ feedback (str): Feedback content
14
+
15
+ Returns:
16
+ Dict[str, Any]: Result dictionary with status and message
17
+
18
+ Raises:
19
+ Exception: If there's an error saving the feedback
20
+ """
21
+ try:
22
+ # Validate inputs
23
+ if not contact or not contact.strip():
24
+ raise ValueError("Contact information is required")
25
+
26
+ if not feedback or not feedback.strip():
27
+ raise ValueError("Feedback content is required")
28
+
29
+ # Create feedback entry
30
+ feedback_entry = {
31
+ "timestamp": datetime.now().isoformat(),
32
+ "contact": contact.strip(),
33
+ "feedback": feedback.strip()
34
+ }
35
+
36
+ # Ensure /data/feedback directory exists
37
+ feedback_dir = "/app/data/feedback"
38
+ try:
39
+ os.makedirs(feedback_dir, exist_ok=True)
40
+ if not os.access(feedback_dir, os.W_OK):
41
+ raise PermissionError(f"No write permission for directory: {feedback_dir}")
42
+ except Exception as e:
43
+ raise Exception(f"Failed to create or access feedback directory: {str(e)}")
44
+
45
+ # Create filename with date
46
+ date_str = datetime.now().strftime('%Y-%m-%d')
47
+ filename = os.path.join(feedback_dir, f"feedback_{date_str}.json")
48
+ temp_filename = f"{filename}.tmp"
49
+
50
+ # Read existing feedback for the day or create new list
51
+ feedback_list = _read_feedback_file(filename)
52
+
53
+ # Add new feedback
54
+ feedback_list.append(feedback_entry)
55
+
56
+ # Write to temporary file first
57
+ try:
58
+ _write_feedback_file(temp_filename, feedback_list)
59
+
60
+ # Verify the temporary file was written correctly
61
+ if not os.path.exists(temp_filename):
62
+ raise Exception("Failed to create temporary feedback file")
63
+
64
+ # Verify the temporary file contains valid JSON
65
+ try:
66
+ with open(temp_filename, 'r', encoding='utf-8') as f:
67
+ json.load(f)
68
+ except json.JSONDecodeError:
69
+ raise Exception("Failed to write valid JSON to temporary file")
70
+
71
+ # If everything is good, replace the original file
72
+ backup_filename = None # Initialize backup_filename
73
+ if os.path.exists(filename):
74
+ # Create backup of original file
75
+ backup_filename = f"{filename}.bak"
76
+ shutil.copy2(filename, backup_filename)
77
+
78
+ # Move temporary file to final location
79
+ shutil.move(temp_filename, filename)
80
+
81
+ # Remove backup if everything succeeded
82
+ if backup_filename and os.path.exists(backup_filename):
83
+ os.remove(backup_filename)
84
+
85
+ except Exception as e:
86
+ # Clean up temporary file if it exists
87
+ if os.path.exists(temp_filename):
88
+ os.remove(temp_filename)
89
+ raise Exception(f"Failed to write feedback file: {str(e)}")
90
+
91
+ # Log to console for immediate visibility
92
+ _log_feedback_to_console(feedback_entry, filename)
93
+
94
+ return {
95
+ "status": "success",
96
+ "message": "Feedback saved successfully",
97
+ "timestamp": feedback_entry["timestamp"],
98
+ "filename": filename
99
+ }
100
+
101
+ except Exception as e:
102
+ error_msg = f"Error saving feedback: {str(e)}"
103
+ print(error_msg)
104
+ raise Exception(error_msg)
105
+
106
+
107
+ def _read_feedback_file(filename: str) -> List[Dict[str, Any]]:
108
+ """Read feedback from JSON file"""
109
+ try:
110
+ if not os.path.exists(filename):
111
+ return []
112
+
113
+ if not os.access(filename, os.R_OK):
114
+ raise PermissionError(f"No read permission for file: {filename}")
115
+
116
+ with open(filename, 'r', encoding='utf-8') as f:
117
+ try:
118
+ return json.load(f)
119
+ except json.JSONDecodeError as e:
120
+ # If file is corrupted, try to read backup
121
+ backup_filename = f"{filename}.bak"
122
+ if os.path.exists(backup_filename):
123
+ print(f"Warning: Corrupted feedback file {filename}, trying backup")
124
+ with open(backup_filename, 'r', encoding='utf-8') as backup_f:
125
+ return json.load(backup_f)
126
+ else:
127
+ print(f"Warning: Corrupted feedback file {filename}, starting fresh")
128
+ return []
129
+ except Exception as e:
130
+ print(f"Error reading feedback file: {str(e)}")
131
+ return []
132
+
133
+
134
+ def _write_feedback_file(filename: str, feedback_list: List[Dict[str, Any]]) -> None:
135
+ """Write feedback to JSON file"""
136
+ try:
137
+ # Ensure the directory exists
138
+ os.makedirs(os.path.dirname(filename), exist_ok=True)
139
+
140
+ # Write to file with proper encoding and formatting
141
+ with open(filename, 'w', encoding='utf-8') as f:
142
+ json.dump(feedback_list, f, indent=2, ensure_ascii=False)
143
+
144
+ # Verify the file was written
145
+ if not os.path.exists(filename):
146
+ raise Exception(f"Failed to create file: {filename}")
147
+
148
+ except Exception as e:
149
+ raise Exception(f"Failed to write feedback file: {str(e)}")
150
+
151
+
152
+ def _log_feedback_to_console(feedback_entry: Dict[str, Any], filename: str) -> None:
153
+ """Log feedback to console for immediate visibility"""
154
+ print("\n" + "="*60)
155
+ print("NEW FEEDBACK RECEIVED")
156
+ print("="*60)
157
+ print(f"Timestamp: {feedback_entry['timestamp']}")
158
+ print(f"Contact: {feedback_entry['contact']}")
159
+ print(f"Feedback: {feedback_entry['feedback']}")
160
+ print(f"Saved to: {filename}")
161
+ print("="*60 + "\n")
{tools → helpers}/helper.py RENAMED
@@ -5,7 +5,6 @@ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
5
  from reportlab.lib.units import inch
6
  from reportlab.lib.colors import HexColor
7
  from io import BytesIO
8
- import re
9
  from datetime import datetime
10
 
11
  def clean_input(input: str) -> str:
 
5
  from reportlab.lib.units import inch
6
  from reportlab.lib.colors import HexColor
7
  from io import BytesIO
 
8
  from datetime import datetime
9
 
10
  def clean_input(input: str) -> str:
prompts.yaml CHANGED
@@ -48,6 +48,7 @@ system_prompt: |-
48
  - Focus on practical career advice
49
  - Do NOT execute code or perform technical analysis
50
  - Do NOT use tools unless specifically needed for research
 
51
  - Keep responses focused and professional
52
 
53
  **NEVER:**
 
48
  - Focus on practical career advice
49
  - Do NOT execute code or perform technical analysis
50
  - Do NOT use tools unless specifically needed for research
51
+ - You can use internet_search to check for proper books, trainings or courses
52
  - Keep responses focused and professional
53
 
54
  **NEVER:**
tools/__init__.py CHANGED
@@ -4,6 +4,5 @@ from .python_repl import run_python_code
4
  from .internet_search import internet_search
5
  from .google_jobs_search import google_job_search
6
  from .date_and_time import current_date_and_time
7
- from .helper import create_pdp_pdf
8
 
9
- __all__ = ["get_current_time", "visit_webpage"]
 
4
  from .internet_search import internet_search
5
  from .google_jobs_search import google_job_search
6
  from .date_and_time import current_date_and_time
 
7
 
8
+ __all__ = ["current_date_and_time", "visit_webpage", "wikipedia_search", "run_python_code", "internet_search", "google_job_search"]
tools/date_and_time.py CHANGED
@@ -1,7 +1,7 @@
1
  from langchain_core.tools import tool
2
  from datetime import datetime
3
  import pytz
4
- from .helper import clean_input
5
 
6
  @tool
7
  def current_date_and_time(timezone: str) -> str:
 
1
  from langchain_core.tools import tool
2
  from datetime import datetime
3
  import pytz
4
+ from helpers.helper import clean_input
5
 
6
  @tool
7
  def current_date_and_time(timezone: str) -> str:
tools/google_jobs_search.py CHANGED
@@ -1,7 +1,7 @@
1
  from langchain_community.tools.google_jobs import GoogleJobsQueryRun
2
  from langchain_community.utilities.google_jobs import GoogleJobsAPIWrapper
3
  from langchain_core.tools import tool
4
- from .helper import clean_input
5
  import os
6
 
7
  if not os.getenv('SERPAPI_API_KEY'):
 
1
  from langchain_community.tools.google_jobs import GoogleJobsQueryRun
2
  from langchain_community.utilities.google_jobs import GoogleJobsAPIWrapper
3
  from langchain_core.tools import tool
4
+ from helpers.helper import clean_input
5
  import os
6
 
7
  if not os.getenv('SERPAPI_API_KEY'):
tools/internet_search.py CHANGED
@@ -1,6 +1,6 @@
1
  from langchain_core.tools import tool
2
  from langchain_community.tools import DuckDuckGoSearchResults
3
- from .helper import clean_input
4
 
5
 
6
  @tool
 
1
  from langchain_core.tools import tool
2
  from langchain_community.tools import DuckDuckGoSearchResults
3
+ from helpers.helper import clean_input
4
 
5
 
6
  @tool
tools/python_repl.py CHANGED
@@ -1,6 +1,6 @@
1
  from langchain_core.tools import tool
2
  from langchain_experimental.utilities import PythonREPL
3
- from .helper import clean_input
4
 
5
  @tool
6
  def run_python_code(code: str) -> str:
 
1
  from langchain_core.tools import tool
2
  from langchain_experimental.utilities import PythonREPL
3
+ from helpers.helper import clean_input
4
 
5
  @tool
6
  def run_python_code(code: str) -> str:
tools/visit_webpage.py CHANGED
@@ -3,7 +3,7 @@ import requests
3
  import markdownify
4
  import re
5
  from requests.exceptions import RequestException
6
- from .helper import clean_input
7
 
8
  @tool
9
  def visit_webpage(url: str) -> str:
 
3
  import markdownify
4
  import re
5
  from requests.exceptions import RequestException
6
+ from helpers.helper import clean_input
7
 
8
  @tool
9
  def visit_webpage(url: str) -> str:
tools/wikipedia_tool.py CHANGED
@@ -1,7 +1,7 @@
1
  from langchain_core.tools import tool
2
  from langchain_community.tools import WikipediaQueryRun
3
  from langchain_community.utilities import WikipediaAPIWrapper
4
- from .helper import clean_input
5
 
6
  @tool
7
  def wikipedia_search(topic: str) -> str:
 
1
  from langchain_core.tools import tool
2
  from langchain_community.tools import WikipediaQueryRun
3
  from langchain_community.utilities import WikipediaAPIWrapper
4
+ from helpers.helper import clean_input
5
 
6
  @tool
7
  def wikipedia_search(topic: str) -> str: