diff --git a/.env b/.env new file mode 100644 index 0000000..b7448d8 --- /dev/null +++ b/.env @@ -0,0 +1,7 @@ +# AI帮你面试 - 环境配置 + +# API密钥配置 +AI_API_KEY=sk-0944c102849e45d9ab3f24d5169de289 + +# Flask应用配置 +FLASK_DEBUG=True \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index 890dfc5..7c11a52 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # AI帮你面试 - +姓名 学号 主要贡献 (具体分工) +蔡朗 2411020227 核心逻辑开发、Prompt 编写 +陆刘青 2411020110 前端界面设计、PPT 制作 +刘俊伯 2411020102 文档撰写、测试与 Bug 修复 一个基于Flask的AI面试辅助系统,可以帮助用户模拟面试流程并提供AI分析反馈。 ## 功能特性 diff --git a/app.py b/app.py new file mode 100644 index 0000000..10dad20 --- /dev/null +++ b/app.py @@ -0,0 +1,124 @@ +# AI帮你面试 - 核心应用程序 +from flask import Flask, render_template, request, jsonify +import os +from dotenv import load_dotenv + +# 加载环境变量 +load_dotenv() + +# 创建Flask应用实例 +app = Flask(__name__) + +# 配置API密钥 +API_KEY = os.getenv('AI_API_KEY', '') # 从环境变量获取API密钥 + +# 定义面试流程数据 +interview_process = [ + {"id": 1, "title": "自我介绍", "description": "请简要介绍您自己,包括教育背景和工作经验", "duration": "3分钟"}, + {"id": 2, "title": "技术能力评估", "description": "回答技术相关问题,展示专业知识", "duration": "15分钟"}, + {"id": 3, "title": "项目经验分享", "description": "分享您参与的重要项目和成果", "duration": "10分钟"}, + {"id": 4, "title": "问题与解答", "description": "您可以向面试官提问", "duration": "7分钟"} +] + +# API密钥验证装饰器 +def require_api_key(func): + def wrapper(*args, **kwargs): + # 从请求头获取API密钥 + api_key = request.headers.get('X-API-Key') + + # 调试信息(实际生产环境应移除) + print(f"[DEBUG] Received API Key: '{api_key}'") + print(f"[DEBUG] Received API Key Length: {len(api_key) if api_key else 0}") + print(f"[DEBUG] Expected API Key: '{API_KEY}'") + print(f"[DEBUG] Expected API Key Length: {len(API_KEY)}") + print(f"[DEBUG] Match: {api_key == API_KEY}") + + # 检查是否有不可见字符 + if api_key: + print(f"[DEBUG] Received API Key Hex: {[hex(ord(c)) for c in api_key]}") + print(f"[DEBUG] Expected API Key Hex: {[hex(ord(c)) for c in API_KEY]}") + + # 验证API密钥 + if not api_key or api_key != API_KEY: + return jsonify({ + 'success': False, + 'error': 'Unauthorized: Invalid API Key' + }), 401 + + return func(*args, **kwargs) + + # 保留原函数的元数据 + wrapper.__name__ = func.__name__ + wrapper.__doc__ = func.__doc__ + return wrapper + +@app.route('/') +def home(): + """首页路由,显示应用主界面""" + return render_template('index.html', app_name='AI帮你面试') + +@app.route('/start_interview') +def start_interview(): + """开始面试路由,显示面试流程""" + return render_template('interview.html', process=interview_process) + +@app.route('/analyze_answer', methods=['POST']) +@require_api_key # 添加API密钥验证 +def analyze_answer(): + """AI分析回答的路由""" + data = request.get_json() + answer = data.get('answer', '') + question_id = data.get('question_id', 1) + + # 模拟AI分析过程 + analysis = { + 'question_id': question_id, + 'content_analysis': { + 'completeness': 85, + 'relevance': 90, + 'depth': 75 + }, + 'sentiment_analysis': { + 'confidence': 92, + 'sentiment': 'positive' + }, + 'suggestions': [ + '可以提供更多具体的项目成果数据', + '注意控制回答时间,保持简洁明了', + '可以结合实例说明技术能力' + ], + 'overall_score': 85 + } + + return jsonify(analysis) + +@app.route('/get_results') +@require_api_key # 添加API密钥验证 +def get_results(): + """获取面试结果的路由""" + results = { + 'overall_score': 88, + 'category_scores': { + 'professional_knowledge': 90, + 'communication_skills': 85, + 'problem_solving': 87, + 'experience_relevance': 92 + }, + 'strengths': [ + '技术知识扎实', + '项目经验丰富', + '表达清晰流畅' + ], + 'improvement_areas': [ + '可以更详细地描述团队协作经历', + '需要加强行业趋势了解', + '回答可以更加结构化' + ], + 'final_recommendation': '推荐进入下一轮面试' + } + + return jsonify(results) + +if __name__ == '__main__': + """应用程序入口点""" + app.run(debug=True, host='0.0.0.0', port=5000) \ No newline at end of file diff --git a/debug_api_key.py b/debug_api_key.py new file mode 100644 index 0000000..e57b59f --- /dev/null +++ b/debug_api_key.py @@ -0,0 +1,43 @@ +# 直接调试API密钥比较问题 +import os +from dotenv import load_dotenv + +# 加载环境变量 +load_dotenv() + +# 从.env获取的密钥 +api_key_from_env = os.getenv('AI_API_KEY', '') + +# 测试用的密钥(与前端和测试脚本中使用的相同) +test_api_key = 'sk-0944c102849e45d9ab3f24d5169de289' + +print(f"API Key from .env: '{api_key_from_env}'") +print(f"Test API Key: '{test_api_key}'") +print(f"Match: {api_key_from_env == test_api_key}") + +# 检查长度 +print(f"\nLength from .env: {len(api_key_from_env)}") +print(f"Length test key: {len(test_api_key)}") + +# 检查不可见字符 +print(f"\nHex from .env: {[hex(ord(c)) for c in api_key_from_env]}") +print(f"Hex test key: {[hex(ord(c)) for c in test_api_key]}") + +# 检查是否有前后空格 +print(f"\nStrip from .env: '{api_key_from_env.strip()}'") +print(f"Strip test key: '{test_api_key.strip()}'") +print(f"Match after strip: {api_key_from_env.strip() == test_api_key.strip()}") + +# 检查是否有换行符或回车符 +print(f"\nHas newline from .env: {'\n' in api_key_from_env}") +print(f"Has newline test key: {'\n' in test_api_key}") +print(f"Has carriage return from .env: {'\r' in api_key_from_env}") +print(f"Has carriage return test key: {'\r' in test_api_key}") + +# 清除所有空白字符后比较 +import re +clean_env_key = re.sub(r'\s', '', api_key_from_env) +clean_test_key = re.sub(r'\s', '', test_api_key) +print(f"\nClean from .env: '{clean_env_key}'") +print(f"Clean test key: '{clean_test_key}'") +print(f"Match after clean: {clean_env_key == clean_test_key}") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..de4e210 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +flask==2.2.2 +werkzeug==2.2.2 +python-dotenv==1.0.0 +requests==2.28.1 \ No newline at end of file diff --git a/static/script.js b/static/script.js new file mode 100644 index 0000000..4d637cd --- /dev/null +++ b/static/script.js @@ -0,0 +1,449 @@ +// AI帮你面试 - 核心JavaScript功能 + +// 全局变量 +let currentQuestionId = 1; +const totalQuestions = 4; + +// API配置 +const API_CONFIG = { + // 开发环境API密钥(实际生产环境应使用更安全的方式管理) + apiKey: 'sk-0944c102849e45d9ab3f24d5169de289', + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': 'sk-0944c102849e45d9ab3f24d5169de289' + } +}; + +// 面试流程数据(与后端保持一致) +const interviewProcess = [ + {"id": 1, "title": "自我介绍", "description": "请简要介绍您自己,包括教育背景和工作经验", "duration": "3分钟"}, + {"id": 2, "title": "技术能力评估", "description": "回答技术相关问题,展示专业知识", "duration": "15分钟"}, + {"id": 3, "title": "项目经验分享", "description": "分享您参与的重要项目和成果", "duration": "10分钟"}, + {"id": 4, "title": "问题与解答", "description": "您可以向面试官提问", "duration": "7分钟"} +]; + +// 页面加载完成后初始化 +document.addEventListener('DOMContentLoaded', function() { + // 初始化面试流程界面 + initInterviewProcess(); + + // 绑定事件监听器 + bindEventListeners(); +}); + +// 初始化面试流程界面 +function initInterviewProcess() { + // 高亮第一个步骤 + updateStepHighlight(currentQuestionId); + + // 更新当前问题显示 + updateCurrentQuestion(currentQuestionId); +} + +// 绑定事件监听器 +function bindEventListeners() { + // 分析按钮 + const analyzeBtn = document.getElementById('analyze-btn'); + if (analyzeBtn) { + analyzeBtn.addEventListener('click', handleAnalyzeAnswer); + } + + // 下一题按钮 + const nextBtn = document.getElementById('next-question-btn'); + if (nextBtn) { + nextBtn.addEventListener('click', handleNextQuestion); + } + + // 返回修改按钮 + const backBtn = document.getElementById('back-to-question-btn'); + if (backBtn) { + backBtn.addEventListener('click', handleBackToQuestion); + } + + // 完成面试按钮 + const finishBtn = document.getElementById('finish-interview-btn'); + if (finishBtn) { + finishBtn.addEventListener('click', handleFinishInterview); + } + + // 关闭结果弹窗按钮 + const closeModalBtn = document.querySelector('.close'); + if (closeModalBtn) { + closeModalBtn.addEventListener('click', closeResultsModal); + } + + // 点击弹窗外部关闭 + const modal = document.getElementById('results-modal'); + if (modal) { + modal.addEventListener('click', function(event) { + if (event.target === modal) { + closeResultsModal(); + } + }); + } + + // 点击流程步骤切换问题 + const processSteps = document.querySelectorAll('.process-step'); + processSteps.forEach(step => { + step.addEventListener('click', function() { + const stepId = parseInt(this.dataset.stepId); + if (stepId <= currentQuestionId) { + switchToQuestion(stepId); + } + }); + }); +} + +// 处理AI分析回答 +function handleAnalyzeAnswer() { + const answerText = document.getElementById('answer-text').value; + + if (!answerText.trim()) { + alert('请先输入您的回答!'); + return; + } + + // 显示加载状态 + const analyzeBtn = document.getElementById('analyze-btn'); + const originalText = analyzeBtn.textContent; + analyzeBtn.textContent = '分析中...'; + analyzeBtn.disabled = true; + + // 模拟AI分析过程(实际项目中这里会调用后端API) + setTimeout(() => { + // 调用AI分析API + analyzeAnswerAPI(currentQuestionId, answerText) + .then(data => { + // 显示分析结果 + displayAnalysisResults(data); + + // 更新UI + document.getElementById('question-stage').classList.add('hidden'); + document.getElementById('analysis-stage').classList.remove('hidden'); + }) + .catch(error => { + console.error('分析失败:', error); + alert('分析失败,请稍后重试!'); + }) + .finally(() => { + // 恢复按钮状态 + analyzeBtn.textContent = originalText; + analyzeBtn.disabled = false; + }); + }, 1500); +} + +// 调用AI分析API +async function analyzeAnswerAPI(questionId, answer) { + try { + const response = await fetch('/analyze_answer', { + method: 'POST', + headers: API_CONFIG.headers, + body: JSON.stringify({ + question_id: questionId, + answer: answer + }) + }); + + if (!response.ok) { + throw new Error('分析请求失败'); + } + + return await response.json(); + } catch (error) { + console.error('API调用错误:', error); + throw error; + } +} + +// 显示AI分析结果 +function displayAnalysisResults(data) { + // 更新综合评分 + document.getElementById('overall-score').textContent = data.overall_score; + + // 更新内容分析进度条 + updateProgressBar('completeness-bar', 'completeness-value', data.content_analysis.completeness); + updateProgressBar('relevance-bar', 'relevance-value', data.content_analysis.relevance); + updateProgressBar('depth-bar', 'depth-value', data.content_analysis.depth); + + // 更新建议列表 + const suggestionsList = document.getElementById('suggestions-list'); + suggestionsList.innerHTML = ''; + + data.suggestions.forEach(suggestion => { + const li = document.createElement('li'); + li.textContent = suggestion; + suggestionsList.appendChild(li); + }); +} + +// 更新进度条 +function updateProgressBar(barId, valueId, percentage) { + const bar = document.getElementById(barId); + const value = document.getElementById(valueId); + + bar.style.width = `${percentage}%`; + value.textContent = `${percentage}%`; +} + +// 处理下一题按钮点击 +function handleNextQuestion() { + if (currentQuestionId < totalQuestions) { + currentQuestionId++; + switchToQuestion(currentQuestionId); + } else { + alert('这是最后一个问题!'); + } +} + +// 切换到指定问题 +function switchToQuestion(questionId) { + currentQuestionId = questionId; + + // 更新当前问题显示 + updateCurrentQuestion(questionId); + + // 更新步骤高亮 + updateStepHighlight(questionId); + + // 切换到问题阶段 + document.getElementById('question-stage').classList.remove('hidden'); + document.getElementById('analysis-stage').classList.add('hidden'); + + // 清空回答输入 + document.getElementById('answer-text').value = ''; +} + +// 更新当前问题显示 +function updateCurrentQuestion(questionId) { + const question = interviewProcess.find(q => q.id === questionId); + if (question) { + document.getElementById('current-question-title').textContent = question.title; + document.getElementById('current-question-description').textContent = question.description; + } +} + +// 更新步骤高亮 +function updateStepHighlight(questionId) { + const steps = document.querySelectorAll('.process-step'); + steps.forEach(step => { + const stepId = parseInt(step.dataset.stepId); + if (stepId === questionId) { + step.classList.add('active'); + } else if (stepId < questionId) { + step.classList.add('completed'); + step.classList.remove('active'); + } else { + step.classList.remove('active', 'completed'); + } + }); +} + +// 处理返回修改按钮 +function handleBackToQuestion() { + document.getElementById('analysis-stage').classList.add('hidden'); + document.getElementById('question-stage').classList.remove('hidden'); +} + +// 处理完成面试按钮 +function handleFinishInterview() { + // 显示确认对话框 + if (confirm('确定要完成面试吗?')) { + // 调用获取面试结果API + getInterviewResults() + .then(results => { + displayInterviewResults(results); + openResultsModal(); + }) + .catch(error => { + console.error('获取结果失败:', error); + alert('获取面试结果失败,请稍后重试!'); + }); + } +} + +// 获取面试结果 +async function getInterviewResults() { + try { + const response = await fetch('/get_results', { + headers: API_CONFIG.headers + }); + if (!response.ok) { + throw new Error('获取结果请求失败'); + } + return await response.json(); + } catch (error) { + console.error('API调用错误:', error); + throw error; + } +} + +// 显示面试结果 +function displayInterviewResults(results) { + const container = document.getElementById('results-container'); + + container.innerHTML = ` +
+

