""" 啟動 Flask 服務器(解決 Windows 編碼問題) """ import sys import os # 設置 UTF-8 編碼 if sys.platform == 'win32': import codecs sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'strict') sys.stderr = codecs.getwriter('utf-8')(sys.stderr.buffer, 'strict') # 導入並啟動 app from flask import Flask, request, jsonify, send_from_directory from flask_cors import CORS from datetime import datetime import json # Import LLM configuration try: from llm_config import LLMConfig llm_config = LLMConfig() LLM_ENABLED = True except ImportError: print("Warning: llm_config not found. LLM features will be disabled.") LLM_ENABLED = False app = Flask(__name__, static_folder='.') CORS(app) # 模擬資料庫 positions_db = {} jobs_db = {} # 預設崗位資料 default_positions = { "MGR-001": { "id": "MGR-001", "basicInfo": { "positionCode": "MGR-001", "positionName": "管理職-資深班長", "positionCategory": "02", "positionCategoryName": "管理職", "positionNature": "FT", "positionNatureName": "全職", "headcount": "5", "positionLevel": "L3", "effectiveDate": "2001-01-01", "positionDesc": "負責生產線的日常管理與人員調度", "positionRemark": "" }, "recruitInfo": { "minEducation": "JC", "requiredGender": "", "salaryRange": "C", "workExperience": "3", "minAge": "25", "maxAge": "45", "jobType": "FT", "recruitPosition": "MGR", "jobTitle": "資深班長", "jobDesc": "", "positionReq": "", "titleReq": "", "majorReq": "", "skillReq": "", "langReq": "", "otherReq": "", "superiorPosition": "", "recruitRemark": "" }, "createdAt": "2024-01-01T00:00:00", "updatedAt": "2024-01-01T00:00:00" } } positions_db.update(default_positions) # 預設職務資料 default_jobs = { "VP-001": { "id": "VP-001", "jobCategoryCode": "MGR", "jobCategoryName": "管理職", "jobCode": "VP-001", "jobName": "副總", "jobNameEn": "Vice President", "jobEffectiveDate": "2001-01-01", "jobHeadcount": 2, "jobSortOrder": 10, "jobRemark": "", "jobLevel": "*保密*", "hasAttendanceBonus": False, "hasHousingAllowance": True, "createdAt": "2024-01-01T00:00:00", "updatedAt": "2024-01-01T00:00:00" } } jobs_db.update(default_jobs) # ==================== 靜態頁面 ==================== @app.route('/') def index(): return send_from_directory('.', 'index.html') @app.route('/api-test') def api_test_page(): return send_from_directory('.', 'api_test.html') # ==================== 崗位資料 API ==================== @app.route('/api/positions', methods=['GET']) def get_positions(): page = request.args.get('page', 1, type=int) size = request.args.get('size', 20, type=int) search = request.args.get('search', '', type=str) filtered = list(positions_db.values()) if search: filtered = [p for p in filtered if search.lower() in p['basicInfo'].get('positionCode', '').lower() or search.lower() in p['basicInfo'].get('positionName', '').lower()] total = len(filtered) start = (page - 1) * size end = start + size paginated = filtered[start:end] return jsonify({ 'success': True, 'data': paginated, 'pagination': { 'page': page, 'size': size, 'total': total, 'totalPages': (total + size - 1) // size } }) @app.route('/api/positions/', methods=['GET']) def get_position(position_id): if position_id not in positions_db: return jsonify({'success': False, 'error': '找不到該崗位資料'}), 404 return jsonify({'success': True, 'data': positions_db[position_id]}) @app.route('/api/positions', methods=['POST']) def create_position(): try: data = request.get_json() if not data: return jsonify({'success': False, 'error': '請提供有效的 JSON 資料'}), 400 basic_info = data.get('basicInfo', {}) if not basic_info.get('positionCode'): return jsonify({'success': False, 'error': '崗位編號為必填欄位'}), 400 if not basic_info.get('positionName'): return jsonify({'success': False, 'error': '崗位名稱為必填欄位'}), 400 position_code = basic_info['positionCode'] if position_code in positions_db: return jsonify({'success': False, 'error': f'崗位編號 {position_code} 已存在'}), 409 now = datetime.now().isoformat() new_position = { 'id': position_code, 'basicInfo': basic_info, 'recruitInfo': data.get('recruitInfo', {}), 'createdAt': now, 'updatedAt': now } positions_db[position_code] = new_position return jsonify({'success': True, 'message': '崗位資料新增成功', 'data': new_position}), 201 except Exception as e: return jsonify({'success': False, 'error': f'新增失敗: {str(e)}'}), 500 # ==================== LLM API ==================== @app.route('/api/llm/config', methods=['GET']) def get_llm_config(): if not LLM_ENABLED: return jsonify({'success': False, 'error': 'LLM 功能未啟用'}), 503 try: config_data = {} for api_name, api_config in llm_config.apis.items(): config_data[api_name] = { 'name': api_config['name'], 'enabled': api_config['enabled'], 'endpoint': api_config['endpoint'], 'api_key': api_config['api_key'][:8] + '...' if api_config['api_key'] else '' } return jsonify(config_data) except Exception as e: return jsonify({'success': False, 'error': f'獲取配置失敗: {str(e)}'}), 500 @app.route('/api/llm/test/', methods=['GET']) def test_llm_api(api_name): if not LLM_ENABLED: return jsonify({'success': False, 'message': 'LLM 功能未啟用'}), 503 try: if api_name not in llm_config.apis: return jsonify({'success': False, 'message': f'不支援的 API: {api_name}'}), 400 if api_name == 'gemini': success, message = llm_config.test_gemini_connection() elif api_name == 'deepseek': success, message = llm_config.test_deepseek_connection() elif api_name == 'openai': success, message = llm_config.test_openai_connection() else: return jsonify({'success': False, 'message': f'未實作的 API: {api_name}'}), 400 return jsonify({'success': success, 'message': message}) except Exception as e: return jsonify({'success': False, 'message': f'測試失敗: {str(e)}'}), 500 @app.route('/api/llm/generate', methods=['POST']) def generate_llm_text(): if not LLM_ENABLED: return jsonify({'success': False, 'error': 'LLM 功能未啟用'}), 503 try: data = request.get_json() if not data: return jsonify({'success': False, 'error': '請提供有效的 JSON 資料'}), 400 api_name = data.get('api', 'gemini') prompt = data.get('prompt', '') max_tokens = data.get('max_tokens', 2000) if not prompt: return jsonify({'success': False, 'error': '請提供提示詞'}), 400 if api_name == 'gemini': success, result = llm_config.generate_text_gemini(prompt, max_tokens) elif api_name == 'deepseek': success, result = llm_config.generate_text_deepseek(prompt, max_tokens) elif api_name == 'openai': model = data.get('model', 'gpt-3.5-turbo') success, result = llm_config.generate_text_openai(prompt, model, max_tokens) else: return jsonify({'success': False, 'error': f'不支援的 API: {api_name}'}), 400 if success: return jsonify({'success': True, 'text': result}) else: return jsonify({'success': False, 'error': result}), 500 except Exception as e: return jsonify({'success': False, 'error': f'生成失敗: {str(e)}'}), 500 # ==================== 錯誤處理 ==================== @app.errorhandler(404) def not_found(e): return jsonify({'success': False, 'error': '找不到請求的資源'}), 404 @app.errorhandler(500) def server_error(e): return jsonify({'success': False, 'error': '伺服器內部錯誤'}), 500 # ==================== 主程式 ==================== if __name__ == '__main__': print("=" * 60) print("HR Position System - Flask Backend") print("=" * 60) print("\nServer starting...") print("URL: http://localhost:5000") print() if LLM_ENABLED: print("[OK] LLM features enabled") enabled_apis = llm_config.get_enabled_apis() if enabled_apis: print(f"Configured APIs: {', '.join(enabled_apis)}") else: print("Warning: No LLM API Keys configured") else: print("[!] LLM features disabled (llm_config.py not found)") print("\nPress Ctrl+C to stop") print("=" * 60) print() app.run(host='0.0.0.0', port=5000, debug=True)