ai_scheduler/tmp_review_diff.txt

1677 lines
154 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

diff --git a/cmd/server/main.go b/cmd/server/main.go
index 1144dde..a2828c9 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -26,5 +26,8 @@ func main() {
cleanup()
}()
+ // 鍚姩鍚庡彴瀹氭椂浠诲姟锛堟瘡鏃ュ鎴风瓑绾ц瘎浼扮瓑锛?+ app.StartJobs()
+
log.Fatal(app.HttpServer.Listen(fmt.Sprintf(":%d", bc.Server.Port)))
}
diff --git a/cmd/server/wire.go b/cmd/server/wire.go
index 7ee4a39..88d4a9e 100644
--- a/cmd/server/wire.go
+++ b/cmd/server/wire.go
@@ -8,6 +8,7 @@ import (
"ai_scheduler/internal/config"
"ai_scheduler/internal/data/impl"
"ai_scheduler/internal/data/mongo_model"
+ "ai_scheduler/internal/jobs"
"ai_scheduler/internal/pkg"
"ai_scheduler/internal/server"
"ai_scheduler/internal/services/advice"
@@ -29,6 +30,7 @@ func InitializeApp(context.Context, *config.Config, log.AllLogger) (*server.Serv
impl.ProviderImpl,
utils.ProviderUtils,
mongo_model.ProviderSetMongo,
+ jobs.NewAdviceTaskJob,
))
}
diff --git a/config/config.yaml b/config/config.yaml
index 971757a..a53dfa4 100644
--- a/config/config.yaml
+++ b/config/config.yaml
@@ -46,6 +46,24 @@ sys:
maxIdleTime: 30 #姣忎釜杩炴帴鏈€澶х┖闂叉椂闂达紝濡傛灉瓒呰繃浜嗚繖涓椂闂翠細琚叧闂? tls: 30
db:
+
+# 鏁板瓧閿€鍞紙AI 瀹㈡埛鑷姩杩愯惀锛?+advicer:
+ # 涓婃父涓汉寰俊鍗忚鏈嶅姟 Token锛堝悗绔富鍔ㄥ彂閫佹秷鎭?鏌ヨ鑱旂郴浜烘椂浣跨敤锛涘彲鐣欑┖锛?+ wx_token: ""
+ # 鍥炶皟鏍¢獙 token锛堣缃洖璋冩椂浣跨敤锛涙敹鍒板洖璋冩椂鑻ラ厤缃簡鍒欐牎楠岋級
+ callback_token: ""
+ # 瀵硅瘽瀹屾垚鍒ゅ畾锛氭渶鍚庝竴鏉℃秷鎭秴杩囪鍒嗛挓鏁拌涓哄璇濈粨鏉燂紙鎵规B 鑷垜杩唬锛?+ dialog_idle_minutes: 30
+ # 鐔熷娑堟伅瓒呮椂鍥炲寤惰繜锛堝垎閽燂紝鎵规B锛?+ regular_reply_delay_minutes: 10
+ # 涓诲姩瑙﹁揪鍏佽鏃堕棿娈碉紙灏忔椂锛屾壒娆锛?+ active_hour_start: 9
+ active_hour_end: 21
+ # AI 鑷姩鍥炲鎬诲紑鍏筹紙鎵规B锛涢粯璁ゅ叧闂級
+ auto_reply: false
+ # 姣忔棩瀹㈡埛绛夌骇璇勪及鎵ц灏忔椂锛?=鍑屾櫒0鐐癸級
+ daily_evaluate_hour: 0
redis:
host: 47.97.27.195:6379
type: node
diff --git a/config/config_env.yaml b/config/config_env.yaml
index 9327cb8..9ea7bfd 100644
--- a/config/config_env.yaml
+++ b/config/config_env.yaml
@@ -40,6 +40,24 @@ sys:
channel_pool_size: 32
llm_pool_len: 5
heartbeat_interval: 30
+
+# 鏁板瓧閿€鍞紙AI 瀹㈡埛鑷姩杩愯惀锛?+advicer:
+ # 涓婃父涓汉寰俊鍗忚鏈嶅姟 Token锛堝悗绔富鍔ㄥ彂閫佹秷鎭?鏌ヨ鑱旂郴浜烘椂浣跨敤锛涘彲鐣欑┖锛?+ wx_token: ""
+ # 鍥炶皟鏍¢獙 token锛堣缃洖璋冩椂浣跨敤锛涙敹鍒板洖璋冩椂鑻ラ厤缃簡鍒欐牎楠岋級
+ callback_token: ""
+ # 瀵硅瘽瀹屾垚鍒ゅ畾锛氭渶鍚庝竴鏉℃秷鎭秴杩囪鍒嗛挓鏁拌涓哄璇濈粨鏉燂紙鎵规B 鑷垜杩唬锛?+ dialog_idle_minutes: 30
+ # 鐔熷娑堟伅瓒呮椂鍥炲寤惰繜锛堝垎閽燂紝鎵规B锛?+ regular_reply_delay_minutes: 10
+ # 涓诲姩瑙﹁揪鍏佽鏃堕棿娈碉紙灏忔椂锛屾壒娆锛?+ active_hour_start: 9
+ active_hour_end: 21
+ # AI 鑷姩鍥炲鎬诲紑鍏筹紙鎵规B锛涢粯璁ゅ叧闂級
+ auto_reply: false
+ # 姣忔棩瀹㈡埛绛夌骇璇勪及鎵ц灏忔椂锛?=鍑屾櫒0鐐癸級
+ daily_evaluate_hour: 0
redis:
host: 47.97.27.195:6379
type: node
diff --git a/config/config_test.yaml b/config/config_test.yaml
index f974976..26276e4 100644
--- a/config/config_test.yaml
+++ b/config/config_test.yaml
@@ -39,6 +39,24 @@ sys:
channel_pool_size: 32
llm_pool_len: 5
heartbeat_interval: 30
+
+# 鏁板瓧閿€鍞紙AI 瀹㈡埛鑷姩杩愯惀锛?+advicer:
+ # 涓婃父涓汉寰俊鍗忚鏈嶅姟 Token锛堝悗绔富鍔ㄥ彂閫佹秷鎭?鏌ヨ鑱旂郴浜烘椂浣跨敤锛涘彲鐣欑┖锛?+ wx_token: ""
+ # 鍥炶皟鏍¢獙 token锛堣缃洖璋冩椂浣跨敤锛涙敹鍒板洖璋冩椂鑻ラ厤缃簡鍒欐牎楠岋級
+ callback_token: ""
+ # 瀵硅瘽瀹屾垚鍒ゅ畾锛氭渶鍚庝竴鏉℃秷鎭秴杩囪鍒嗛挓鏁拌涓哄璇濈粨鏉燂紙鎵规B 鑷垜杩唬锛?+ dialog_idle_minutes: 30
+ # 鐔熷娑堟伅瓒呮椂鍥炲寤惰繜锛堝垎閽燂紝鎵规B锛?+ regular_reply_delay_minutes: 10
+ # 涓诲姩瑙﹁揪鍏佽鏃堕棿娈碉紙灏忔椂锛屾壒娆锛?+ active_hour_start: 9
+ active_hour_end: 21
+ # AI 鑷姩鍥炲鎬诲紑鍏筹紙鎵规B锛涢粯璁ゅ叧闂級
+ auto_reply: false
+ # 姣忔棩瀹㈡埛绛夌骇璇勪及鎵ц灏忔椂锛?=鍑屾櫒0鐐癸級
+ daily_evaluate_hour: 0
redis:
host: 47.97.27.195:6379
type: node
diff --git a/internal/biz/advice_chat.go b/internal/biz/advice_chat.go
index b628a1e..631429b 100644
--- a/internal/biz/advice_chat.go
+++ b/internal/biz/advice_chat.go
@@ -155,7 +155,7 @@ func (a *AdviceChatBiz) Chat(ctx context.Context, chat *entitys.AdvicerChatReq)
OutToken: outToken,
CreatAt: time.Now(),
})
- if assistant.MissionStatus == "fail" || assistant.MissionStatus == "complete" {
+ if assistant.MissionStatus == "fail" || assistant.MissionStatus == "completed" {
cond = builder.NewCond()
cond = cond.And(builder.Eq{"session_id": chat.SessionId})
session.MissionStatus = assistant.MissionStatus
@@ -174,7 +174,7 @@ func (a *AdviceChatBiz) buildChatPromptResponse(ctx context.Context, chat *entit
},
{
Role: openai.ChatMessageRoleSystem,
- Content: "鍘嗗彶鑱婂ぉ璁板綍锛歕n" + pkg.JsonStringIgonErr(chatList),
+ Content: formatChatHis(chatList),
},
{
Role: openai.ChatMessageRoleUser,
@@ -183,11 +183,28 @@ func (a *AdviceChatBiz) buildChatPromptResponse(ctx context.Context, chat *entit
}
return message, nil
}
+
+// formatChatHis 灏嗗巻鍙茶褰曡浆鍐欎负銆屽鎴?鎴戙€嶇殑鑷劧瀵硅瘽鏂囨湰锛屾洿璐磋繎鐪熶汉鑱婂ぉ璇
+func formatChatHis(chatList []mongo_model.AdvicerChatHisMongoEntity) string {
+ if len(chatList) == 0 {
+ return "锛堟殏鏃犲巻鍙茶亰澶╄褰曪紝杩欐槸浣犱笌瀹㈡埛鐨勭涓€杞璇濓級"
+ }
+ var b strings.Builder
+ b.WriteString("浠ヤ笅鏄綘锛堥【闂級涓庡鎴锋渶杩戠殑鑱婂ぉ璁板綍锛堟寜鏃堕棿鍏堝悗鎺掑垪锛夛細\n")
+ for _, h := range chatList {
+ b.WriteString("瀹㈡埛锛? + h.User + "\n")
+ b.WriteString("鎴戯細" + h.Assistant + "\n")
+ }
+ return b.String()
+}
+
func (a *AdviceChatBiz) getChatHis(ctx context.Context, sessionId string, limit int64) (chatList []mongo_model.AdvicerChatHisMongoEntity, err error) {
chatList = make([]mongo_model.AdvicerChatHisMongoEntity, 0)
filter := bson.M{}
filter["sessionId"] = sessionId
- cursor, err := a.mongo.Co(a.advicerChatHisMongo).Find(ctx, filter, options.Find().SetLimit(limit))
+ // 鎸夋椂闂村€掑簭鍙栨渶杩?limit 鏉★紙淇濊瘉鑾峰彇鐨勬槸鏈€鏂板璇濓級
+ cursor, err := a.mongo.Co(a.advicerChatHisMongo).Find(ctx, filter,
+ options.Find().SetSort(bson.D{{Key: "creatAt", Value: -1}}).SetLimit(limit))
if err != nil {
return chatList, err
}
@@ -202,11 +219,25 @@ func (a *AdviceChatBiz) getChatHis(ctx context.Context, sessionId string, limit
if err := cursor.Err(); err != nil {
return nil, err
}
+ // Mongo 鎸夋椂闂村€掑簭鏌ヨ锛屽弽杞洖鏃堕棿姝e簭鍚庡啀鎷煎叆 prompt
+ for i, j := 0, len(chatList)-1; i < j; i, j = i+1, j-1 {
+ chatList[i], chatList[j] = chatList[j], chatList[i]
+ }
return
}
func (a *AdviceChatBiz) taskPrompt(session *dbmodel.AiAdviceSession) string {
- return "[褰撳墠鏃堕棿]" + time.Now().Format("2006-01-02 15:04:05")
+ var b strings.Builder
+ b.WriteString("[褰撳墠鏃堕棿]" + time.Now().Format("2006-01-02 15:04:05"))
+ status := session.MissionStatus
+ if status != "completed" && status != "fail" {
+ status = "in_progress"
+ }
+ b.WriteString("\n[褰撳墠浠诲姟鐘舵€乚" + status)
+ if session.MissionCompleteDesc != "" {
+ b.WriteString("\n[浠诲姟鏈€鏂拌繘灞昡" + session.MissionCompleteDesc)
+ }
+ return b.String()
}
func (a *AdviceChatBiz) buildBasePromptResponse(ctx context.Context, chatData *entitys.ChatData, req *entitys.AdvicerChatRegistReq) ([]openai.ResponseInputMessage, error) {
@@ -215,11 +246,18 @@ func (a *AdviceChatBiz) buildBasePromptResponse(ctx context.Context, chatData *e
Role: openai.ChatMessageRoleSystem,
Content: a.sysPrompt(chatData, req),
},
- {
+ }
+ // 閿€鍞汉璁鹃鏍兼寚浠わ紙鑷劧璇█瑙勫垯 + 鐪熷疄瀵硅瘽鑼冧緥 few-shot锛夛紝鏃犱汉璁炬暟鎹椂璺宠繃
+ if persona := a.personaPrompt(chatData); persona != "" {
+ message = append(message, openai.ResponseInputMessage{
Role: openai.ChatMessageRoleSystem,
- Content: a.assistantPrompt(chatData),
- },
+ Content: persona,
+ })
}
+ message = append(message, openai.ResponseInputMessage{
+ Role: openai.ChatMessageRoleSystem,
+ Content: "[缁撴瀯鍖栨暟鎹甝\n" + pkg.JsonStringIgonErr(chatData),
+ })
return message, nil
}
@@ -227,13 +265,104 @@ func (a *AdviceChatBiz) sysPrompt(chatData *entitys.ChatData, req *entitys.Advic
var prompt strings.Builder
prompt.WriteString(constants.BasePrompt)
prompt.WriteString(req.Mission)
+ prompt.WriteString("\n")
+ // 椤圭洰椋庢帶绾㈢嚎锛堟潵鑷」鐩ā鏉?rule_dimension锛夛紝鏈€楂樹紭鍏堢骇绾︽潫
+ if rule := strings.TrimSpace(chatData.RuleDimension); rule != "" {
+ prompt.WriteString(constants.RulePromptPrefix)
+ prompt.WriteString(rule)
+ prompt.WriteString("\n")
+ }
prompt.WriteString(constants.BasePrompt2)
return prompt.String()
}
-func (a *AdviceChatBiz) assistantPrompt(chatData *entitys.ChatData) string {
- return pkg.JsonStringIgonErr(chatData)
+// personaPrompt 浼氳瘽娉ㄥ唽鏃舵敞鍏ョ殑浜鸿椋庢牸鎻愮ず锛堝唴閮ㄥ鎵?formatPersonaPrompt锛?+func (a *AdviceChatBiz) personaPrompt(chatData *entitys.ChatData) string {
+ return formatPersonaPrompt(chatData.AdvicerVersion)
+}
+
+// formatPersonaPrompt 灏嗛攢鍞増鏈殑缁撴瀯鍖栦汉璁剧紪璇戜负鑷劧璇█椋庢牸鎸囦护 + 鐪熷疄瀵硅瘽鑼冧緥锛坒ew-shot锛夛紝
+// 璁╂ā鍨嬫ā浠裤€屾湰浜恒€嶇殑璇磋瘽鏂瑰紡锛岃€屼笉鏄満姊拌鍙栨暟鎹紱鏃犱汉璁炬暟鎹椂杩斿洖绌轰覆銆?+// chat 浼氳瘽涓庣瓥鐣ュ紩鎿庯紙AI 鍥炲/涓诲姩瑙﹁揪锛夊叡鐢ㄦ湰鍑芥暟锛屼繚璇佸悇閾捐矾璇皵涓€鑷淬€?+func formatPersonaPrompt(v *mongo_model.AdvicerVersionMongoEntity) string {
+ if v == nil {
+ return ""
+ }
+ var b strings.Builder
+ items := 0
+ b.WriteString("[鐪熶汉椋庢牸鎸囦护]浠ヤ笅鏄綘鏈汉鐨勮璇濋鏍硷紝瀵硅瘽鏃跺繀椤讳弗鏍兼ā浠匡紝璁╁鏂硅涓嶅嚭浣犳槸AI锛歕n")
+ // 鏂硅█鐗瑰緛
+ if v.DialectFeatures.Region != "" || len(v.DialectFeatures.KeyWords) > 0 {
+ b.WriteString("- 璇█涔犳儻锛?)
+ if v.DialectFeatures.Region != "" {
+ b.WriteString("璇磋瘽甯? + v.DialectFeatures.Region + "鍙i煶")
+ if v.DialectFeatures.Intensity > 0 {
+ b.WriteString(fmt.Sprintf("锛屾柟瑷€浣跨敤寮哄害绾?d%%锛堣嚜鐒剁┛鎻掑嵆鍙紝鍒瘡鍙ヨ瘽閮界敤锛?, int(v.DialectFeatures.Intensity*100)))
+ }
+ }
+ if len(v.DialectFeatures.KeyWords) > 0 {
+ b.WriteString("锛涘父鐢ㄥ彛璇瘝锛? + strings.Join(v.DialectFeatures.KeyWords, "銆?))
+ }
+ b.WriteString("\n")
+ items++
+ }
+ // 鍙ュ紡涔犳儻
+ sp := v.SentencePatterns
+ if len(sp.OpeningMode)+len(sp.ExplanationMode)+len(sp.ConfirmationMode)+len(sp.SummaryMode)+len(sp.TransitionMode) > 0 {
+ b.WriteString("- 鍙ュ紡涔犳儻锛?)
+ if len(sp.OpeningMode) > 0 {
+ b.WriteString("寮€鍦虹埍鐢ㄣ€? + strings.Join(sp.OpeningMode, "銆嶃€?) + "銆嶏紱")
+ }
+ if len(sp.ExplanationMode) > 0 {
+ b.WriteString("璁茶В鏃剁埍璇淬€? + strings.Join(sp.ExplanationMode, "銆嶃€?) + "銆嶏紱")
+ }
+ if len(sp.ConfirmationMode) > 0 {
+ b.WriteString("璺熷鎴风‘璁ゆ椂鐖辩敤銆? + strings.Join(sp.ConfirmationMode, "銆嶃€?) + "銆嶏紱")
+ }
+ if len(sp.SummaryMode) > 0 {
+ b.WriteString("鎬荤粨鏃剁埍璇淬€? + strings.Join(sp.SummaryMode, "銆嶃€?) + "銆嶏紱")
+ }
+ if len(sp.TransitionMode) > 0 {
+ b.WriteString("鎹㈣瘽棰樻椂鐖辩敤銆? + strings.Join(sp.TransitionMode, "銆嶃€?) + "銆?)
+ }
+ b.WriteString("\n")
+ items++
+ }
+ // 璇皵鍩鸿皟
+ t := v.ToneTags
+ if t.Enthusiasm+t.Patience+t.Confidence+t.Friendliness+t.Persuasion > 0 {
+ b.WriteString(fmt.Sprintf("- 璇皵鍩鸿皟锛氱儹鎯呭害%d%%銆佽€愬績搴?d%%銆佽嚜淇″害%d%%銆佷翰鍜屽姏%d%%銆佽鏈嶅姏%d%%\n",
+ int(t.Enthusiasm*100), int(t.Patience*100), int(t.Confidence*100), int(t.Friendliness*100), int(t.Persuasion*100)))
+ items++
+ }
+ // 鎬ф牸鐗瑰緛
+ if len(v.PersonalityTags) > 0 {
+ b.WriteString("- 鎬ф牸鐗瑰緛锛? + strings.Join(v.PersonalityTags, "銆?) + "\n")
+ items++
+ }
+ // 鐪熷疄瀵硅瘽鑼冧緥锛坒ew-shot锛屾渶澶?5 鏉★級
+ if len(v.SignatureDialogues) > 0 {
+ b.WriteString("- 浠ヤ笅鏄綘浠ュ墠鍜屽鎴风殑鐪熷疄鍘熻瘽锛屽彧妯′豢璇磋瘽鏂瑰紡锛堣姘?鐢ㄨ瘝/鏂彞锛夛紝涓嶈鐓ф妱鍐呭锛歕n")
+ for i, d := range v.SignatureDialogues {
+ if i >= 5 {
+ break
+ }
+ if d.Dialogue == "" {
+ continue
+ }
+ if d.Context != "" {
+ b.WriteString(fmt.Sprintf(" 瀹㈡埛鍦烘櫙銆?s銆嶆椂锛屼綘璇磋繃锛氥€?s銆峔n", d.Context, d.Dialogue))
+ } else {
+ b.WriteString(fmt.Sprintf(" 浣犺杩囷細銆?s銆峔n", d.Dialogue))
+ }
+ }
+ items++
+ }
+ if items == 0 {
+ return ""
+ }
+ return b.String()
}
func (a *AdviceChatBiz) callLlmResponse(ctx context.Context, request []openai.ResponseInputMessage, key string, url string, modelName string, id string) (*openai.CreateResponseResponse, error) {
diff --git a/internal/biz/advice_client.go b/internal/biz/advice_client.go
index 38cb4e2..56080d2 100644
--- a/internal/biz/advice_client.go
+++ b/internal/biz/advice_client.go
@@ -12,6 +12,7 @@ import (
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
+ "go.mongodb.org/mongo-driver/mongo"
)
type AdviceClientBiz struct {
@@ -30,10 +31,17 @@ func NewAdviceClientBiz(
}
func (a *AdviceClientBiz) Add(ctx context.Context, param *entitys.AdvicerClientAddReq) (id interface{}, err error) {
+ // AI 鍙備笌寮€鍏筹細榛樿寮€鍚?+ aiEnabled := true
+ if param.AiEnabled != nil {
+ aiEnabled = *param.AiEnabled
+ }
res, err := a.mongo.Co(a.AdvicerClientMongo).InsertOne(ctx, &mongo_model.AdvicerClientMongo{
ProjectId: param.ProjectId,
AdvicerId: param.AdvicerId,
+ Wxid: param.Wxid,
+ AiEnabled: aiEnabled,
PersonalInfo: param.PersonalInfo,
PurchasePurpose: param.PurchasePurpose,
CoreDemands: param.CoreDemands,
@@ -57,10 +65,33 @@ func (a *AdviceClientBiz) Update(ctx context.Context, param *entitys.AdvicerrCli
return fmt.Errorf("ID杞崲澶辫触: %w", err)
}
filter["_id"] = objectID
+
+ // 璇诲彇鏃ц褰曪紝淇濈暀鍥炶皟/绛夌骇璇勪及鑷姩缁存姢鐨勫瓧娈碉紝閬垮厤浜哄伐缂栬緫璧勬枡鏃惰瑕嗙洊
+ var old mongo_model.AdvicerClientMongo
+ if err = a.mongo.Co(a.AdvicerClientMongo).FindOne(ctx, filter).Decode(&old); err != nil {
+ return fmt.Errorf("瀹㈡埛涓嶅瓨鍦? %w", err)
+ }
+ // AI 鍙備笌寮€鍏筹細鏈紶淇濇寔鍘熷€?+ aiEnabled := old.AiEnabled
+ if param.AiEnabled != nil {
+ aiEnabled = *param.AiEnabled
+ }
+
update := bson.M{
"$set": &mongo_model.AdvicerClientMongo{
ProjectId: param.ProjectId,
AdvicerId: param.AdvicerId,
+ Wxid: old.Wxid,
+ AppId: old.AppId,
+ ClientLevel: old.ClientLevel,
+ LevelReason: old.LevelReason,
+ LevelUpdateAt: old.LevelUpdateAt,
+ LastContactAt: old.LastContactAt,
+ LastClientMsgAt: old.LastClientMsgAt,
+ LastReplyAt: old.LastReplyAt,
+ MsgCount: old.MsgCount,
+ AiEnabled: aiEnabled,
+ LastIterateAt: old.LastIterateAt,
PersonalInfo: param.PersonalInfo,
PurchasePurpose: param.PurchasePurpose,
CoreDemands: param.CoreDemands,
@@ -150,3 +181,64 @@ func (a *AdviceClientBiz) Info(ctx context.Context, param *entitys.AdvicerClient
}
return
}
+
+// Bind 瀹㈡埛缁戝畾寰俊锛坵xid 鐢ㄤ簬鍥炶皟娑堟伅鍏宠仈鍒拌瀹㈡埛锛?+func (a *AdviceClientBiz) Bind(ctx context.Context, param *entitys.AdvicerClientBindReq) (err error) {
+ if len(param.Id) == 0 {
+ return errors.New("ID涓嶈兘涓虹┖")
+ }
+ if len(param.Wxid) == 0 {
+ return errors.New("wxid涓嶈兘涓虹┖")
+ }
+ objectID, err := primitive.ObjectIDFromHex(param.Id)
+ if err != nil {
+ return fmt.Errorf("ID杞崲澶辫触: %w", err)
+ }
+ set := bson.M{"wxid": param.Wxid, "lastUpdateTime": time.Now()}
+ if len(param.AppId) != 0 {
+ set["appId"] = param.AppId
+ }
+ _, err = a.mongo.Co(a.AdvicerClientMongo).UpdateOne(ctx, bson.M{"_id": objectID}, bson.M{"$set": set})
+ return err
+}
+
+// FindByWxid 鎸夊鎴峰井淇?wxid 鏌ヨ瀹㈡埛锛堝洖璋冮摼鎸夋秷鎭潵婧愬畾浣嶅鎴凤級
+func (a *AdviceClientBiz) FindByWxid(ctx context.Context, wxid string) (info mongo_model.AdvicerClientItem, found bool, err error) {
+ if len(wxid) == 0 {
+ return info, false, errors.New("wxid涓嶈兘涓虹┖")
+ }
+ res := a.mongo.Co(a.AdvicerClientMongo).FindOne(ctx, bson.M{"wxid": wxid})
+ if res.Err() != nil {
+ if errors.Is(res.Err(), mongo.ErrNoDocuments) {
+ return info, false, nil
+ }
+ return info, false, res.Err()
+ }
+ if err = res.Decode(&info); err != nil {
+ return info, false, err
+ }
+ return info, true, nil
+}
+
+// TouchInteraction 鏇存柊瀹㈡埛浜掑姩鏃堕棿涓庢秷鎭暟锛堝洖璋冮摼閽堝宸茬粦瀹氬鎴疯皟鐢級
+// direction: customer 瀹㈡埛鍙戞潵锛堟洿鏂?lastClientMsgAt锛夛紱self/ai 鎴戞柟鍥炲锛堟洿鏂?lastReplyAt锛?+func (a *AdviceClientBiz) TouchInteraction(ctx context.Context, wxid string, direction string) (err error) {
+ if len(wxid) == 0 {
+ return nil
+ }
+ now := time.Now()
+ set := bson.M{
+ "lastContactAt": now,
+ }
+ if direction == mongo_model.WxMsgDirectionCustomer {
+ set["lastClientMsgAt"] = now
+ } else {
+ set["lastReplyAt"] = now
+ }
+ update := bson.M{
+ "$set": set,
+ "$inc": bson.M{"msgCount": 1},
+ }
+ _, err = a.mongo.Co(a.AdvicerClientMongo).UpdateOne(ctx, bson.M{"wxid": wxid}, update)
+ return err
+}
diff --git a/internal/biz/llm_service/third_party/openai.go b/internal/biz/llm_service/third_party/openai.go
index 26dabbb..21f122c 100644
--- a/internal/biz/llm_service/third_party/openai.go
+++ b/internal/biz/llm_service/third_party/openai.go
@@ -96,10 +96,16 @@ func (o *OpenAi) CreateResponseMessages(
id string,
) (*openai.CreateResponseResponse, error) {
store := true
+ // 瀵硅瘽鍦烘櫙閲囨牱鍙傛暟锛氶€傚害鍙戞暎 + 楂樻鐜囬噰鏍凤紝闄嶄綆銆孉I 鑵斻€嶄笌澶嶈鎰燂紝鏇存帴杩戠湡浜鸿亰澶?+ temperature := float32(0.8)
+ topP := float32(0.9)
req := openai.CreateResponseRequest{
- Model: modelName,
- Input: input,
- Store: &store,
+ Model: modelName,
+ Input: input,
+ Store: &store,
+ Temperature: &temperature,
+ TopP: &topP,
+ MaxOutputTokens: 800,
Text: &openai.ResponseTextConfig{
Format: &openai.ResponseTextFormat{
Type: "json_object",
diff --git a/internal/biz/provider_set.go b/internal/biz/provider_set.go
index 5ab75d7..939bdc9 100644
--- a/internal/biz/provider_set.go
+++ b/internal/biz/provider_set.go
@@ -17,4 +17,11 @@ var ProviderSetBiz = wire.NewSet(
NewAdviceChatBiz,
NewAdvicerIndustryBiz,
NewAdvicerAdminBiz,
+ NewAdviceWxBiz,
+ NewAdviceActivityBiz,
+ NewAdviceEvaluateBiz,
+ NewAdviceWxSendBiz,
+ NewAdviceStrategyBiz,
+ NewAdviceProactiveBiz,
+ NewAdviceIterateBiz,
)
diff --git a/internal/config/config.go b/internal/config/config.go
index 9f5d8a9..30050dc 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -30,6 +30,8 @@ type Config struct {
Dingtalk DingtalkConfig `mapstructure:"dingtalk"`
Qywx QywxConfig `mapstructure:"qywx"`
JwtSecret string `mapstructure:"jwt_secret"`
+ // Advicer 鏁板瓧閿€鍞紙AI 瀹㈡埛鑷姩杩愯惀锛夐厤缃?+ Advicer AdvicerConfig `mapstructure:"advicer"`
}
type ZLTX struct {
@@ -311,6 +313,44 @@ type KnowledgeConfig struct {
OnlyRAG bool `mapstructure:"only_rag"`
}
+// AdvicerConfig 鏁板瓧閿€鍞紙AI 瀹㈡埛鑷姩杩愯惀锛夐厤缃?+// 璇存槑锛氭壒娆锛堝熀寤猴級浣跨敤 CallbackToken / DailyEvaluateHour锛涘叾浣欏瓧娈典緵鎵规B锛堟櫤鑳界瓥鐣ワ級浣跨敤锛屽厛钀藉湴閰嶇疆浠ラ伩鍏嶄簩娆℃敼閰嶇疆銆?+type AdvicerConfig struct {
+ // WxToken 涓婃父涓汉寰俊鍗忚鏈嶅姟璁块棶 Token锛堝悗绔富鍔ㄥ彂閫佹秷鎭?鏌ヨ鑱旂郴浜烘椂浣跨敤锛涘彲鐣欑┖锛屾鏃跺悗绔笉涓诲姩璋冪敤涓婃父锛?+ WxToken string `mapstructure:"wx_token"`
+ // CallbackToken 鍥炶皟鏍¢獙 token锛堣皟鐢?setCallback 鏃朵娇鐢紱鏀跺埌鍥炶皟鎶ユ枃鏃惰嫢閰嶇疆浜嗗垯鏍¢獙锛屼负绌轰笉鏍¢獙锛?+ CallbackToken string `mapstructure:"callback_token"`
+ // DialogIdleMinutes 瀵硅瘽瀹屾垚鍒ゅ畾锛氭渶鍚庝竴鏉℃秷鎭秴杩囪鍒嗛挓鏁拌涓哄璇濈粨鏉燂紙榛樿 30锛?+ DialogIdleMinutes int `mapstructure:"dialog_idle_minutes"`
+ // RegularReplyDelayMinutes 鐔熷娑堟伅瓒呮椂鍥炲寤惰繜锛堝垎閽燂紝榛樿 10锛?+ RegularReplyDelayMinutes int `mapstructure:"regular_reply_delay_minutes"`
+ // ActiveHourStart 涓诲姩瑙﹁揪鍏佽鐨勮捣濮嬪皬鏃讹紙榛樿 9锛?+ ActiveHourStart int `mapstructure:"active_hour_start"`
+ // ActiveHourEnd 涓诲姩瑙﹁揪鍏佽鐨勭粨鏉熷皬鏃讹紙榛樿 21锛?+ ActiveHourEnd int `mapstructure:"active_hour_end"`
+ // AutoReply AI 鑷姩鍥炲鎬诲紑鍏筹紙榛樿鍏抽棴锛岄渶鏄惧紡寮€鍚級
+ AutoReply bool `mapstructure:"auto_reply"`
+ // DailyEvaluateHour 姣忔棩瀹㈡埛绛夌骇璇勪及鎵ц灏忔椂锛?=鍑屾櫒 0 鐐癸級
+ DailyEvaluateHour int `mapstructure:"daily_evaluate_hour"`
+}
+
+// WithDefaults 杩斿洖濉厖榛樿鍊煎悗鐨勯厤缃紙鏈厤缃垨涓?0 鐨勫瓧娈典娇鐢ㄩ粯璁ゅ€硷級
+func (a AdvicerConfig) WithDefaults() AdvicerConfig {
+ if a.DialogIdleMinutes <= 0 {
+ a.DialogIdleMinutes = 30
+ }
+ if a.RegularReplyDelayMinutes <= 0 {
+ a.RegularReplyDelayMinutes = 10
+ }
+ if a.ActiveHourStart <= 0 {
+ a.ActiveHourStart = 9
+ }
+ if a.ActiveHourEnd <= 0 {
+ a.ActiveHourEnd = 21
+ }
+ return a
+}
+
// LoadConfig 鍔犺浇閰嶇疆
func LoadConfig(configPath string) (*Config, error) {
viper.SetConfigFile(configPath)
diff --git a/internal/data/constants/prompt.go b/internal/data/constants/prompt.go
index 583ee87..b73e609 100644
--- a/internal/data/constants/prompt.go
+++ b/internal/data/constants/prompt.go
@@ -1,12 +1,123 @@
package constants
+// BasePrompt 瀵硅瘽鍦烘櫙浜鸿涓庤鍒欙紙chat/regis 浼氳瘽娉ㄥ唽鏃舵敞鍏ョ郴缁熸彁绀猴級
const BasePrompt = `**[鍦烘櫙]**鐜板湪浣犳槸涓撲笟鐨勯【闂紝鎴戜滑鍦ㄨ亰澶╄蒋浠堕噷闈㈣亰澶? **[瑕佹眰]**鎻愪緵鐨刯son鍐呭鍖呭惈椤鹃棶锛岄」鐩紝瀵硅瘽鎶€宸э紝瀹㈡埛淇℃伅锛岃鏍规嵁json鍐呭杩涜妯′豢鐪熶汉杩涜瀵硅瘽锛?* 缁濆涓嶈兘璁╁鏂圭湅鍑烘潵浣犳槸ai!**
**[瑙勫垯]**
1.蹇呴』妯′豢椤鹃棶鐨勮亰澶╂柟寮忥紝涓嶈兘鏈塵arkdown鏍煎紡锛岃亰澶╁唴瀹瑰繀椤昏创杩戣亰澶╄蒋浠剁殑姝e父鑱婂ぉ锛岄潪瀹㈡埛瑕佹眰涓嶈鑷【鑷殑浠嬬粛椤圭洰锛佸紑鍦虹櫧涓嶈澶暱锛?-2.涓嶈鏈変换浣曠壒娈婃牸寮忥紝鏈夋瘮杈冮暱鐨勫璇濆唴瀹规敞鎰忔彁琛?+2.鍍忕湡浜哄彂寰俊涓€鏍疯璇濓細涓€鏉℃秷鎭彧琛ㄨ揪涓€涓剰鎬濓紝姣忔潯灏介噺绠€鐭紙涓€鑸笉瓒呰繃40涓瓧锛夛紱瑕佽鐨勫唴瀹硅緝澶氭椂锛屾媶鎴?~3鏉$煭娑堟伅锛屽湪result閲岀敤\n鍒嗛殧锛屼笉瑕佹妸涓€澶ф璇濆杩涘悓涓€鏉℃秷鎭紒
3.缁撳悎[浠诲姟鍐呭]淇℃伅锛岃嫢浠诲姟鐘舵€佷负"in_progress",鍒欏湪瀵硅瘽涓敖閲忓畬鎴愪换鍔?濡傛灉浠诲姟鐘舵€佷负"completed"鎴?fail",鍒欎富鍔ㄧ粨鏉熷璇?涓旇杞璇濅腑涓嶅啀鏇存柊浠诲姟鐘舵€侊紝mission_complete_desc璁板綍鏈€缁堢粨鏋滀笖涓嶅啀鏇存敼銆備笉瑕佹瘡涓€鍙ュ璇濋兘鍖呭惈浠诲姟鍐呭锛屼笉瑕佺洰鐨勬€уお寮猴紒
4.涓嶈兘鏈変换浣曠敾澶栭煶鐨勬枃瀛楁牸寮忓嚭鐜帮紒姣斿锛堢瓑寰呭鏂瑰洖澶嶏級
**[浠诲姟鍐呭]**`
-const BasePrompt2 = "**[杈撳嚭鏍煎紡]**\n- **鏍煎紡绫诲瀷**锛氫弗鏍艰緭鍑簀son鏍煎紡瀛楃涓诧紝涓嶉渶瑕佸叾浠栦换浣曟牸寮忓拰鍐呭\n- **鏁版嵁缁撴瀯**锛歕n {\n \"result\": \"{{chat_content}}\",\n \"mission\": \"{{mission}}\"\n \"mission_status\": \"{{mission_status}}\",\n \"mission_complete_desc\": \"{{mission_complete_desc}}\"\n }\n\n**[瀛楁璇存槑]**\n1. **result** (瀛楃涓?\n - 瀵瑰簲鍙橀噺锛歚{{chat_content}}`\n - 鍐呭锛氶【闂殑瀹為檯瀵硅瘽鍐呭\n - 瑕佹眰锛氳嚜鐒惰瑷€鍥炲锛岄潰鍚戠敤鎴穃n\n2. **mission** (瀛楃涓?\n - 瀵瑰簲鍙橀噺锛歚{{mission}}`\n - 鍙栧€硷細浠诲姟鍐呭\n - 璇存槑锛氶渶瑕佸畬鎴愮殑浠诲姟鍐呭\n\n3. **mission_status** (瀛楃涓?\n - 瀵瑰簲鍙橀噺锛歚{{mission_status}}`\n - 鍙栧€硷細`\"completed\"` 鎴?`\"in_progress\"` 鎴?`\"fail\"`\n - 璇存槑锛氭爣璇嗗綋鍓嶄换鍔″畬鎴愮姸鎬乗n - `\"completed\"`锛氫换鍔″凡鍏ㄩ儴瀹屾垚\n - `\"in_progress\"`锛氫换鍔′粛鍦ㄨ繘琛屼腑\n - `\"fail\"`锛氫换鍔″け璐ワ紝瀹㈡埛宸茬粡鏄庣‘鎷掔粷鎴栬€呭浠诲姟鍐呭琛ㄨ揪鍙嶅\n\n4. **mission_complete_desc** (瀛楃涓?\n - 瀵瑰簲鍙橀噺锛歚{{mission_complete_desc}}`\n - 鍐呭锛氭牴鎹甿ission_status鎻愪緵鐩稿簲鎻忚堪\n - 褰揱mission_status: \"completed\"`鏃讹細绠€瑕佹€荤粨浠诲姟瀹屾垚鎯呭喌\n - 褰揱mission_status: \"in_progress\"`鏃讹細璇存槑涓嬩竴姝ラ渶瑕佸仛浠€涔圽n\n**[绀轰緥]**\n{\n\"result\": \"闇€瑕佹垜缁欐偍瀹夋帓鏃堕棿鍚梊",\n\"mission\": \"閭€璇峰鎴峰埌鍞ゼ閮╘",\n\"mission_status\": \"in_progress\",\n\"mission_complete_desc\": \"闇€瑕佺敤鎴风‘璁や粈涔堟椂鍊欏埌鍞ゼ閮╘"\n}\n\n{\n\"result\": \"濂界殑锛岄偅灏卞懆鏃ヤ笅鍗堜袱鐐癸紝鎴戝埌鏃跺€欏湪鍞ゼ閮ㄧ瓑鎮紝鏉ヤ簡璁板緱缁欐垜鎵撶數璇漒",\n\"mission_status\": \"completed\",\n\"mission_complete_desc\": \"瀹㈡埛纭鍛ㄦ棩涓嬪崍涓ょ偣鍒板敭妤奸儴\"\n}\n\n**[寮哄埗瑕佹眰]**\n1. 蹇呴』杈撳嚭瀹屾暣銆佹湁鏁堢殑JSON瀵硅薄\n2. 鎵€鏈夊瓧娈靛潎涓哄繀闇€瀛楁锛屼笉鍙渷鐣n3. JSON鏍煎紡蹇呴』涓ユ牸姝g‘锛屾棤璇硶閿欒\n4. `mission_status`鍙兘浣跨敤鎸囧畾鐨勪袱涓€糪n5. `result`瀛楁鍐呭闇€绗﹀悎瀵硅瘽璇"
+// RulePromptPrefix 椋庢帶绾㈢嚎鍓嶇紑锛堥」鐩骇 rule_dimension 闈炵┖鏃舵嫾鍏ョ郴缁熸彁绀猴級
+const RulePromptPrefix = "**[椋庢帶绾㈢嚎]**锛堟渶楂樹紭鍏堢骇锛屼换浣曟儏鍐典笅涓嶅緱杩濆弽锛涘鎴疯瘽棰樿Е纰扮孩绾挎椂鑷劧杞Щ璇濋锛屼笉寰楁闈㈡壙璇猴級\n"
+
+// BasePrompt2 杈撳嚭鏍煎紡绾︽潫锛堜弗鏍?json锛歳esult / mission / mission_status / mission_complete_desc锛?+const BasePrompt2 = "**[杈撳嚭鏍煎紡]**\n" +
+ "- **鏍煎紡绫诲瀷**锛氫弗鏍艰緭鍑簀son鏍煎紡瀛楃涓诧紝涓嶉渶瑕佸叾浠栦换浣曟牸寮忓拰鍐呭\n" +
+ "- **鏁版嵁缁撴瀯**锛歕n" +
+ " {\n" +
+ " \"result\": \"{{chat_content}}\",\n" +
+ " \"mission\": \"{{mission}}\"\n" +
+ " \"mission_status\": \"{{mission_status}}\",\n" +
+ " \"mission_complete_desc\": \"{{mission_complete_desc}}\"\n" +
+ " }\n\n" +
+ "**[瀛楁璇存槑]**\n" +
+ "1. **result** (瀛楃涓?\n" +
+ " - 瀵瑰簲鍙橀噺锛歚{{chat_content}}`\n" +
+ " - 鍐呭锛氶【闂殑瀹為檯瀵硅瘽鍐呭\n" +
+ " - 瑕佹眰锛氳嚜鐒惰瑷€鍥炲锛岄潰鍚戠敤鎴凤紱鍙h鍖栥€佺畝鐭紝澶氭潯娑堟伅蹇呴』鐢╘\n杞箟绗﹀垎闅旓紙姣忔潯涓€鑸笉瓒呰繃40涓瓧锛塡n\n" +
+ "2. **mission** (瀛楃涓?\n" +
+ " - 瀵瑰簲鍙橀噺锛歚{{mission}}`\n" +
+ " - 鍙栧€硷細浠诲姟鍐呭\n" +
+ " - 璇存槑锛氶渶瑕佸畬鎴愮殑浠诲姟鍐呭\n\n" +
+ "3. **mission_status** (瀛楃涓?\n" +
+ " - 瀵瑰簲鍙橀噺锛歚{{mission_status}}`\n" +
+ " - 鍙栧€硷細`\"completed\"` 鎴?`\"in_progress\"` 鎴?`\"fail\"`\n" +
+ " - 璇存槑锛氭爣璇嗗綋鍓嶄换鍔″畬鎴愮姸鎬乗n" +
+ " - `\"completed\"`锛氫换鍔″凡鍏ㄩ儴瀹屾垚\n" +
+ " - `\"in_progress\"`锛氫换鍔′粛鍦ㄨ繘琛屼腑\n" +
+ " - `\"fail\"`锛氫换鍔″け璐ワ紝瀹㈡埛宸茬粡鏄庣‘鎷掔粷鎴栬€呭浠诲姟鍐呭琛ㄨ揪鍙嶅\n\n" +
+ "4. **mission_complete_desc** (瀛楃涓?\n" +
+ " - 瀵瑰簲鍙橀噺锛歚{{mission_complete_desc}}`\n" +
+ " - 鍐呭锛氭牴鎹甿ission_status鎻愪緵鐩稿簲鎻忚堪\n" +
+ " - 褰揱mission_status: \"completed\"`鏃讹細绠€瑕佹€荤粨浠诲姟瀹屾垚鎯呭喌\n" +
+ " - 褰揱mission_status: \"in_progress\"`鏃讹細璇存槑涓嬩竴姝ラ渶瑕佸仛浠€涔圽n\n" +
+ "**[绀轰緥]**\n" +
+ "{\n\"result\": \"闇€瑕佹垜缁欐偍瀹夋帓鏃堕棿鍚楋紵\",\n\"mission\": \"閭€璇峰鎴峰埌鍞ゼ閮╘",\n\"mission_status\": \"in_progress\",\n\"mission_complete_desc\": \"闇€瑕佺敤鎴风‘璁や粈涔堟椂鍊欏埌鍞ゼ閮╘"\n}\n\n" +
+ "{\n\"result\": \"琛岀殑锛岄偅灏卞懆鏃ヤ笅鍗堜袱鐐瑰惂\\n鎴戝厛鎶婁綅缃粰鎮ㄥ彂涓€涓嬶紝瀵艰埅鎼溿€岀俊缈犳咕鍞ゼ閮ㄣ€嶅氨鍒颁簡\\n鍒颁簡缁欐垜鎵撶數璇濆搱\",\n\"mission\": \"閭€璇峰鎴峰埌鍞ゼ閮╘",\n\"mission_status\": \"completed\",\n\"mission_complete_desc\": \"瀹㈡埛纭鍛ㄦ棩涓嬪崍涓ょ偣鍒板敭妤奸儴\"\n}\n\n" +
+ "**[寮哄埗瑕佹眰]**\n" +
+ "1. 蹇呴』杈撳嚭瀹屾暣銆佹湁鏁堢殑JSON瀵硅薄\n" +
+ "2. 鎵€鏈夊瓧娈靛潎涓哄繀闇€瀛楁锛屼笉鍙渷鐣n" +
+ "3. JSON鏍煎紡蹇呴』涓ユ牸姝g‘锛屾棤璇硶閿欒\n" +
+ "4. `mission_status`鍙兘浣跨敤鎸囧畾鐨勪笁涓€硷細completed / in_progress / fail\n" +
+ "5. `result`瀛楁鍐呭闇€绗﹀悎瀵硅瘽璇锛屽儚鐪熶汉寰俊鑱婂ぉ涓€鏍峰彛璇寲\n" +
+ "6. result 鎷嗗垎澶氭潯娑堟伅鏃跺繀椤讳娇鐢?\\n 杞箟绗︼紝绂佹鍦↗SON瀛楃涓查噷鐩存帴鎹㈣"
+
+// ==================== 鎵规B锛氭櫤鑳界瓥鐣?/ 涓诲姩瀵硅瘽 / 鑷垜杩唬 ====================
+
+// StrategyOutputHint 绛栫暐寮曟搸鍥炲杈撳嚭绾︽潫锛堢函鏂囨湰銆佹崲琛屽垎璇嶏紝涓嶈姹?JSON锛?+const StrategyOutputHint = "**[杈撳嚭鏍煎紡]**\n" +
+ "鍙緭鍑鸿鍙戠粰瀹㈡埛鐨勬秷鎭唴瀹规湰韬細绾枃鏈€佸彛璇寲锛屼笉瑕佷换浣曡В閲婅鏄庛€佸墠鍚庣紑銆乵arkdown 鎴栧紩鍙峰寘瑁癸紱\n" +
+ "鍐呭杈冨鏃舵媶鎴?~3鏉$煭娑堟伅锛屾瘡鏉′箣闂寸敤 \\n 鍒嗛殧锛屼笉瑕佹妸涓€澶ф璇濆杩涘悓涓€鏉℃秷鎭€?
+
+// StrategyReplyMission 鎰忓悜瀹㈡埛鍥炲鍦烘櫙浠诲姟璇存槑
+const StrategyReplyMission = "瀹㈡埛鍒氬彂鏉ヤ簡鏂版秷鎭紝璇蜂互椤鹃棶鏈汉鐨勮韩浠借嚜鐒跺洖澶嶏細璁ょ湡瑙g瓟浠栫殑闂锛? +
+ "缁撳悎瀹㈡埛鐢诲儚涓庢渶杩戣亰澶╄褰曚繚鎸佸璇濊繛璐紱濡傛灉鏈夊悎閫傜殑鍦ㄥ敭娲诲姩鍙互鑷劧椤哄甫鎺ㄨ崘锛堜笉瑕佸己鎺級銆?
+
+// StrategyRegularTimeoutMission 鐔熷瓒呮椂鍏滃簳鍦烘櫙浠诲姟璇存槑锛堜汉宸ヨ秴鏃舵湭鍥炲鏃?AI 鍏堝厹搴曪級
+const StrategyRegularTimeoutMission = "杩欎綅鑰佸鎴蜂箣鍓嶅彂鏉ユ秷鎭紝浜哄伐閿€鍞秴杩囩害瀹氭椂闂磋繕娌″洖澶嶃€傝鍏堢ぜ璨屽湴璇存槑" +
+ "鏆傛椂鍦ㄥ繖/绋嶅悗鍥炲锛屽啀瀵瑰鎴风殑闂鍋氱畝鍗曡В绛旓紙涓嶇‘瀹氱殑淇℃伅涓嶈缂栭€狅級锛屽畨鎶氬ソ瀹㈡埛鎯呯华锛岃姘旇鍜岄攢鍞湰浜轰竴鑷淬€?
+
+// StrategySleepingActivityMission 娌夌潯瀹㈡埛娲诲姩鎺ㄩ€佸満鏅换鍔¤鏄?+const StrategySleepingActivityMission = "杩欎綅瀹㈡埛宸茬粡寰堜箙娌℃湁浜掑姩浜嗐€傜幇鍦ㄦ湁涓€涓柊娲诲姩锛岃鐢ㄨ嚜鐒躲€佷笉鎵撴壈鐨勬柟寮? +
+ "鎶婃椿鍔ㄤ俊鎭憡璇変粬锛屽苟灏濊瘯閭€绾︼紙渚嬪鍒拌/鍜ㄨ锛夛紝涓嶈鏄惧緱鍍忕兢鍙戝箍鍛娿€?
+
+// StrategyBirthdayMission 鐢熸棩涓诲姩绁濈鍦烘櫙浠诲姟璇存槑
+const StrategyBirthdayMission = "浠婂ぉ鏄繖浣嶅鎴风殑鐢熸棩锛岃涓诲姩鍙戜竴鏉$畝鐭殑鐢熸棩绁濈锛堢О鍛煎緱浣撱€佺湡璇氳嚜鐒讹紝涓嶈鎵撳箍鍛婏級锛? +
+ "璇皵淇濇寔椤鹃棶鏈汉涓€璐殑璇磋瘽鏂瑰紡銆?
+
+// StrategyProactiveActivityMission 鏂版椿鍔ㄤ富鍔ㄨЕ杈惧満鏅换鍔¤鏄?+const StrategyProactiveActivityMission = "椤圭洰鎺ㄥ嚭浜嗘柊娲诲姩锛岃涓诲姩鑱旂郴杩欎綅瀹㈡埛锛岀敤鑷劧鐨勬柟寮忎粙缁嶆椿鍔ㄤ寒鐐瑰苟灏濊瘯閭€绾? +
+ "锛堜緥濡傚埌璁?鍜ㄨ/鍚嶉鏈夐檺锛夛紝涓嶈鏄惧緱鍍忕兢鍙戝箍鍛娿€?
+
+// StrategyFollowupMission 鏃ュ父璺熻繘涓诲姩瑙﹁揪鍦烘櫙浠诲姟璇存槑锛堟棤娲诲姩绱犳潗鏃朵娇鐢級
+const StrategyFollowupMission = "杩欎綅瀹㈡埛鏈変竴娈垫椂闂存病鏈変簰鍔ㄤ簡銆傝浠ラ【闂湰浜虹殑韬唤涓诲姩鍙戣捣涓€娆$畝鐭€佽嚜鐒剁殑闂€?璺熻繘锛? +
+ "灏濊瘯浜嗚В浠栨渶杩戠殑杩涘睍涓庨【铏戯紙鍙互鍏冲績鍐崇瓥杩涘害锛夛紝涓嶈鎺ㄩ攢鎰熷お寮猴紝涔熶笉瑕佺兢鍙戝彛鍚汇€?
+
+// ClientIteratePrompt 鐢诲儚杩唬绯荤粺鎻愮ず锛堝璇濆畬鎴愬悗鍒嗘瀽鑱婂ぉ璁板綍锛岃緭鍑虹敾鍍忓閲?JSON锛?+const ClientIteratePrompt = `**[瑙掕壊]**浣犳槸璧勬繁鐨勯攢鍞暟鎹垎鏋愬笀锛岃礋璐f牴鎹攢鍞笌瀹㈡埛鐨勭湡瀹炶亰澶╄褰曪紝鎻愬彇骞舵洿鏂板鎴风敾鍍忋€?+**[浠诲姟]**鍒嗘瀽瀵硅瘽鍐呭锛屾壘鍑猴細瀹㈡埛涓汉淇℃伅锛堝鍚?鎬у埆/灞呬綇鍦?鏄惁棣栧/瀹跺涵鎯呭喌/鐢熸棩锛夈€佽喘鎴跨洰鐨勩€佹牳蹇冮渶姹傦紙棰勭畻/鎴峰瀷/杩版眰锛夈€佸叧娉ㄧ偣涓庨【铏戙€佸喅绛栫壒寰併€?+**[瑕佹眰]**
+1. 鍙彁鍙栬亰澶╄褰曚腑鐪熷疄鍑虹幇鎴栧彲浠ユ槑纭帹鏂殑淇℃伅锛屼弗绂佺紪閫狅紱
+2. 鍙緭鍑恒€屾柊鍙戠幇銆嶆垨銆岄渶瑕佷慨姝c€嶇殑瀛楁锛屾湭娑夊強鎴栨棤娉曠‘璁ょ殑瀛楁鐩存帴鐪佺暐锛屼笉瑕佽緭鍑虹┖鍊硷紱
+3. 鐢熸棩鍙緭鍑哄嚭鐜拌繃鐨勬湀鏃ユ垨瀹屾暣鏃ユ湡锛堝 "05-20" 鎴?"1995-05-20"锛夛紱
+4. concerns/decisionProfile 鍙緭鍑烘柊澧炴潯鐩紝涓嶈閲嶅宸叉湁鍐呭銆?+**[杈撳嚭鏍煎紡]**涓ユ牸杈撳嚭 json锛屼笉瑕佷换浣曞叾浠栧唴瀹癸細
+{
+ "personalInfo": {"name": "", "gender": "", "location": "", "isFirstHome": true, "familyOrganize": "", "birthday": ""},
+ "purchasePurpose": {"primaryPurpose": "", "secondaryPurpose": "", "decisionMakers": ""},
+ "coreDemands": {"totalBudget": "", "preferredLayout": "", "coreAppeal": ""},
+ "concerns": ["鏂板鍏虫敞鐐?],
+ "decisionProfile": ["鏂板鍐崇瓥鐗瑰緛"],
+ "summary": "鏈杩唬鐨勭畝瑕佽鏄?
+}`
+
+// ClientEvaluatePrompt 瀹㈡埛绛夌骇璇勪及鎻愮ず璇嶏紙姣忔棩 0 鐐瑰畾鏃惰瘎浼?/ 鎵嬪姩璇勪及锛孡LM 涓轰富锛?+const ClientEvaluatePrompt = `浣犳槸璧勬繁閿€鍞鐞嗕笓瀹躲€傝鏍规嵁銆愬鎴风敾鍍忋€戙€愪簰鍔ㄧ粺璁°€戙€愭渶杩戣亰澶╄褰曘€戯紝璇勪及璇ュ鎴峰綋鍓嶆墍澶勭殑瀹㈡埛闃舵銆?+
+銆愮瓑绾у畾涔夈€戯紙鍙兘鍥涢€変竴锛?+- "regular" 鐔熷锛氬凡鎴愪氦鎴栧凡寤虹珛闀挎湡绋冲畾鍏崇郴锛屼簰鍔ㄩ绻併€佷俊浠诲害楂橈紝澶勪簬澶嶈喘/杞粙缁?鍞悗闃舵銆?+- "intent" 鎰忓悜瀹㈡埛锛氭湁鏄庣‘璐拱鎰忓悜锛岃繎鏈熶富鍔ㄥ挩璇㈢粏鑺傦紙浠锋牸銆佹柟妗堛€佹椂闂村畨鎺掔瓑锛夛紝澶勪簬鎺ㄨ繘鎴愪氦闃舵銆?+- "sleeping" 娌夌潯瀹㈡埛锛氭浘鏈夎繃浜掑姩浣嗚繎鏈熸槑鏄炬矇榛橈紙閫氬父瓒呰繃 30 澶╂棤鏈夋晥浜掑姩锛夛紝鐑儏鍑忛€€浣嗘湭鏄庣‘鎷掔粷銆?+- "non" 闈炲鎴凤細鏄庣‘鎷掔粷銆佹棤璐拱鎰忔効銆佹帹閿€/鍚岃/楠氭壈绫伙紝鎴栦笌涓氬姟瀹屽叏鏃犲叧銆?+
+銆愯瘎浼拌姹傘€?+1. 缁煎悎鐢诲儚淇℃伅涓庤亰澶╄褰曡涔夊垽鏂紝涓嶈鍙湅娑堟伅鏁伴噺銆?+2. 鑻ヨ亰澶╄褰曟樉绀哄鎴锋槑纭嫆缁濇垨琛ㄧず涓嶉渶瑕侊紝鍒や负 "non"銆?+3. 鑻ュ鎴疯繎鏈熸椿璺冨挩璇€佸叧蹇冩垚浜や笌浜や粯缁嗚妭锛屼紭鍏堟寜褰撳墠瀹為檯鐘舵€佽瘎浼帮紙宸叉湁鎴愪氦鍘嗗彶鐨勫彲鍒?intent锛屽浜庣淮鎶ゆ湡鐨勫垽 regular锛夈€?+4. reason 鐢ㄤ笉瓒呰繃 30 涓瓧姒傛嫭涓昏渚濇嵁锛岄渶缁撳悎鍏蜂綋鑱婂ぉ鍐呭銆?+
+銆愯緭鍑烘牸寮忋€戝彧杈撳嚭涓ユ牸 JSON锛屼笉瑕佽緭鍑哄叾浠栦换浣曞唴瀹癸細
+{"level": "intent", "reason": "杩戞湡澶氭鍜ㄨ浠锋牸涓庝氦浠樻椂闂?}`
diff --git a/internal/data/mongo_model/advicer_client.go b/internal/data/mongo_model/advicer_client.go
index 053ff42..f8adfd2 100644
--- a/internal/data/mongo_model/advicer_client.go
+++ b/internal/data/mongo_model/advicer_client.go
@@ -4,9 +4,40 @@ import (
"time"
)
+// 瀹㈡埛绛夌骇
+const (
+ ClientLevelUnknown = "" // 鏈瘎浼?+ ClientLevelRegular = "regular" // 鐔熷锛氬凡鎴愪氦/楂橀浜掑姩锛孉I 灏介噺涓嶄粙鍏ワ紝浜哄伐鎺ョ
+ ClientLevelIntent = "intent" // 鎰忓悜瀹㈡埛锛氫富鍔ㄨ窡韪€佷笅鍗曢個绾︺€佹椿鍔ㄦ帹閫?+ ClientLevelSleeping = "sleeping" // 娌夌潯瀹㈡埛锛氬彧鎺ㄦ椿鍔ㄥ苟灏濊瘯閭€绾?+ ClientLevelNon = "non" // 闈炲鎴凤細AI 涓嶅弬涓庡洖澶?+)
+
+// ClientLevelDesc 瀹㈡埛绛夌骇涓枃鎻忚堪
+type ClientLevelDescMap map[string]string
+
+var ClientLevelDesc = ClientLevelDescMap{
+ ClientLevelUnknown: "鏈瘎浼?,
+ ClientLevelRegular: "鐔熷",
+ ClientLevelIntent: "鎰忓悜瀹㈡埛",
+ ClientLevelSleeping: "娌夌潯瀹㈡埛",
+ ClientLevelNon: "闈炲鎴?,
+}
+
type AdvicerClientMongo struct {
ProjectId int32 `json:"projectId" bson:"projectId"`
AdvicerId int32 `json:"advicerId" bson:"advicerId"`
+ Wxid string `json:"wxid" bson:"wxid"` // 瀹㈡埛寰俊 wxid锛堝洖璋冨敮涓€鏍囪瘑锛?+ AppId string `json:"appId" bson:"appId"` // 缁戝畾鐨勭櫥褰曞疄渚?appId
+ ClientLevel string `json:"clientLevel" bson:"clientLevel"` // 瀹㈡埛绛夌骇锛歳egular/intent/sleeping/non
+ LevelReason string `json:"levelReason" bson:"levelReason"` // 绛夌骇鍒ゅ畾鐞嗙敱锛堣瘎浼颁骇鍑猴級
+ LevelUpdateAt time.Time `json:"levelUpdateAt" bson:"levelUpdateAt"` // 绛夌骇鏈€鍚庤瘎浼版椂闂?+ LastContactAt time.Time `json:"lastContactAt" bson:"lastContactAt"` // 鏈€鍚庝簰鍔ㄦ椂闂达紙浠绘剰鏂瑰悜锛?+ LastClientMsgAt time.Time `json:"lastClientMsgAt" bson:"lastClientMsgAt"` // 鏈€鍚庡鎴锋秷鎭椂闂?+ LastReplyAt time.Time `json:"lastReplyAt" bson:"lastReplyAt"` // 鏈€鍚庡洖澶嶅鎴锋椂闂?+ MsgCount int `json:"msgCount" bson:"msgCount"` // 绱浜掑姩娑堟伅鏁?+ AiEnabled bool `json:"aiEnabled" bson:"aiEnabled"` // 鏄惁鍏佽 AI 鍙備笌瀵硅瘽
+ LastIterateAt time.Time `json:"lastIterateAt" bson:"lastIterateAt"` // 鐢诲儚鏈€鍚庤凯浠f椂闂达紙瀵硅瘽瀹屾垚鑷垜鍗囩骇娓告爣锛? PersonalInfo PersonalInfo `json:"personalInfo" bson:"personalInfo"`
PurchasePurpose PurchasePurpose `json:"purchasePurpose" bson:"purchasePurpose"`
CoreDemands CoreDemands `json:"coreDemands" bson:"coreDemands"`
@@ -66,6 +97,7 @@ type PersonalInfo struct {
Location string `json:"location"` // 鏉ユ簮鍦?褰撳墠灞呬綇鍦? IsFirstHome bool `json:"isFirstHome"` // 鏄惁棣栧鎴? FamilyOrganize string `json:"familyOrganize"` // 瀹跺涵浜烘暟
+ Birthday string `json:"birthday"` // 鐢熸棩锛堝 05-20 鎴?1995-05-20锛岀敤浜?AI 涓诲姩绁濈锛? }
type PurchasePurpose struct {
diff --git a/internal/data/mongo_model/common.go b/internal/data/mongo_model/common.go
index 21d8f8a..594f0d0 100644
--- a/internal/data/mongo_model/common.go
+++ b/internal/data/mongo_model/common.go
@@ -22,6 +22,24 @@ type AdvicerClientItem struct {
AdvicerClientMongo `bson:",inline"`
}
+// AdvicerWxMsgItem 寰俊娑堟伅娴佹按锛堝惈 _id锛?+type AdvicerWxMsgItem struct {
+ Id primitive.ObjectID `bson:"_id" json:"id"`
+ AdvicerWxMsgMongo `bson:",inline"`
+}
+
+// AdvicerActivityItem 浜у搧娲诲姩锛堝惈 _id锛?+type AdvicerActivityItem struct {
+ Id primitive.ObjectID `bson:"_id" json:"id"`
+ AdvicerActivityMongo `bson:",inline"`
+}
+
+// AdvicerProactiveLogItem AI 涓诲姩瑙﹁揪璁板綍锛堝惈 _id锛?+type AdvicerProactiveLogItem struct {
+ Id primitive.ObjectID `bson:"_id" json:"id"`
+ AdvicerProactiveLogMongo `bson:",inline"`
+}
+
type AdviceRole string
const (
diff --git a/internal/data/mongo_model/provider_set.go b/internal/data/mongo_model/provider_set.go
index 916b5dd..f068058 100644
--- a/internal/data/mongo_model/provider_set.go
+++ b/internal/data/mongo_model/provider_set.go
@@ -8,4 +8,7 @@ var ProviderSetMongo = wire.NewSet(
NewAdvicerProjectMongo,
NewAdvicerClientMongo,
NewAdvicerChatHisMongo,
+ NewAdvicerWxMsgMongo,
+ NewAdvicerActivityMongo,
+ NewAdvicerProactiveLogMongo,
)
diff --git a/internal/entitys/advicer.go b/internal/entitys/advicer.go
index eea6d85..60441af 100644
--- a/internal/entitys/advicer.go
+++ b/internal/entitys/advicer.go
@@ -11,4 +11,6 @@ type ChatData struct {
ProjectInfo *mongo_model.AdvicerProjectMongoEntity `json:"projectInfo"`
AdvicerInfo *model.AiAdviceAdvicerEntity `json:"advicerInfo"`
AdvicerVersion *mongo_model.AdvicerVersionMongoEntity `json:"advicerVersion"`
+ // RuleDimension 椤圭洰椋庢帶绾㈢嚎锛堟潵鑷」鐩ā鏉?rule_dimension锛屽彲涓虹┖锛?+ RuleDimension string `json:"ruleDimension"`
}
diff --git a/internal/entitys/advicer_data.go b/internal/entitys/advicer_data.go
index d791c87..7a0a254 100644
--- a/internal/entitys/advicer_data.go
+++ b/internal/entitys/advicer_data.go
@@ -175,6 +175,8 @@ type AdvicerProjectInfoRes struct {
type AdvicerClientAddReq struct {
ProjectId int32 `json:"projectId" bson:"projectId"`
AdvicerId int32 `json:"advicerId" bson:"advicerId"`
+ Wxid string `json:"wxid" bson:"wxid"` // 瀹㈡埛寰俊 wxid锛堝彲閫夛紝鐢ㄤ簬鍏宠仈鍥炶皟娑堟伅锛?+ AiEnabled *bool `json:"aiEnabled" bson:"aiEnabled"` // 鏄惁鍏佽 AI 鍙備笌瀵硅瘽锛堜笉浼犻粯璁ゅ紑鍚級
PersonalInfo mongo_model.PersonalInfo `json:"personalInfo" bson:"personalInfo"`
PurchasePurpose mongo_model.PurchasePurpose `json:"purchasePurpose" bson:"purchasePurpose"`
CoreDemands mongo_model.CoreDemands `json:"coreDemands" bson:"coreDemands"`
@@ -186,6 +188,7 @@ type AdvicerrClientUpdateReq struct {
Id string `json:"id"`
ProjectId int32 `json:"projectId" bson:"projectId"`
AdvicerId int32 `json:"advicerId" bson:"advicerId"`
+ AiEnabled *bool `json:"aiEnabled" bson:"aiEnabled"` // 鏄惁鍏佽 AI 鍙備笌瀵硅瘽锛堜笉浼犱繚鎸佸師鍊硷級
PersonalInfo mongo_model.PersonalInfo `json:"personalInfo" bson:"personalInfo"`
PurchasePurpose mongo_model.PurchasePurpose `json:"purchasePurpose" bson:"purchasePurpose"`
CoreDemands mongo_model.CoreDemands `json:"coreDemands" bson:"coreDemands"`
@@ -226,3 +229,167 @@ type AdvicerChatReq struct {
type AdvicerChatRes struct {
Content int32 `json:"content"`
}
+
+// AdvicerClientBindReq 瀹㈡埛缁戝畾寰俊锛堢粦瀹氬悗鍥炶皟娑堟伅鍙嚜鍔ㄥ叧鑱旇瀹㈡埛锛?+type AdvicerClientBindReq struct {
+ Id string `json:"id"` // 瀹㈡埛璁板綍 id锛圡ongo _id锛?+ Wxid string `json:"wxid"` // 瀹㈡埛寰俊 wxid
+ AppId string `json:"appId"` // 鐧诲綍瀹炰緥 appId锛堝彲閫夛級
+}
+
+// AdvicerWxCallbackReq 璁剧疆涓婃父娑堟伅鍥炶皟鍦板潃
+type AdvicerWxCallbackReq struct {
+ AppId string `json:"appId"` // 鐧诲綍瀹炰緥 appId锛堝彲閫夛級
+ CallbackUrl string `json:"callbackUrl"` // 鍥炶皟鍦板潃锛堝叕缃戝彲璁块棶锛屽 https://鍩熷悕/api/v1/advicer/wx/callback锛?+}
+
+// AdvicerActivityAddReq 鏂板浜у搧娲诲姩
+type AdvicerActivityAddReq struct {
+ ProjectId int32 `json:"projectId"`
+ AdvicerId int32 `json:"advicerId"`
+ Name string `json:"name"`
+ Content string `json:"content"`
+ StartAt string `json:"startAt"` // 寮€濮嬫椂闂达紙2006-01-02 15:04 鎴?2006-01-02 15:04:05锛?+ EndAt string `json:"endAt"` // 缁撴潫鏃堕棿
+ TargetLevels []string `json:"targetLevels"`
+ Status int `json:"status"` // 0鑽夌/1杩涜涓?2鍋滄
+}
+
+// AdvicerActivityUpdateReq 鏇存柊浜у搧娲诲姩
+type AdvicerActivityUpdateReq struct {
+ Id string `json:"id"`
+ ProjectId int32 `json:"projectId"`
+ AdvicerId int32 `json:"advicerId"`
+ Name string `json:"name"`
+ Content string `json:"content"`
+ StartAt string `json:"startAt"`
+ EndAt string `json:"endAt"`
+ TargetLevels []string `json:"targetLevels"`
+ Status int `json:"status"`
+}
+
+// AdvicerActivityListReq 娲诲姩鍒楄〃鏌ヨ
+type AdvicerActivityListReq struct {
+ Id string `json:"id"`
+ ProjectId int32 `json:"projectId"`
+ AdvicerId int32 `json:"advicerId"`
+ Status *int32 `json:"status"` // 涓嶄紶=鍏ㄩ儴锛屼紶 0/1/2 鎸夌姸鎬佽繃婊?+ Page int `json:"page"`
+ PageSize int `json:"page_size"`
+}
+
+// AdvicerActivityDelReq 鍒犻櫎娲诲姩
+type AdvicerActivityDelReq struct {
+ Id string `json:"id"`
+}
+
+// AdvicerActivityInfoReq 娲诲姩璇︽儏
+type AdvicerActivityInfoReq struct {
+ Id string `json:"id"`
+}
+
+// AdvicerWxMsgListReq 寰俊娑堟伅娴佹按鏌ヨ
+type AdvicerWxMsgListReq struct {
+ AppId string `json:"appId"`
+ Wxid string `json:"wxid"`
+ Direction string `json:"direction"`
+ StartAt string `json:"startAt"`
+ EndAt string `json:"endAt"`
+ Page int `json:"page"`
+ PageSize int `json:"page_size"`
+}
+
+// AdvicerClientEvaluateReq 瀹㈡埛绛夌骇璇勪及锛堟墜鍔ㄨЕ鍙戯級
+type AdvicerClientEvaluateReq struct {
+ ClientId string `json:"clientId"` // 涓虹┖鍒欒瘎浼板叏閮ㄥ鎴?+ Force bool `json:"force"` // 蹇界暐褰撴棩宸茶瘎浼伴檺鍒讹紝寮哄埗閲嶆柊璇勪及
+}
+
+// AdvicerClientLevelDetail 瀹㈡埛绛夌骇璇勪及鏄庣粏
+type AdvicerClientLevelDetail struct {
+ ClientId string `json:"clientId"`
+ ClientName string `json:"clientName"`
+ Level string `json:"level"`
+ LevelDesc string `json:"levelDesc"`
+ Reason string `json:"reason"`
+}
+
+// AdvicerClientEvaluateRes 瀹㈡埛绛夌骇璇勪及缁撴灉
+type AdvicerClientEvaluateRes struct {
+ Total int `json:"total"`
+ Evaluated int `json:"evaluated"`
+ Details []AdvicerClientLevelDetail `json:"details"`
+}
+
+// ==================== 鎵规B锛氭櫤鑳界瓥鐣?/ 涓诲姩瀵硅瘽 / 鑷垜杩唬 ====================
+
+// AdvicerStrategyTestReq 绛栫暐骞茶窇娴嬭瘯锛堜笉瀹為檯鍙戦€侊級
+type AdvicerStrategyTestReq struct {
+ Wxid string `json:"wxid"` // 瀹㈡埛寰俊 wxid
+ Content string `json:"content"` // 妯℃嫙瀹㈡埛娑堟伅锛堢┖鍒欏垎鏋愭渶鏂版湭鍥炲娑堟伅锛?+ Send bool `json:"send"` // true=瀹為檯鍙戦€侊紱榛樿 false 浠呭共璺戦瑙?+}
+
+// AdvicerStrategyTestRes 绛栫暐鍐崇瓥缁撴灉
+type AdvicerStrategyTestRes struct {
+ Level string `json:"level"` // 瀹㈡埛绛夌骇锛坮egular/intent/sleeping/non锛?+ LevelDesc string `json:"levelDesc"` // 绛夌骇涓枃鎻忚堪
+ Action string `json:"action"` // 鍔ㄤ綔锛歳eply/activity_push/regular_wait/skip/not_bound/ai_off/non_client
+ Reason string `json:"reason"` // 鍐崇瓥璇存槑
+ Replies []string `json:"replies"` // 鐢熸垚鐨勬秷鎭紙鎷嗘潯鍚庯級
+ Sent bool `json:"sent"` // 鏄惁宸插疄闄呭彂閫?+}
+
+// AdvicerIterateRunReq 鎵嬪姩瑙﹀彂鐢诲儚杩唬
+type AdvicerIterateRunReq struct {
+ ClientId string `json:"clientId"` // 涓虹┖鍒欏鐞嗗叏閮ㄥ鎴?+ Force bool `json:"force"` // 蹇界暐瀵硅瘽瀹屾垚鍚庨棽缃瓑寰咃紙鎵嬪姩瑙﹀彂绔嬪嵆杩唬锛?+}
+
+// AdvicerIterateRunRes 鐢诲儚杩唬缁撴灉
+type AdvicerIterateRunRes struct {
+ Scanned int `json:"scanned"` // 鎵弿鍒扮殑鏈垎鏋愭秷鎭暟
+ Idle int `json:"idle"` // 瀵硅瘽鏈畬鎴愶紙鏈弧闂茬疆鏃堕暱锛夎€岃烦杩?+ Updated int `json:"updated"` // 鐢诲儚鏇存柊瀹㈡埛鏁?+ Details []string `json:"details"` // 鏄庣粏锛堝鎴?+ 鏇存柊璇存槑锛?+}
+
+// AdvicerProactiveRunReq 涓诲姩瑙﹁揪鎵弿锛堢敓鏃ョ绂?鏂版椿鍔ㄦ帹閫侊級
+type AdvicerProactiveRunReq struct {
+ ProjectId int32 `json:"projectId"`
+ AdvicerId int32 `json:"advicerId"`
+ Force bool `json:"force"` // 鎵嬪姩瑙﹀彂锛氬拷鐣?AutoReply 寮€鍏充笌娲昏穬鏃堕棿绐楀彛锛屽苟鍏佽褰撳ぉ閲嶅瑙﹁揪
+ DryRun bool `json:"dryRun"` // 鍙壂鎻忎笉鍙戦€侊紙浠呭垪鍑哄皢瑙﹁揪鐨勫鎴凤級
+}
+
+// AdvicerProactiveRunRes 涓诲姩瑙﹁揪缁撴灉
+type AdvicerProactiveRunRes struct {
+ Scanned int `json:"scanned"` // 鎵弿瀹㈡埛鏁?+ Birthday int `json:"birthday"` // 鐢熸棩瑙﹁揪鏁?+ Activity int `json:"activity"` // 娲诲姩瑙﹁揪鏁?+ Skipped int `json:"skipped"` // 璺宠繃鏁?+ Failed int `json:"failed"` // 澶辫触鏁?+ Details []string `json:"details"`
+}
+
+// AdvicerProactiveSendReq 鎵嬪姩鎺ㄩ€侊紙鎸囧畾瀹㈡埛 + 娲诲姩锛?+type AdvicerProactiveSendReq struct {
+ ClientId string `json:"clientId"`
+ ActivityId string `json:"activityId"` // 涓虹┖鍒欑敤褰撳墠鐢熸晥娲诲姩
+ Content string `json:"content"` // 鑷畾涔夎瘽鏈紙绌哄垯 AI 鐢熸垚锛?+}
+
+// AdvicerActivityPushReq 娲诲姩绔嬪嵆鎺ㄩ€侊紙瀵硅娲诲姩鐩爣瀹㈡埛缇ら€愪汉鎺ㄩ€侊級
+type AdvicerActivityPushReq struct {
+ Id string `json:"id"` // 娲诲姩 id
+ DryRun bool `json:"dryRun"` // 鍙壂鎻忎笉鍙戦€?+}
+
+// AdvicerProactiveLogListReq 涓诲姩瑙﹁揪璁板綍鏌ヨ
+type AdvicerProactiveLogListReq struct {
+ ClientId string `json:"clientId"`
+ Wxid string `json:"wxid"`
+ Type string `json:"type"`
+ Page int `json:"page"`
+ PageSize int `json:"page_size"`
+}
diff --git a/internal/server/http.go b/internal/server/http.go
index 35c9461..dbe1359 100644
--- a/internal/server/http.go
+++ b/internal/server/http.go
@@ -24,6 +24,9 @@ func NewHTTPServer(
industry *advice.IndustryService,
admin *advice.AdminService,
modelSup *advice.ModelSupService,
+ activity *advice.ActivityService,
+ wxHook *advice.WxHookService,
+ smartService *advice.SmartService,
) *fiber.App {
//鏋勫缓 server
app := initRoute()
@@ -31,7 +34,7 @@ func NewHTTPServer(
// 涓婁紶鏂囦欢闈欐€佹墭绠★細/upload/...锛堣亰澶╄褰曠瓑锛? app.Static("/upload", "./upload")
app.Static("/", "./web")
- router.SetupRoutes(cfg, app, adviceFile, adviceData, adviceChat, adviceProject, adviceTalkSkill, adviceClient, industry, admin, modelSup)
+ router.SetupRoutes(cfg, app, adviceFile, adviceData, adviceChat, adviceProject, adviceTalkSkill, adviceClient, industry, admin, modelSup, activity, wxHook, smartService)
return app
}
diff --git a/internal/server/router/advicer.go b/internal/server/router/advicer.go
index 9ecb30a..cd9ee12 100644
--- a/internal/server/router/advicer.go
+++ b/internal/server/router/advicer.go
@@ -20,6 +20,9 @@ func AdvicerRouterRegist(
industry *advice.IndustryService,
admin *advice.AdminService,
modelSup *advice.ModelSupService,
+ activity *advice.ActivityService,
+ wxHook *advice.WxHookService,
+ smart *advice.SmartService,
) {
advicer := r.Group("advice/admin")
// 鐧诲綍鏀捐
@@ -60,6 +63,31 @@ func AdvicerRouterRegist(
advicer.Post("client/update", adviceClient.Update)
advicer.Post("client/list", adviceClient.List)
advicer.Post("client/del", adviceClient.Del)
+ advicer.Post("client/bind", adviceClient.Bind) // 瀹㈡埛缁戝畾寰俊 wxid
+ advicer.Post("client/evaluate", adviceClient.Evaluate) // 瀹㈡埛绛夌骇璇勪及锛堢啛瀹?鎰忓悜/娌夌潯/闈炲鎴凤級
+
+ //浜у搧娲诲姩
+ advicer.Post("activity/add", activity.Add)
+ advicer.Post("activity/update", activity.Update)
+ advicer.Post("activity/list", activity.List)
+ advicer.Post("activity/del", activity.Del)
+ advicer.Post("activity/info", activity.Info)
+ advicer.Post("activity/active", activity.ActiveList)
+
+ //寰俊鍥炶皟涓庢秷鎭祦姘?+ advicer.Post("wx/callback/set", wxHook.SetCallback) // 璁剧疆涓婃父娑堟伅鍥炶皟鍦板潃
+ advicer.Post("wx/callback/info", wxHook.CallbackInfo) // 鑾峰彇寤鸿鐨勫洖璋冨湴鍧€
+ advicer.Post("wxmsg/list", wxHook.MsgList) // 娑堟伅娴佹按鏌ヨ
+
+ //鏅鸿兘绛栫暐锛堝洓绾у洖澶嶇瓥鐣?涓诲姩瀵硅瘽/鑷垜杩唬锛?+ advicer.Post("smart/reply/test", smart.ReplyTest) // 绛栫暐骞茶窇棰勮锛坰end=true 瀹為檯鍙戦€侊級
+ advicer.Post("smart/reply", smart.Reply) // 瀵瑰鎴锋渶鏂版秷鎭墜鍔ㄦ墽琛岀瓥鐣ュ洖澶?+ advicer.Post("smart/regular/scan", smart.RegularScan) // 鐔熷瓒呮椂鍏滃簳鎵弿
+ advicer.Post("smart/iterate/run", smart.IterateRun) // 鎵嬪姩瑙﹀彂鐢诲儚杩唬
+ advicer.Post("smart/proactive/scan", smart.ProactiveScan) // 涓诲姩瑙﹁揪鎵弿锛堢敓鏃?娲诲姩锛?+ advicer.Post("smart/proactive/send", smart.ProactiveSend) // 鎵嬪姩鎺ㄩ€佺粰鎸囧畾瀹㈡埛
+ advicer.Post("smart/activity/push", smart.ActivityPush) // 娲诲姩绔嬪嵆缇ゆ帹
+ advicer.Post("smart/proactive/log", smart.ProactiveLog) // 涓诲姩瑙﹁揪璁板綍鏌ヨ
//浼氳瘽
advicer.Post("chat/regis", adviceChat.Regis)
diff --git a/internal/server/router/router.go b/internal/server/router/router.go
index 3d38960..46aa312 100644
--- a/internal/server/router/router.go
+++ b/internal/server/router/router.go
@@ -19,6 +19,8 @@ import (
func SetupRoutes(cfg *config.Config, app *fiber.App, adviceFile *advice.FileService, adviceData *advice.AdvicerService,
adviceChat *advice.ChatService, adviceProject *advice.ProjectService, adviceTalkSkill *advice.TalkSkillService, adviceClient *advice.ClientService,
industry *advice.IndustryService, admin *advice.AdminService, modelSup *advice.ModelSupService,
+ activity *advice.ActivityService, wxHook *advice.WxHookService,
+ smart *advice.SmartService,
) {
app.Use(func(c *fiber.Ctx) error {
// 璁剧疆 CORS 澶?@@ -51,6 +53,9 @@ func SetupRoutes(cfg *config.Config, app *fiber.App, adviceFile *advice.FileServ
projectR := r.Group("project")
WxRegist(projectR)
+ // 涓汉寰俊娑堟伅鍥炶皟鍏ュ彛锛?api/v1/advicer/wx/callback锛堜笂娓歌皟鐢紝鏃?JWT锛屽洖璋?token 鏍¢獙鍦ㄦ湇鍔″唴瀹屾垚锛?+ r.Post("advicer/wx/callback", wxHook.Callback)
+
// 绠$悊鍚庡彴璺敱锛堥渶瑕佺櫥褰曪級: /api/v1/admin/...
adminR := r.Group("admin")
// 鐧诲綍鎺ュ彛鏀捐锛屽叾浣欐帴鍙f牎楠?JWT 鐧诲綍鎬?@@ -67,6 +72,9 @@ func SetupRoutes(cfg *config.Config, app *fiber.App, adviceFile *advice.FileServ
industry,
admin,
modelSup,
+ activity,
+ wxHook,
+ smart,
)
}
diff --git a/internal/server/server.go b/internal/server/server.go
index 4aa5ab6..d1a2077 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -2,18 +2,28 @@ package server
import (
"ai_scheduler/internal/config"
+ "ai_scheduler/internal/jobs"
"github.com/gofiber/fiber/v2"
)
type Servers struct {
- cfg *config.Config
- HttpServer *fiber.App
+ cfg *config.Config
+ HttpServer *fiber.App
+ adviceTaskJob *jobs.AdviceTaskJob
}
-func NewServers(cfg *config.Config, fiber *fiber.App) *Servers {
+func NewServers(cfg *config.Config, fiber *fiber.App, adviceTaskJob *jobs.AdviceTaskJob) *Servers {
return &Servers{
- HttpServer: fiber,
- cfg: cfg,
+ HttpServer: fiber,
+ cfg: cfg,
+ adviceTaskJob: adviceTaskJob,
+ }
+}
+
+// StartJobs 鍚姩鍚庡彴瀹氭椂浠诲姟锛堟瘡鏃ュ鎴风瓑绾ц瘎浼扮瓑锛?+func (s *Servers) StartJobs() {
+ if s.adviceTaskJob != nil {
+ s.adviceTaskJob.Start()
}
}
diff --git a/internal/services/advice/chat.go b/internal/services/advice/chat.go
index d8baf09..603b4f7 100644
--- a/internal/services/advice/chat.go
+++ b/internal/services/advice/chat.go
@@ -113,6 +113,7 @@ func (a *ChatService) Regis(c *fiber.Ctx) error {
ProjectInfo: projectInfo.ConfigInfo.Entity(),
AdvicerInfo: advicerInfo.Entity(),
AdvicerVersion: versionInfo.Entity(),
+ RuleDimension: projectInfo.Base.RuleDimension,
}
sessionId, err := a.adviceChatBiz.Regis(c.UserContext(), &chat, req, projectInfo)
diff --git a/internal/services/advice/client.go b/internal/services/advice/client.go
index d8377b4..2a26d0f 100644
--- a/internal/services/advice/client.go
+++ b/internal/services/advice/client.go
@@ -5,24 +5,29 @@ import (
"ai_scheduler/internal/config"
"ai_scheduler/internal/entitys"
"ai_scheduler/internal/pkg"
+ "context"
+ "time"
"github.com/gofiber/fiber/v2"
)
// ClientService 鏁版嵁澶勭悊
type ClientService struct {
- AdviceClientBiz *biz.AdviceClientBiz
- cfg *config.Config
+ AdviceClientBiz *biz.AdviceClientBiz
+ AdviceEvaluateBiz *biz.AdviceEvaluateBiz
+ cfg *config.Config
}
// NewDataService
func NewClientService(
AdviceClientBiz *biz.AdviceClientBiz,
+ AdviceEvaluateBiz *biz.AdviceEvaluateBiz,
cfg *config.Config,
) *ClientService {
return &ClientService{
- AdviceClientBiz: AdviceClientBiz,
- cfg: cfg,
+ AdviceClientBiz: AdviceClientBiz,
+ AdviceEvaluateBiz: AdviceEvaluateBiz,
+ cfg: cfg,
}
}
@@ -60,3 +65,31 @@ func (d *ClientService) Del(c *fiber.Ctx) error {
return d.AdviceClientBiz.Del(c.UserContext(), req)
}
+
+// Bind 瀹㈡埛缁戝畾寰俊 wxid锛堢粦瀹氬悗鍥炶皟娑堟伅鍙嚜鍔ㄥ叧鑱旇瀹㈡埛锛?+func (d *ClientService) Bind(c *fiber.Ctx) error {
+ req := &entitys.AdvicerClientBindReq{}
+ if err := c.BodyParser(req); err != nil {
+ return err
+ }
+ return d.AdviceClientBiz.Bind(c.UserContext(), req)
+}
+
+// Evaluate 瀹㈡埛绛夌骇璇勪及锛氭寚瀹?clientId 鍚屾璇勪及鍗曚釜瀹㈡埛锛涗笉鎸囧畾鏃跺悗鍙板紓姝ヨ瘎浼板叏閮?+func (d *ClientService) Evaluate(c *fiber.Ctx) error {
+ req := &entitys.AdvicerClientEvaluateReq{}
+ if err := c.BodyParser(req); err != nil {
+ return err
+ }
+ if len(req.ClientId) == 0 {
+ // 鍏ㄩ噺璇勪及鑰楁椂杈冮暱锛堝惈 LLM 璋冪敤锛夛紝鍚庡彴寮傛鎵ц锛屽墠绔◢鍚庡埛鏂版煡鐪?+ go func() {
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
+ defer cancel()
+ _, _ = d.AdviceEvaluateBiz.EvaluateAll(ctx, req)
+ }()
+ return pkg.HandleResponse(c, fiber.Map{"async": true, "msg": "璇勪及浠诲姟宸插惎鍔紝璇风◢鍚庡埛鏂版煡鐪?}, nil)
+ }
+ res, err := d.AdviceEvaluateBiz.EvaluateAll(c.UserContext(), req)
+ return pkg.HandleResponse(c, res, err)
+}
diff --git a/internal/services/advice/provider_set.go b/internal/services/advice/provider_set.go
index c800cfd..2823317 100644
--- a/internal/services/advice/provider_set.go
+++ b/internal/services/advice/provider_set.go
@@ -15,4 +15,7 @@ var ProviderService = wire.NewSet(
NewProjectService,
NewModelSupService,
NewTalkSkillService,
+ NewActivityService,
+ NewWxHookService,
+ NewSmartService,
)
diff --git a/web/assets/js/core.js b/web/assets/js/core.js
index 158feaf..8c9397e 100644
--- a/web/assets/js/core.js
+++ b/web/assets/js/core.js
@@ -276,6 +276,18 @@
location.reload();
}
+ /* ---------------- 澶氭皵娉″伐鍏?---------------- */
+ // splitBubbles 灏嗘枃鏈寜鎹㈣鎷嗗垎涓哄鏉℃秷鎭紙妯℃嫙鐪熶汉杩炲彂澶氭潯鐭秷鎭級
+ function splitBubbles(text) {
+ if (!text) return [];
+ return String(text).split(/\r?\n+/).map(function (s) { return s.trim(); }).filter(function (s) { return s.length > 0; });
+ }
+
+ // sleep 寮傛绛夊緟锛堝彂閫佽妭濂忔帶鍒讹級
+ function sleep(ms) {
+ return new Promise(function (resolve) { setTimeout(resolve, ms); });
+ }
+
/* ---------------- 瀵煎嚭 ---------------- */
var Core = {
ADMIN_BASE: ADMIN_BASE,
@@ -295,6 +307,8 @@
nowTime: nowTime,
fmtSize: fmtSize,
debounce: debounce,
+ splitBubbles: splitBubbles,
+ sleep: sleep,
copyText: copyText,
qs: qs,
prettyJson: prettyJson,
diff --git a/web/assets/js/project.js b/web/assets/js/project.js
index 5fbd372..b95edeb 100644
--- a/web/assets/js/project.js
+++ b/web/assets/js/project.js
@@ -37,6 +37,7 @@
detail: null, // project/info 鍝嶅簲
analysis: { file: null, result: null, running: false },
data: { tab: "advicers", advicerId: 0 },
+ activity: { list: [] },
drill: { sessionId: null, msgs: [], mission: "", busy: false },
wxMounted: false
};
@@ -46,6 +47,7 @@
projectDetail: { title: "椤圭洰璁剧疆", sub: "缂栬緫鍩虹淇℃伅涓庨」鐩淮搴︽ā鏉?, render: renderProjectDetail },
analysis: { title: "鏁版嵁鍒嗘瀽", sub: "涓婁紶閿€鍞笌瀹㈡埛鐨勮亰澶╄褰曪紝AI 鎻愬彇鍥涘ぇ缁村害", render: renderAnalysis, needProject: true },
data: { title: "鏁版嵁涓績", sub: "绠$悊閿€鍞?/ 鐗堟湰 / 鎶€宸?/ 椤圭洰璧勬枡 / 瀹㈡埛", render: renderData, needProject: true },
+ activity: { title: "娲诲姩绠$悊", sub: "鍙戝竷浜у搧娲诲姩锛孉I 鎸夊鎴风瓑绾э紙鐔熷/鎰忓悜/娌夌潯/闈炲鎴凤級鑷姩鎺ㄩ€侀個绾?, render: renderActivity, needProject: true },
drill: { title: "AI 婕旂粌", sub: "妯℃嫙閿€鍞笌瀹㈡埛瀵硅瘽锛岄獙璇佽瘽鏈晥鏋?, render: renderDrill, needProject: true },
wx: { title: "寰俊宸ヤ綔鍙?, sub: "鎵爜鐧诲綍寰俊 路 閫氳褰?路 鑱婂ぉ鍙戦€?路 AI 璇濇湳寤鸿", render: renderWx }
};
@@ -1067,19 +1069,30 @@
return;
}
box.innerHTML = '<div class="table-wrap"><table class="table"><thead><tr>' +
- "<th>ID</th><th>濮撳悕</th><th>鎬у埆</th><th>棰勭畻</th><th>鍋忓ソ</th><th>椤捐檻</th><th>閿€鍞?/th><th>鎿嶄綔</th></tr></thead><tbody>" +
+ "<th>ID</th><th>濮撳悕</th><th>瀹㈡埛绛夌骇</th><th>寰俊</th><th>棰勭畻</th><th>鍋忓ソ</th><th>閿€鍞?/th><th>鎿嶄綔</th></tr></thead><tbody>" +
list.map(function (c) {
var pi = c.personalInfo || {};
var cd = c.coreDemands || {};
+ var lv = c.clientLevel || "";
+ var lvDesc = CLIENT_LEVELS[lv] || "鏈瘎浼?;
+ var lvCls = CLIENT_LEVEL_CLS[lv] || "gray";
+ var lvTip = c.levelReason || "鏆傛棤璇勪及鐞嗙敱锛堟瘡鏅?0 鐐硅嚜鍔ㄨ瘎浼帮級";
+ if (!isZeroTime(c.levelUpdateAt)) lvTip += "\n鏇存柊锛? + Core.fmtTime(c.levelUpdateAt);
+ var wxCell = c.wxid
+ ? '<span class="mono fs-12">' + Core.esc(c.wxid) + "</span>"
+ : '<button class="btn ghost sm cli-bind" data-id="' + c.id + '" data-name="' + Core.esc(pi.name || "") + '">缁戝畾寰俊</button>';
return "<tr>" +
'<td class="mono fs-12">' + Core.esc(String(c.id).slice(-8)) + "</td>" +
- '<td><span class="name">' + Core.esc(pi.name || "-") + "</span></td>" +
- "<td>" + Core.esc(pi.gender || "-") + "</td>" +
+ '<td><span class="name">' + Core.esc(pi.name || "-") + "</span>" +
+ (pi.gender ? '<span class="fs-12 c-dim" style="margin-left:6px">' + Core.esc(pi.gender) + "</span>" : "") + "</td>" +
+ '<td><span class="badge ' + lvCls + '" title="' + Core.esc(lvTip) + '">' + Core.esc(lvDesc) + "</span></td>" +
+ "<td>" + wxCell + "</td>" +
'<td class="fs-12">' + Core.esc(cd.totalBudget || "-") + "</td>" +
'<td class="fs-12 cell-clip">' + Core.esc(cd.preferredLayout || "-") + "</td>" +
- '<td><span class="badge gray">' + ((c.concerns || []).length) + " 椤?/span></td>" +
'<td class="mono">#' + c.advicerId + "</td>" +
'<td class="ops"><button class="btn ghost sm cli-edit" data-id="' + c.id + '">缂栬緫</button>' +
+ '<button class="btn ghost sm cli-eval" data-id="' + c.id + '" data-name="' + Core.esc(pi.name || "") + '">璇勪及</button>' +
+ (c.wxid ? '<button class="btn ghost sm cli-iterate" data-id="' + c.id + '" data-name="' + Core.esc(pi.name || "") + '">杩唬</button>' : "") +
'<button class="btn danger sm cli-del" data-id="' + c.id + '">鍒犻櫎</button></td></tr>';
}).join("") + "</tbody></table></div>";
$$(".cli-edit").forEach(function (b) {
@@ -1098,11 +1111,89 @@
});
};
});
+ $$(".cli-bind").forEach(function (b) {
+ b.onclick = function () { bindClientDialog(b.dataset.id, b.dataset.name); };
+ });
+ $$(".cli-eval").forEach(function (b) {
+ b.onclick = function () { evaluateClient(b.dataset.id, b.dataset.name, b); };
+ });
+ $$(".cli-iterate").forEach(function (b) {
+ b.onclick = function () { iterateClient(b.dataset.id, b.dataset.name, b); };
+ });
}).catch(function (e) {
box.innerHTML = '<div class="empty"><div class="big">鈿?/div>' + Core.esc(e.message) + "</div>";
});
}
+ /* ---------- 瀹㈡埛绛夌骇甯搁噺 / 寰俊缁戝畾 / 绛夌骇璇勪及 ---------- */
+ var CLIENT_LEVELS = { regular: "鐔熷", intent: "鎰忓悜瀹㈡埛", sleeping: "娌夌潯瀹㈡埛", non: "闈炲鎴? };
+ var CLIENT_LEVEL_CLS = { regular: "purple", intent: "green", sleeping: "yellow", non: "gray" };
+
+ function isZeroTime(t) {
+ return !t || String(t).indexOf("0001-01-01") === 0;
+ }
+
+ function bindClientDialog(id, name) {
+ var m = Core.modal({
+ title: "缁戝畾寰俊" + (name ? " 路 " + name : ""),
+ body:
+ '<div class="field"><label class="label">瀹㈡埛寰俊 wxid<span class="req">*</span></label>' +
+ '<input id="cb-wxid" class="input mono" placeholder="濡傦細wxid_xxxxxxxx"></div>' +
+ '<div class="field"><label class="label">鐧诲綍瀹炰緥 appId锛堝彲閫夛級</label>' +
+ '<input id="cb-appid" class="input mono" placeholder="澶氬疄渚嬫椂鎸囧畾锛岀暀绌?榛樿瀹炰緥"></div>' +
+ '<div class="fs-12 c-dim" style="line-height:1.9">缁戝畾鍚庤寰俊鍙戞潵鐨勬秷鎭嚜鍔ㄥ叧鑱旀湰瀹㈡埛锛涘鎴风敾鍍忚凯浠c€佹瘡鏅氱瓑绾ц瘎浼颁笌 AI 涓诲姩瀵硅瘽鍧囦緷璧栫粦瀹氬叧绯汇€?/div>',
+ footer: '<button class="btn ghost mf-cancel">鍙栨秷</button><button class="btn primary mf-save">缁戝畾</button>'
+ });
+ $(".mf-cancel", m.root).onclick = m.close;
+ $(".mf-save", m.root).onclick = function () {
+ var wxid = $("#cb-wxid", m.root).value.trim();
+ if (!wxid) { Core.toast("璇疯緭鍏?wxid", "err"); return; }
+ Core.api("client/bind", { id: id, wxid: wxid, appId: $("#cb-appid", m.root).value.trim() })
+ .then(function () { m.close(); Core.toast("缁戝畾鎴愬姛", "ok"); loadClients(); })
+ .catch(function (e) { Core.toast(e.message, "err"); });
+ };
+ }
+
+ function evaluateClient(id, name, btn) {
+ Core.confirm("瀵瑰鎴枫€? + (name || "鏈懡鍚?) + "銆嶆墽琛岀瓑绾ц瘎浼帮紵锛圓I 灏嗗垎鏋愯繎鏈熻亰澶╄褰曞垽瀹氱瓑绾э級").then(function (ok) {
+ if (!ok) return;
+ var oldText = btn ? btn.textContent : "";
+ if (btn) { btn.textContent = "璇勪及涓€?; btn.disabled = true; }
+ Core.api("client/evaluate", { clientId: id, force: true })
+ .then(function (res) {
+ if (btn) { btn.textContent = oldText; btn.disabled = false; }
+ if (res && res.async) { Core.toast(res.msg || "璇勪及浠诲姟宸插惎鍔紝璇风◢鍚庡埛鏂?, "ok"); return; }
+ var d = res && res.details && res.details[0];
+ Core.toast(d ? "璇勪及瀹屾垚锛? + (d.levelDesc || d.level) + " 路 " + (d.reason || "") : "璇勪及瀹屾垚", "ok");
+ loadClients();
+ })
+ .catch(function (e) {
+ if (btn) { btn.textContent = oldText; btn.disabled = false; }
+ Core.toast(e.message, "err");
+ });
+ });
+ }
+
+ function iterateClient(id, name, btn) {
+ Core.confirm("瀵瑰鎴枫€? + (name || "鏈懡鍚?) + "銆嶇珛鍗虫墽琛岀敾鍍忚凯浠o紵锛圓I 灏嗗啀娆″垎鏋愯繎鏈熻亰澶╄褰曪紝鑷姩鏂板缓/瀹屽杽瀹㈡埛鐢诲儚锛?).then(function (ok) {
+ if (!ok) return;
+ var oldText = btn ? btn.textContent : "";
+ if (btn) { btn.textContent = "杩唬涓€?; btn.disabled = true; }
+ Core.api("smart/iterate/run", { clientId: id, force: true })
+ .then(function (res) {
+ if (btn) { btn.textContent = oldText; btn.disabled = false; }
+ res = res || {};
+ var detail = (res.details || [])[0] || "";
+ Core.toast("杩唬瀹屾垚锛氭壂鎻?" + (res.scanned || 0) + " 鏉℃湭鍒嗘瀽娑堟伅路" + (detail || "鐢诲儚鏃犳柊澧炰俊鎭?), "ok");
+ loadClients();
+ })
+ .catch(function (e) {
+ if (btn) { btn.textContent = oldText; btn.disabled = false; }
+ Core.toast(e.message, "err");
+ });
+ });
+ }
+
function clientForm(item) {
var d = item || {};
var isEdit = !!d.id;
@@ -1120,8 +1211,9 @@
'<div class="field"><label class="label">鎬у埆</label><input id="cf-gender" class="input" value="' + Core.esc(pi.gender || "") + '" placeholder="鐢?濂?></div>' +
'<div class="field"><label class="label">鏉ユ簮鍦?/label><input id="cf-loc" class="input" value="' + Core.esc(pi.location || "") + '"></div>' +
"</div>" +
- '<div class="grid cols-2">' +
+ '<div class="grid cols-3">' +
'<div class="field"><label class="label">瀹跺涵缁撴瀯</label><input id="cf-family" class="input" value="' + Core.esc(pi.familyOrganize || "") + '" placeholder="濡傦細澶+1瀛?></div>' +
+ '<div class="field"><label class="label">鐢熸棩锛堢敤浜庣敓鏃ュ叧鎬€锛?/label><input id="cf-birthday" class="input" type="date" value="' + Core.esc(pi.birthday || "") + '"></div>' +
'<div class="field" style="display:flex;align-items:flex-end"><label class="checkbox-row"><input type="checkbox" id="cf-first" ' + (pi.isFirstHome ? "checked" : "") + "> 棣栨缃笟</label></div>" +
"</div>" +
'<div class="grid cols-3">' +
@@ -1157,7 +1249,8 @@
gender: $("#cf-gender", m.root).value.trim(),
location: $("#cf-loc", m.root).value.trim(),
isFirstHome: $("#cf-first", m.root).checked,
- familyOrganize: $("#cf-family", m.root).value.trim()
+ familyOrganize: $("#cf-family", m.root).value.trim(),
+ birthday: $("#cf-birthday", m.root).value
},
purchasePurpose: {
primaryPurpose: $("#cf-p1", m.root).value.trim(),
@@ -1179,7 +1272,176 @@
};
}
- /* ==================== 瑙嗗浘浜旓細AI 婕旂粌 ==================== */
+ /* ==================== 瑙嗗浘浜旓細娲诲姩绠$悊 ==================== */
+ var ACT_STATUS = { 0: ["gray", "鑽夌"], 1: ["green", "杩涜涓?], 2: ["yellow", "宸插仠姝?] };
+ var ACT_LEVEL_OPTS = [["regular", "鐔熷"], ["intent", "鎰忓悜瀹㈡埛"], ["sleeping", "娌夌潯瀹㈡埛"], ["non", "闈炲鎴?]];
+
+ function renderActivity() {
+ if (!state.project || !state.project.projectId) { switchView("projects"); return; }
+ $("#view-activity").innerHTML =
+ '<div class="card">' +
+ '<div class="card-title"><span class="bar"></span>浜у搧娲诲姩<span class="spacer"></span>' +
+ '<button class="btn primary sm" id="act-add">+ 鍙戝竷娲诲姩</button></div>' +
+ '<div class="card-sub">娲诲姩鍒版椂闂磋寖鍥磋嚜鍔ㄧ敓鏁堬紱AI 涓诲姩瀵硅瘽涓庢椿鍔ㄦ帹閫佹寜鐩爣瀹㈡埛绛夌骇绛涢€夊璞★紙涓嶉€夌瓑绾?鍏ㄩ儴瀹㈡埛锛夈€?/div>' +
+ '<div id="act-list"></div>' +
+ "</div>";
+ $("#act-add").onclick = function () { activityForm(null); };
+ loadActivity();
+ }
+
+ function loadActivity() {
+ var box = $("#act-list");
+ if (!box) return;
+ box.innerHTML = '<div class="empty"><span class="spin"></span></div>';
+ Core.api("activity/list", { projectId: state.project.projectId }).then(function (list) {
+ list = list || [];
+ state.activity.list = list;
+ if (!list.length) {
+ box.innerHTML = '<div class="empty"><div class="big">鉂?/div>鏆傛棤娲诲姩锛岀偣鍑诲彸涓婅鍙戝竷绗竴涓椿鍔?/div>';
+ return;
+ }
+ box.innerHTML = '<div class="table-wrap"><table class="table"><thead><tr>' +
+ "<th>娲诲姩鍚嶇О</th><th>鏃堕棿鑼冨洿</th><th>鐩爣绛夌骇</th><th>閿€鍞?/th><th>鐘舵€?/th><th>鎿嶄綔</th></tr></thead><tbody>" +
+ list.map(function (a) {
+ var st = ACT_STATUS[a.status] || ACT_STATUS[0];
+ var levels = (a.targetLevels && a.targetLevels.length)
+ ? a.targetLevels.map(function (l) { return CLIENT_LEVELS[l] || l; }).join(" / ")
+ : "鍏ㄩ儴瀹㈡埛";
+ return "<tr>" +
+ '<td><span class="name">' + Core.esc(a.name || "-") + "</span>" +
+ (a.content ? '<div class="fs-12 c-dim cell-clip" title="' + Core.esc(a.content) + '">' + Core.esc(String(a.content).slice(0, 36)) + "</div>" : "") + "</td>" +
+ '<td class="fs-12">' + Core.fmtTime(a.startAt) + "<br>~ " + (isZeroTime(a.endAt) ? "闀挎湡鏈夋晥" : Core.fmtTime(a.endAt)) + "</td>" +
+ '<td class="fs-12">' + Core.esc(levels) + "</td>" +
+ '<td class="mono">' + (a.advicerId ? "#" + a.advicerId : "鍏ㄥ憳") + "</td>" +
+ '<td><span class="badge ' + st[0] + '">' + st[1] + "</span></td>" +
+ '<td class="ops"><button class="btn ghost sm act-edit" data-id="' + a.id + '">缂栬緫</button>' +
+ '<button class="btn ghost sm act-push" data-id="' + a.id + '">鎺ㄩ€?/button>' +
+ (a.status === 1
+ ? '<button class="btn ghost sm act-stop" data-id="' + a.id + '">鍋滄</button>'
+ : '<button class="btn ghost sm act-start" data-id="' + a.id + '">鍚敤</button>') +
+ '<button class="btn danger sm act-del" data-id="' + a.id + '">鍒犻櫎</button></td></tr>';
+ }).join("") + "</tbody></table></div>";
+ $$(".act-edit").forEach(function (b) {
+ b.onclick = function () {
+ var item = list.filter(function (x) { return String(x.id) === b.dataset.id; })[0];
+ activityForm(item);
+ };
+ });
+ $$(".act-push").forEach(function (b) {
+ b.onclick = function () {
+ var item = list.filter(function (x) { return String(x.id) === b.dataset.id; })[0];
+ var actName = (item && item.name) || "璇ユ椿鍔?;
+ Core.confirm("绔嬪嵆鍚戙€? + actName + "銆嶇殑鐩爣瀹㈡埛閫愪汉鎺ㄩ€佸井淇℃秷鎭紵锛堢啛瀹?闈炲鎴疯嚜鍔ㄨ烦杩囷紝AI 閫愪汉鐢熸垚璇濇湳锛?).then(function (ok) {
+ if (!ok) return;
+ if (b.disabled) return;
+ var oldText = b.textContent;
+ b.textContent = "鎺ㄩ€佷腑鈥?;
+ b.disabled = true;
+ Core.api("smart/activity/push", { id: b.dataset.id })
+ .then(function (res) {
+ b.textContent = oldText;
+ b.disabled = false;
+ res = res || {};
+ Core.toast("鎺ㄩ€佸畬鎴愶細鎴愬姛 " + (res.activity || 0) + " 浜?路 璺宠繃 " + (res.skipped || 0) + " 浜?路 澶辫触 " + (res.failed || 0) + " 浜?, "ok");
+ loadActivity();
+ })
+ .catch(function (e) {
+ b.textContent = oldText;
+ b.disabled = false;
+ Core.toast(e.message, "err");
+ });
+ });
+ };
+ });
+ $$(".act-del").forEach(function (b) {
+ b.onclick = function () {
+ Core.confirm("纭畾鍒犻櫎璇ユ椿鍔紵").then(function (ok) {
+ if (!ok) return;
+ Core.api("activity/del", { id: b.dataset.id })
+ .then(function () { Core.toast("宸插垹闄?, "ok"); loadActivity(); })
+ .catch(function (e) { Core.toast(e.message, "err"); });
+ });
+ };
+ });
+ $$(".act-start").forEach(function (b) {
+ b.onclick = function () { setActivityStatus(b.dataset.id, 1); };
+ });
+ $$(".act-stop").forEach(function (b) {
+ b.onclick = function () { setActivityStatus(b.dataset.id, 2); };
+ });
+ }).catch(function (e) {
+ box.innerHTML = '<div class="empty"><div class="big">鈿?/div>' + Core.esc(e.message) + "</div>";
+ });
+ }
+
+ function setActivityStatus(id, status) {
+ Core.api("activity/update", { id: id, status: status })
+ .then(function () { Core.toast(status === 2 ? "娲诲姩宸插仠姝? : "娲诲姩宸插惎鐢?, "ok"); loadActivity(); })
+ .catch(function (e) { Core.toast(e.message, "err"); });
+ }
+
+ function activityForm(item) {
+ var d = item || {};
+ var isEdit = !!d.id;
+ var startVal = isZeroTime(d.startAt) ? "" : Core.fmtTime(d.startAt).replace(" ", "T");
+ var endVal = isZeroTime(d.endAt) ? "" : Core.fmtTime(d.endAt).replace(" ", "T");
+ var selected = d.targetLevels || [];
+ var levelBoxes = ACT_LEVEL_OPTS.map(function (o) {
+ return '<label class="checkbox-row"><input type="checkbox" class="af-level" value="' + o[0] + '"' +
+ (selected.indexOf(o[0]) >= 0 ? " checked" : "") + "> " + o[1] + "</label>";
+ }).join("");
+ var m = Core.modal({
+ title: isEdit ? "缂栬緫娲诲姩" : "鍙戝竷娲诲姩",
+ width: "wide",
+ body:
+ '<div class="grid cols-2">' +
+ '<div class="field"><label class="label">娲诲姩鍚嶇О<span class="req">*</span></label><input id="af-name" class="input" value="' + Core.esc(d.name || "") + '" placeholder="濡傦細閲戠璐埧鑺?9 鎶樼壒鎯?></div>' +
+ '<div class="field"><label class="label">褰掑睘閿€鍞?/label><select id="af-advicer" class="select"></select></div>' +
+ "</div>" +
+ '<div class="grid cols-2">' +
+ '<div class="field"><label class="label">寮€濮嬫椂闂?span class="req">*</span></label><input id="af-start" class="input" type="datetime-local" value="' + startVal + '"></div>' +
+ '<div class="field"><label class="label">缁撴潫鏃堕棿锛堢暀绌?闀挎湡鏈夋晥锛?/label><input id="af-end" class="input" type="datetime-local" value="' + endVal + '"></div>' +
+ "</div>" +
+ '<div class="field"><label class="label">鐩爣瀹㈡埛绛夌骇锛堜笉閫?鍏ㄩ儴瀹㈡埛锛?/label><div class="row" style="flex-wrap:wrap;gap:14px 20px">' + levelBoxes + "</div></div>" +
+ '<div class="field"><label class="label">娲诲姩鍐呭<span class="req">*</span></label><textarea id="af-content" class="textarea" style="min-height:96px" placeholder="娲诲姩鍗栫偣 / 浼樻儬鏀跨瓥 / 閭€绾﹁瘽鏈礌鏉愶紝AI 涓诲姩瀵硅瘽涓庢帹閫佹椂灏嗗洿缁曡鍐呭灞曞紑">' + Core.esc(d.content || "") + "</textarea></div>" +
+ '<div class="field"><label class="label">鐘舵€?/label><select id="af-status" class="select">' +
+ '<option value="1"' + (d.status === 1 ? " selected" : "") + ">杩涜涓紙鍒版椂闂磋寖鍥磋嚜鍔ㄧ敓鏁堬級</option>" +
+ '<option value="0"' + (d.status !== 1 && d.status !== 2 ? " selected" : "") + ">鑽夌锛堜繚瀛樹絾鏆備笉鐢熸晥锛?/option>" +
+ '<option value="2"' + (d.status === 2 ? " selected" : "") + ">宸插仠姝?/option>" +
+ "</select></div>",
+ footer: '<button class="btn ghost mf-cancel">鍙栨秷</button><button class="btn primary mf-save">淇濆瓨</button>'
+ });
+ loadAdvicerOptions($("#af-advicer", m.root), d.advicerId || 0).then(function () {
+ var sel = $("#af-advicer", m.root);
+ if (sel && sel.options.length) sel.options[0].textContent = "鍏ㄥ憳鍏变韩锛堜笉闄愰攢鍞級";
+ });
+ $(".mf-cancel", m.root).onclick = m.close;
+ $(".mf-save", m.root).onclick = function () {
+ var name = $("#af-name", m.root).value.trim();
+ if (!name) { Core.toast("璇峰~鍐欐椿鍔ㄥ悕绉?, "err"); return; }
+ var start = $("#af-start", m.root).value;
+ if (!start) { Core.toast("璇烽€夋嫨寮€濮嬫椂闂?, "err"); return; }
+ var content = $("#af-content", m.root).value.trim();
+ if (!content) { Core.toast("璇峰~鍐欐椿鍔ㄥ唴瀹?, "err"); return; }
+ var levels = $$(".af-level", m.root).filter(function (c) { return c.checked; }).map(function (c) { return c.value; });
+ var payload = {
+ projectId: state.project.projectId,
+ advicerId: Number($("#af-advicer", m.root).value) || 0,
+ name: name,
+ content: content,
+ startAt: start.replace("T", " "),
+ endAt: $("#af-end", m.root).value ? $("#af-end", m.root).value.replace("T", " ") : "",
+ targetLevels: levels,
+ status: Number($("#af-status", m.root).value)
+ };
+ if (isEdit) payload.id = d.id;
+ Core.api(isEdit ? "activity/update" : "activity/add", payload)
+ .then(function () { m.close(); Core.toast("淇濆瓨鎴愬姛", "ok"); loadActivity(); })
+ .catch(function (e) { Core.toast(e.message, "err"); });
+ };
+ }
+
+ /* ==================== 瑙嗗浘鍏細AI 婕旂粌 ==================== */
function renderDrill() {
if (!state.project || !state.project.projectId) { switchView("projects"); return; }
var dr = state.drill;
@@ -1293,12 +1555,17 @@
dr.busy = false;
assistant = assistant || {};
var result = assistant.result || (typeof assistant === "string" ? assistant : "");
- dr.msgs.push({
- role: "ai",
- text: result || "锛圓I 鏈繑鍥炲唴瀹癸級",
- status: assistant.mission_status || "",
- desc: assistant.mission_complete_desc || "",
- time: Core.nowTime()
+ var bubbles = Core.splitBubbles(result);
+ if (!bubbles.length) bubbles = ["锛圓I 鏈繑鍥炲唴瀹癸級"];
+ bubbles.forEach(function (b, i) {
+ var isLast = i === bubbles.length - 1;
+ dr.msgs.push({
+ role: "ai",
+ text: b,
+ status: isLast ? (assistant.mission_status || "") : "",
+ desc: isLast ? (assistant.mission_complete_desc || "") : "",
+ time: Core.nowTime()
+ });
});
renderDrillMsgs();
})
@@ -1339,7 +1606,7 @@
box.scrollTop = box.scrollHeight;
}
- /* ==================== 瑙嗗浘鍏細寰俊宸ヤ綔鍙?==================== */
+ /* ==================== 瑙嗗浘涓冿細寰俊宸ヤ綔鍙?==================== */
function renderWx() {
var el = $("#view-wx");
el.innerHTML = "";
diff --git a/web/assets/js/wx.js b/web/assets/js/wx.js
index 3a8a120..36bdb62 100644
--- a/web/assets/js/wx.js
+++ b/web/assets/js/wx.js
@@ -163,10 +163,12 @@
"</div>" +
'<div class="row mt-1">' +
'<button class="btn ghost sm" id="wxOnlineBtn">妫€娴嬪湪绾?/button>' +
+ '<button class="btn ghost sm" id="wxCallbackBtn" title="閰嶇疆涓婃父娑堟伅鍥炶皟锛堟帴鏀跺鎴锋秷鎭叆搴擄級">鈿?璁剧疆鍥炶皟</button>' +
'<button class="btn danger sm" id="wxLogoutBtn">閫€鍑虹櫥褰?/button>' +
"</div>" +
"</div>";
$("#wxOnlineBtn", box).onclick = function () { checkOnline().then(function () { renderLoginCard(); }); };
+ $("#wxCallbackBtn", box).onclick = openCallbackDialog;
$("#wxLogoutBtn", box).onclick = doLogout;
}
@@ -266,6 +268,36 @@
});
}
+ /* ---------- 娑堟伅鍥炶皟璁剧疆锛堢敤鎴锋秷鎭叆搴撯啋鐢诲儚杩唬/绛夌骇璇勪及/涓诲姩瀵硅瘽锛?---------- */
+ function openCallbackDialog() {
+ Core.loading(true, "鑾峰彇鍥炶皟閰嶇疆鈥?);
+ Core.api("wx/callback/info", {}).then(function (info) {
+ Core.loading(false);
+ var suggested = (info && info.callbackUrl) || (location.origin + "/api/v1/advicer/wx/callback");
+ var m = Core.modal({
+ title: "璁剧疆娑堟伅鍥炶皟",
+ body:
+ '<div class="field"><label class="label">鍥炶皟鍦板潃<span class="req">*</span></label>' +
+ '<input id="wcb-url" class="input mono" value="' + Core.esc(suggested) + '"></div>' +
+ '<div class="fs-12 c-dim" style="line-height:1.9">' +
+ "路 闇€涓哄叕缃戝彲璁块棶鍦板潃锛屽井淇″崗璁湇鍔′細鎶婂鎴蜂笌閿€鍞殑娑堟伅鎺ㄩ€佸埌璇ュ湴鍧€锛?br>" +
+ "路 閰嶇疆鎴愬姛鍚庢秷鎭嚜鍔ㄥ叆搴擄紝鐢ㄤ簬瀹㈡埛鐢诲儚杩唬銆佹瘡鏅氱瓑绾ц瘎浼颁笌 AI 涓诲姩瀵硅瘽锛?br>" +
+ "路 鑻ユ湇鍔″櫒閮ㄧ讲鍦ㄥ弽鍚戜唬鐞嗗悗锛岃鎶婂煙鍚嶆浛鎹负瀹為檯鍏綉璁块棶鍦板潃銆? +
+ "</div>",
+ footer: '<button class="btn ghost mf-cancel">鍙栨秷</button><button class="btn primary mf-save">淇濆瓨骞剁敓鏁?/button>'
+ });
+ $(".mf-cancel", m.root).onclick = m.close;
+ $(".mf-save", m.root).onclick = function () {
+ var url = $("#wcb-url", m.root).value.trim();
+ if (!url) { Core.toast("璇疯緭鍏ュ洖璋冨湴鍧€", "err"); return; }
+ Core.loading(true, "姝e湪璁剧疆鍥炶皟鈥?);
+ Core.api("wx/callback/set", { appId: (state.app && state.app.appId) || "", callbackUrl: url })
+ .then(function () { Core.loading(false); m.close(); Core.toast("鍥炶皟鍦板潃宸茶缃苟鐢熸晥", "ok"); })
+ .catch(function (e) { Core.loading(false); Core.toast("璁剧疆澶辫触锛? + e.message, "err"); });
+ };
+ }).catch(function (e) { Core.loading(false); Core.toast(e.message, "err"); });
+ }
+
/* ==================== 閫氳褰?==================== */
function loadContacts() {
if (!state.app || !state.app.appId) { Core.toast("璇峰厛鐧诲綍寰俊", "err"); return; }
@@ -418,10 +450,12 @@
if (!state.active) { Core.toast("璇峰厛閫夋嫨鑱旂郴浜?, "err"); return; }
var text = $("#wxText", state.container).value.trim();
if (!text) { Core.toast("璇疯緭鍏ユ秷鎭唴瀹?, "err"); return; }
- sendText(state.active.wxid, text).then(function () { $("#wxText", state.container).value = ""; });
+ $("#wxText", state.container).value = "";
+ sendBubbles(state.active.wxid, text);
}
- function sendText(wxid, text) {
+ function sendText(wxid, text, opts) {
+ opts = opts || {};
var hint = $("#wxSendHint", state.container);
if (hint) hint.textContent = "鍙戦€佷腑鈥?;
return Core.wxApi("message/postText", { appId: state.app.appId, toWxid: wxid, content: text, ats: [] })
@@ -430,7 +464,7 @@
state.msgs[wxid].push({ role: "self", text: text, time: Core.nowTime() });
if (hint) hint.textContent = "宸插彂閫?;
renderChat();
- Core.toast("宸插彂閫?, "ok");
+ if (!opts.quiet) Core.toast("宸插彂閫?, "ok");
})
.catch(function (e) {
if (state.msgs[wxid] === undefined) state.msgs[wxid] = [];
@@ -441,6 +475,30 @@
});
}
+ // sendBubbles 妯℃嫙鐪熶汉鑺傚锛氭妸鏂囨湰鎸夋崲琛屾媶鎴愬鏉$煭娑堟伅锛岄€愭潯鍙戦€侊紙闅忔満闂撮殧锛?+ function sendBubbles(wxid, text) {
+ var bubbles = Core.splitBubbles(text);
+ if (!bubbles.length) return Promise.resolve();
+ var hint = $("#wxSendHint", state.container);
+ var i = 0;
+ function next() {
+ if (i >= bubbles.length) {
+ if (hint) hint.textContent = bubbles.length > 1 ? "宸插彂閫?" + bubbles.length + " 鏉? : "宸插彂閫?;
+ return Promise.resolve();
+ }
+ var b = bubbles[i++];
+ if (hint) hint.textContent = "鍙戦€佷腑鈥? + (bubbles.length > 1 ? "锛? + i + "/" + bubbles.length + "锛? : "");
+ return sendText(wxid, b, { quiet: true }).then(function () {
+ if (i >= bubbles.length) return;
+ // 鐪熶汉杩炲彂娑堟伅鐨勮嚜鐒堕棿闅旓細绾?0.9s ~ 2.5s
+ return Core.sleep(900 + Math.random() * 1600).then(next);
+ });
+ }
+ return next().then(function () {
+ Core.toast(bubbles.length > 1 ? "宸插彂閫侊紙" + bubbles.length + " 鏉℃秷鎭級" : "宸插彂閫?, "ok");
+ });
+ }
+
/* ==================== AI 鍔╂墜 ==================== */
function openAiConfig() {
var proj = Core.store(Core.KEYS.project);
@@ -554,7 +612,9 @@
wrap.innerHTML =
'<div class="ai-tip">' +
'<div class="ai-head">鉁?AI 璇濇湳寤鸿 ' + badge + '<span class="spacer"></span><span class="fs-12 c-dim">' + Core.esc(tip.missionDesc || "") + "</span></div>" +
- '<div class="ai-body">' + Core.esc(tip.text) + "</div>" +
+ '<div class="ai-body">' + Core.splitBubbles(tip.text).map(function (b) {
+ return '<div class="msg-bubble" style="border-color:rgba(45,224,255,.35);margin:0 0 6px">' + Core.esc(b) + "</div>";
+ }).join("") + "</div>" +
'<div class="ai-foot">' +
'<button class="btn success sm" id="aiFill">濉叆杈撳叆妗?/button>' +
(state.active ? '<button class="btn ghost sm" id="aiSendNow">鐩存帴鍙戦€?/button>' : "") +
@@ -568,10 +628,23 @@
var sendNow = $("#aiSendNow", wrap);
if (sendNow) sendNow.onclick = function () {
if (!state.active) return;
- sendText(state.active.wxid, tip.text).then(function () { clearAiTip(); });
+ if (sendNow.disabled) return;
+ sendNow.disabled = true;
+ sendNow.textContent = "鍙戦€佷腑鈥?;
+ sendBubbles(state.active.wxid, tip.text).then(function () {
+ sendNow.disabled = false;
+ sendNow.textContent = "鐩存帴鍙戦€?;
+ clearAiTip();
+ });
};
$("#aiClose", wrap).onclick = function () {
- if (state.active) state.msgs[state.active.wxid].push({ role: "ai", text: tip.text, time: Core.nowTime() });
+ if (state.active) {
+ var bs = Core.splitBubbles(tip.text);
+ if (!bs.length) bs = ["锛堢┖娑堟伅锛?];
+ bs.forEach(function (b) {
+ state.msgs[state.active.wxid].push({ role: "ai", text: b, time: Core.nowTime() });
+ });
+ }
clearAiTip();
renderChat();
};
diff --git a/web/project.html b/web/project.html
index dc1f797..c831910 100644
--- a/web/project.html
+++ b/web/project.html
@@ -43,6 +43,7 @@
<div class="nav-item" data-view="projects"><span class="ico">鈻?/span>椤圭洰绠$悊</div>
<div class="nav-item need-project" data-view="analysis"><span class="ico">鉁?/span>鏁版嵁鍒嗘瀽</div>
<div class="nav-item need-project" data-view="data"><span class="ico">鈼?/span>鏁版嵁涓績</div>
+ <div class="nav-item need-project" data-view="activity"><span class="ico">鉂?/span>娲诲姩绠$悊</div>
<div class="nav-item need-project" data-view="drill"><span class="ico">鉄?/span>AI 婕旂粌</div>
</div>
<div class="nav-group">
@@ -76,6 +77,7 @@
<section id="view-projectDetail" class="view hidden"></section>
<section id="view-analysis" class="view hidden"></section>
<section id="view-data" class="view hidden"></section>
+ <section id="view-activity" class="view hidden"></section>
<section id="view-drill" class="view hidden"></section>
<section id="view-wx" class="view hidden"></section>
</div>