kamalakn commited on
Commit
8d76a24
Β·
verified Β·
1 Parent(s): 220728c

Upload 5 files

Browse files
Files changed (5) hide show
  1. .gitignore +43 -0
  2. LICENSE +21 -0
  3. README.md +100 -13
  4. app.py +1075 -0
  5. requirements.txt +11 -0
.gitignore ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ venv*/
8
+ env/
9
+ build/
10
+ develop-eggs/
11
+ dist/
12
+ downloads/
13
+ eggs/
14
+ .eggs/
15
+ lib/
16
+ lib64/
17
+ parts/
18
+ sdist/
19
+ var/
20
+ wheels/
21
+ *.egg-info/
22
+ .installed.cfg
23
+ *.egg
24
+
25
+ # Virtual Environment
26
+ venv-HFHACK/
27
+
28
+ # IDE
29
+ .vscode/
30
+ .idea/
31
+
32
+ # Environment variables
33
+ .env
34
+
35
+ # Temporary files
36
+ *.tmp
37
+ *.temp
38
+ .DS_Store
39
+
40
+ # Data files (if they contain sensitive information)
41
+ data/*.xlsx
42
+ data/*.csv
43
+ data/*.docx
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Supplier Performance Analysis Agent
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,13 +1,100 @@
1
- ---
2
- title: Supplier Performance Analysis Agent
3
- emoji: 😻
4
- colorFrom: pink
5
- colorTo: blue
6
- sdk: gradio
7
- sdk_version: 5.33.0
8
- app_file: app.py
9
- pinned: false
10
- license: gpl-3.0
11
- ---
12
-
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Supplier Performance Analysis Agent
2
+
3
+ A powerful AI-powered application for analyzing supplier performance using audit reports and supplier data. This application helps organizations make data-driven decisions about their supplier relationships.
4
+
5
+ ## Features
6
+
7
+ - πŸ“Š Supplier Performance Analysis
8
+ - πŸ“ Audit Report Processing
9
+ - πŸ€– AI-Powered Insights
10
+ - πŸ’¬ Interactive Chat Interface
11
+ - πŸ“ˆ Visual Performance Metrics
12
+ - πŸ“‘ Detailed Reports Generation
13
+
14
+ ## Live Demo
15
+
16
+ Try the application live on Hugging Face Spaces: [Link to be added after deployment]
17
+
18
+ ## Getting Started
19
+
20
+ ### Prerequisites
21
+
22
+ - Python 3.8 or higher
23
+ - OpenAI API key
24
+
25
+ ### Installation
26
+
27
+ 1. Clone the repository:
28
+ ```bash
29
+ git clone https://huggingface.co/spaces/[your-username]/supplier-analysis-agent
30
+ cd supplier-analysis-agent
31
+ ```
32
+
33
+ 2. Install dependencies:
34
+ ```bash
35
+ pip install -r requirements.txt
36
+ ```
37
+
38
+ ### Usage
39
+
40
+ 1. Run the application:
41
+ ```bash
42
+ python app.py
43
+ ```
44
+
45
+ 2. Open your web browser and navigate to the local URL (typically http://localhost:7860)
46
+
47
+ 3. Enter your OpenAI API key in the application interface
48
+
49
+ 4. Upload your supplier data (Excel/CSV) and audit reports (Word documents)
50
+
51
+ 5. Click "Analyze" to process the data and generate insights
52
+
53
+ ## Input Requirements
54
+
55
+ ### Supplier Data File
56
+ - Excel (.xlsx) or CSV format
57
+ - Required columns: [Add your specific requirements]
58
+
59
+ ### Audit Reports
60
+ - Word documents (.docx)
61
+ - Should contain audit findings and observations
62
+
63
+ ## Features in Detail
64
+
65
+ ### Performance Analysis
66
+ - Calculates supplier performance scores
67
+ - Identifies top performers
68
+ - Generates performance trends
69
+ - Provides detailed metrics
70
+
71
+ ### AI-Powered Insights
72
+ - Natural language processing of audit reports
73
+ - Automated summary generation
74
+ - Interactive Q&A about supplier data
75
+ - Smart recommendations
76
+
77
+ ### Visualization
78
+ - Interactive performance charts
79
+ - Comparative analysis graphs
80
+ - Trend visualization
81
+ - Customizable metrics display
82
+
83
+ ## Contributing
84
+
85
+ Contributions are welcome! Please feel free to submit a Pull Request.
86
+
87
+ ## License
88
+
89
+ This project is licensed under the MIT License - see the LICENSE file for details.
90
+
91
+ ## Acknowledgments
92
+
93
+ - Built with Gradio
94
+ - Powered by OpenAI's GPT models
95
+ - Uses LangChain for AI processing
96
+ - Visualization powered by Plotly
97
+
98
+ ## Contact
99
+
100
+ For any questions or support, please open an issue in the repository.
app.py ADDED
@@ -0,0 +1,1075 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import plotly.express as px
4
+ import plotly.graph_objects as go
5
+ from agent.supplier_agent import SupplierAgent
6
+ from agent.utils import DataValidator, ReportFormatter
7
+ import io
8
+ from PIL import Image
9
+ import os
10
+ import tempfile
11
+ import json
12
+
13
+ class SupplierAnalysisApp:
14
+ def __init__(self):
15
+ self.api_key = None
16
+ self.analysis_results = None
17
+ self.current_agent = None
18
+ self.chat_history = []
19
+
20
+ def validate_api_key(self, api_key):
21
+ """Validate the OpenAI API key"""
22
+ if not api_key:
23
+ return "⚠️ Please enter your OpenAI API key"
24
+ self.api_key = api_key
25
+ return "βœ… API key set successfully"
26
+
27
+ def process_files(self, api_key, excel_file, audit_files):
28
+ """Process uploaded files and run analysis"""
29
+ if not api_key:
30
+ return "⚠️ Please enter your OpenAI API key first", None, None, None, None, None, None, None, None, None
31
+
32
+ if not excel_file:
33
+ return "⚠️ Please upload supplier data (Excel/CSV file)", None, None, None, None, None, None, None, None, None
34
+
35
+ if not audit_files:
36
+ return "⚠️ Please upload at least one audit report (Word document)", None, None, None, None, None, None, None, None, None
37
+
38
+ try:
39
+ # Initialize agent
40
+ self.current_agent = SupplierAgent(api_key)
41
+
42
+ # Handle Gradio NamedString objects - they are the file paths themselves
43
+ excel_path = str(excel_file) # Convert NamedString to string
44
+
45
+ # Handle audit files - they come as a list of strings
46
+ audit_paths = [str(f) for f in audit_files] if audit_files else []
47
+
48
+ print(f"Debug - excel_path: {excel_path}")
49
+ print(f"Debug - audit_paths: {audit_paths}")
50
+
51
+ # Verify files exist
52
+ if not os.path.exists(excel_path):
53
+ return f"⚠️ Excel file not found: {excel_path}", None, None, None, None, None, None, None, None, None, None, None, None
54
+
55
+ for audit_path in audit_paths:
56
+ if not os.path.exists(audit_path):
57
+ return f"⚠️ Audit file not found: {audit_path}", None, None, None, None, None, None, None, None, None, None, None, None
58
+
59
+ # Create file objects with .name attribute that the agent expects
60
+ class FileWithName:
61
+ def __init__(self, path):
62
+ self.name = path
63
+ self.path = path
64
+ self._file = None
65
+
66
+ def _ensure_file_open(self):
67
+ if self._file is None:
68
+ self._file = open(self.path, 'rb')
69
+
70
+ def read(self, size=-1):
71
+ self._ensure_file_open()
72
+ return self._file.read(size)
73
+
74
+ def seek(self, pos, whence=0):
75
+ self._ensure_file_open()
76
+ return self._file.seek(pos, whence)
77
+
78
+ def tell(self):
79
+ self._ensure_file_open()
80
+ return self._file.tell()
81
+
82
+ def seekable(self):
83
+ return True
84
+
85
+ def readable(self):
86
+ return True
87
+
88
+ def writable(self):
89
+ return False
90
+
91
+ def close(self):
92
+ if self._file is not None:
93
+ self._file.close()
94
+ self._file = None
95
+
96
+ def __enter__(self):
97
+ return self
98
+
99
+ def __exit__(self, exc_type, exc_val, exc_tb):
100
+ self.close()
101
+
102
+ def __getattr__(self, name):
103
+ # Delegate any other attributes to the underlying file object
104
+ self._ensure_file_open()
105
+ return getattr(self._file, name)
106
+
107
+ # Create file objects for the agent
108
+ excel_file_obj = FileWithName(excel_path)
109
+ audit_file_objs = [FileWithName(path) for path in audit_paths]
110
+
111
+ # Process data
112
+ result = self.current_agent.process_supplier_data(excel_file_obj, audit_file_objs)
113
+
114
+ if not result["success"]:
115
+ return f"❌ Analysis failed: {result['error']}", None, None, None, None, None, None, None, None, None
116
+
117
+ self.analysis_results = result["data"]
118
+
119
+ # Generate visualizations
120
+ scores = self.analysis_results["score_table"]
121
+
122
+ # Performance chart
123
+ fig = go.Figure(data=[
124
+ go.Bar(
125
+ x=list(scores.keys()),
126
+ y=list(scores.values()),
127
+ marker_color='lightblue',
128
+ marker_line_color='navy',
129
+ marker_line_width=2
130
+ )
131
+ ])
132
+
133
+ fig.update_layout(
134
+ title="Supplier Performance Scores",
135
+ xaxis_title="Suppliers",
136
+ yaxis_title="Performance Score",
137
+ template="plotly_white"
138
+ )
139
+
140
+ # Create metrics
141
+ avg_score = round(sum(scores.values()) / len(scores), 2)
142
+ best_supplier = max(scores, key=scores.get)
143
+ best_score = scores[best_supplier]
144
+
145
+ # Format performance weights
146
+ weights = self.analysis_results.get("weights", {})
147
+ weights_formatted = "\n".join([f"β€’ **{metric}**: {weight:.1%}" for metric, weight in weights.items()])
148
+
149
+ # Get AI-generated summary
150
+ ai_summary = self.analysis_results.get("summary", "No summary available.")
151
+
152
+ # Format detailed scores table
153
+ scores_df = ReportFormatter.format_score_table(scores)
154
+ scores_html = scores_df.to_html(index=False, classes="scores-table", table_id="scores-table")
155
+
156
+ # Format audit findings summary
157
+ audit_findings = self.analysis_results.get("audit_findings", {})
158
+ if audit_findings:
159
+ findings_df = ReportFormatter.format_findings_summary(audit_findings)
160
+ findings_html = findings_df.to_html(index=False, classes="findings-table", table_id="findings-table")
161
+ else:
162
+ findings_html = "<p>No audit findings available.</p>"
163
+
164
+ return (
165
+ "βœ… Analysis completed successfully!",
166
+ fig,
167
+ len(scores), # total_suppliers
168
+ avg_score, # avg_score
169
+ best_supplier, # top_performer
170
+ best_score, # best_score
171
+ weights_formatted, # performance_weights
172
+ ai_summary, # ai_summary
173
+ scores_html, # detailed_scores
174
+ findings_html # audit_findings
175
+ )
176
+
177
+ except Exception as e:
178
+ return f"❌ Error during analysis: {str(e)}", None, None, None, None, None, None, None, None, None
179
+
180
+ def chat_with_ai(self, question):
181
+ """Handle chat interactions"""
182
+ if not self.analysis_results:
183
+ return "⚠️ No analysis data available. Please run analysis first."
184
+
185
+ if not self.api_key:
186
+ return "⚠️ Please enter your OpenAI API key first."
187
+
188
+ if not question.strip():
189
+ return "⚠️ Please enter a question."
190
+
191
+ try:
192
+ from langchain_openai import ChatOpenAI
193
+
194
+ # Create data context
195
+ context = self._create_data_context()
196
+
197
+ llm = ChatOpenAI(model="gpt-3.5-turbo", api_key=self.api_key)
198
+
199
+ prompt = f"""
200
+ You are a supplier management expert analyzing performance data. Answer the user's question based on the supplier data provided.
201
+
202
+ SUPPLIER DATA CONTEXT:
203
+ {context}
204
+
205
+ USER QUESTION: {question}
206
+
207
+ Guidelines for your response:
208
+ - Be specific and reference actual data from the context
209
+ - Provide actionable insights and recommendations
210
+ - Use supplier names and specific scores/metrics when relevant
211
+ - Keep responses concise but comprehensive
212
+ - If the question can't be answered from the data, say so clearly
213
+ - Focus on business value and practical next steps
214
+
215
+ Answer:
216
+ """
217
+
218
+ result = llm.invoke(prompt)
219
+ response = result.content
220
+
221
+ # Add to chat history
222
+ self.chat_history.append((question, response))
223
+
224
+ # Format chat history for display
225
+ formatted_history = ""
226
+ for i, (q, a) in enumerate(self.chat_history, 1):
227
+ formatted_history += f"**Question {i}:** {q}\n\n**Answer:** {a}\n\n---\n\n"
228
+
229
+ return formatted_history
230
+
231
+ except Exception as e:
232
+ return f"❌ Error: {str(e)}"
233
+
234
+ def chat_with_suggested_question(self, question):
235
+ """Handle suggested question clicks"""
236
+ return self.chat_with_ai(question)
237
+
238
+ def clear_chat(self):
239
+ """Clear chat history"""
240
+ self.chat_history = []
241
+ return ""
242
+
243
+ def _create_data_context(self):
244
+ """Create context for AI chat"""
245
+ scores = self.analysis_results.get("score_table", {})
246
+ findings = self.analysis_results.get("audit_findings", {})
247
+ weights = self.analysis_results.get("weights", {})
248
+ summary = self.analysis_results.get("summary", "")
249
+ supplier_data = self.analysis_results.get("structured_data", [])
250
+
251
+ context = f"""
252
+ SUPPLIER PERFORMANCE ANALYSIS CONTEXT:
253
+
254
+ PERFORMANCE SCORES:
255
+ {', '.join([f"{supplier}: {score}" for supplier, score in scores.items()])}
256
+
257
+ AUDIT FINDINGS:
258
+ {', '.join([f"{supplier}: {count} findings" for supplier, count in findings.items()])}
259
+
260
+ PERFORMANCE WEIGHTS USED:
261
+ {', '.join([f"{metric}: {weight:.1%}" for metric, weight in weights.items()])}
262
+
263
+ DETAILED SUPPLIER DATA:
264
+ """
265
+
266
+ for supplier in supplier_data:
267
+ context += f"\n{supplier.get('Supplier', 'Unknown')}: "
268
+ context += f"On-Time Delivery: {supplier.get('OnTimeDeliveryRate', 'N/A')}%, "
269
+ context += f"Defect Rate: {supplier.get('DefectRate', 'N/A')}%"
270
+
271
+ context += f"\n\nAI GENERATED SUMMARY:\n{summary}"
272
+
273
+ return context
274
+
275
+ def generate_report(self):
276
+ """Generate and return Word report"""
277
+ if not self.current_agent or not self.analysis_results:
278
+ return None
279
+
280
+ try:
281
+ doc_bytes = self.current_agent.save_to_doc(self.analysis_results)
282
+ # Create a temporary file for the report
283
+ report_temp = tempfile.NamedTemporaryFile(delete=False, suffix='.docx')
284
+ with open(report_temp.name, 'wb') as f:
285
+ f.write(doc_bytes)
286
+ return report_temp.name
287
+ except Exception:
288
+ return None
289
+
290
+
291
+
292
+ def main():
293
+ app = SupplierAnalysisApp()
294
+
295
+ # Notion-inspired CSS styling with light blue accents and dark mode support
296
+ css = """
297
+ /* Light mode (default) */
298
+ .gradio-container {
299
+ --bg-primary: #ffffff;
300
+ --bg-secondary: #f8fafc;
301
+ --bg-tertiary: #f1f5f9;
302
+ --text-primary: #1e293b;
303
+ --text-secondary: #475569;
304
+ --text-tertiary: #64748b;
305
+ --border-primary: #e2e8f0;
306
+ --border-secondary: #cbd5e1;
307
+ --shadow-light: rgba(0, 0, 0, 0.05);
308
+ --shadow-medium: rgba(0, 0, 0, 0.1);
309
+ max-width: 100% !important;
310
+ padding: 0 !important;
311
+ }
312
+
313
+ /* Dark mode */
314
+ .dark .gradio-container,
315
+ [data-theme="dark"] .gradio-container,
316
+ .gradio-container.dark {
317
+ --bg-primary: #1e293b;
318
+ --bg-secondary: #334155;
319
+ --bg-tertiary: #475569;
320
+ --text-primary: #f1f5f9;
321
+ --text-secondary: #cbd5e1;
322
+ --text-tertiary: #94a3b8;
323
+ --border-primary: #475569;
324
+ --border-secondary: #64748b;
325
+ --shadow-light: rgba(0, 0, 0, 0.2);
326
+ --shadow-medium: rgba(0, 0, 0, 0.3);
327
+ }
328
+
329
+ /* Auto-detect system dark mode */
330
+ @media (prefers-color-scheme: dark) {
331
+ .gradio-container:not(.light) {
332
+ --bg-primary: #1e293b;
333
+ --bg-secondary: #334155;
334
+ --bg-tertiary: #475569;
335
+ --text-primary: #f1f5f9;
336
+ --text-secondary: #cbd5e1;
337
+ --text-tertiary: #94a3b8;
338
+ --border-primary: #475569;
339
+ --border-secondary: #64748b;
340
+ --shadow-light: rgba(0, 0, 0, 0.2);
341
+ --shadow-medium: rgba(0, 0, 0, 0.3);
342
+ }
343
+ }
344
+
345
+ /* Container layout adjustments */
346
+ .gradio-container .main {
347
+ gap: 1rem !important;
348
+ padding: 1rem !important;
349
+ }
350
+
351
+ .main-content {
352
+ background: var(--bg-secondary) !important;
353
+ padding: 1.5rem !important;
354
+ border-radius: 16px !important;
355
+ border: 1px solid var(--border-primary) !important;
356
+ box-shadow: 0 4px 6px var(--shadow-light) !important;
357
+ margin-left: 1rem !important;
358
+ width: 100% !important;
359
+ min-height: 800px !important;
360
+ }
361
+
362
+ /* Improved metrics grid */
363
+ .metrics-grid {
364
+ display: grid;
365
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) !important;
366
+ gap: 1rem !important;
367
+ margin: 1rem 0 !important;
368
+ }
369
+
370
+ /* Section cards */
371
+ .section-card {
372
+ background: var(--bg-primary);
373
+ border: 1px solid var(--border-primary);
374
+ border-radius: 16px;
375
+ padding: 1.5rem;
376
+ margin: 1rem 0;
377
+ box-shadow: 0 2px 4px var(--shadow-light);
378
+ transition: all 0.2s ease;
379
+ width: 100% !important;
380
+ }
381
+
382
+ .section-card:hover {
383
+ box-shadow: 0 6px 12px var(--shadow-medium);
384
+ border-color: var(--border-secondary);
385
+ transform: translateY(-1px);
386
+ }
387
+
388
+ .section-header {
389
+ font-size: 1.5rem;
390
+ font-weight: 600;
391
+ color: var(--text-primary);
392
+ margin-bottom: 1.5rem;
393
+ display: flex;
394
+ align-items: center;
395
+ gap: 0.75rem;
396
+ border-bottom: 1px solid var(--border-primary);
397
+ padding-bottom: 1rem;
398
+ letter-spacing: -0.025em;
399
+ }
400
+
401
+ /* Upload section */
402
+ .upload-section {
403
+ background: var(--bg-primary);
404
+ border: 2px dashed var(--border-secondary);
405
+ border-radius: 16px;
406
+ padding: 2rem !important;
407
+ margin: 1rem 0 !important;
408
+ transition: all 0.3s ease;
409
+ text-align: center;
410
+ width: 100% !important;
411
+ }
412
+
413
+ .upload-section:hover {
414
+ border-color: #3b82f6;
415
+ background: rgba(59, 130, 246, 0.05);
416
+ }
417
+
418
+ /* Make plots and tables utilize full width */
419
+ .gradio-plot, .scores-table, .findings-table {
420
+ width: 100% !important;
421
+ max-width: none !important;
422
+ }
423
+
424
+ /* Adjust chat section for better space utilization */
425
+ .chat-section {
426
+ width: 100% !important;
427
+ max-width: none !important;
428
+ }
429
+
430
+ /* Header adjustments */
431
+ .app-header {
432
+ margin: 1rem !important;
433
+ padding: 2.5rem 2rem !important;
434
+ width: calc(100% - 2rem) !important;
435
+ }
436
+
437
+ .app-header {
438
+ background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
439
+ padding: 3rem 2rem;
440
+ border-radius: 16px;
441
+ text-align: center;
442
+ margin-bottom: 2rem;
443
+ color: white;
444
+ box-shadow: 0 8px 32px rgba(59, 130, 246, 0.15);
445
+ border: 1px solid rgba(255, 255, 255, 0.1);
446
+ }
447
+ .app-title {
448
+ font-size: 2.75rem;
449
+ font-weight: 600;
450
+ margin-bottom: 0.75rem;
451
+ text-shadow: 0 2px 4px rgba(0,0,0,0.1);
452
+ letter-spacing: -0.025em;
453
+ }
454
+ .app-subtitle {
455
+ font-size: 1.125rem;
456
+ opacity: 0.9;
457
+ margin-bottom: 0;
458
+ font-weight: 400;
459
+ letter-spacing: 0.025em;
460
+ }
461
+ .sidebar {
462
+ background: var(--bg-secondary) !important;
463
+ padding: 2rem !important;
464
+ border-radius: 16px !important;
465
+ min-height: 800px !important;
466
+ border: 1px solid var(--border-primary) !important;
467
+ box-shadow: 0 4px 6px var(--shadow-light) !important;
468
+ }
469
+ .sidebar h3 {
470
+ color: var(--text-primary) !important;
471
+ margin-bottom: 1.5rem !important;
472
+ font-size: 1.25rem !important;
473
+ font-weight: 600 !important;
474
+ display: flex !important;
475
+ align-items: center !important;
476
+ gap: 0.75rem !important;
477
+ letter-spacing: -0.025em !important;
478
+ }
479
+ .nav-button {
480
+ width: 100% !important;
481
+ margin-bottom: 0.75rem !important;
482
+ text-align: left !important;
483
+ background: var(--bg-primary) !important;
484
+ border: 1px solid var(--border-primary) !important;
485
+ color: var(--text-secondary) !important;
486
+ padding: 14px 18px !important;
487
+ border-radius: 12px !important;
488
+ transition: all 0.2s ease !important;
489
+ font-weight: 500 !important;
490
+ box-shadow: 0 1px 3px var(--shadow-light) !important;
491
+ }
492
+ .nav-button:hover {
493
+ background: var(--bg-tertiary) !important;
494
+ border-color: var(--border-secondary) !important;
495
+ transform: translateY(-1px) !important;
496
+ box-shadow: 0 4px 6px var(--shadow-light) !important;
497
+ }
498
+ .nav-button.active {
499
+ background: rgba(59, 130, 246, 0.15) !important;
500
+ border-color: #3b82f6 !important;
501
+ color: #3b82f6 !important;
502
+ box-shadow: 0 4px 6px rgba(59, 130, 246, 0.15) !important;
503
+ }
504
+ .info-banner {
505
+ background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
506
+ border: 1px solid #bfdbfe;
507
+ padding: 1.25rem 1.5rem;
508
+ border-radius: 12px;
509
+ margin: 1.5rem 0;
510
+ text-align: center;
511
+ color: #1e40af;
512
+ font-weight: 500;
513
+ box-shadow: 0 1px 3px rgba(59, 130, 246, 0.1);
514
+ }
515
+ .metric-card {
516
+ background: rgba(59, 130, 246, 0.08);
517
+ border: 1px solid rgba(59, 130, 246, 0.2);
518
+ border-radius: 12px;
519
+ padding: 1.5rem;
520
+ text-align: center;
521
+ transition: all 0.2s ease;
522
+ box-shadow: 0 1px 3px var(--shadow-light);
523
+ }
524
+ .metric-card:hover {
525
+ transform: translateY(-2px);
526
+ box-shadow: 0 4px 6px var(--shadow-medium);
527
+ border-color: rgba(59, 130, 246, 0.4);
528
+ }
529
+ .metric-label {
530
+ font-size: 0.875rem;
531
+ color: var(--text-tertiary);
532
+ font-weight: 500;
533
+ margin-bottom: 0.75rem;
534
+ text-transform: uppercase;
535
+ letter-spacing: 0.05em;
536
+ }
537
+ .metric-value {
538
+ font-size: 2rem;
539
+ font-weight: 700;
540
+ color: var(--text-primary);
541
+ letter-spacing: -0.025em;
542
+ }
543
+ .scores-table, .findings-table {
544
+ width: 100%;
545
+ border-collapse: collapse;
546
+ margin: 1.5rem 0;
547
+ background: var(--bg-primary);
548
+ border-radius: 12px;
549
+ overflow: hidden;
550
+ box-shadow: 0 1px 3px var(--shadow-light);
551
+ border: 1px solid var(--border-primary);
552
+ }
553
+ .scores-table th, .findings-table th {
554
+ background: var(--bg-secondary);
555
+ color: var(--text-secondary);
556
+ font-weight: 600;
557
+ padding: 16px 20px;
558
+ text-align: left;
559
+ border-bottom: 1px solid var(--border-primary);
560
+ font-size: 0.875rem;
561
+ text-transform: uppercase;
562
+ letter-spacing: 0.05em;
563
+ }
564
+ .scores-table td, .findings-table td {
565
+ padding: 16px 20px;
566
+ border-bottom: 1px solid var(--border-primary);
567
+ color: var(--text-secondary);
568
+ font-weight: 500;
569
+ }
570
+ .scores-table tr:last-child td, .findings-table tr:last-child td {
571
+ border-bottom: none;
572
+ }
573
+ .scores-table tr:hover, .findings-table tr:hover {
574
+ background: rgba(59, 130, 246, 0.05);
575
+ }
576
+ .gradio-button[variant="primary"] {
577
+ background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important;
578
+ border: none !important;
579
+ color: white !important;
580
+ font-weight: 600 !important;
581
+ padding: 12px 24px !important;
582
+ border-radius: 12px !important;
583
+ box-shadow: 0 4px 6px rgba(59, 130, 246, 0.25) !important;
584
+ transition: all 0.2s ease !important;
585
+ }
586
+ .gradio-button[variant="primary"]:hover {
587
+ transform: translateY(-1px) !important;
588
+ box-shadow: 0 6px 8px rgba(59, 130, 246, 0.3) !important;
589
+ }
590
+ .gradio-textbox, .gradio-file {
591
+ border-radius: 12px !important;
592
+ border-color: var(--border-primary) !important;
593
+ box-shadow: 0 1px 3px var(--shadow-light) !important;
594
+ background: var(--bg-primary) !important;
595
+ color: var(--text-primary) !important;
596
+ }
597
+ .gradio-textbox:focus, .gradio-file:focus-within {
598
+ border-color: #3b82f6 !important;
599
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
600
+ }
601
+ .analysis-results {
602
+ margin-top: 1.5rem;
603
+ padding-top: 1.5rem;
604
+ border-top: 1px solid var(--border-primary);
605
+ }
606
+
607
+ /* Ensure text inputs are properly styled in dark mode */
608
+ .gradio-textbox input, .gradio-textbox textarea {
609
+ background: var(--bg-primary) !important;
610
+ color: var(--text-primary) !important;
611
+ border-color: var(--border-primary) !important;
612
+ }
613
+
614
+ /* File upload area styling */
615
+ .gradio-file {
616
+ background: var(--bg-primary) !important;
617
+ color: var(--text-primary) !important;
618
+ }
619
+
620
+ /* Ensure labels are visible in dark mode */
621
+ label, .gradio-label {
622
+ color: var(--text-secondary) !important;
623
+ }
624
+
625
+ /* Additional text elements */
626
+ .gradio-markdown p, .gradio-html {
627
+ color: var(--text-primary) !important;
628
+ }
629
+
630
+ /* Fix all text visibility issues in dark mode */
631
+ .gradio-textbox input::placeholder,
632
+ .gradio-textbox textarea::placeholder {
633
+ color: var(--text-tertiary) !important;
634
+ opacity: 0.7 !important;
635
+ }
636
+
637
+ /* Ensure all body text is readable */
638
+ .gradio-container * {
639
+ color: var(--text-primary) !important;
640
+ }
641
+
642
+ /* Override for specific elements that should keep their colors */
643
+ .app-header, .app-header * {
644
+ color: white !important;
645
+ }
646
+
647
+ .info-banner, .info-banner * {
648
+ color: #1e40af !important;
649
+ }
650
+
651
+ .nav-button.active, .nav-button.active * {
652
+ color: #3b82f6 !important;
653
+ }
654
+
655
+ /* Gradio specific text elements */
656
+ .gradio-textbox label,
657
+ .gradio-file label,
658
+ .gradio-number label,
659
+ .gradio-markdown label,
660
+ .gradio-plot label,
661
+ .gradio-html label,
662
+ .gradio-button {
663
+ color: var(--text-primary) !important;
664
+ }
665
+
666
+ /* Form descriptions and help text */
667
+ .gradio-form .description,
668
+ .gradio-textbox .description,
669
+ .gradio-file .description {
670
+ color: var(--text-tertiary) !important;
671
+ }
672
+
673
+ /* Ensure status messages are visible */
674
+ .gradio-textbox input[readonly],
675
+ .gradio-textbox textarea[readonly] {
676
+ color: var(--text-secondary) !important;
677
+ background: var(--bg-tertiary) !important;
678
+ }
679
+
680
+ /* File upload text */
681
+ .gradio-file .file-preview,
682
+ .gradio-file .upload-text {
683
+ color: var(--text-primary) !important;
684
+ }
685
+
686
+ /* Secondary buttons */
687
+ .gradio-button[variant="secondary"] {
688
+ background: var(--bg-tertiary) !important;
689
+ border: 1px solid var(--border-primary) !important;
690
+ color: var(--text-primary) !important;
691
+ font-weight: 500 !important;
692
+ padding: 10px 20px !important;
693
+ border-radius: 8px !important;
694
+ }
695
+
696
+ .gradio-button[variant="secondary"]:hover {
697
+ background: var(--bg-primary) !important;
698
+ border-color: var(--border-secondary) !important;
699
+ }
700
+
701
+ /* Number input styling */
702
+ .gradio-number input {
703
+ background: var(--bg-primary) !important;
704
+ color: var(--text-primary) !important;
705
+ border-color: var(--border-primary) !important;
706
+ }
707
+
708
+ /* Plot/chart container */
709
+ .gradio-plot {
710
+ background: var(--bg-primary) !important;
711
+ border: 1px solid var(--border-primary) !important;
712
+ border-radius: 12px !important;
713
+ }
714
+
715
+ /* Ensure all interactive elements are visible */
716
+ button, input, textarea, select {
717
+ color: var(--text-primary) !important;
718
+ }
719
+
720
+ /* Fix any remaining text contrast issues */
721
+ p, span, div, h1, h2, h3, h4, h5, h6 {
722
+ color: inherit !important;
723
+ }
724
+
725
+ /* Special handling for custom HTML content */
726
+ .section-header * {
727
+ color: var(--text-primary) !important;
728
+ }
729
+
730
+ /* Upload area text */
731
+ .upload-section * {
732
+ color: var(--text-secondary) !important;
733
+ }
734
+
735
+ /* Metric card text is already handled, but ensure consistency */
736
+ .metric-card .metric-label {
737
+ color: var(--text-tertiary) !important;
738
+ }
739
+
740
+ .metric-card .metric-value {
741
+ color: var(--text-primary) !important;
742
+ }
743
+
744
+ /* Remove first section card margin to align with header */
745
+ .section-card:first-child {
746
+ margin-top: 0;
747
+ }
748
+
749
+ /* Sidebar width control */
750
+ .sidebar {
751
+ max-width: 280px !important;
752
+ min-width: 250px !important;
753
+ width: 100% !important;
754
+ }
755
+
756
+ /* Enhanced button styling */
757
+ .action-button {
758
+ background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%) !important;
759
+ color: white !important;
760
+ font-weight: 600 !important;
761
+ padding: 14px 28px !important;
762
+ border-radius: 12px !important;
763
+ box-shadow: 0 4px 12px rgba(59, 130, 246, 0.25) !important;
764
+ transition: all 0.2s ease !important;
765
+ border: none !important;
766
+ cursor: pointer !important;
767
+ position: relative !important;
768
+ overflow: hidden !important;
769
+ }
770
+
771
+ .action-button::before {
772
+ content: "" !important;
773
+ position: absolute !important;
774
+ top: 0 !important;
775
+ left: 0 !important;
776
+ width: 100% !important;
777
+ height: 100% !important;
778
+ background: linear-gradient(rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0)) !important;
779
+ opacity: 0 !important;
780
+ transition: opacity 0.2s ease !important;
781
+ }
782
+
783
+ .action-button:hover {
784
+ transform: translateY(-2px) !important;
785
+ box-shadow: 0 6px 16px rgba(59, 130, 246, 0.35) !important;
786
+ }
787
+
788
+ .action-button:hover::before {
789
+ opacity: 1 !important;
790
+ }
791
+
792
+ .action-button:active {
793
+ transform: translateY(1px) !important;
794
+ box-shadow: 0 2px 8px rgba(59, 130, 246, 0.2) !important;
795
+ }
796
+
797
+ /* Chat suggestion buttons */
798
+ .suggestion-button {
799
+ background: rgba(59, 130, 246, 0.1) !important;
800
+ color: #3b82f6 !important;
801
+ font-weight: 500 !important;
802
+ padding: 12px 20px !important;
803
+ border-radius: 10px !important;
804
+ border: 1px solid rgba(59, 130, 246, 0.2) !important;
805
+ transition: all 0.2s ease !important;
806
+ cursor: pointer !important;
807
+ width: 100% !important;
808
+ text-align: left !important;
809
+ margin-bottom: 8px !important;
810
+ display: flex !important;
811
+ align-items: center !important;
812
+ gap: 8px !important;
813
+ }
814
+
815
+ .suggestion-button:hover {
816
+ background: rgba(59, 130, 246, 0.15) !important;
817
+ border-color: rgba(59, 130, 246, 0.4) !important;
818
+ transform: translateY(-1px) !important;
819
+ box-shadow: 0 4px 8px rgba(59, 130, 246, 0.15) !important;
820
+ }
821
+
822
+ .suggestion-button:active {
823
+ transform: translateY(0) !important;
824
+ box-shadow: 0 2px 4px rgba(59, 130, 246, 0.1) !important;
825
+ }
826
+
827
+ /* Download section styling */
828
+ .download-section {
829
+ display: flex !important;
830
+ align-items: center !important;
831
+ gap: 16px !important;
832
+ background: rgba(59, 130, 246, 0.05) !important;
833
+ padding: 20px !important;
834
+ border-radius: 12px !important;
835
+ border: 1px dashed rgba(59, 130, 246, 0.2) !important;
836
+ margin-top: 16px !important;
837
+ }
838
+
839
+ .download-section .action-button {
840
+ flex-shrink: 0 !important;
841
+ }
842
+
843
+ /* Ensure all buttons have pointer cursor */
844
+ .gradio-button {
845
+ cursor: pointer !important;
846
+ }
847
+ """
848
+
849
+ def switch_page(page):
850
+ """Switch between different pages"""
851
+ if page == "analysis":
852
+ return (
853
+ gr.update(visible=True), # analysis_section
854
+ gr.update(visible=False), # chat_section
855
+ gr.update(variant="primary", elem_classes="nav-button active"), # analysis_btn
856
+ gr.update(variant="secondary", elem_classes="nav-button") # chat_btn
857
+ )
858
+ else: # chat
859
+ return (
860
+ gr.update(visible=False), # analysis_section
861
+ gr.update(visible=True), # chat_section
862
+ gr.update(variant="secondary", elem_classes="nav-button"), # analysis_btn
863
+ gr.update(variant="primary", elem_classes="nav-button active") # chat_btn
864
+ )
865
+
866
+ # Define the interface
867
+ with gr.Blocks(title="Supplier Management Agent", theme=gr.themes.Default(), css=css) as interface:
868
+ # Header
869
+ gr.HTML("""
870
+ <div class="app-header">
871
+ <div class="app-title">🏭 Supplier Management Agent</div>
872
+ <div class="app-subtitle">AI-Powered Supplier Performance Analysis using LangGraph and Gradio</div>
873
+ </div>
874
+ """)
875
+
876
+ with gr.Row():
877
+ # Sidebar
878
+ with gr.Column(scale=1, elem_classes="sidebar", min_width=250):
879
+ gr.HTML('<h3>🧭 Navigation</h3>')
880
+
881
+ analysis_nav_btn = gr.Button("πŸ“Š Analysis", variant="primary", elem_classes="nav-button active")
882
+ chat_nav_btn = gr.Button("πŸ’¬ Chat with AI", variant="secondary", elem_classes="nav-button")
883
+
884
+ gr.HTML('<hr style="border-color: #e2e8f0; margin: 2rem 0;">')
885
+
886
+ # API Key section in sidebar
887
+ gr.HTML('<h3>πŸ”‘ API Configuration</h3>')
888
+ api_key_input = gr.Textbox(
889
+ label="OpenAI API Key",
890
+ placeholder="Enter your OpenAI API key",
891
+ type="password",
892
+ container=True
893
+ )
894
+ api_status = gr.Textbox(label="Status", interactive=False, container=True)
895
+
896
+ # Main content area - increased scale for wider content
897
+ with gr.Column(scale=8, elem_classes="main-content"):
898
+ # Analysis Section
899
+ with gr.Group(visible=True) as analysis_section:
900
+ # Upload section
901
+ with gr.Group(elem_classes="section-card"):
902
+ gr.HTML('<div class="section-header">πŸ“ Upload Your Data</div>')
903
+ gr.HTML('<div class="info-banner">πŸ‘‡ Upload your supplier data and audit reports to get started with AI-powered analysis!</div>')
904
+
905
+ with gr.Row():
906
+ with gr.Column():
907
+ gr.HTML('<div style="font-size: 1.125rem; font-weight: 600; color: #374151; margin-bottom: 1rem;">πŸ“Š Supplier Data</div>')
908
+ excel_file = gr.File(
909
+ label="Upload Excel/CSV file",
910
+ file_types=[".xlsx", ".xls", ".csv"],
911
+ height=120,
912
+ elem_classes="upload-section"
913
+ )
914
+ with gr.Column():
915
+ gr.HTML('<div style="font-size: 1.125rem; font-weight: 600; color: #374151; margin-bottom: 1rem;">πŸ“‹ Audit Reports</div>')
916
+ audit_files = gr.File(
917
+ label="Upload Word documents",
918
+ file_types=[".docx"],
919
+ file_count="multiple",
920
+ height=120,
921
+ elem_classes="upload-section"
922
+ )
923
+
924
+ # Analysis button
925
+ with gr.Row():
926
+ analyze_btn = gr.Button("πŸš€ Run Analysis", elem_classes="action-button", size="lg")
927
+ analysis_status = gr.Textbox(label="Analysis Status", interactive=False)
928
+
929
+ # Analysis Results Container (hidden initially)
930
+ with gr.Group(visible=False, elem_classes="analysis-results") as results_container:
931
+ # Key Metrics section
932
+ with gr.Group(elem_classes="section-card"):
933
+ gr.HTML('<div class="section-header">πŸ“Š Key Metrics</div>')
934
+ with gr.Row(elem_classes="metrics-grid"):
935
+ with gr.Column(elem_classes="metric-card"):
936
+ total_suppliers = gr.Number(label="Total Suppliers", interactive=False)
937
+ with gr.Column(elem_classes="metric-card"):
938
+ avg_score = gr.Number(label="Average Score", interactive=False)
939
+ with gr.Column(elem_classes="metric-card"):
940
+ top_performer = gr.Textbox(label="Top Performer", interactive=False)
941
+ with gr.Column(elem_classes="metric-card"):
942
+ best_score = gr.Number(label="Best Score", interactive=False)
943
+
944
+ # Performance Scores section
945
+ with gr.Group(elem_classes="section-card"):
946
+ gr.HTML('<div class="section-header">πŸ“ˆ Performance Scores</div>')
947
+ performance_plot = gr.Plot(label="Performance Chart")
948
+
949
+ # Performance Weights section
950
+ with gr.Group(elem_classes="section-card"):
951
+ gr.HTML('<div class="section-header">βš–οΈ Performance Weights</div>')
952
+ performance_weights = gr.Markdown(label="Weights Used in Analysis")
953
+
954
+ # AI-Generated Summary section
955
+ with gr.Group(elem_classes="section-card"):
956
+ gr.HTML('<div class="section-header">πŸ€– AI-Generated Summary</div>')
957
+ ai_summary = gr.Markdown(label="Analysis Summary")
958
+
959
+ # Detailed Scores section
960
+ with gr.Group(elem_classes="section-card"):
961
+ gr.HTML('<div class="section-header">πŸ“‹ Detailed Scores</div>')
962
+ detailed_scores = gr.HTML(label="Supplier Performance Details")
963
+
964
+ # Audit Findings Summary section
965
+ with gr.Group(elem_classes="section-card"):
966
+ gr.HTML('<div class="section-header">πŸ” Audit Findings Summary</div>')
967
+ audit_findings = gr.HTML(label="Audit Results Overview")
968
+
969
+ # Download Report section
970
+ with gr.Group(elem_classes="section-card"):
971
+ gr.HTML('<div class="section-header">πŸ“₯ Download Report</div>')
972
+ with gr.Row(elem_classes="download-section"):
973
+ report_btn = gr.Button("πŸ“„ Generate Word Report", elem_classes="action-button", size="lg")
974
+ report_download = gr.File(label="Download Report")
975
+
976
+ # Chat Section
977
+ with gr.Group(visible=False) as chat_section:
978
+ with gr.Group(elem_classes="section-card"):
979
+ gr.HTML('<div class="section-header">πŸ’¬ Ask Questions About Your Supplier Data</div>')
980
+
981
+ # Chat interface
982
+ with gr.Row():
983
+ with gr.Column():
984
+ chat_input = gr.Textbox(
985
+ label="Your Question",
986
+ placeholder="e.g., Which supplier should I focus on improving first?",
987
+ lines=2
988
+ )
989
+ with gr.Row():
990
+ chat_btn = gr.Button("Send Question", elem_classes="action-button")
991
+ clear_btn = gr.Button("Clear Chat", variant="secondary")
992
+
993
+ chat_output = gr.Markdown(label="Chat History", value="")
994
+
995
+ # Suggested questions
996
+ gr.HTML('<div style="font-size: 1.25rem; font-weight: 600; color: #374151; margin: 2rem 0 1rem 0;">πŸ’‘ Suggested Questions</div>')
997
+ suggested_questions = [
998
+ "Who is my best performing supplier and why?",
999
+ "Which suppliers need immediate improvement?",
1000
+ "What are the biggest risks in my supplier base?",
1001
+ "How can I improve overall supplier performance?",
1002
+ "What should be my top 3 priorities?",
1003
+ "Which suppliers have the most audit findings?"
1004
+ ]
1005
+
1006
+ with gr.Row():
1007
+ for i in range(0, len(suggested_questions), 2):
1008
+ with gr.Column():
1009
+ if i < len(suggested_questions):
1010
+ q1_btn = gr.Button(f"πŸ’­ {suggested_questions[i]}", elem_classes="suggestion-button")
1011
+ q1_btn.click(
1012
+ fn=lambda q=suggested_questions[i]: app.chat_with_suggested_question(q),
1013
+ outputs=chat_output
1014
+ )
1015
+ if i + 1 < len(suggested_questions):
1016
+ q2_btn = gr.Button(f"πŸ’­ {suggested_questions[i+1]}", elem_classes="suggestion-button")
1017
+ q2_btn.click(
1018
+ fn=lambda q=suggested_questions[i+1]: app.chat_with_suggested_question(q),
1019
+ outputs=chat_output
1020
+ )
1021
+
1022
+ # Set up event handlers
1023
+ api_key_input.change(
1024
+ app.validate_api_key,
1025
+ inputs=[api_key_input],
1026
+ outputs=[api_status]
1027
+ )
1028
+
1029
+ # Navigation handlers
1030
+ analysis_nav_btn.click(
1031
+ fn=lambda: switch_page("analysis"),
1032
+ outputs=[analysis_section, chat_section, analysis_nav_btn, chat_nav_btn]
1033
+ )
1034
+
1035
+ chat_nav_btn.click(
1036
+ fn=lambda: switch_page("chat"),
1037
+ outputs=[analysis_section, chat_section, analysis_nav_btn, chat_nav_btn]
1038
+ )
1039
+
1040
+ # Modified analyze button to show results container on success
1041
+ def analyze_and_show_results(api_key, excel_file, audit_files):
1042
+ result = app.process_files(api_key, excel_file, audit_files)
1043
+ # Check if analysis was successful (status message starts with βœ…)
1044
+ success = result[0].startswith("βœ…") if result[0] else False
1045
+ return result + (gr.update(visible=success),) # Show results container if successful
1046
+
1047
+ analyze_btn.click(
1048
+ analyze_and_show_results,
1049
+ inputs=[api_key_input, excel_file, audit_files],
1050
+ outputs=[analysis_status, performance_plot, total_suppliers, avg_score, top_performer, best_score, performance_weights, ai_summary, detailed_scores, audit_findings, results_container]
1051
+ )
1052
+
1053
+ chat_btn.click(
1054
+ app.chat_with_ai,
1055
+ inputs=[chat_input],
1056
+ outputs=[chat_output]
1057
+ )
1058
+
1059
+ clear_btn.click(
1060
+ app.clear_chat,
1061
+ outputs=[chat_output]
1062
+ )
1063
+
1064
+ report_btn.click(
1065
+ app.generate_report,
1066
+ outputs=[report_download]
1067
+ )
1068
+
1069
+
1070
+
1071
+ # Launch the interface
1072
+ interface.launch(share=False)
1073
+
1074
+ if __name__ == "__main__":
1075
+ main()
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ pandas
2
+ plotly
3
+ matplotlib
4
+ openpyxl
5
+ python-docx
6
+ langgraph
7
+ langchain-openai
8
+ langchain
9
+ python-dotenv
10
+ Pillow
11
+ gradio