Spaces:
Runtime error
Runtime error
Nikolay Angelov
commited on
Commit
·
459ee0b
1
Parent(s):
4d396f0
adding send_feedback feature and refactor code
Browse files- .dockerignore +1 -1
- .gitignore +1 -1
- Dockerfile +4 -3
- README.md +123 -27
- app.py +26 -9
- frontend/package.json +7 -1
- frontend/src/components/ChatBot.tsx +37 -3
- frontend/src/components/ChatInput.tsx +26 -5
- frontend/src/components/SendFeedback.tsx +255 -0
- frontend/src/types.ts +10 -0
- helpers/__init__.py +4 -0
- helpers/feedback_handler.py +161 -0
- {tools → helpers}/helper.py +0 -1
- prompts.yaml +1 -0
- tools/__init__.py +1 -2
- tools/date_and_time.py +1 -1
- tools/google_jobs_search.py +1 -1
- tools/internet_search.py +1 -1
- tools/python_repl.py +1 -1
- tools/visit_webpage.py +1 -1
- tools/wikipedia_tool.py +1 -1
.dockerignore
CHANGED
@@ -11,4 +11,4 @@ venv/
|
|
11 |
.venv/
|
12 |
node_modules/
|
13 |
npm-debug.log
|
14 |
-
|
|
|
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 |
-
|
|
|
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
|
13 |
-
RUN mkdir -p
|
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
|
19 |
|
20 |
-
|
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 |
-
├──
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
│
|
|
|
|
|
|
|
|
|
|
|
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
|
74 |
```
|
75 |
|
76 |
-
3.
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
|
78 |
## Running the Application
|
79 |
|
80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
```bash
|
82 |
-
uvicorn
|
83 |
```
|
84 |
|
85 |
The application will be available at:
|
86 |
-
- Web Interface: http://localhost:
|
87 |
-
- API Documentation: http://localhost:
|
88 |
-
- Alternative API Documentation: http://localhost:
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
128 |
|
129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
- **
|
140 |
-
-
|
141 |
-
-
|
142 |
-
-
|
143 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
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", [])
|
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
|
|
|
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:
|
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__ = ["
|
|
|
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:
|