thread-master/internal/worker/job/analyze_copy_mission.go

187 lines
5.9 KiB
Go

package job
import (
"context"
"fmt"
"strings"
app "haixun-backend/internal/library/errors"
"haixun-backend/internal/library/errors/code"
libviral "haixun-backend/internal/library/viral"
domai "haixun-backend/internal/model/ai/domain/usecase"
aiusecase "haixun-backend/internal/model/ai/usecase"
missionentity "haixun-backend/internal/model/copy_mission/domain/entity"
missiondomain "haixun-backend/internal/model/copy_mission/domain/usecase"
jobdom "haixun-backend/internal/model/job/domain/usecase"
personadomain "haixun-backend/internal/model/persona/domain/usecase"
placementusecase "haixun-backend/internal/model/placement/usecase"
threadsaccountdomain "haixun-backend/internal/model/threads_account/domain/usecase"
)
type AnalyzeCopyMissionDeps struct {
Jobs jobdom.UseCase
CopyMission missiondomain.UseCase
Persona personadomain.UseCase
ThreadsAccount threadsaccountdomain.UseCase
Placement placementusecase.UseCase
AI aiusecase.UseCase
}
func RegisterAnalyzeCopyMissionHandler(runner *Runner, deps AnalyzeCopyMissionDeps) {
if runner == nil {
return
}
runner.RegisterStepHandler("copy_mission_map", func(ctx context.Context, step StepContext) error {
return runAnalyzeCopyMission(ctx, step, deps)
})
}
func runAnalyzeCopyMission(ctx context.Context, step StepContext, deps AnalyzeCopyMissionDeps) error {
payload := step.Run.Payload
tenantID, ownerUID := runActorFromPayload(payload, step.Run)
personaID := stringField(payload, "persona_id")
missionID := stringField(payload, "copy_mission_id")
if tenantID == "" || ownerUID == "" || personaID == "" || missionID == "" {
return fmt.Errorf("analyze-copy-mission payload missing tenant_id, owner_uid, persona_id, or copy_mission_id")
}
mission, err := deps.CopyMission.Get(ctx, tenantID, ownerUID, personaID, missionID)
if err != nil {
return err
}
persona, err := deps.Persona.Get(ctx, tenantID, ownerUID, personaID)
if err != nil {
return err
}
updateProgress := func(summary string, percentage int) {
_ = step.Heartbeat(ctx)
_, _ = deps.Jobs.UpdateProgress(ctx, jobdom.UpdateProgressRequest{
JobID: step.JobID,
WorkerID: step.WorkerID,
Phase: "copy_mission_map",
Summary: summary,
Percentage: percentage,
})
}
updateProgress("產生拷貝任務研究地圖…", 15)
credential, err := deps.ThreadsAccount.ResolveMemberAiCredential(ctx, tenantID, ownerUID)
if err != nil {
return err
}
providerID, err := aiusecase.MapWorkerProvider(credential.Provider)
if err != nil {
return err
}
result, err := deps.AI.GenerateText(ctx, domai.GenerateRequest{
Provider: providerID,
Model: credential.Model,
Credential: domai.Credential{
APIKey: credential.APIKey,
},
System: libviral.BuildMissionResearchMapSystemPrompt(),
Messages: []domai.Message{
{
Role: "user",
Content: libviral.BuildMissionResearchMapUserPrompt(libviral.CopyResearchMapInput{
Label: mission.Label,
SeedQuery: mission.SeedQuery,
Brief: mission.Brief,
Persona: persona.Persona,
StyleBenchmark: persona.StyleBenchmark,
PersonaAudienceSummary: persona.CopyResearchMap.AudienceSummary,
PersonaContentGoal: persona.CopyResearchMap.ContentGoal,
PersonaQuestions: append([]string(nil), persona.CopyResearchMap.Questions...),
PersonaPillars: append([]string(nil), persona.CopyResearchMap.Pillars...),
}),
},
},
})
if err != nil {
return err
}
parsed, err := libviral.ParseMissionResearchMapOutput(result.Text)
if err != nil {
return app.For(code.AI).SvcThirdParty("拷貝任務研究地圖 LLM 回傳無法解析:" + err.Error())
}
entityTags := make([]missionentity.SuggestedTag, 0, len(parsed.SuggestedTags))
for _, tag := range parsed.SuggestedTags {
entityTags = append(entityTags, missionentity.SuggestedTag{
Tag: tag.Tag,
Reason: tag.Reason,
SearchIntent: tag.SearchIntent,
SearchType: tag.SearchType,
})
}
researchMap := missionentity.ResearchMap{
AudienceSummary: parsed.AudienceSummary,
ContentGoal: parsed.ContentGoal,
Questions: parsed.Questions,
Pillars: parsed.Pillars,
Exclusions: parsed.Exclusions,
BenchmarkNotes: parsed.BenchmarkNotes,
}
entityTags = make([]missionentity.SuggestedTag, 0, len(parsed.SuggestedTags))
for _, tag := range parsed.SuggestedTags {
entityTags = append(entityTags, missionentity.SuggestedTag{
Tag: tag.Tag,
Reason: tag.Reason,
SearchIntent: tag.SearchIntent,
SearchType: tag.SearchType,
})
}
researchMap.SimilarAccounts = nil
researchMap.SuggestedTags = entityTags
selected := libviral.PickDefaultSelectedTags(parsed.SuggestedTags)
mapped := missionentity.StatusMapped
updateProgress("儲存研究地圖與預設標籤…", 85)
_, err = deps.CopyMission.Update(ctx, missiondomain.UpdateRequest{
TenantID: tenantID,
OwnerUID: ownerUID,
PersonaID: personaID,
MissionID: missionID,
Patch: missiondomain.MissionPatch{
ResearchMap: &researchMap,
SelectedTagsSet: true,
SelectedTags: selected,
Status: &mapped,
},
})
if err != nil {
return err
}
handoff := map[string]any{
"flow": "copy",
"persona_id": personaID,
"copy_mission_id": missionID,
"summary": fmt.Sprintf("研究地圖就緒,已預選 %d 個搜尋標籤", len(selected)),
"next_route": fmt.Sprintf("/matrix/missions/%s", missionID),
}
_, err = deps.Jobs.CompleteRun(ctx, jobdom.CompleteRunRequest{
JobID: step.JobID,
WorkerID: step.WorkerID,
Result: map[string]any{
"tag_count": len(entityTags),
"account_count": 0,
"selected_count": len(selected),
"handoff": handoff,
},
})
return err
}
func copyMissionIDFromPayload(payload map[string]any) string {
if id := stringField(payload, "copy_mission_id"); id != "" {
return id
}
return strings.TrimSpace(stringField(payload, "mission_id"))
}