ai_scheduler/tmp_review_diff.txt

154 KiB
Raw Permalink Blame History

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>