最终评分

+
${results.overall_score}
+
+ +
+

分类评分

+
+
+ +
+
+
+ ${results.category_scores.professional_knowledge}% +
+ +
+ +
+
+
+ ${results.category_scores.communication_skills}% +
+ +
+ +
+
+
+ ${results.category_scores.problem_solving}% +
+ +
+ +
+
+
+ ${results.category_scores.experience_relevance}% +
+
+
+ +
+
+

👍 优点

+ +
+ +
+

📈 需要改进的地方

+ +
+
+ +
+

💡 最终建议

+

${results.final_recommendation}

+
+ `; + + // 添加结果页面样式 + addResultsStyles(); +} + +// 添加结果页面样式 +function addResultsStyles() { + // 动态添加结果页面需要的CSS样式 + const style = document.createElement('style'); + style.textContent = ` + .results-details { + margin: 30px 0; + } + + .category-scores { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; + } + + .score-item { + background: white; + padding: 15px; + border-radius: 10px; + box-shadow: 0 2px 5px rgba(0,0,0,0.05); + } + + .score-item label { + display: block; + margin-bottom: 10px; + font-weight: bold; + color: #555; + } + + .strengths-improvements { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 30px; + margin: 30px 0; + } + + .strengths, .improvements { + background: white; + padding: 20px; + border-radius: 10px; + box-shadow: 0 2px 5px rgba(0,0,0,0.05); + } + + .strengths h3, .improvements h3 { + margin-bottom: 15px; + color: #333; + border-bottom: 2px solid #eee; + padding-bottom: 10px; + } + + .strengths ul, .improvements ul { + list-style: none; + } + + .strengths li, .improvements li { + padding: 10px 0; + border-bottom: 1px solid #f0f0f0; + color: #555; + } + + .strengths li:last-child, .improvements li:last-child { + border-bottom: none; + } + + .final-recommendation { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 30px; + border-radius: 10px; + text-align: center; + } + + .final-recommendation h3 { + margin-bottom: 20px; + font-size: 1.5rem; + } + + .recommendation-text { + font-size: 1.2rem; + font-weight: bold; + line-height: 1.6; + } + `; + document.head.appendChild(style); +} + +// 打开结果弹窗 +function openResultsModal() { + document.getElementById('results-modal').classList.remove('hidden'); +} + +// 关闭结果弹窗 +function closeResultsModal() { + document.getElementById('results-modal').classList.add('hidden'); +} + diff --git a/static/styles.css b/static/styles.css new file mode 100644 index 0000000..13c5690 --- /dev/null +++ b/static/styles.css @@ -0,0 +1,567 @@ +/* 全局样式 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Arial', sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: #333; + line-height: 1.6; + min-height: 100vh; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +/* 通用按钮样式 */ +.btn { + display: inline-block; + padding: 10px 20px; + border: none; + border-radius: 5px; + font-size: 16px; + cursor: pointer; + transition: all 0.3s ease; + text-decoration: none; + text-align: center; + margin: 5px; +} + +.btn-primary { + background: linear-gradient(45deg, #667eea, #764ba2); + color: white; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(0,0,0,0.2); +} + +.btn-secondary { + background: #e0e0e0; + color: #333; +} + +.btn-secondary:hover { + background: #bdbdbd; +} + +/* 头部样式 */ +header { + background: rgba(255,255,255,0.95); + padding: 20px; + border-radius: 10px; + box-shadow: 0 5px 15px rgba(0,0,0,0.1); + margin-bottom: 30px; + display: flex; + justify-content: space-between; + align-items: center; +} + +header h1 { + font-size: 2.5rem; + background: linear-gradient(45deg, #667eea, #764ba2); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.subtitle { + font-size: 1.2rem; + color: #666; + margin-top: 10px; +} + +/* 首页英雄区域 */ +.hero { + display: flex; + align-items: center; + gap: 40px; + background: rgba(255,255,255,0.95); + padding: 40px; + border-radius: 15px; + box-shadow: 0 10px 30px rgba(0,0,0,0.1); +} + +.hero-content { + flex: 1; +} + +.hero-content h2 { + font-size: 2rem; + margin-bottom: 20px; + color: #333; +} + +.hero-content p { + font-size: 1.1rem; + margin-bottom: 30px; + color: #666; +} + +.features { + list-style: none; + margin-bottom: 30px; +} + +.features li { + padding: 10px 0; + font-size: 1.1rem; + color: #555; + border-bottom: 1px solid #eee; +} + +.features li:last-child { + border-bottom: none; +} + +/* 面试流程图解 */ +.hero-image { + flex: 1; + display: flex; + justify-content: center; + align-items: center; +} + +.interview-illustration { + position: relative; + width: 300px; + height: 200px; +} + +.person, .ai-robot { + width: 80px; + height: 80px; + border-radius: 50%; + position: absolute; + display: flex; + justify-content: center; + align-items: center; + font-size: 2rem; +} + +.person { + background: #667eea; + left: 0; + bottom: 0; +} + +.ai-robot { + background: #4ecdc4; + right: 0; + bottom: 0; +} + +.chat-bubbles { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 100%; +} + +.bubble { + padding: 15px; + border-radius: 20px; + margin: 10px 0; + max-width: 180px; + position: relative; + font-size: 0.9rem; +} + +.person-bubble { + background: #667eea; + color: white; + margin-left: 0; +} + +.person-bubble::after { + content: ''; + position: absolute; + bottom: -8px; + left: 20px; + border-width: 8px 8px 0; + border-style: solid; + border-color: #667eea transparent; +} + +.ai-bubble { + background: #4ecdc4; + color: white; + margin-left: auto; +} + +.ai-bubble::after { + content: ''; + position: absolute; + bottom: -8px; + right: 20px; + border-width: 8px 8px 0; + border-style: solid; + border-color: #4ecdc4 transparent; +} + +/* 面试流程页面布局 */ +.interview-container { + display: flex; + gap: 20px; + background: rgba(255,255,255,0.95); + padding: 20px; + border-radius: 15px; + box-shadow: 0 10px 30px rgba(0,0,0,0.1); +} + +/* 侧边栏样式 */ +.process-sidebar { + width: 300px; + background: #f8f9fa; + padding: 20px; + border-radius: 10px; + height: fit-content; + position: sticky; + top: 20px; +} + +.process-sidebar h2 { + margin-bottom: 20px; + color: #333; + font-size: 1.3rem; +} + +.process-step { + display: flex; + gap: 15px; + padding: 15px; + border-radius: 8px; + margin-bottom: 10px; + cursor: pointer; + transition: all 0.3s ease; + background: white; + border: 2px solid transparent; +} + +.process-step:hover { + transform: translateX(5px); + box-shadow: 0 5px 15px rgba(0,0,0,0.08); +} + +.process-step.active { + border-color: #667eea; + background: #f0f4ff; +} + +.step-number { + width: 30px; + height: 30px; + border-radius: 50%; + background: #667eea; + color: white; + display: flex; + justify-content: center; + align-items: center; + font-weight: bold; + flex-shrink: 0; +} + +.process-step.active .step-number { + background: white; + color: #667eea; + border: 2px solid #667eea; +} + +.step-content h3 { + font-size: 1.1rem; + margin-bottom: 5px; + color: #333; +} + +.step-content p { + font-size: 0.9rem; + color: #666; + margin-bottom: 5px; +} + +.step-duration { + font-size: 0.8rem; + color: #999; +} + +/* 主面试区域 */ +.interview-main { + flex: 1; + background: white; + padding: 30px; + border-radius: 10px; +} + +.interview-stage { + transition: all 0.3s ease; +} + +.interview-stage.hidden { + display: none; +} + +#current-question-title { + font-size: 1.8rem; + margin-bottom: 15px; + color: #333; +} + +#current-question-description { + font-size: 1.1rem; + margin-bottom: 30px; + color: #666; + line-height: 1.8; +} + +/* 回答输入区域 */ +.answer-input textarea { + width: 100%; + height: 200px; + padding: 15px; + border: 2px solid #ddd; + border-radius: 10px; + font-size: 1.1rem; + font-family: inherit; + resize: vertical; + margin-bottom: 20px; + transition: border-color 0.3s ease; +} + +.answer-input textarea:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +.input-actions { + display: flex; + gap: 15px; + justify-content: flex-start; +} + +/* AI分析结果区域 */ +.analysis-results { + background: #f8f9fa; + padding: 30px; + border-radius: 10px; + margin-bottom: 30px; +} + +.score-card { + text-align: center; + background: white; + padding: 30px; + border-radius: 10px; + margin-bottom: 30px; + box-shadow: 0 5px 15px rgba(0,0,0,0.05); +} + +.score-card h3 { + font-size: 1.2rem; + color: #666; + margin-bottom: 10px; +} + +.score-value { + font-size: 3rem; + font-weight: bold; + background: linear-gradient(45deg, #667eea, #764ba2); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.analysis-details h3, .suggestions h3 { + font-size: 1.3rem; + margin-bottom: 20px; + color: #333; + border-bottom: 2px solid #eee; + padding-bottom: 10px; +} + +.progress-bars { + margin-bottom: 30px; +} + +.progress-bar { + margin-bottom: 20px; +} + +.progress-bar label { + display: block; + margin-bottom: 8px; + font-weight: bold; + color: #555; +} + +.progress { + height: 15px; + background: #e0e0e0; + border-radius: 10px; + overflow: hidden; + margin-bottom: 5px; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, #4ecdc4, #45b7aa); + border-radius: 10px; + transition: width 0.5s ease; +} + +.progress-value { + font-size: 0.9rem; + color: #666; + font-weight: bold; +} + +.suggestions ul { + list-style: none; +} + +.suggestions li { + padding: 12px 20px; + margin-bottom: 10px; + background: white; + border-left: 4px solid #667eea; + border-radius: 5px; + color: #555; + transition: all 0.3s ease; +} + +.suggestions li:hover { + transform: translateX(5px); + box-shadow: 0 5px 15px rgba(0,0,0,0.05); +} + +/* 面试结果弹窗 */ +.modal { + display: flex; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.5); + justify-content: center; + align-items: center; +} + +.modal.hidden { + display: none; +} + +.modal-content { + background: white; + padding: 40px; + border-radius: 15px; + width: 80%; + max-width: 600px; + max-height: 80vh; + overflow-y: auto; + position: relative; + box-shadow: 0 20px 40px rgba(0,0,0,0.15); +} + +.close { + position: absolute; + right: 20px; + top: 20px; + font-size: 28px; + font-weight: bold; + color: #aaa; + cursor: pointer; +} + +.close:hover { + color: #333; +} + +/* 页脚 */ +footer { + margin-top: 40px; + padding: 20px; + text-align: center; + background: rgba(255,255,255,0.95); + border-radius: 10px; + color: #666; +} + +/* 响应式设计 */ +@media (max-width: 968px) { + .hero { + flex-direction: column; + text-align: center; + } + + .interview-container { + flex-direction: column; + } + + .process-sidebar { + width: 100%; + position: static; + } + + .input-actions { + flex-direction: column; + } + + .input-actions .btn { + width: 100%; + margin: 5px 0; + } +} + +@media (max-width: 768px) { + header { + flex-direction: column; + gap: 15px; + text-align: center; + } + + header h1 { + font-size: 2rem; + } + + .hero { + padding: 20px; + } + + .hero-content h2 { + font-size: 1.6rem; + } + + .interview-main { + padding: 20px; + } + + #current-question-title { + font-size: 1.5rem; + } +} + +/* 动画效果 */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.hero, .interview-stage, .process-step { + animation: fadeIn 0.5s ease-out; +} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..13d6ef9 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,47 @@ + + + + + + {{ app_name }} + + + +
+
+

🤖 {{ app_name }}

+

AI智能面试助手 - 助力求职者成功

+
+ +
+
+
+

专业的AI面试辅导

+

通过智能分析提升您的面试表现,获得针对性的反馈和建议

+
    +
  • 📋 个性化面试流程设置
  • +
  • 🧠 AI智能回答分析
  • +
  • 📊 详细的面试结果评估
  • +
  • 💡 专业的改进建议
  • +
+ 开始面试 +
+
+
+
+
+
+
我有丰富的项目经验...
+
很好,请详细说明...
+
+
+
+
+
+ + +
+ + \ No newline at end of file diff --git a/templates/interview.html b/templates/interview.html new file mode 100644 index 0000000..e5a6353 --- /dev/null +++ b/templates/interview.html @@ -0,0 +1,116 @@ + + + + + + 面试流程 - AI帮你面试 + + + +
+
+

📋 面试流程

+ 返回首页 +
+ +
+ + + + +
+
+

自我介绍

+

请简要介绍您自己,包括教育背景和工作经验

+
+ +
+ + +
+
+
+ + + +
+
+ + + +
+ + + + \ No newline at end of file diff --git a/test_api.py b/test_api.py new file mode 100644 index 0000000..69d40fd --- /dev/null +++ b/test_api.py @@ -0,0 +1,32 @@ +import requests +import json + +# API测试脚本 +BASE_URL = 'http://127.0.0.1:5000' +TEST_HEADER_URL = 'http://127.0.0.1:5001' +API_KEY = 'sk-0944c102849e45d9ab3f24d5169de289' + +# 设置请求头 +headers = { + 'Content-Type': 'application/json', + 'X-API-Key': API_KEY +} + +try: + print("=== 测试Header调试端点 ===") + response = requests.get(f'{TEST_HEADER_URL}/test_headers', headers=headers) + print(f"状态码: {response.status_code}") + print(f"响应内容: {json.dumps(response.json(), ensure_ascii=False, indent=2)}") + + # 测试分析回答API + print("\n=== 测试分析回答API ===") + analyze_data = { + 'question_id': 1, + 'answer': '我是一名有5年经验的软件工程师,主要从事Python开发工作。' + } + response = requests.post(f'{BASE_URL}/analyze_answer', headers=headers, json=analyze_data) + print(f"状态码: {response.status_code}") + print(f"响应内容: {json.dumps(response.json(), ensure_ascii=False, indent=2)}") + +except Exception as e: + print(f"测试失败: {str(e)}") diff --git a/test_header.py b/test_header.py new file mode 100644 index 0000000..69abb20 --- /dev/null +++ b/test_header.py @@ -0,0 +1,31 @@ +from flask import Flask, request, jsonify +import os +from dotenv import load_dotenv + +# 加载环境变量 +load_dotenv() + +app = Flask(__name__) +API_KEY = os.getenv('AI_API_KEY', '') + +@app.route('/test_headers', methods=['GET', 'POST']) +def test_headers(): + print("[DEBUG] All Request Headers:") + for key, value in request.headers.items(): + print(f" {key}: {value}") + + # 特别检查X-API-Key + api_key = request.headers.get('X-API-Key') + print(f"[DEBUG] X-API-Key specifically: '{api_key}'") + print(f"[DEBUG] Expected API Key: '{API_KEY}'") + print(f"[DEBUG] Match: {api_key == API_KEY}") + + return jsonify({ + 'received_headers': dict(request.headers), + 'api_key': api_key, + 'expected_api_key': API_KEY, + 'match': api_key == API_KEY + }) + +if __name__ == '__main__': + app.run(debug=True, port=5001)