|
@@ -0,0 +1,492 @@
|
|
|
+package wechat
|
|
|
+
|
|
|
+import (
|
|
|
+ "context"
|
|
|
+ "encoding/json"
|
|
|
+ "fmt"
|
|
|
+ "github.com/google/uuid"
|
|
|
+ "github.com/zeromicro/go-zero/core/logx"
|
|
|
+ "strconv"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
+ "wechat-api/ent/contact"
|
|
|
+ "wechat-api/ent/contactfield"
|
|
|
+ "wechat-api/ent/contactfieldtemplate"
|
|
|
+ "wechat-api/ent/custom_types"
|
|
|
+ "wechat-api/ent/predicate"
|
|
|
+ "wechat-api/ent/usagedetail"
|
|
|
+ "wechat-api/internal/svc"
|
|
|
+ "wechat-api/internal/types"
|
|
|
+ "wechat-api/internal/utils/compapi"
|
|
|
+)
|
|
|
+
|
|
|
+type ResponseItem struct {
|
|
|
+ DataIndex string `json:"dataIndex"`
|
|
|
+ Value []string `json:"value"`
|
|
|
+}
|
|
|
+
|
|
|
+type FieldPropsOptions struct {
|
|
|
+ Label string `json:"label"`
|
|
|
+ Value string `json:"value"`
|
|
|
+}
|
|
|
+
|
|
|
+type FieldProps struct {
|
|
|
+ Options []FieldPropsOptions `json:"options"`
|
|
|
+}
|
|
|
+
|
|
|
+type FormData struct {
|
|
|
+ Title string `json:"title"`
|
|
|
+ DataIndex string `json:"dataIndex"`
|
|
|
+ ValueType string `json:"valueType"`
|
|
|
+ FieldProps FieldProps `json:"fieldProps"`
|
|
|
+}
|
|
|
+
|
|
|
+type AnalyzeContactField struct {
|
|
|
+ ctx context.Context
|
|
|
+ svcCtx *svc.ServiceContext
|
|
|
+}
|
|
|
+
|
|
|
+func NewAnalyzeContactField(ctx context.Context, svcCtx *svc.ServiceContext) *AnalyzeContactField {
|
|
|
+ return &AnalyzeContactField{
|
|
|
+ ctx: ctx,
|
|
|
+ svcCtx: svcCtx,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (l *AnalyzeContactField) Analyze() {
|
|
|
+ usageDetails := make(map[string]map[string]string)
|
|
|
+ contactFieldTemplates := make(map[string][]custom_types.ContactFieldTemplate)
|
|
|
+ template_type_text := "text"
|
|
|
+ template_type_radio := "radio"
|
|
|
+ //template_type_date := "date"
|
|
|
+ template_sex_id := "sex"
|
|
|
+ template_sex_label := "性别"
|
|
|
+ template_sex_options_man_label := "男"
|
|
|
+ template_sex_options_man_value := "男"
|
|
|
+ template_sex_options_woman_label := "女"
|
|
|
+ template_sex_options_woman_value := "男"
|
|
|
+
|
|
|
+ template_phone_id := "phone"
|
|
|
+ template_phone_label := "手机号"
|
|
|
+
|
|
|
+ //template_name_id := "name"
|
|
|
+ //template_name_label := "姓名"
|
|
|
+ //
|
|
|
+ //template_age_id := "age"
|
|
|
+ //template_age_label := "年龄(以字符串形式返回阿拉伯数字)"
|
|
|
+ //
|
|
|
+ //template_area_id := "area"
|
|
|
+ //template_area_label := "地区"
|
|
|
+ //
|
|
|
+ //template_birthday_id := "birthday"
|
|
|
+ //template_birthday_label := "出生日期"
|
|
|
+ //
|
|
|
+ //template_birtharea_id := "birtharea"
|
|
|
+ //template_birtharea_label := "出生地"
|
|
|
+ //
|
|
|
+ //template_idcard_no_id := "idcard_no"
|
|
|
+ //template_idcard_no_label := "身份证号"
|
|
|
+ //
|
|
|
+ //template_title_id := "title"
|
|
|
+ //template_title_label := "称呼"
|
|
|
+ contactBasicFieldTemplates := []custom_types.ContactFieldTemplate{
|
|
|
+ {
|
|
|
+ Label: &template_sex_label,
|
|
|
+ Id: &template_sex_id,
|
|
|
+ Type: &template_type_radio,
|
|
|
+ Options: []custom_types.ContactFieldTemplateOptions{
|
|
|
+ {
|
|
|
+ Label: &template_sex_options_man_label,
|
|
|
+ Value: &template_sex_options_man_value,
|
|
|
+ }, {
|
|
|
+ Label: &template_sex_options_woman_label,
|
|
|
+ Value: &template_sex_options_woman_value,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ Label: &template_phone_label,
|
|
|
+ Id: &template_phone_id,
|
|
|
+ Type: &template_type_text,
|
|
|
+ },
|
|
|
+ //{
|
|
|
+ // Label: &template_name_label,
|
|
|
+ // Id: &template_name_id,
|
|
|
+ // Type: &template_type_text,
|
|
|
+ //}, {
|
|
|
+ // Label: &template_age_label,
|
|
|
+ // Id: &template_age_id,
|
|
|
+ // Type: &template_type_text,
|
|
|
+ //}, {
|
|
|
+ // Label: &template_area_label,
|
|
|
+ // Id: &template_area_id,
|
|
|
+ // Type: &template_type_text,
|
|
|
+ //}, {
|
|
|
+ // Label: &template_birthday_label,
|
|
|
+ // Id: &template_birthday_id,
|
|
|
+ // Type: &template_type_date,
|
|
|
+ //}, {
|
|
|
+ // Label: &template_birtharea_label,
|
|
|
+ // Id: &template_birtharea_id,
|
|
|
+ // Type: &template_type_text,
|
|
|
+ //}, {
|
|
|
+ // Label: &template_idcard_no_label,
|
|
|
+ // Id: &template_idcard_no_id,
|
|
|
+ // Type: &template_type_text,
|
|
|
+ //}, {
|
|
|
+ // Label: &template_title_label,
|
|
|
+ // Id: &template_title_id,
|
|
|
+ // Type: &template_type_text,
|
|
|
+ //},
|
|
|
+ }
|
|
|
+
|
|
|
+ var predicates []predicate.UsageDetail
|
|
|
+ predicates = append(predicates, usagedetail.TypeIn(1, 3, 4, 6))
|
|
|
+ predicates = append(predicates, usagedetail.AppIn(1, 3, 4, 5))
|
|
|
+ //yesterdayStart := time.Now().AddDate(0, 0, -1).Truncate(24 * time.Hour)
|
|
|
+ //yesterdayEnd := yesterdayStart.Add(24 * time.Hour)
|
|
|
+ loc, _ := time.LoadLocation("Asia/Shanghai")
|
|
|
+ now := time.Now().In(loc)
|
|
|
+ yesterdayEnd := now.Truncate(24 * time.Hour)
|
|
|
+ yesterdayStart := yesterdayEnd.AddDate(0, 0, -2)
|
|
|
+ predicates = append(predicates, usagedetail.CreatedAtGTE(yesterdayStart))
|
|
|
+ predicates = append(predicates, usagedetail.CreatedAtLT(yesterdayEnd))
|
|
|
+
|
|
|
+ //todayStart := time.Now().AddDate(0, 0, 0).Truncate(24 * time.Hour)
|
|
|
+ //todayEnd := todayStart.Add(24 * time.Hour)
|
|
|
+ //predicates = append(predicates, usagedetail.CreatedAtGTE(todayStart))
|
|
|
+ //predicates = append(predicates, usagedetail.CreatedAtLT(todayEnd))
|
|
|
+
|
|
|
+ data, err := l.svcCtx.DB.UsageDetail.Query().Where(predicates...).All(l.ctx)
|
|
|
+ logx.Info("usageDetails: ", data)
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ wxs, err := l.svcCtx.DB.Wx.Query().All(l.ctx)
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, wx := range wxs {
|
|
|
+ c, _ := l.svcCtx.DB.ContactFieldTemplate.Query().Where(contactfieldtemplate.OrganizationID(wx.OrganizationID)).First(l.ctx)
|
|
|
+ if c != nil {
|
|
|
+ contactFieldTemplates[wx.Wxid] = c.Template
|
|
|
+ } else {
|
|
|
+ contactFieldTemplates[wx.Wxid] = nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, u := range data {
|
|
|
+ if contactFieldTemplates[u.BotID] == nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if _, ok := usageDetails[u.BotID]; !ok {
|
|
|
+ usageDetails[u.BotID] = make(map[string]string)
|
|
|
+ }
|
|
|
+ usageDetails[u.BotID][u.ReceiverID] += fmt.Sprintf("用户:%s\n机器人:%s\n", u.Request, u.Response)
|
|
|
+ }
|
|
|
+ logx.Info("usageDetails: ", usageDetails)
|
|
|
+ for botID, template := range contactFieldTemplates {
|
|
|
+ if template == nil {
|
|
|
+ template = contactBasicFieldTemplates
|
|
|
+ } else {
|
|
|
+ template = append(template, contactBasicFieldTemplates...)
|
|
|
+ }
|
|
|
+ for receiverID, messages := range usageDetails[botID] {
|
|
|
+ result, _ := l.openaiRequest(messages, template)
|
|
|
+ logx.Info("result: ", result)
|
|
|
+ if result == nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ _ = l.UpdateContactFields(botID, receiverID, result)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (l *AnalyzeContactField) openaiRequest(messages string, template []custom_types.ContactFieldTemplate) ([]ResponseItem, error) {
|
|
|
+ formData := ConvertFormData(template)
|
|
|
+ jsonBytes, err := json.Marshal(formData)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ jsonStr := string(jsonBytes)
|
|
|
+ logx.Info("contactFieldTemplates: ", jsonStr)
|
|
|
+ req := &types.CompApiReq{
|
|
|
+ types.CompCtlReq{
|
|
|
+ "form",
|
|
|
+ "",
|
|
|
+ false,
|
|
|
+ "",
|
|
|
+ },
|
|
|
+ types.StdCompApiReq{
|
|
|
+ "gpt-4o",
|
|
|
+ []types.StdCompMessage{},
|
|
|
+ false,
|
|
|
+ nil,
|
|
|
+ },
|
|
|
+ types.FastGptSpecReq{
|
|
|
+ "",
|
|
|
+ "",
|
|
|
+ "",
|
|
|
+ false,
|
|
|
+ map[string]string{
|
|
|
+ "form_data": jsonStr,
|
|
|
+ "chat_history": messages,
|
|
|
+ "external_id": uuid.New().String(),
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+ resp, err := compapi.NewClient(l.ctx, compapi.WithApiBase("http://new-api.gkscrm.com/v1/"),
|
|
|
+ compapi.WithApiKey("sk-wwttAtdLcTfeF7F2Eb9d3592Bd4c487f8e8fA544D6C4BbA9")).
|
|
|
+ Chat(req)
|
|
|
+ logx.Info("resp: ", resp)
|
|
|
+ if err == nil && resp != nil && len(resp.Choices) > 0 {
|
|
|
+ logx.Info("resp.Choices: ", resp.Choices[0].Message.Content)
|
|
|
+ // 尝试第一层解析成 string
|
|
|
+
|
|
|
+ items, err := parseContent(resp.Choices[0].Message.Content)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return items, nil
|
|
|
+ } else if resp != nil && len(resp.Choices) == 0 {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ //url := "https://toolsapi-debug.gkscrm.com/call_center/form/extract"
|
|
|
+ //bodyData := map[string]interface{}{
|
|
|
+ // "form_data": ConvertFormData(template),
|
|
|
+ // "chat_history": messages,
|
|
|
+ // "external_id": uuid.New().String(),
|
|
|
+ //}
|
|
|
+ //logx.Info("bodyData: %+v", bodyData)
|
|
|
+ //bodyBytes, err := json.Marshal(bodyData)
|
|
|
+ //if err != nil {
|
|
|
+ // return nil, err
|
|
|
+ //}
|
|
|
+ //
|
|
|
+ //req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyBytes))
|
|
|
+ //if err != nil {
|
|
|
+ // return nil, err
|
|
|
+ //}
|
|
|
+ //req.Header.Set("Content-Type", "application/json")
|
|
|
+ //req.Header.Set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIn0.ZS9jnsLPCnmc8L_lu4yaQFp34vwWF1mHlHSBYrY5JVs")
|
|
|
+ //
|
|
|
+ //client := &http.Client{}
|
|
|
+ //resp, err := client.Do(req)
|
|
|
+ //if err != nil || resp == nil || resp.Body == nil {
|
|
|
+ // logx.Error("read body error: ", err)
|
|
|
+ // return nil, err
|
|
|
+ //}
|
|
|
+ //
|
|
|
+ //logx.Info("err: ", err)
|
|
|
+ //if err != nil {
|
|
|
+ // return nil, err
|
|
|
+ //}
|
|
|
+ //defer resp.Body.Close()
|
|
|
+ //
|
|
|
+ //if resp.StatusCode != http.StatusOK {
|
|
|
+ // return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
|
|
+ //}
|
|
|
+ //
|
|
|
+ ////var result []ResponseItem
|
|
|
+ //var fullResp struct {
|
|
|
+ // Data []ResponseItem `json:"data"`
|
|
|
+ //}
|
|
|
+ //err = json.NewDecoder(resp.Body).Decode(&fullResp)
|
|
|
+ //if err != nil {
|
|
|
+ // return nil, err
|
|
|
+ //}
|
|
|
+ //
|
|
|
+ //return fullResp.Data, nil
|
|
|
+ return nil, err
|
|
|
+}
|
|
|
+
|
|
|
+func (l *AnalyzeContactField) UpdateContactFields(botID string, receiverID string, fields []ResponseItem) error {
|
|
|
+ basic_ids := []string{"sex", "phone", "name", "age", "area", "birthday", "birtharea", "idcard_no", "title"}
|
|
|
+ c, _ := l.svcCtx.DB.Contact.Query().Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).First(l.ctx)
|
|
|
+ if c == nil {
|
|
|
+ return fmt.Errorf("Contact not find")
|
|
|
+ }
|
|
|
+ for _, field := range fields {
|
|
|
+ if contains(basic_ids, field.DataIndex) {
|
|
|
+ if len(field.Value) == 0 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ value := 0
|
|
|
+ if field.DataIndex == "sex" && c.Sex == 0 {
|
|
|
+ if field.Value[0] == "男" {
|
|
|
+ value = 1
|
|
|
+ } else if field.Value[0] == "女" {
|
|
|
+ value = 2
|
|
|
+ }
|
|
|
+ _, err := l.svcCtx.DB.Contact.Update().
|
|
|
+ Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
|
|
|
+ SetSex(value).
|
|
|
+ Save(l.ctx)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ } else if field.DataIndex == "phone" && c.Phone == "" {
|
|
|
+ _, err := l.svcCtx.DB.Contact.Update().
|
|
|
+ Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
|
|
|
+ SetPhone(field.Value[0]).
|
|
|
+ Save(l.ctx)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ } else if field.DataIndex == "name" && c.Cname == "" {
|
|
|
+ _, err := l.svcCtx.DB.Contact.Update().
|
|
|
+ Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
|
|
|
+ SetCname(field.Value[0]).
|
|
|
+ Save(l.ctx)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ } else if field.DataIndex == "age" && c.Cage == 0 {
|
|
|
+ num, err := strconv.Atoi(field.Value[0])
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ _, err = l.svcCtx.DB.Contact.Update().
|
|
|
+ Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
|
|
|
+ SetCage(num).
|
|
|
+ Save(l.ctx)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ } else if field.DataIndex == "area" && c.Carea == "" {
|
|
|
+ _, err := l.svcCtx.DB.Contact.Update().
|
|
|
+ Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
|
|
|
+ SetCarea(field.Value[0]).
|
|
|
+ Save(l.ctx)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ } else if field.DataIndex == "birthday" && c.Cbirthday == "" {
|
|
|
+ _, err := l.svcCtx.DB.Contact.Update().
|
|
|
+ Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
|
|
|
+ SetCbirthday(field.Value[0]).
|
|
|
+ Save(l.ctx)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ } else if field.DataIndex == "birtharea" && c.Cbirtharea == "" {
|
|
|
+ _, err := l.svcCtx.DB.Contact.Update().
|
|
|
+ Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
|
|
|
+ SetCbirtharea(field.Value[0]).
|
|
|
+ Save(l.ctx)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ } else if field.DataIndex == "idcard_no" && c.CidcardNo == "" {
|
|
|
+ _, err := l.svcCtx.DB.Contact.Update().
|
|
|
+ Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
|
|
|
+ SetCidcardNo(field.Value[0]).
|
|
|
+ Save(l.ctx)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ } else if field.DataIndex == "title" && c.Ctitle == "" {
|
|
|
+ _, err := l.svcCtx.DB.Contact.Update().
|
|
|
+ Where(contact.WxWxidEQ(botID), contact.WxidEQ(receiverID)).
|
|
|
+ SetCtitle(field.Value[0]).
|
|
|
+ Save(l.ctx)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ f, _ := l.svcCtx.DB.ContactField.Query().Where(contactfield.ContactID(c.ID), contactfield.FormID(field.DataIndex)).First(l.ctx)
|
|
|
+ if f == nil {
|
|
|
+ if field.Value != nil && len(field.Value) > 0 && field.Value[0] != "" {
|
|
|
+ _, err := l.svcCtx.DB.ContactField.Create().
|
|
|
+ SetContactID(c.ID).
|
|
|
+ SetFormID(field.DataIndex).
|
|
|
+ SetValue(field.Value).
|
|
|
+ Save(l.ctx)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if len(f.Value) == 0 || f.Value[0] == "" {
|
|
|
+ if field.Value != nil && len(field.Value) > 0 && field.Value[0] != "" {
|
|
|
+ _, err := l.svcCtx.DB.ContactField.UpdateOneID(f.ID).
|
|
|
+ SetValue(field.Value).
|
|
|
+ Save(l.ctx)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func ConvertFormData(input []custom_types.ContactFieldTemplate) []FormData {
|
|
|
+ result := make([]FormData, len(input))
|
|
|
+ for i, item := range input {
|
|
|
+ options := make([]FieldPropsOptions, len(item.Options))
|
|
|
+ for j, opt := range item.Options {
|
|
|
+ options[j] = FieldPropsOptions{
|
|
|
+ Label: *opt.Label,
|
|
|
+ Value: *opt.Value,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ result[i] = FormData{
|
|
|
+ Title: *item.Label,
|
|
|
+ DataIndex: *item.Id,
|
|
|
+ ValueType: *item.Type,
|
|
|
+ FieldProps: FieldProps{
|
|
|
+ Options: options,
|
|
|
+ },
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result
|
|
|
+}
|
|
|
+
|
|
|
+func contains(strs []string, target string) bool {
|
|
|
+ for _, s := range strs {
|
|
|
+ if s == target {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false
|
|
|
+}
|
|
|
+
|
|
|
+func parseContent(content string) ([]ResponseItem, error) {
|
|
|
+ content = strings.TrimSpace(content)
|
|
|
+
|
|
|
+ // ① 双引号包裹的再来一次:"{\"dataIndex\":...}"
|
|
|
+ if unq, err := strconv.Unquote(content); err == nil {
|
|
|
+ return parseContent(unq) // 尝试递归
|
|
|
+ }
|
|
|
+
|
|
|
+ // ② 直接数组形式: [...]
|
|
|
+ if strings.HasPrefix(content, "[") {
|
|
|
+ var list []ResponseItem
|
|
|
+ if err := json.Unmarshal([]byte(content), &list); err == nil {
|
|
|
+ return list, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ③ 对象形式
|
|
|
+ if strings.HasPrefix(content, "{") {
|
|
|
+ // 3‑1 尝试对象包裹数组: {"values":[...]}
|
|
|
+ var wrapper struct {
|
|
|
+ Values []ResponseItem `json:"values"`
|
|
|
+ }
|
|
|
+ if err := json.Unmarshal([]byte(content), &wrapper); err == nil && len(wrapper.Values) > 0 {
|
|
|
+ return wrapper.Values, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3‑2 尝试单对象形式: {"dataIndex": ...}
|
|
|
+ var item ResponseItem
|
|
|
+ if err := json.Unmarshal([]byte(content), &item); err == nil {
|
|
|
+ return []ResponseItem{item}, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil, fmt.Errorf("unsupported content format: %q", content)
|
|
|
+}
|