Complete Course Design Project
This commit is contained in:
parent
ee757f2c72
commit
c4e220ed62
7
.env
Normal file
7
.env
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# AI帮你面试 - 环境配置
|
||||||
|
|
||||||
|
# API密钥配置
|
||||||
|
AI_API_KEY=sk-0944c102849e45d9ab3f24d5169de289
|
||||||
|
|
||||||
|
# Flask应用配置
|
||||||
|
FLASK_DEBUG=True
|
||||||
0
.gitignore
vendored
Normal file
0
.gitignore
vendored
Normal file
@ -1,5 +1,8 @@
|
|||||||
# AI帮你面试
|
# AI帮你面试
|
||||||
|
姓名 学号 主要贡献 (具体分工)
|
||||||
|
蔡朗 2411020227 核心逻辑开发、Prompt 编写
|
||||||
|
陆刘青 2411020110 前端界面设计、PPT 制作
|
||||||
|
刘俊伯 2411020102 文档撰写、测试与 Bug 修复
|
||||||
一个基于Flask的AI面试辅助系统,可以帮助用户模拟面试流程并提供AI分析反馈。
|
一个基于Flask的AI面试辅助系统,可以帮助用户模拟面试流程并提供AI分析反馈。
|
||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|||||||
124
app.py
Normal file
124
app.py
Normal 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
43
debug_api_key.py
Normal 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
4
requirements.txt
Normal 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
449
static/script.js
Normal 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
567
static/styles.css
Normal 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
47
templates/index.html
Normal 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>© 2026 {{ app_name }} | 智能面试助手</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
116
templates/interview.html
Normal file
116
templates/interview.html
Normal 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">×</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
32
test_api.py
Normal 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
31
test_header.py
Normal 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)
|
||||||
Loading…
Reference in New Issue
Block a user