Spaces:
Sleeping
Sleeping
Download web_server.py from dharu007/ArtifaXChatBot: direct link, hf CLI and curl.
- Browser
- Download file 30.8 kB
-
https://huggingface.co/spaces/dharu007/ArtifaXChatBot/resolve/main/web_server.py
- Command line
-
hf download hf://spaces/dharu007/ArtifaXChatBot/web_server.py
-
curl -L -o web_server.py https://huggingface.co/spaces/dharu007/ArtifaXChatBot/resolve/main/web_server.py
30.8 kB
| #!/usr/bin/env python3 | |
| # Removed asyncio and subprocess - not needed for direct agent calls | |
| import json | |
| import os | |
| import sys | |
| import re | |
| sys.path.append(os.path.join(os.path.dirname(__file__), 'agents')) | |
| from flask import Flask, render_template, request, jsonify | |
| from werkzeug.utils import secure_filename | |
| from docx import Document | |
| import PyPDF2 | |
| import io | |
| from flask_socketio import SocketIO | |
| from mapping_agent import MappingAgent | |
| from testcase_agent import TestCaseAgent | |
| from sequence_agent import SequenceAgent | |
| from conversation_agent import ConversationAgent | |
| from content_filter import ContentFilter | |
| from jira_client import JIRAClient | |
| from document_generator import DocumentGenerator | |
| from mermaid_png_generator import MermaidPNGGenerator | |
| from chat_logger import ChatLogger | |
| from csv_generator import CSVGenerator | |
| from stats_manager import StatsManager | |
| from email_sender import EmailSender | |
| import urllib.parse | |
| import base64 | |
| def extract_json_from_response(response): | |
| """Extract JSON data from agent response""" | |
| try: | |
| # Look for JSON code block | |
| json_pattern = r'```json\s*({.*?})\s*```' | |
| match = re.search(json_pattern, response, re.DOTALL) | |
| if match: | |
| json_str = match.group(1) | |
| return json.loads(json_str) | |
| except: | |
| pass | |
| return None | |
| app = Flask(__name__) | |
| app.config['JSON_AS_ASCII'] = False | |
| socketio = SocketIO(app, cors_allowed_origins="*", async_mode='eventlet') | |
| # Store chat sessions | |
| chat_sessions = {} | |
| # Direct agent architecture - no MCP protocol used | |
| def load_ui_config(): | |
| config_path = os.path.join('config', 'ui_config.json') | |
| with open(config_path, 'r') as f: | |
| return json.load(f) | |
| ui_config = load_ui_config() | |
| content_filter = ContentFilter() | |
| jira_client = JIRAClient() | |
| doc_generator = DocumentGenerator() | |
| png_generator = MermaidPNGGenerator() | |
| chat_logger = ChatLogger() | |
| csv_generator = CSVGenerator() | |
| stats_manager = StatsManager() | |
| email_sender = EmailSender() | |
| # Store last generated output for JIRA posting with user isolation | |
| last_generated_output = {} | |
| # User session mapping to track which sessions belong to which user | |
| user_sessions = {} | |
| def index(): | |
| return render_template('chat.html', welcome_message=ui_config['welcome_message']) | |
| def modern(): | |
| return render_template('chat_modern.html', welcome_message=ui_config['welcome_message']) | |
| def upload_file(): | |
| try: | |
| if 'file' not in request.files: | |
| return jsonify({'error': 'No file uploaded'}), 400 | |
| file = request.files['file'] | |
| if file.filename == '': | |
| return jsonify({'error': 'No file selected'}), 400 | |
| # Check file extension and determine agent type | |
| filename_lower = file.filename.lower() | |
| if filename_lower.endswith(('.json', '.xml')): | |
| # Mapping documents | |
| agent_type = 'mapping' | |
| elif filename_lower.endswith(('.docx', '.pdf', '.txt')): | |
| # Determine if it's FRD or process flow based on content or filename | |
| if 'frd' in filename_lower or 'requirement' in filename_lower or 'functional' in filename_lower: | |
| agent_type = 'testcase' | |
| elif 'process' in filename_lower or 'flow' in filename_lower or 'sequence' in filename_lower: | |
| agent_type = 'sequence' | |
| else: | |
| # Default to testcase for document files | |
| agent_type = 'testcase' | |
| else: | |
| return jsonify({'error': 'Unsupported file type. Use .json/.xml for mapping, .docx/.doc/.pdf/.txt for test cases/sequence diagrams'}), 400 | |
| # Get additional instructions if provided | |
| additional_instructions = request.form.get('instructions', '') | |
| # Read file content based on file type | |
| filename = secure_filename(file.filename) | |
| filename_lower = filename.lower() | |
| try: | |
| if filename_lower.endswith('.docx'): | |
| # Parse DOCX file | |
| doc = Document(io.BytesIO(file.read())) | |
| file_content = '\n'.join([paragraph.text for paragraph in doc.paragraphs]) | |
| elif filename_lower.endswith('.pdf'): | |
| # Parse PDF file | |
| pdf_reader = PyPDF2.PdfReader(io.BytesIO(file.read())) | |
| file_content = '\n'.join([page.extract_text() for page in pdf_reader.pages]) | |
| elif filename_lower.endswith('.doc'): | |
| # For .doc files, return error message | |
| return jsonify({'error': 'Legacy .doc files are not supported. Please convert to .docx format.'}), 400 | |
| else: | |
| # Plain text files | |
| file.seek(0) | |
| file_content = file.read().decode('utf-8') | |
| except Exception as e: | |
| return jsonify({'error': f'Error reading file: {str(e)}'}), 400 | |
| # Content filtering for uploaded files | |
| is_safe, reason = content_filter.is_safe(file_content) | |
| if not is_safe: | |
| blocked_message = content_filter.get_blocked_message(reason) | |
| return jsonify({'error': blocked_message}), 400 | |
| # Process with appropriate agent based on file type | |
| if agent_type == 'mapping': | |
| agent = MappingAgent() | |
| elif agent_type == 'testcase': | |
| agent = TestCaseAgent() | |
| elif agent_type == 'sequence': | |
| agent = SequenceAgent() | |
| # Prepare input with file content and additional instructions | |
| agent_input = f"File: {filename}\n\nContent:\n{file_content}" | |
| if additional_instructions: | |
| agent_input += f"\n\nAdditional Instructions: {additional_instructions}" | |
| response = agent.process(agent_input) | |
| # Track file upload stats | |
| stats_manager.increment_files() | |
| # Store file upload output for potential JIRA posting (with user isolation) | |
| user_ip = request.remote_addr | |
| user_id = f"{user_ip}_file_upload" | |
| session_id = 'file_upload_' + str(hash(filename)) # Generate session ID for file uploads | |
| # Track user sessions for file uploads | |
| if user_id not in user_sessions: | |
| user_sessions[user_id] = [] | |
| if session_id not in user_sessions[user_id]: | |
| user_sessions[user_id].append(session_id) | |
| if session_id not in last_generated_output: | |
| last_generated_output[session_id] = {} | |
| # Determine correct type for JIRA posting | |
| if agent_type == 'mapping': | |
| content_type = 'mapping_document' | |
| elif agent_type == 'testcase': | |
| content_type = 'test_cases' | |
| elif agent_type == 'sequence': | |
| content_type = 'sequence_diagram' | |
| else: | |
| content_type = agent_type + 's' | |
| # Extract JSON data for CSV if it's a mapping document | |
| json_data = None | |
| if content_type == 'mapping_document': | |
| json_data = extract_json_from_response(response) | |
| last_generated_output[session_id] = { | |
| 'content': response, | |
| 'type': content_type, | |
| 'json_data': json_data | |
| } | |
| # Ask for CSV first for mapping documents, then JIRA | |
| if content_type == 'mapping_document': | |
| response += f"\n\nπ Would you like to download this mapping as a CSV file?\n\n<div class='option-buttons'><button class='option-btn' onclick='handleOptionClick(\"Download CSV\")'>π₯ Download CSV</button><button class='option-btn secondary' onclick='handleOptionClick(\"No CSV\")'>β No Thanks</button></div>" | |
| elif jira_client.is_enabled(): | |
| attachment_type = "PNG diagram" if content_type == 'sequence_diagram' and '```mermaid' in response else "Word document" | |
| response += f"\n\nπ Would you like to post this as a {attachment_type} to a JIRA ticket?\n\n<div class='option-buttons'><button class='option-btn' onclick='handleOptionClick(\"Post to JIRA\")'>π Post to JIRA</button><button class='option-btn secondary' onclick='handleOptionClick(\"No JIRA\")'>β No Thanks</button></div>" | |
| return jsonify({'response': response}) | |
| except Exception as e: | |
| return jsonify({'error': str(e)}), 500 | |
| def handle_jira_request(user_message, session_id, user_id): | |
| import re | |
| # Extract JIRA ticket ID from message | |
| jira_pattern = r'([A-Z]+-\d+)' | |
| match = re.search(jira_pattern, user_message.upper()) | |
| if not match: | |
| return "Please provide a valid JIRA ticket ID (e.g., 'Post to JIRA ABC-123' or 'SCRUM-456')" | |
| ticket_id = match.group(1) | |
| # Check if we have recent output to post (only from current user's sessions) | |
| output_data = None | |
| user_session_list = user_sessions.get(user_id, []) | |
| for sid in user_session_list: | |
| if sid in last_generated_output: | |
| output_data = last_generated_output[sid] | |
| break | |
| if not output_data: | |
| return "No recent output found to post to JIRA. Please generate content first (mapping document, test cases, or sequence diagram)." | |
| # Validate ticket exists | |
| is_valid, validation_msg = jira_client.validate_ticket(ticket_id) | |
| if not is_valid: | |
| return f"JIRA Error: {validation_msg}" | |
| # Handle different content types | |
| try: | |
| if output_data['type'] == 'sequence_diagram' and '```mermaid' in output_data['content']: | |
| # Generate PNG for sequence diagrams | |
| png_path, png_filename = png_generator.generate_png_from_mermaid( | |
| output_data['content'], | |
| f"ArtifaX_{ticket_id}" | |
| ) | |
| if png_path: | |
| # Post PNG to JIRA | |
| success, result_msg = jira_client.post_with_attachment( | |
| ticket_id, | |
| output_data['content'], | |
| output_data['type'], | |
| png_path, | |
| png_filename | |
| ) | |
| # Clean up temp PNG file | |
| os.remove(png_path) | |
| else: | |
| return f"β Failed to generate PNG: {png_filename}" | |
| elif output_data['type'] == 'mapping_document': | |
| # Generate CSV for mapping documents | |
| csv_path, csv_filename = csv_generator.generate_csv_from_mapping( | |
| output_data['content'], | |
| f"ArtifaX_{ticket_id}" | |
| ) | |
| if csv_path: | |
| # Post CSV to JIRA | |
| success, result_msg = jira_client.post_with_attachment( | |
| ticket_id, | |
| output_data['content'], | |
| output_data['type'], | |
| csv_path, | |
| csv_filename | |
| ) | |
| # Clean up temp CSV file | |
| os.remove(csv_path) | |
| else: | |
| return f"β Failed to generate CSV: {csv_filename}" | |
| else: | |
| # Create Word document for other content types (test cases) | |
| doc_path, doc_filename = doc_generator.create_word_document( | |
| output_data['content'], | |
| output_data['type'], | |
| f"ArtifaX_{ticket_id}" | |
| ) | |
| # Post to JIRA with attachment | |
| success, result_msg = jira_client.post_with_attachment( | |
| ticket_id, | |
| output_data['content'], | |
| output_data['type'], | |
| doc_path, | |
| doc_filename | |
| ) | |
| # Clean up temp file | |
| os.remove(doc_path) | |
| except Exception as e: | |
| return f"β Failed to create attachment: {str(e)}" | |
| if success: | |
| # Track JIRA upload stats | |
| stats_manager.increment_jira() | |
| return f"β {result_msg}\n\nContent type: {output_data['type'].replace('_', ' ').title()}" | |
| else: | |
| return f"β {result_msg}" | |
| def chat(): | |
| user_message = request.json.get('message', '') | |
| session_id = request.json.get('session_id', 'default') | |
| user_ip = request.remote_addr | |
| # Create user identifier (IP + session prefix for isolation) | |
| user_id = f"{user_ip}_{session_id.split('_')[0]}" | |
| # Track user sessions | |
| if user_id not in user_sessions: | |
| user_sessions[user_id] = [] | |
| if session_id not in user_sessions[user_id]: | |
| user_sessions[user_id].append(session_id) | |
| # Debug logging | |
| print(f"DEBUG: User: {user_id}, Session: {session_id}, Message: '{user_message}'") | |
| print(f"DEBUG: User sessions: {user_sessions.get(user_id, [])}") | |
| # Content filtering | |
| is_safe, reason = content_filter.is_safe(user_message) | |
| if not is_safe: | |
| blocked_message = content_filter.get_blocked_message(reason) | |
| return jsonify({'response': blocked_message}) | |
| # Message length check | |
| if len(user_message) > content_filter.config['max_message_length']: | |
| return jsonify({'response': 'Message too long. Please keep messages under 10,000 characters.'}) | |
| # Initialize session if not exists | |
| if session_id not in chat_sessions: | |
| chat_sessions[session_id] = [] | |
| # Add user message to history | |
| chat_sessions[session_id].append({"role": "user", "content": user_message}) | |
| # Check if message contains file content | |
| if 'File uploaded:' in user_message and 'Content:' in user_message: | |
| # Extract file content for processing | |
| file_content = user_message.split('Content:\n', 1)[1] if 'Content:\n' in user_message else user_message | |
| try: | |
| # Direct agent calls without MCP | |
| # Get chat history for context (increased window for better continuity) | |
| chat_history = "\n".join([f"{msg['role']}: {msg['content']}" for msg in chat_sessions[session_id][-10:]]) # Last 10 messages | |
| context_message = f"Chat History:\n{chat_history}\n\nCurrent Message: {user_message}" | |
| # Determine which BA tool to use based on explicit requests | |
| if 'File uploaded:' in user_message: | |
| agent = MappingAgent() | |
| response = agent.process(context_message) | |
| elif ("generate" in user_message.lower() and "mapping" in user_message.lower()) or ("swagger" in user_message.lower() and "mapping" in user_message.lower()): | |
| # Only allow mapping via file upload | |
| agent = ConversationAgent() | |
| response = agent.process(context_message) | |
| if "upload" not in response.lower(): | |
| response = "I'd be happy to generate a mapping document for you! Please upload your Swagger/OpenAPI file (.json or .xml format) using the Upload button." | |
| elif ("generate" in user_message.lower() and "test case" in user_message.lower()) or ("frd" in user_message.lower() and "test" in user_message.lower()): | |
| # Only allow test case generation via file upload | |
| agent = ConversationAgent() | |
| response = agent.process(context_message) | |
| if "upload" not in response.lower(): | |
| response = "I'd be happy to generate test cases for you! Please upload your FRD document using the π attachment icon (.docx, .pdf, .txt formats supported)." | |
| elif ("generate" in user_message.lower() and "sequence" in user_message.lower()) or "sequence diagram" in user_message.lower() or "sequences" in user_message.lower() or "flowchart" in user_message.lower() or "graph" in user_message.lower() or ("sequence" in user_message.lower() and ("flow" in user_message.lower() or "diagram" in user_message.lower() or "process" in user_message.lower())): | |
| # Check if user provided sufficient details for sequence generation | |
| if len(user_message.split()) > 2 and any(word in user_message.lower() for word in ['for', 'with', 'between', 'process', 'flow', 'api', 'system']): | |
| agent = SequenceAgent() | |
| response = agent.process(context_message) | |
| # Clean up HTML entities in Mermaid output | |
| import html | |
| response = html.unescape(response) | |
| response = response.replace('>', '>').replace('<', '<').replace('&', '&') | |
| response = response.replace('->>', '->>') | |
| response = response.replace('-->>', '-->>') | |
| else: | |
| # Use conversation agent to understand context before suggesting | |
| agent = ConversationAgent() | |
| response = agent.process(context_message) | |
| if "more details" not in response.lower(): | |
| response = "I'd be happy to generate sequence diagrams for you! Please provide more details about the process flow you'd like to visualize.\n\nFor example:\n- 'Generate sequence diagram for user login process'\n- 'Create sequences for API integration between eStore and SAP'\n- 'Sequence diagram for payment processing flow'\n\nOr upload your process flow document using the π attachment icon." | |
| else: | |
| # Use conversation agent for general interactions | |
| agent = ConversationAgent() | |
| response = agent.process(context_message) | |
| # Store last generated output for potential JIRA posting (with user isolation) | |
| if any(keyword in user_message.lower() for keyword in ['generate', 'mapping', 'test case', 'sequence']) and not response.startswith("I'd be happy to") and '```mermaid' in response: | |
| last_generated_output[session_id] = { | |
| 'content': response, | |
| 'type': 'mapping_document' if 'mapping' in user_message.lower() else | |
| 'test_cases' if 'test' in user_message.lower() else 'sequence_diagram' | |
| } | |
| # Ensure session is tracked for this user | |
| if session_id not in user_sessions[user_id]: | |
| user_sessions[user_id].append(session_id) | |
| # Ask user if they want to post to JIRA (only for actual generated content) | |
| if jira_client.is_enabled() and not response.startswith("I'd be happy to"): | |
| attachment_type = "PNG diagram" if last_generated_output[session_id]['type'] == 'sequence_diagram' and '```mermaid' in response else "Word document" | |
| response += f"\n\nπ Would you like to post this as a {attachment_type} to a JIRA ticket? If yes, please provide the JIRA ticket number (e.g., 'Post to JIRA ABC-123')." | |
| # Handle button clicks FIRST before other logic | |
| print(f"DEBUG: Checking button actions for message: '{user_message}'") | |
| print(f"DEBUG: Session ID: {session_id}") | |
| print(f"DEBUG: Available sessions: {list(last_generated_output.keys())}") | |
| if session_id in last_generated_output: | |
| print(f"DEBUG: Session content type: {last_generated_output[session_id]['type']}") | |
| if user_message == 'Download CSV': | |
| # Find mapping document ONLY in current user's sessions | |
| mapping_session = None | |
| user_session_list = user_sessions.get(user_id, []) | |
| for sid in user_session_list: | |
| if sid in last_generated_output and last_generated_output[sid]['type'] == 'mapping_document': | |
| mapping_session = sid | |
| break | |
| # Also check for file upload sessions from same user | |
| for sid, data in last_generated_output.items(): | |
| if (sid.startswith('file_upload_') and | |
| data['type'] == 'mapping_document' and | |
| sid not in user_session_list): | |
| user_sessions[user_id].append(sid) | |
| mapping_session = sid | |
| break | |
| if mapping_session: | |
| # Direct CSV conversion from stored content | |
| output_data = last_generated_output[mapping_session] | |
| csv_path, csv_filename = csv_generator.generate_csv_from_mapping( | |
| output_data['content'], | |
| f"mapping_{session_id}" | |
| ) | |
| if csv_path: | |
| with open(csv_path, 'r', encoding='utf-8') as f: | |
| csv_content = f.read() | |
| os.remove(csv_path) | |
| # Track CSV download stats | |
| stats_manager.increment_csv() | |
| csv_b64 = base64.b64encode(csv_content.encode('utf-8')).decode('utf-8') | |
| response = f"π CSV file generated!\n\n<a href='data:text/csv;base64,{csv_b64}' download='{csv_filename}' style='background: #28a745; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block; margin: 10px 0;'>π₯ Download CSV</a>\n\nπ Would you like to upload this mapping document to JIRA?\n\n<div class='option-buttons'><button class='option-btn' onclick='handleOptionClick(\"Upload to JIRA\")'>π Yes, Upload to JIRA</button><button class='option-btn secondary' onclick='handleOptionClick(\"No JIRA Upload\")'>β No Thanks</button></div>" | |
| else: | |
| response = f"β Failed to generate CSV: {csv_filename}" | |
| else: | |
| response = "No mapping document found. Please generate a mapping document first." | |
| elif user_message == 'No CSV': | |
| # User declined CSV, offer JIRA | |
| if jira_client.is_enabled(): | |
| response = "π Would you like to upload this mapping document to JIRA?\n\n<div class='option-buttons'><button class='option-btn' onclick='handleOptionClick(\"Upload to JIRA\")'>π Yes, Upload to JIRA</button><button class='option-btn secondary' onclick='handleOptionClick(\"No JIRA Upload\")'>β No Thanks</button></div>" | |
| else: | |
| response = "No problem! Your mapping document is ready above. You can copy it or generate a new one anytime." | |
| elif user_message == 'Post to JIRA': | |
| response = "Please provide the JIRA ticket number (e.g., 'ABC-123' or 'SCRUM-456'):" | |
| elif user_message == 'Upload to JIRA': | |
| response = "Please provide the JIRA ticket number (e.g., 'ABC-123' or 'SCRUM-456'):" | |
| elif user_message == 'No JIRA' or user_message == 'No JIRA Upload': | |
| response = "Perfect! Your document is ready above. You can copy it or generate a new one anytime." | |
| # Check for JIRA ticket number input | |
| else: | |
| jira_pattern = r'([A-Z]+-\d+)' | |
| if re.search(jira_pattern, user_message.upper()): | |
| jira_response = handle_jira_request(user_message, session_id, user_id) | |
| response = jira_response | |
| # Aggressive HTML entity cleanup for Mermaid diagrams | |
| if '```mermaid' in response: | |
| import html | |
| # Multiple passes to handle nested encoding | |
| for _ in range(3): | |
| response = html.unescape(response) | |
| response = response.replace('&gt;>', '>>') | |
| response = response.replace('&gt;', '>') | |
| response = response.replace('>>', '>>') | |
| response = response.replace('>', '>') | |
| response = response.replace('<', '<') | |
| response = response.replace('&', '&') | |
| response = response.replace(''', "'") | |
| response = response.replace('"', '"') | |
| # Fix arrow syntax specifically | |
| response = response.replace('-->', '-->') | |
| response = response.replace('->', '->') | |
| response = response.replace('->>', '->') | |
| response = response.replace('-->>', '-->') | |
| # Remove trailing backslashes | |
| response = response.replace('\\', '') | |
| # Fix broken alt syntax | |
| response = response.replace('alt After some time -->>| timeout |', 'alt After some time') | |
| response = response.replace('-->>|', '-->') | |
| # Add missing end statement | |
| if 'alt ' in response and response.count('end') < response.count('alt '): | |
| response = response.replace('deactivate IIB', 'end\n deactivate IIB') | |
| # Add bot response to history | |
| chat_sessions[session_id].append({"role": "assistant", "content": response}) | |
| # Log chat interaction | |
| chat_logger.log_chat(session_id, user_message, response, user_ip) | |
| # Track stats | |
| stats_manager.increment_messages() | |
| return jsonify({"response": response}) | |
| except Exception as e: | |
| return jsonify({"response": f"Error: {str(e)}"}) | |
| def get_logs_stats(): | |
| """Get usage statistics""" | |
| stats = chat_logger.get_usage_stats() | |
| return jsonify(stats) | |
| def get_stats(): | |
| """Get dashboard statistics""" | |
| return jsonify(stats_manager.get_stats()) | |
| def push_logs(): | |
| """Manually push logs to GitHub""" | |
| success = chat_logger.push_logs_to_github() | |
| return jsonify({'success': success}) | |
| def sync_to_github(): | |
| """Sync files modified since last sync to GitHub""" | |
| from github_manager import GitHubManager | |
| import time | |
| from datetime import datetime | |
| LAST_SYNC_FILE = '.last_sync' | |
| def get_last_sync_time(): | |
| if os.path.exists(LAST_SYNC_FILE): | |
| try: | |
| with open(LAST_SYNC_FILE, 'r') as f: | |
| data = json.load(f) | |
| return data.get('timestamp', 0) | |
| except: | |
| pass | |
| return 0 | |
| def save_last_sync_time(): | |
| with open(LAST_SYNC_FILE, 'w') as f: | |
| json.dump({ | |
| 'timestamp': time.time(), | |
| 'datetime': datetime.now().strftime('%Y-%m-%d %H:%M:%S') | |
| }, f, indent=2) | |
| github_manager = GitHubManager() | |
| files_synced = [] | |
| # Get last sync timestamp | |
| last_sync = get_last_sync_time() | |
| # Define file extensions to track | |
| tracked_extensions = {'.py', '.js', '.css', '.html', '.md', '.json', '.txt'} | |
| # Scan for files modified since last sync | |
| success_count = 0 | |
| for root, dirs, files in os.walk('.'): | |
| # Skip certain directories | |
| dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ['__pycache__', 'temp', 'uploads', 'logs']] | |
| for file in files: | |
| file_path = os.path.join(root, file).replace('\\', '/').lstrip('./') | |
| # Check if file extension is tracked | |
| if any(file.endswith(ext) for ext in tracked_extensions): | |
| try: | |
| mod_time = os.path.getmtime(os.path.join(root, file)) | |
| if mod_time > last_sync: | |
| # File was modified since last sync, sync it | |
| try: | |
| with open(file_path, 'r', encoding='utf-8') as f: | |
| content = f.read() | |
| repo = github_manager.github.get_user().get_repo('ArtifaX') | |
| try: | |
| # Try to update existing file on AIIntOps branch | |
| file_obj = repo.get_contents(file_path, ref="AIIntOps") | |
| repo.update_file(file_path, f"AIIntOps: Update {file_path}", content, file_obj.sha, branch="AIIntOps") | |
| success = True | |
| except Exception as e: | |
| if 'Not Found' in str(e): | |
| # File doesn't exist, create it on AIIntOps branch | |
| repo.create_file(file_path, f"AIIntOps: Add {file_path}", content, branch="AIIntOps") | |
| success = True | |
| else: | |
| success = False | |
| if success: | |
| success_count += 1 | |
| files_synced.append({ | |
| 'file': file_path, | |
| 'success': success, | |
| 'modified': datetime.fromtimestamp(mod_time).strftime('%Y-%m-%d %H:%M:%S') | |
| }) | |
| except Exception as e: | |
| files_synced.append({ | |
| 'file': file_path, | |
| 'success': False, | |
| 'error': str(e), | |
| 'modified': datetime.fromtimestamp(mod_time).strftime('%Y-%m-%d %H:%M:%S') | |
| }) | |
| except OSError: | |
| continue | |
| # Save sync timestamp only if at least one file was successfully synced | |
| if success_count > 0: | |
| save_last_sync_time() | |
| return jsonify({ | |
| 'files_synced': files_synced, | |
| 'last_sync': datetime.fromtimestamp(last_sync).strftime('%Y-%m-%d %H:%M:%S') if last_sync > 0 else 'Never', | |
| 'total_files': len(files_synced), | |
| 'successful_syncs': success_count | |
| }) | |
| def submit_feedback(): | |
| try: | |
| feedback_data = request.json | |
| success, message = email_sender.send_feedback(feedback_data) | |
| return jsonify({'success': success, 'message': message}) | |
| except Exception as e: | |
| return jsonify({'success': False, 'message': str(e)}) | |
| def jira_config(): | |
| global jira_client | |
| if request.method == 'GET': | |
| config = jira_client.config.copy() | |
| # Don't expose sensitive data | |
| config['api_token'] = '***' if config['api_token'] else '' | |
| return jsonify(config) | |
| elif request.method == 'POST': | |
| # Update JIRA configuration | |
| new_config = request.json | |
| config_path = os.path.join('config', 'jira_config.json') | |
| # Update config file | |
| with open(config_path, 'w') as f: | |
| json.dump(new_config, f, indent=2) | |
| # Reinitialize client | |
| jira_client = JIRAClient() | |
| return jsonify({'status': 'JIRA configuration updated'}) | |
| if __name__ == '__main__': | |
| socketio.run(app, debug=False, host='0.0.0.0', port=8080) |