264 lines
7.4 KiB
Go
264 lines
7.4 KiB
Go
package biz
|
||
|
||
import (
|
||
"ai_scheduler/internal/biz/llm_service/third_party"
|
||
"ai_scheduler/internal/data/constants"
|
||
"ai_scheduler/internal/data/mongo_model"
|
||
"ai_scheduler/internal/entitys"
|
||
"ai_scheduler/internal/pkg"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"strings"
|
||
"time"
|
||
|
||
"context"
|
||
|
||
"go.mongodb.org/mongo-driver/bson"
|
||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||
)
|
||
|
||
type AdviceSkillBiz struct {
|
||
AdvicerTalkSkillMongo *mongo_model.AdvicerTalkSkillMongo
|
||
mongo *pkg.Mongo
|
||
adviceProjectBiz *AdviceProjectBiz
|
||
}
|
||
|
||
func NewAdviceSkillBiz(
|
||
advicerTalkSkillMongo *mongo_model.AdvicerTalkSkillMongo,
|
||
mongo *pkg.Mongo,
|
||
adviceProjectBiz *AdviceProjectBiz,
|
||
) *AdviceSkillBiz {
|
||
return &AdviceSkillBiz{
|
||
AdvicerTalkSkillMongo: advicerTalkSkillMongo,
|
||
mongo: mongo,
|
||
adviceProjectBiz: adviceProjectBiz,
|
||
}
|
||
}
|
||
|
||
func (a *AdviceSkillBiz) VersionAdd(ctx context.Context, param *entitys.AdvicerTalkSkillAddReq) (id interface{}, err error) {
|
||
|
||
res, err := a.mongo.Co(a.AdvicerTalkSkillMongo).InsertOne(ctx, &mongo_model.AdvicerTalkSkillMongo{
|
||
ProjectId: param.ProjectId,
|
||
Desc: param.Desc,
|
||
Data: param.Data,
|
||
NeedsMining: param.NeedsMining,
|
||
PainPointResponse: param.PainPointResponse,
|
||
ValueBuilding: param.ValueBuilding,
|
||
ClosingTechniques: param.ClosingTechniques,
|
||
CommunicationRhythm: param.CommunicationRhythm,
|
||
LastUpdateTime: time.Now(),
|
||
})
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return res.InsertedID, err
|
||
}
|
||
|
||
func (a *AdviceSkillBiz) VersionUpdate(ctx context.Context, param *entitys.AdvicerTalkSkillUpdateReq) (err error) {
|
||
filter := bson.M{}
|
||
if len(param.Id) == 0 {
|
||
return errors.New("ID不能为空")
|
||
}
|
||
objectID, err := primitive.ObjectIDFromHex(param.Id)
|
||
if err != nil {
|
||
return fmt.Errorf("ID转换失败: %w", err)
|
||
}
|
||
filter["_id"] = objectID
|
||
update := bson.M{
|
||
"$set": &mongo_model.AdvicerTalkSkillMongo{
|
||
ProjectId: param.ProjectId,
|
||
Desc: param.Desc,
|
||
Data: param.Data,
|
||
NeedsMining: param.NeedsMining,
|
||
PainPointResponse: param.PainPointResponse,
|
||
ValueBuilding: param.ValueBuilding,
|
||
ClosingTechniques: param.ClosingTechniques,
|
||
CommunicationRhythm: param.CommunicationRhythm,
|
||
LastUpdateTime: time.Now(),
|
||
},
|
||
}
|
||
res := a.mongo.Co(a.AdvicerTalkSkillMongo).FindOneAndUpdate(ctx, filter, update)
|
||
return res.Err()
|
||
}
|
||
|
||
func (a *AdviceSkillBiz) VersionList(ctx context.Context, param *entitys.AdvicerTalkSkillListReq) (list []mongo_model.AdvicerTalkSkillItem, err error) {
|
||
filter := bson.M{}
|
||
|
||
if param.ProjectId != 0 {
|
||
filter["projectId"] = param.ProjectId
|
||
}
|
||
|
||
// 2. _id 条件
|
||
if len(param.Id) != 0 {
|
||
objectID, err := primitive.ObjectIDFromHex(param.Id)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("ID转换失败: %w", err)
|
||
}
|
||
filter["_id"] = objectID
|
||
}
|
||
|
||
// 3. version_desc 模糊查询
|
||
if len(param.Desc) != 0 {
|
||
// 正确的方式:指定字段名
|
||
filter["desc"] = bson.M{
|
||
"$regex": primitive.Regex{
|
||
Pattern: param.Desc,
|
||
Options: "i",
|
||
},
|
||
}
|
||
}
|
||
|
||
cursor, err := a.mongo.Co(a.AdvicerTalkSkillMongo).Find(ctx, filter)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
// 遍历结果
|
||
for cursor.Next(ctx) {
|
||
var advicerVersion mongo_model.AdvicerTalkSkillItem
|
||
if err := cursor.Decode(&advicerVersion); err != nil {
|
||
return nil, err
|
||
}
|
||
fillSkillData(&advicerVersion.AdvicerTalkSkillMongo)
|
||
list = append(list, advicerVersion)
|
||
}
|
||
|
||
if err := cursor.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return list, err
|
||
}
|
||
|
||
func (a *AdviceSkillBiz) VersionDel(ctx context.Context, param *entitys.AdvicerTalkSkillDelReq) (err error) {
|
||
filter := bson.M{}
|
||
|
||
if len(param.Id) != 0 {
|
||
objectID, err := primitive.ObjectIDFromHex(param.Id)
|
||
if err != nil {
|
||
return fmt.Errorf("ID转换失败: %w", err)
|
||
}
|
||
filter["_id"] = objectID
|
||
}
|
||
|
||
_, err = a.mongo.Co(a.AdvicerTalkSkillMongo).DeleteOne(ctx, filter)
|
||
|
||
return err
|
||
}
|
||
|
||
func (a *AdviceSkillBiz) Info(ctx context.Context, param *entitys.AdvicerTalkSkillInfoReq) (info mongo_model.AdvicerTalkSkillMongo, err error) {
|
||
filter := bson.M{}
|
||
|
||
if len(param.Id) != 0 {
|
||
objectID, err := primitive.ObjectIDFromHex(param.Id)
|
||
if err != nil {
|
||
return info, fmt.Errorf("ID转换失败: %w", err)
|
||
}
|
||
filter["_id"] = objectID
|
||
}
|
||
res := a.mongo.Co(a.AdvicerTalkSkillMongo).FindOne(ctx, filter)
|
||
if res.Err() != nil {
|
||
return info, err
|
||
}
|
||
// 遍历结果
|
||
|
||
if err = res.Decode(&info); err != nil {
|
||
return info, err
|
||
}
|
||
|
||
// 向后兼容:旧记录无 Data 字段时,从类型化字段转换
|
||
fillSkillData(&info)
|
||
|
||
return info, nil
|
||
|
||
}
|
||
|
||
// SkillGenerate AI 生成聊天技巧:基于项目信息 + 项目资料调用 LLM 生成 5 维度
|
||
func (a *AdviceSkillBiz) SkillGenerate(ctx context.Context, req *entitys.AdvicerSkillGenerateReq) (map[string]interface{}, error) {
|
||
// 1. 查询项目基础信息
|
||
baseInfo, err := a.adviceProjectBiz.BaseInfo(req.ProjectId)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("查询项目信息失败: %w", err)
|
||
}
|
||
if baseInfo.ProjectID == 0 {
|
||
return nil, fmt.Errorf("项目不存在")
|
||
}
|
||
|
||
// 2. 确定模型配置
|
||
supId := req.SupId
|
||
if supId == 0 {
|
||
supId = baseInfo.ModelSupID
|
||
}
|
||
if supId == 0 {
|
||
return nil, fmt.Errorf("项目未配置模型,请手动选择模型配置")
|
||
}
|
||
sup, err := a.adviceProjectBiz.ModelInfo(supId)
|
||
if err != nil || sup.SupID == 0 {
|
||
return nil, fmt.Errorf("指定的模型配置不存在")
|
||
}
|
||
|
||
// 3. 查询项目资料(可为空)
|
||
projectData, _ := a.adviceProjectBiz.ProjectDataLoad(ctx, req.ProjectId)
|
||
projectDataStr := "暂无项目资料"
|
||
if len(projectData.Data) > 0 {
|
||
dataBytes, _ := json.Marshal(projectData.Data)
|
||
projectDataStr = string(dataBytes)
|
||
}
|
||
|
||
// 4. 构建提示词
|
||
prompt := constants.PromptSkillGenerate
|
||
prompt = strings.ReplaceAll(prompt, "{{项目名称}}", baseInfo.Name)
|
||
desc := baseInfo.TemplateDesc
|
||
if desc == "" {
|
||
desc = baseInfo.TemplateAdvicerDesc
|
||
}
|
||
prompt = strings.ReplaceAll(prompt, "{{项目描述}}", desc)
|
||
prompt = strings.ReplaceAll(prompt, "{{项目资料}}", projectDataStr)
|
||
|
||
// 5. 调用 LLM
|
||
result, err := third_party.NewOpenAi().RequestResponsesJson(ctx, sup.Key, sup.URL, sup.JSONModel, prompt)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("AI 生成失败: %w", err)
|
||
}
|
||
|
||
// 6. 解析返回的 JSON
|
||
var parsed map[string]interface{}
|
||
if err := json.Unmarshal([]byte(result), &parsed); err != nil {
|
||
return nil, fmt.Errorf("解析 AI 返回结果失败: %w", err)
|
||
}
|
||
|
||
// 7. 提取 5 个维度(中文 key)
|
||
resp := make(map[string]interface{})
|
||
for _, key := range []string{"需求挖掘", "痛点应对", "价值塑造", "促单话术", "沟通节奏"} {
|
||
if v, ok := parsed[key]; ok {
|
||
resp[key] = v
|
||
}
|
||
}
|
||
if len(resp) == 0 {
|
||
return nil, fmt.Errorf("AI 未返回有效的聊天技巧数据")
|
||
}
|
||
return resp, nil
|
||
}
|
||
|
||
// fillSkillData 向后兼容:旧记录无 Data 字段时,从类型化字段转换到 Data
|
||
func fillSkillData(info *mongo_model.AdvicerTalkSkillMongo) {
|
||
if len(info.Data) > 0 {
|
||
return
|
||
}
|
||
info.Data = make(map[string]interface{})
|
||
if len(info.NeedsMining) > 0 {
|
||
info.Data["需求挖掘"] = info.NeedsMining
|
||
}
|
||
if len(info.PainPointResponse) > 0 {
|
||
info.Data["痛点应对"] = info.PainPointResponse
|
||
}
|
||
if len(info.ValueBuilding) > 0 {
|
||
info.Data["价值塑造"] = info.ValueBuilding
|
||
}
|
||
if len(info.ClosingTechniques) > 0 {
|
||
info.Data["促单话术"] = info.ClosingTechniques
|
||
}
|
||
if len(info.CommunicationRhythm) > 0 {
|
||
info.Data["沟通节奏"] = info.CommunicationRhythm
|
||
}
|
||
}
|