AI接入逻辑完善
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
package com.vetti.socket.agents;
|
||||
|
||||
import okhttp3.*;
|
||||
import okio.ByteString;
|
||||
import org.springframework.web.socket.BinaryMessage;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class ElevenLabsAgentClient extends TextWebSocketHandler{
|
||||
|
||||
// 存储Vue会话与ElevenLabs WebSocket的映射(多客户端隔离)
|
||||
private static final Map<WebSocketSession, WebSocket> SESSION_MAP = new ConcurrentHashMap<>();
|
||||
// ElevenLabs配置
|
||||
private static final String ELEVEN_LABS_API_KEY = "你的ElevenLabs API Key";
|
||||
private static final String AGENT_ID = "你的ElevenLabs Agent ID";
|
||||
private static final String ELEVEN_LABS_WSS_URL = "wss://api.elevenlabs.io/v1/agents/" + AGENT_ID + "/stream";
|
||||
// OkHttp客户端
|
||||
private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient.Builder()
|
||||
.readTimeout(0, TimeUnit.MILLISECONDS) // WebSocket长连接取消读超时
|
||||
.build();
|
||||
|
||||
// ==================== 1. 处理Vue前端连接 ====================
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession vueSession) throws Exception {
|
||||
super.afterConnectionEstablished(vueSession);
|
||||
System.out.println("Vue前端连接成功:" + vueSession.getId());
|
||||
|
||||
// 建立与ElevenLabs Agents的WSS连接
|
||||
buildElevenLabsWssConnection(vueSession);
|
||||
}
|
||||
|
||||
// ==================== 2. 处理Vue前端发送的文本消息(如语音指令、配置信息) ====================
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession vueSession, TextMessage message) throws Exception {
|
||||
super.handleTextMessage(vueSession, message);
|
||||
System.out.println("接收Vue文本消息:" + message.getPayload());
|
||||
// 获取对应ElevenLabs WebSocket连接,转发消息
|
||||
WebSocket elevenLabsWs = SESSION_MAP.get(vueSession);
|
||||
if (elevenLabsWs != null && elevenLabsWs.queueSize() == 0) {
|
||||
elevenLabsWs.send(message.getPayload());
|
||||
System.out.println("转发文本消息到ElevenLabs成功");
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 3. 处理Vue前端发送的二进制消息(核心:语音流数据) ====================
|
||||
@Override
|
||||
protected void handleBinaryMessage(WebSocketSession vueSession, BinaryMessage message) {
|
||||
super.handleBinaryMessage(vueSession, message);
|
||||
byte[] voiceData = message.getPayload().array();
|
||||
System.out.println("接收Vue语音流数据,字节长度:" + voiceData.length);
|
||||
|
||||
// 获取对应ElevenLabs WebSocket连接,转发语音流(二进制)
|
||||
WebSocket elevenLabsWs = SESSION_MAP.get(vueSession);
|
||||
if (elevenLabsWs != null && elevenLabsWs.queueSize() == 0) {
|
||||
elevenLabsWs.send(ByteString.of(voiceData));
|
||||
System.out.println("转发语音流到ElevenLabs成功");
|
||||
}
|
||||
|
||||
// 释放二进制消息资源
|
||||
// message.isLast();
|
||||
}
|
||||
|
||||
// ==================== 4. 处理Vue前端连接关闭 ====================
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession vueSession, org.springframework.web.socket.CloseStatus status) throws Exception {
|
||||
super.afterConnectionClosed(vueSession, status);
|
||||
System.out.println("Vue前端连接关闭:" + vueSession.getId() + ",原因:" + status.getReason());
|
||||
|
||||
// 关闭对应的ElevenLabs WSS连接
|
||||
WebSocket elevenLabsWs = SESSION_MAP.remove(vueSession);
|
||||
if (elevenLabsWs != null) {
|
||||
elevenLabsWs.close(1000, "Vue客户端断开连接");
|
||||
System.out.println("关闭ElevenLabs WSS连接成功");
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 5. 建立与ElevenLabs Agents的WSS连接 ====================
|
||||
private void buildElevenLabsWssConnection(WebSocketSession vueSession) {
|
||||
// 1. 构建ElevenLabs WSS请求(携带认证头)
|
||||
Request request = new Request.Builder()
|
||||
.url(ELEVEN_LABS_WSS_URL)
|
||||
.header("xi-api-key", ELEVEN_LABS_API_KEY) // 必选认证头
|
||||
.build();
|
||||
|
||||
// 2. 构建ElevenLabs WSS监听器(处理响应并回流到Vue)
|
||||
WebSocketListener elevenLabsListener = new WebSocketListener() {
|
||||
// ElevenLabs WSS连接建立
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
super.onOpen(webSocket, response);
|
||||
System.out.println("与ElevenLabs Agents WSS连接成功");
|
||||
// 存储Vue会话与ElevenLabs WS的映射
|
||||
SESSION_MAP.put(vueSession, webSocket);
|
||||
}
|
||||
|
||||
// 接收ElevenLabs文本消息(如状态、错误提示),回流到Vue
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, String text) {
|
||||
super.onMessage(webSocket, text);
|
||||
System.out.println("接收ElevenLabs文本消息:" + text);
|
||||
try {
|
||||
// 回流到Vue前端
|
||||
if (vueSession.isOpen()) {
|
||||
vueSession.sendMessage(new TextMessage(text));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("回流文本消息到Vue失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 接收ElevenLabs二进制消息(核心:音频流响应),回流到Vue
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, ByteString bytes) {
|
||||
super.onMessage(webSocket, bytes);
|
||||
byte[] audioData = bytes.toByteArray();
|
||||
System.out.println("接收ElevenLabs音频流数据,字节长度:" + audioData.length);
|
||||
try {
|
||||
// 回流二进制音频流到Vue前端
|
||||
if (vueSession.isOpen()) {
|
||||
vueSession.sendMessage(new BinaryMessage(audioData));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("回流音频流到Vue失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ElevenLabs WSS连接关闭
|
||||
@Override
|
||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||
super.onClosed(webSocket, code, reason);
|
||||
System.out.println("ElevenLabs WSS连接关闭:" + reason + ",状态码:" + code);
|
||||
// 移除映射,关闭Vue连接
|
||||
SESSION_MAP.remove(vueSession);
|
||||
try {
|
||||
if (vueSession.isOpen()) {
|
||||
vueSession.close(org.springframework.web.socket.CloseStatus.NORMAL);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// ElevenLabs WSS连接异常
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
super.onFailure(webSocket, t, response);
|
||||
System.err.println("ElevenLabs WSS连接异常:" + t.getMessage());
|
||||
// 移除映射,关闭Vue连接
|
||||
SESSION_MAP.remove(vueSession);
|
||||
try {
|
||||
if (vueSession.isOpen()) {
|
||||
vueSession.close(org.springframework.web.socket.CloseStatus.SERVER_ERROR);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 3. 建立ElevenLabs WSS连接
|
||||
OK_HTTP_CLIENT.newWebSocket(request, elevenLabsListener);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,7 @@ import com.vetti.common.core.controller.BaseController;
|
||||
import com.vetti.common.core.domain.AjaxResult;
|
||||
import com.vetti.common.core.domain.R;
|
||||
import com.vetti.common.core.domain.dto.RealtimeClientSecretDto;
|
||||
import com.vetti.common.enums.MinioBucketNameEnum;
|
||||
import com.vetti.common.utils.readFile.FileContentUtil;
|
||||
import com.vetti.web.service.ICommonService;
|
||||
import io.minio.GetObjectArgs;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -21,10 +18,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* AI 共通测试接口处理
|
||||
*
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.vetti.web.controller.ai;
|
||||
|
||||
import com.vetti.common.core.controller.BaseController;
|
||||
import com.vetti.common.core.domain.R;
|
||||
import com.vetti.hotake.domain.HotakeInitialScreeningQuestionsInfo;
|
||||
import com.vetti.hotake.domain.dto.HotakeCvOptimizeDto;
|
||||
import com.vetti.hotake.domain.dto.HotakeInitialQuestionEliminationScoreDto;
|
||||
import com.vetti.hotake.domain.dto.HotakeJobDescriptionGeneratorDto;
|
||||
import com.vetti.hotake.domain.vo.HotakeInitialScreeningQuestionsVo;
|
||||
import com.vetti.hotake.domain.vo.HotakeJobDescriptionGeneratorVo;
|
||||
import com.vetti.hotake.domain.vo.HotakeResumeJobMatchingScoreVo;
|
||||
import com.vetti.hotake.service.IHotakeAiCommonToolsService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI共通工具 信息Controller
|
||||
*
|
||||
* @author wangxiangshun
|
||||
* @date 2025-12-14
|
||||
*/
|
||||
@Api(tags ="AI共通工具信息")
|
||||
@RestController
|
||||
@RequestMapping("/hotake/aiCommonTools")
|
||||
public class HotakeAiCommonToolsController extends BaseController {
|
||||
|
||||
@Autowired
|
||||
private IHotakeAiCommonToolsService hotakeAiCommonToolsService;
|
||||
|
||||
|
||||
/**
|
||||
* 职位描述生成器
|
||||
*/
|
||||
@ApiOperation("职位描述生成器")
|
||||
@PostMapping(value = "/jobDescriptionGenerator")
|
||||
public R<HotakeJobDescriptionGeneratorDto> handleJobDescriptionGenerator(@RequestBody HotakeJobDescriptionGeneratorVo jobDescriptionGeneratorVo)
|
||||
{
|
||||
return R.ok(hotakeAiCommonToolsService.getJobDescriptionGenerator(jobDescriptionGeneratorVo.getRoleId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 初筛问题生成
|
||||
*/
|
||||
@ApiOperation("初筛问题生成")
|
||||
@PostMapping(value = "/initialScreeningQuestionsGenerator")
|
||||
public R<List<HotakeInitialScreeningQuestionsInfo>> handleInitialScreeningQuestions(@RequestBody HotakeInitialScreeningQuestionsVo questionsVo)
|
||||
{
|
||||
return R.ok(hotakeAiCommonToolsService.getInitialScreeningQuestionsGenerator(questionsVo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 简历岗位匹配度评分
|
||||
*/
|
||||
@ApiOperation("简历岗位匹配度评分")
|
||||
@GetMapping(value = "/resumeJobMatchingScore")
|
||||
public R<?> handleResumeJobMatchingScore(@RequestBody HotakeResumeJobMatchingScoreVo scoreVo)
|
||||
{
|
||||
return R.ok(hotakeAiCommonToolsService.getResumeJobMatchingScore(scoreVo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 简历分析优化器
|
||||
*/
|
||||
@ApiOperation("简历分析优化器")
|
||||
@GetMapping(value = "/resumeAnalysisOptimizer")
|
||||
public R<HotakeCvOptimizeDto> handleResumeAnalysisOptimizer()
|
||||
{
|
||||
return R.ok(hotakeAiCommonToolsService.getResumeAnalysisOptimizer(""));
|
||||
}
|
||||
|
||||
/**
|
||||
* 初步筛选问题淘汰评分
|
||||
*/
|
||||
@ApiOperation("初步筛选问题淘汰评分")
|
||||
@GetMapping(value = "/initialQuestionEliminationScore")
|
||||
public R<HotakeInitialQuestionEliminationScoreDto> handleInitialQuestionEliminationScore()
|
||||
{
|
||||
return R.ok(hotakeAiCommonToolsService.getInitialQuestionEliminationScore(null));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import com.vetti.hotake.domain.HotakeCvInfo;
|
||||
import com.vetti.hotake.service.IHotakeCvInfoService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -60,7 +61,7 @@ public class HotakeCvInfoController extends BaseController
|
||||
*/
|
||||
@ApiOperation("获取简历信息详细信息")
|
||||
@GetMapping(value = "/{id}")
|
||||
public R<HotakeCvInfo> getInfo(@PathVariable("id") Long id)
|
||||
public R<HotakeCvInfo> getInfo(@ApiParam("简历ID") @PathVariable("id") Long id)
|
||||
{
|
||||
return R.ok(hotakeCvInfoService.selectHotakeCvInfoById(id),"");
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.vetti.common.core.page.TableDataInfo;
|
||||
import com.vetti.common.core.page.TableWebDataInfo;
|
||||
import com.vetti.common.enums.BusinessType;
|
||||
import com.vetti.hotake.domain.HotakeInitialScreeningQuestionsInfo;
|
||||
import com.vetti.hotake.domain.vo.HotakeInitialScreeningQuestionsInfoVo;
|
||||
import com.vetti.hotake.service.IHotakeInitialScreeningQuestionsInfoService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -94,4 +95,16 @@ public class HotakeInitialScreeningQuestionsInfoController extends BaseControlle
|
||||
{
|
||||
return R.ok(hotakeInitialScreeningQuestionsInfoService.deleteHotakeInitialScreeningQuestionsInfoByIds(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增初步筛选问题信息
|
||||
*/
|
||||
@ApiOperation("批量新增初步筛选问题信息")
|
||||
@Log(title = "批量新增初步筛选问题信息", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/batchAdd")
|
||||
public R<?> batchAdd(@RequestBody HotakeInitialScreeningQuestionsInfoVo questionsInfoVo)
|
||||
{
|
||||
hotakeInitialScreeningQuestionsInfoService.batchInsertHotakeInitialScreeningQuestionsInfo(questionsInfoVo);
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.vetti.common.core.page.TableDataInfo;
|
||||
import com.vetti.common.core.page.TableWebDataInfo;
|
||||
import com.vetti.common.enums.BusinessType;
|
||||
import com.vetti.hotake.domain.HotakeMeetingCalendarInfo;
|
||||
import com.vetti.hotake.domain.dto.HotakeMeetingCalendarDataDto;
|
||||
import com.vetti.hotake.domain.vo.HotakeMeetingCalendarVo;
|
||||
import com.vetti.hotake.service.IHotakeMeetingCalendarInfoService;
|
||||
import io.swagger.annotations.Api;
|
||||
@@ -47,12 +48,12 @@ public class HotakeMeetingCalendarInfoController extends BaseController
|
||||
/**
|
||||
* 查询会议日历记录主列表(无分页)
|
||||
*/
|
||||
@ApiOperation("查询会议日历记录列表(无分页)")
|
||||
@ApiOperation("候选人-查询会议日历记录列表(无分页)")
|
||||
@GetMapping("/getList")
|
||||
public R<List<HotakeMeetingCalendarInfo>> list(HotakeMeetingCalendarInfo hotakeMeetingCalendarInfo)
|
||||
{
|
||||
List<HotakeMeetingCalendarInfo> list = hotakeMeetingCalendarInfoService.selectHotakeMeetingCalendarInfoList(hotakeMeetingCalendarInfo);
|
||||
return R.ok(list);
|
||||
List<HotakeMeetingCalendarInfo> list = hotakeMeetingCalendarInfoService.selectHotakeMeetingCalendarInfoCandidateList(hotakeMeetingCalendarInfo);
|
||||
return R.ok(list,"");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,4 +111,15 @@ public class HotakeMeetingCalendarInfoController extends BaseController
|
||||
hotakeMeetingCalendarInfoService.saveHotakeMeetingCalendarInfo(calendarVo);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前候选者会议日历数据
|
||||
*/
|
||||
@ApiOperation("查询当前候选者会议日历数据")
|
||||
@GetMapping("/getCandidateCalendarList")
|
||||
public R<List<HotakeMeetingCalendarDataDto>> listCandidateAll()
|
||||
{
|
||||
List<HotakeMeetingCalendarDataDto> list = hotakeMeetingCalendarInfoService.selectHotakeMeetingCalendarDataDtoList();
|
||||
return R.ok(list,"");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public class HotakeRolesApplyInfoController extends BaseController
|
||||
* 删除候选人岗位申请信息
|
||||
*/
|
||||
@ApiOperation("删除候选人岗位申请信息")
|
||||
@Log(title = "候选人岗位申请信息", businessType = BusinessType.DELETE)
|
||||
@Log(title = "删除候选人岗位申请信息", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<?> remove(@PathVariable Long[] ids)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.vetti.web.controller.system;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import com.vetti.common.core.domain.R;
|
||||
import com.vetti.common.core.domain.dto.LoginDto;
|
||||
import com.vetti.common.utils.MessageUtils;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@@ -58,11 +60,11 @@ public class SysLoginController
|
||||
*/
|
||||
@ApiOperation("登录方法")
|
||||
@PostMapping("/login")
|
||||
public AjaxResult login(@RequestBody LoginBody loginBody)
|
||||
public R<LoginDto> login(@RequestBody LoginBody loginBody)
|
||||
{
|
||||
AjaxResult ajax = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
|
||||
LoginDto loginDto = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
|
||||
loginBody.getUuid());
|
||||
return ajax;
|
||||
return R.ok(loginDto,"");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -123,6 +123,7 @@ public class SysProfileController extends BaseController
|
||||
//个人展示数据存储
|
||||
currentUser.setBestSideJson(JSONUtil.toJsonStr(user.getBestSideDtoList()));
|
||||
}
|
||||
currentUser.setUserOperStatus(user.getUserOperStatus());
|
||||
currentUser.setSteps(user.getSteps());
|
||||
if (userService.updateUserProfile(currentUser) > 0)
|
||||
{
|
||||
|
||||
@@ -173,6 +173,11 @@ chatGpt:
|
||||
modelQuestion: ft:gpt-4o-mini-2024-07-18:vetti:interview-corpus:ChvLmzLu
|
||||
modelCV: ft:gpt-3.5-turbo-0125:vetti:vetti-resume-full:CYT0C8JG
|
||||
modelJxCv: gpt-4o-mini
|
||||
modelJd: gpt-4o-mini
|
||||
modelIsq: ft:gpt-4o-mini-2024-07-18:vetti:question-gen-expanded:CncFPHBB
|
||||
modelRoleCv: ft:gpt-4o-mini-2024-07-18:vetti:resume-scoring-v2:CnbgEHQQ
|
||||
modelCvJx: gpt-4o-mini
|
||||
modelCbqpf: gpt-4o-mini
|
||||
role: system
|
||||
|
||||
http:
|
||||
|
||||
@@ -173,6 +173,11 @@ chatGpt:
|
||||
modelQuestion: ft:gpt-4o-mini-2024-07-18:vetti:interview-corpus:ChvLmzLu
|
||||
modelCV: ft:gpt-3.5-turbo-0125:vetti:vetti-resume-full:CYT0C8JG
|
||||
modelJxCv: gpt-4o-mini
|
||||
modelJd: gpt-4o-mini
|
||||
modelIsq: ft:gpt-4o-mini-2024-07-18:vetti:question-gen-expanded:CncFPHBB
|
||||
modelRoleCv: ft:gpt-4o-mini-2024-07-18:vetti:resume-scoring-v2:CnbgEHQQ
|
||||
modelCvJx: gpt-4o-mini
|
||||
modelCbqpf: gpt-4o-mini
|
||||
role: system
|
||||
|
||||
http:
|
||||
|
||||
Reference in New Issue
Block a user