|
@@ -1,6 +1,7 @@
|
|
import { Component, OnInit, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
|
|
import { Component, OnInit, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { CommonModule } from '@angular/common';
|
|
import * as echarts from 'echarts';
|
|
import * as echarts from 'echarts';
|
|
|
|
+import { AiApiService } from '../../../../lib/ai-api.service';
|
|
|
|
|
|
@Component({
|
|
@Component({
|
|
selector: 'app-page-interview',
|
|
selector: 'app-page-interview',
|
|
@@ -10,25 +11,30 @@ import * as echarts from 'echarts';
|
|
styleUrl: './page-interview.scss'
|
|
styleUrl: './page-interview.scss'
|
|
})
|
|
})
|
|
export class PageInterview implements OnInit, AfterViewInit {
|
|
export class PageInterview implements OnInit, AfterViewInit {
|
|
-remainingTime = '04:32';
|
|
|
|
-isAnalyzing = false;
|
|
|
|
-showSubmitButton = false;
|
|
|
|
-userAnswer = '';
|
|
|
|
-// 在组件中定义Base64编码的SVG
|
|
|
|
-avatarImages = {
|
|
|
|
- default: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzNiAzNiI+PHBhdGggZmlsbD0iI0ZGQjkwMCIgZD0iTTM2IDE4YzAgOS45NDEtOC4wNTkgMTgtMTggMThTMCAyNy45NDEgMCAxOCA4LjA1OSAwIDE4IDBzMTggOC4wNTkgMTggMTgiLz48cGF0aCBmaWxsPSIjNjYyMjEyIiBkPSJNMTguODQ1IDEzLjE0OGMuMTM0LS4yNDYuMzU2LS4zNjkuNjU3LS4zNjkuMzA5IDAgLjUyOS4xMjMuNjY0LjM2OS4xMzYuMjQ1LjIwNC41NzkuMjA0IDEuMDAzIDAgLjQxNS0uMDY4Ljc0LS4yMDQuOTg1LS4xMzUuMjQ0LS4zNTUuMzY2LS42NjQuMzY2LS4zMDEgMC0uNTIzLS4xMjItLjY1Ny0uMzY2LS4xMzUtLjI0NS0uMjAyLS41Ny0uMjAyLS45ODUgMC0uNDI0LjA2Ny0uNzU4LjIwMi0xLjAwM3ptLTUuNDQ4IDBjLjEzNS0uMjQ2LjM1Ni0uMzY5LjY1OC0uMzY5LjMwOSAwIC41MjkuMTIzLjY2NC4zNjkuMTM2LjI0NS4yMDQuNTc5LjIwNCAxLjAwMyAwIC40MTUtLjA2OC43NC0uMjA0Ljk4NS0uMTM1LjI0NC0uMzU1LjM2Ni0uNjY0LjM2Ni0uMzAyIDAtLjUyMy0uMTIyLS42NTgtLjM2Ni0uMTM0LS4yNDUtLjIwMS0uNTctLjIwMS0uOTg1IDAtLjQyNC4wNjctLjc1OC4yMDEtMS4wMDN6Ii8+PHBhdGggZmlsbD0iIzY2MjIxMiIgZD0iTTE4IDIxLjUzYy0yLjc2NCAwLTQuOTUxLS40MTktNC45NTEtMS4wNTQgMC0uNjM1IDIuMTg3LTEuMDU0IDQuOTUxLTEuMDU0IDIuNzYzIDAgNC45NTEuNDE5IDQuOTUxIDEuMDU0IDAgLjYzNS0yLjE4OCAxLjA1NC00Ljk1MSAxLjA1NHptMC0yLjEwOGMtMy4wMTcgMC01LjQ1MS0uNDk0LTUuNDUxLTEuMDU0czIuNDM0LTEuMDU0IDUuNDUxLTEuMDU0YzMuMDE4IDAgNS40NTEuNDk0IDUuNDUxIDEuMDU0cy0yLjQzMyAxLjA1NC01LjQ1MSAxLjA1NHoiLz48L3N2Zz4=',
|
|
|
|
- speaking: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzNiAzNiI+PHBhdGggZmlsbD0iI0ZGQjkwMCIgZD0iTTM2IDE4YzAgOS45NDEtOC4wNTkgMTgtMTggMThTMCAyNy45NDEgMCAxOCA4LjA1OSAwIDE4IDBzMTggOC4wNTkgMTggMTgiLz48cGF0aCBmaWxsPSIjNjYyMjEyIiBkPSJNMTggMjEuNTNjLTIuNzY0IDAtNC45NTEtLjQxOS00Ljk1MS0xLjA1NCAwLS42MzUgMi4xODctMS4wNTQgNC45NTEtMS4wNTQgMi43NjMgMCA0Ljk1MS40MTkgNC45NTEgMS4wNTQgMCAuNjM1LTIuMTg4IDEuMDU0LTQuOTUxIDEuMDU0em0wLTIuMTA4Yy0zLjAxNyAwLTUuNDUxLS40OTQtNS40NTEtMS4wNTRzMi40MzQtMS4wNTQgNS40NTEtMS4wNTRjMy4wMTggMCA1LjQ1MS40OTQgNS40NTEgMS4wNTRzLTIuNDMzIDEuMDU0LTUuNDUxIDEuMDU0eiIvPjwvc3ZnPg==',
|
|
|
|
- // 其他表情...
|
|
|
|
-};
|
|
|
|
- // 问题相关
|
|
|
|
- questions = [
|
|
|
|
|
|
+ remainingTime = '04:32';
|
|
|
|
+ isAnalyzing = false;
|
|
|
|
+ showSubmitButton = false;
|
|
|
|
+ showCancelButton = false; // 添加缺失的属性
|
|
|
|
+ userAnswer = '';
|
|
|
|
+ isFollowUpQuestion = false; // 添加缺失的属性
|
|
|
|
+
|
|
|
|
+ // 面试问题相关
|
|
|
|
+ mainQuestions = [
|
|
"欢迎参加本次AI面试,我是您的面试官AI助手。",
|
|
"欢迎参加本次AI面试,我是您的面试官AI助手。",
|
|
"首先,请简单介绍一下您自己。",
|
|
"首先,请简单介绍一下您自己。",
|
|
"您在过去工作中遇到的最大技术挑战是什么?您是如何解决的?",
|
|
"您在过去工作中遇到的最大技术挑战是什么?您是如何解决的?",
|
|
"请描述一个您与团队意见不合时,您是如何处理的案例。"
|
|
"请描述一个您与团队意见不合时,您是如何处理的案例。"
|
|
];
|
|
];
|
|
|
|
+
|
|
|
|
+ followUpQuestions = [
|
|
|
|
+ "", // 第一个问题不需要追问
|
|
|
|
+ "您能详细说明一下您最擅长的技术领域吗?",
|
|
|
|
+ "在这个解决方案中,您学到了什么重要的经验?",
|
|
|
|
+ "这次经历如何影响了您后续的团队协作方式?"
|
|
|
|
+ ];
|
|
|
|
+
|
|
currentQuestionIndex = 0;
|
|
currentQuestionIndex = 0;
|
|
- currentQuestion = this.questions[0];
|
|
|
|
|
|
+ currentQuestion = this.mainQuestions[0];
|
|
showQuestionCard = false;
|
|
showQuestionCard = false;
|
|
progress = 30;
|
|
progress = 30;
|
|
|
|
|
|
@@ -51,87 +57,112 @@ avatarImages = {
|
|
relevance: 73
|
|
relevance: 73
|
|
};
|
|
};
|
|
|
|
|
|
- // 预设回答
|
|
|
|
- presetAnswers = [
|
|
|
|
- "我是张伟,有5年Java开发经验,擅长分布式系统设计。",
|
|
|
|
- "我们曾遇到高并发下的数据库瓶颈,我通过引入Redis缓存和分库分表解决了问题。",
|
|
|
|
- "有一次在技术方案选择上,我通过数据对比和原型验证说服了团队采用我的方案。"
|
|
|
|
- ];
|
|
|
|
-
|
|
|
|
@ViewChild('dialogContainer') dialogContainer!: ElementRef;
|
|
@ViewChild('dialogContainer') dialogContainer!: ElementRef;
|
|
private radarChart: any;
|
|
private radarChart: any;
|
|
|
|
|
|
|
|
+ constructor(private aiService: AiApiService) {}
|
|
|
|
+
|
|
ngOnInit(): void {
|
|
ngOnInit(): void {
|
|
this.initConversation();
|
|
this.initConversation();
|
|
this.startProgressTimer();
|
|
this.startProgressTimer();
|
|
}
|
|
}
|
|
|
|
|
|
ngAfterViewInit(): void {
|
|
ngAfterViewInit(): void {
|
|
- // 初始化雷达图
|
|
|
|
this.initRadarChart();
|
|
this.initRadarChart();
|
|
}
|
|
}
|
|
|
|
|
|
- // 初始化对话
|
|
|
|
|
|
+ // 添加缺失的方法
|
|
|
|
+ updateAvatarState(state: "speaking" | "listening" | "waiting" | "analyzing"): void {
|
|
|
|
+ switch(state) {
|
|
|
|
+ case "speaking":
|
|
|
|
+ this.avatarImage = "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/1f5e3.svg";
|
|
|
|
+ this.expressionText = "正在播报问题...";
|
|
|
|
+ break;
|
|
|
|
+ case "listening":
|
|
|
|
+ this.avatarImage = "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/1f4ac.svg";
|
|
|
|
+ this.expressionText = "正在聆听您的回答...";
|
|
|
|
+ break;
|
|
|
|
+ case "waiting":
|
|
|
|
+ this.avatarImage = "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/1f914.svg";
|
|
|
|
+ this.expressionText = "请确认您的回答";
|
|
|
|
+ break;
|
|
|
|
+ case "analyzing":
|
|
|
|
+ this.avatarImage = "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/1f9d0.svg";
|
|
|
|
+ this.expressionText = "正在分析您的回答...";
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
initConversation(): void {
|
|
initConversation(): void {
|
|
- this.addAIMessage(this.questions[0]);
|
|
|
|
|
|
+ this.addAIMessage(this.mainQuestions[0]);
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
this.askQuestion(1);
|
|
this.askQuestion(1);
|
|
}, 1500);
|
|
}, 1500);
|
|
}
|
|
}
|
|
|
|
|
|
- // 提问问题
|
|
|
|
askQuestion(index: number): void {
|
|
askQuestion(index: number): void {
|
|
- if (index >= this.questions.length) return;
|
|
|
|
|
|
+ if (index >= this.mainQuestions.length) {
|
|
|
|
+ this.endInterview();
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
|
|
this.currentQuestionIndex = index;
|
|
this.currentQuestionIndex = index;
|
|
- this.currentQuestion = this.questions[index];
|
|
|
|
|
|
+ this.isFollowUpQuestion = false;
|
|
|
|
+ this.currentQuestion = this.mainQuestions[index];
|
|
|
|
|
|
- // 添加到对话框
|
|
|
|
this.addAIMessage(this.currentQuestion);
|
|
this.addAIMessage(this.currentQuestion);
|
|
|
|
+ this.updateAvatarState("speaking");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ askFollowUpQuestion(): void {
|
|
|
|
+ if (this.currentQuestionIndex >= this.followUpQuestions.length ||
|
|
|
|
+ !this.followUpQuestions[this.currentQuestionIndex]) {
|
|
|
|
+ this.askQuestion(this.currentQuestionIndex + 1);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
|
|
- // 更新头像状态
|
|
|
|
- this.avatarImage = "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/1f4ac.svg";
|
|
|
|
- this.expressionText = "等待您的回答...";
|
|
|
|
|
|
+ this.isFollowUpQuestion = true;
|
|
|
|
+ this.currentQuestion = this.followUpQuestions[this.currentQuestionIndex];
|
|
|
|
+
|
|
|
|
+ this.addAIMessage(this.currentQuestion);
|
|
|
|
+ this.updateAvatarState("speaking");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ endInterview(): void {
|
|
|
|
+ this.addAIMessage("感谢您的回答,面试即将结束,正在生成最终报告...");
|
|
|
|
+ this.updateAvatarState("analyzing");
|
|
}
|
|
}
|
|
|
|
|
|
- // 添加AI消息
|
|
|
|
addAIMessage(text: string): void {
|
|
addAIMessage(text: string): void {
|
|
this.messages.push({
|
|
this.messages.push({
|
|
sender: 'ai',
|
|
sender: 'ai',
|
|
text: text
|
|
text: text
|
|
});
|
|
});
|
|
|
|
|
|
- // 滚动到底部
|
|
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
this.dialogContainer.nativeElement.scrollTop = this.dialogContainer.nativeElement.scrollHeight;
|
|
this.dialogContainer.nativeElement.scrollTop = this.dialogContainer.nativeElement.scrollHeight;
|
|
}, 0);
|
|
}, 0);
|
|
|
|
|
|
- // 自动语音播报
|
|
|
|
this.speakText(text);
|
|
this.speakText(text);
|
|
}
|
|
}
|
|
|
|
|
|
- // 添加用户消息
|
|
|
|
addUserMessage(text: string): void {
|
|
addUserMessage(text: string): void {
|
|
this.messages.push({
|
|
this.messages.push({
|
|
sender: 'user',
|
|
sender: 'user',
|
|
text: text
|
|
text: text
|
|
});
|
|
});
|
|
|
|
|
|
- // 滚动到底部
|
|
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
this.dialogContainer.nativeElement.scrollTop = this.dialogContainer.nativeElement.scrollHeight;
|
|
this.dialogContainer.nativeElement.scrollTop = this.dialogContainer.nativeElement.scrollHeight;
|
|
}, 0);
|
|
}, 0);
|
|
}
|
|
}
|
|
|
|
|
|
- // 语音播报
|
|
|
|
speakText(text: string): void {
|
|
speakText(text: string): void {
|
|
if (this.isSpeaking) return;
|
|
if (this.isSpeaking) return;
|
|
|
|
|
|
this.isSpeaking = true;
|
|
this.isSpeaking = true;
|
|
- this.avatarImage = "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/1f5e3.svg";
|
|
|
|
- this.expressionText = "正在播报问题...";
|
|
|
|
|
|
+ this.updateAvatarState("speaking");
|
|
|
|
|
|
- // 使用Web Speech API
|
|
|
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
const utterance = new SpeechSynthesisUtterance(text);
|
|
utterance.lang = 'zh-CN';
|
|
utterance.lang = 'zh-CN';
|
|
utterance.rate = 0.9;
|
|
utterance.rate = 0.9;
|
|
@@ -139,64 +170,87 @@ avatarImages = {
|
|
|
|
|
|
utterance.onend = () => {
|
|
utterance.onend = () => {
|
|
this.isSpeaking = false;
|
|
this.isSpeaking = false;
|
|
- this.avatarImage = "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/svg/1f4ac.svg";
|
|
|
|
- this.expressionText = "等待您的回答...";
|
|
|
|
|
|
+ this.updateAvatarState("listening");
|
|
};
|
|
};
|
|
|
|
|
|
speechSynthesis.speak(utterance);
|
|
speechSynthesis.speak(utterance);
|
|
}
|
|
}
|
|
|
|
|
|
- // 开始语音输入
|
|
|
|
startVoiceInput(): void {
|
|
startVoiceInput(): void {
|
|
this.isListening = true;
|
|
this.isListening = true;
|
|
- this.expressionText = "正在聆听您的回答...";
|
|
|
|
|
|
+ this.updateAvatarState("listening");
|
|
console.log('语音输入开始');
|
|
console.log('语音输入开始');
|
|
}
|
|
}
|
|
|
|
|
|
- // 停止语音输入
|
|
|
|
stopVoiceInput(): void {
|
|
stopVoiceInput(): void {
|
|
this.isListening = false;
|
|
this.isListening = false;
|
|
|
|
|
|
// 模拟语音识别结果
|
|
// 模拟语音识别结果
|
|
- this.userAnswer = this.presetAnswers[this.currentQuestionIndex - 1] || "这是我的回答...";
|
|
|
|
- this.showSubmitButton = true; // 显示提交按钮
|
|
|
|
|
|
+ this.userAnswer = "这是我的回答...";
|
|
|
|
+ this.showSubmitButton = true;
|
|
|
|
+ this.showCancelButton = true;
|
|
|
|
|
|
- // 不再自动提交,等待用户点击
|
|
|
|
- this.avatarImage = this.avatarImages.default;
|
|
|
|
- this.expressionText = "请确认您的回答";
|
|
|
|
|
|
+ this.updateAvatarState("waiting");
|
|
}
|
|
}
|
|
- submitAnswer(): void {
|
|
|
|
|
|
+
|
|
|
|
+ async submitAnswer(): Promise<void> {
|
|
this.showSubmitButton = false;
|
|
this.showSubmitButton = false;
|
|
|
|
+ this.showCancelButton = false;
|
|
this.addUserMessage(this.userAnswer);
|
|
this.addUserMessage(this.userAnswer);
|
|
|
|
|
|
- // 开始分析
|
|
|
|
this.isAnalyzing = true;
|
|
this.isAnalyzing = true;
|
|
- this.avatarImage = this.avatarImages.default;
|
|
|
|
- this.expressionText = "正在分析您的回答...";
|
|
|
|
|
|
+ this.updateAvatarState("analyzing");
|
|
|
|
|
|
- // 模拟分析过程 (2秒)
|
|
|
|
- setTimeout(() => {
|
|
|
|
- this.isAnalyzing = false;
|
|
|
|
|
|
+ try {
|
|
|
|
+ const evaluation = await this.aiService.evaluateInterviewAnswer(
|
|
|
|
+ this.currentQuestion,
|
|
|
|
+ this.userAnswer,
|
|
|
|
+ (content) => console.log('分析进度:', content)
|
|
|
|
+ );
|
|
|
|
|
|
- // 问下一题或结束
|
|
|
|
- if (this.currentQuestionIndex < this.questions.length - 1) {
|
|
|
|
- this.askQuestion(this.currentQuestionIndex + 1);
|
|
|
|
|
|
+ this.metrics = {
|
|
|
|
+ expressiveness: evaluation.metrics.expressiveness,
|
|
|
|
+ professionalism: evaluation.metrics.professionalism,
|
|
|
|
+ relevance: evaluation.metrics.relevance
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ this.addAIMessage(evaluation.feedback);
|
|
|
|
+
|
|
|
|
+ if (!this.isFollowUpQuestion) {
|
|
|
|
+ this.addAIMessage("接下来,我有一个跟进问题...");
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ this.currentQuestion = evaluation.followUpQuestion;
|
|
|
|
+ this.isFollowUpQuestion = true;
|
|
|
|
+ this.addAIMessage(this.currentQuestion);
|
|
|
|
+ }, 1500);
|
|
} else {
|
|
} else {
|
|
- this.addAIMessage("感谢您的回答,面试即将结束,正在生成最终报告...");
|
|
|
|
- this.avatarImage = this.avatarImages.speaking;
|
|
|
|
- this.expressionText = "生成最终评估中...";
|
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ this.askQuestion(this.currentQuestionIndex + 1);
|
|
|
|
+ }, 1500);
|
|
}
|
|
}
|
|
- }, 2000);
|
|
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('评估失败:', error);
|
|
|
|
+ this.addAIMessage("分析完成,让我们继续下一个问题");
|
|
|
|
+ this.askQuestion(this.currentQuestionIndex + 1);
|
|
|
|
+ } finally {
|
|
|
|
+ this.isAnalyzing = false;
|
|
|
|
+ this.userAnswer = '';
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ cancelAnswer(): void {
|
|
|
|
+ this.showSubmitButton = false;
|
|
|
|
+ this.showCancelButton = false;
|
|
|
|
+ this.userAnswer = '';
|
|
|
|
+ this.updateAvatarState("listening");
|
|
}
|
|
}
|
|
|
|
|
|
- // 播放问题
|
|
|
|
playQuestion(): void {
|
|
playQuestion(): void {
|
|
if (!this.isSpeaking) {
|
|
if (!this.isSpeaking) {
|
|
- this.speakText(this.questions[this.currentQuestionIndex]);
|
|
|
|
|
|
+ this.speakText(this.currentQuestion);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // 切换雷达图
|
|
|
|
toggleRadarChart(): void {
|
|
toggleRadarChart(): void {
|
|
this.showRadarChart = !this.showRadarChart;
|
|
this.showRadarChart = !this.showRadarChart;
|
|
if (this.showRadarChart) {
|
|
if (this.showRadarChart) {
|
|
@@ -206,7 +260,6 @@ avatarImages = {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- // 初始化雷达图
|
|
|
|
initRadarChart(): void {
|
|
initRadarChart(): void {
|
|
if (!this.showRadarChart) return;
|
|
if (!this.showRadarChart) return;
|
|
|
|
|
|
@@ -289,16 +342,14 @@ avatarImages = {
|
|
|
|
|
|
this.radarChart.setOption(option);
|
|
this.radarChart.setOption(option);
|
|
|
|
|
|
- // 响应式调整
|
|
|
|
window.addEventListener('resize', () => {
|
|
window.addEventListener('resize', () => {
|
|
this.radarChart?.resize();
|
|
this.radarChart?.resize();
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
- // 进度条计时器
|
|
|
|
startProgressTimer(): void {
|
|
startProgressTimer(): void {
|
|
setInterval(() => {
|
|
setInterval(() => {
|
|
this.progress = Math.min(this.progress + 10, 100);
|
|
this.progress = Math.min(this.progress + 10, 100);
|
|
}, 5000);
|
|
}, 5000);
|
|
}
|
|
}
|
|
-}
|
|
|
|
|
|
+}
|