Complete Course Design Project

This commit is contained in:
Group12 2026-01-07 14:05:46 +08:00
parent ee757f2c72
commit c4e220ed62
12 changed files with 1424 additions and 1 deletions

7
.env Normal file
View File

@ -0,0 +1,7 @@
# AI帮你面试 - 环境配置
# API密钥配置
AI_API_KEY=sk-0944c102849e45d9ab3f24d5169de289
# Flask应用配置
FLASK_DEBUG=True

0
.gitignore vendored Normal file
View File

View File

@ -1,5 +1,8 @@
# AI帮你面试
姓名 学号 主要贡献 (具体分工)
蔡朗 2411020227 核心逻辑开发、Prompt 编写
陆刘青 2411020110 前端界面设计、PPT 制作
刘俊伯 2411020102 文档撰写、测试与 Bug 修复
一个基于Flask的AI面试辅助系统可以帮助用户模拟面试流程并提供AI分析反馈。
## 功能特性

124
app.py Normal file
View File

@ -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)

43
debug_api_key.py Normal file
View File

@ -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}")

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
flask==2.2.2
werkzeug==2.2.2
python-dotenv==1.0.0
requests==2.28.1

449
static/script.js Normal file
View File

@ -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 = `
<div class="score-card">
<h3>最终评分</h3>
<div class="score-value">${results.overall_score}</div>
</div>
<div class="results-details">
<h3>分类评分</h3>
<div class="category-scores">
<div class="score-item">
<label>专业知识</label>
<div class="progress">
<div class="progress-fill" style="width: ${results.category_scores.professional_knowledge}%"></div>
</div>
<span>${results.category_scores.professional_knowledge}%</span>
</div>
<div class="score-item">
<label>沟通能力</label>
<div class="progress">
<div class="progress-fill" style="width: ${results.category_scores.communication_skills}%"></div>
</div>
<span>${results.category_scores.communication_skills}%</span>
</div>
<div class="score-item">
<label>解决问题</label>
<div class="progress">
<div class="progress-fill" style="width: ${results.category_scores.problem_solving}%"></div>
</div>
<span>${results.category_scores.problem_solving}%</span>
</div>
<div class="score-item">
<label>经验相关性</label>
<div class="progress">
<div class="progress-fill" style="width: ${results.category_scores.experience_relevance}%"></div>
</div>
<span>${results.category_scores.experience_relevance}%</span>
</div>
</div>
</div>
<div class="strengths-improvements">
<div class="strengths">
<h3>👍 优点</h3>
<ul>
${results.strengths.map(strength => `<li>${strength}</li>`).join('')}
</ul>
</div>
<div class="improvements">
<h3>📈 需要改进的地方</h3>
<ul>
${results.improvement_areas.map(area => `<li>${area}</li>`).join('')}
</ul>
</div>
</div>
<div class="final-recommendation">
<h3>💡 最终建议</h3>
<p class="recommendation-text">${results.final_recommendation}</p>
</div>
`;
// 添加结果页面样式
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');
}

567
static/styles.css Normal file
View File

@ -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;
}

47
templates/index.html Normal file
View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ app_name }}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="container">
<header>
<h1>🤖 {{ app_name }}</h1>
<p class="subtitle">AI智能面试助手 - 助力求职者成功</p>
</header>
<main>
<section class="hero">
<div class="hero-content">
<h2>专业的AI面试辅导</h2>
<p>通过智能分析提升您的面试表现,获得针对性的反馈和建议</p>
<ul class="features">
<li>📋 个性化面试流程设置</li>
<li>🧠 AI智能回答分析</li>
<li>📊 详细的面试结果评估</li>
<li>💡 专业的改进建议</li>
</ul>
<a href="{{ url_for('start_interview') }}" class="btn btn-primary">开始面试</a>
</div>
<div class="hero-image">
<div class="interview-illustration">
<div class="person"></div>
<div class="ai-robot"></div>
<div class="chat-bubbles">
<div class="bubble person-bubble">我有丰富的项目经验...</div>
<div class="bubble ai-bubble">很好,请详细说明...</div>
</div>
</div>
</div>
</section>
</main>
<footer>
<p>&copy; 2026 {{ app_name }} | 智能面试助手</p>
</footer>
</div>
</body>
</html>

116
templates/interview.html Normal file
View File

@ -0,0 +1,116 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>面试流程 - AI帮你面试</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="container">
<header>
<h1>📋 面试流程</h1>
<a href="{{ url_for('home') }}" class="btn btn-secondary">返回首页</a>
</header>
<main class="interview-container">
<!-- 面试流程侧边栏 -->
<aside class="process-sidebar">
<h2>面试环节</h2>
<div class="process-steps">
{% for step in process %}
<div class="process-step" data-step-id="{{ step.id }}">
<div class="step-number">{{ step.id }}</div>
<div class="step-content">
<h3>{{ step.title }}</h3>
<p>{{ step.description }}</p>
<span class="step-duration">⏱️ {{ step.duration }}</span>
</div>
</div>
{% endfor %}
</div>
<button id="finish-interview-btn" class="btn btn-primary">完成面试</button>
</aside>
<!-- 主面试区域 -->
<section class="interview-main">
<div class="interview-stage" id="question-stage">
<h2 id="current-question-title">自我介绍</h2>
<p id="current-question-description">请简要介绍您自己,包括教育背景和工作经验</p>
<div class="answer-input">
<textarea id="answer-text" placeholder="请输入您的回答..."></textarea>
<div class="input-actions">
<button id="analyze-btn" class="btn btn-primary">AI分析</button>
<button id="next-question-btn" class="btn btn-secondary">下一题</button>
</div>
</div>
</div>
<!-- AI分析结果区域 -->
<div class="interview-stage hidden" id="analysis-stage">
<h2>🤖 AI分析结果</h2>
<div class="analysis-results">
<div class="score-card">
<h3>综合评分</h3>
<div class="score-value" id="overall-score">85</div>
</div>
<div class="analysis-details">
<h3>内容分析</h3>
<div class="progress-bars">
<div class="progress-bar">
<label>完整性</label>
<div class="progress">
<div class="progress-fill" id="completeness-bar" style="width: 85%"></div>
</div>
<span class="progress-value" id="completeness-value">85%</span>
</div>
<div class="progress-bar">
<label>相关性</label>
<div class="progress">
<div class="progress-fill" id="relevance-bar" style="width: 90%"></div>
</div>
<span class="progress-value" id="relevance-value">90%</span>
</div>
<div class="progress-bar">
<label>深度</label>
<div class="progress">
<div class="progress-fill" id="depth-bar" style="width: 75%"></div>
</div>
<span class="progress-value" id="depth-value">75%</span>
</div>
</div>
</div>
<div class="suggestions">
<h3>改进建议</h3>
<ul id="suggestions-list">
<li>可以提供更多具体的项目成果数据</li>
<li>注意控制回答时间,保持简洁明了</li>
<li>可以结合实例说明技术能力</li>
</ul>
</div>
</div>
<button id="back-to-question-btn" class="btn btn-secondary">返回修改</button>
</div>
</section>
</main>
<!-- 面试结果弹窗 -->
<div class="modal hidden" id="results-modal">
<div class="modal-content">
<span class="close">&times;</span>
<h2>🏆 面试结果</h2>
<div id="results-container">
<!-- 结果将通过JavaScript动态加载 -->
</div>
<a href="{{ url_for('home') }}" class="btn btn-primary">返回首页</a>
</div>
</div>
</div>
<script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>

32
test_api.py Normal file
View File

@ -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)}")

31
test_header.py Normal file
View File

@ -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)