thread-master/backend/internal/logic/copy_mission/inspire_copy_mission_logic.go

155 lines
4.8 KiB
Go
Raw Normal View History

2026-06-26 08:37:04 +00:00
package copy_mission
import (
"context"
"strings"
app "haixun-backend/internal/library/errors"
"haixun-backend/internal/library/errors/code"
"haixun-backend/internal/library/placement"
"haixun-backend/internal/library/style8d"
libviral "haixun-backend/internal/library/viral"
"haixun-backend/internal/library/websearch"
domai "haixun-backend/internal/model/ai/domain/usecase"
aiusecase "haixun-backend/internal/model/ai/usecase"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
)
type InspireCopyMissionLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewInspireCopyMissionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *InspireCopyMissionLogic {
return &InspireCopyMissionLogic{ctx: ctx, svcCtx: svcCtx}
}
func (l *InspireCopyMissionLogic) InspireCopyMission(req *types.PersonaCopyMissionsPath) (*types.CopyMissionInspirationData, error) {
tenantID, uid, err := actorFrom(l.ctx)
if err != nil {
return nil, err
}
personaID := strings.TrimSpace(req.PersonaID)
persona, err := l.svcCtx.Persona.Get(l.ctx, tenantID, uid, personaID)
if err != nil {
return nil, err
}
missionList, err := l.svcCtx.CopyMission.List(l.ctx, tenantID, uid, personaID)
if err != nil {
return nil, err
}
recentLabels := make([]string, 0, 8)
recentSeeds := make([]string, 0, 8)
for _, mission := range missionList.List {
if mission.Status == "archived" {
continue
}
if label := strings.TrimSpace(mission.Label); label != "" {
recentLabels = append(recentLabels, label)
}
if seed := strings.TrimSpace(mission.SeedQuery); seed != "" {
recentSeeds = append(recentSeeds, seed)
}
if len(recentLabels) >= 8 && len(recentSeeds) >= 8 {
break
}
}
trendSnippets := []libviral.MissionInspireTrendSnippet{}
webSearchProvider := ""
llmOnly := true
research, researchErr := l.svcCtx.Placement.ResearchSettings(l.ctx, tenantID, uid)
if researchErr == nil && placement.WebSearchAvailable(research) {
memberCtx, memberErr := l.svcCtx.ThreadsAccount.ResolveMemberPlacementContext(l.ctx, tenantID, uid, research)
if memberErr == nil {
webClient := websearch.New(memberCtx.WebSearchConfig())
trendSnippets = libviral.CollectMissionInspireTrends(
l.ctx,
webClient,
memberCtx,
persona.Brief,
persona.StyleBenchmark,
)
webSearchProvider = memberCtx.WebSearchProviderLabel()
llmOnly = len(trendSnippets) == 0
}
}
webSearchUsed := len(trendSnippets) > 0
credential, err := l.svcCtx.ThreadsAccount.ResolveMemberAiCredential(l.ctx, tenantID, uid)
if err != nil {
return nil, err
}
providerID, err := aiusecase.MapWorkerProvider(credential.Provider)
if err != nil {
return nil, err
}
personaBlock := style8d.ResolvePersonaBlock(persona.Persona, persona.StyleProfile, persona.Brief)
result, err := l.svcCtx.AI.GenerateText(l.ctx, domai.GenerateRequest{
Provider: providerID,
Model: credential.Model,
Credential: domai.Credential{
APIKey: credential.APIKey,
},
System: libviral.BuildMissionInspireSystemPrompt(),
Messages: []domai.Message{
{
Role: "user",
Content: libviral.BuildMissionInspireUserPrompt(libviral.MissionInspireInput{
PersonaDisplayName: persona.DisplayName,
PersonaBrief: persona.Brief,
PersonaBlock: personaBlock,
StyleBenchmark: persona.StyleBenchmark,
PersonaAudience: persona.CopyResearchMap.AudienceSummary,
PersonaContentGoal: persona.CopyResearchMap.ContentGoal,
PersonaQuestions: append([]string(nil), persona.CopyResearchMap.Questions...),
PersonaPillars: append([]string(nil), persona.CopyResearchMap.Pillars...),
RecentMissionLabels: recentLabels,
RecentSeedQueries: recentSeeds,
TrendSnippets: trendSnippets,
WebSearchProvider: webSearchProvider,
LLMOnly: llmOnly,
}),
},
},
})
if err != nil {
return nil, err
}
parsed, err := libviral.ParseMissionInspireOutput(result.Text)
if err != nil {
return nil, app.For(code.AI).SvcThirdParty("靈感骰子 LLM 回傳無法解析:" + err.Error())
}
sources := make([]types.CopyMissionInspirationSourceData, 0, len(trendSnippets))
for _, item := range trendSnippets {
sources = append(sources, types.CopyMissionInspirationSourceData{
Query: item.Query,
Title: item.Title,
Snippet: item.Snippet,
URL: item.URL,
})
}
message := "已依近期趨勢產出任務靈感,可微調後建立任務"
if !webSearchUsed {
message = "未設定搜尋 API key已改由 LLM 依人設推測靈感;補上 BraveExa key 可取得更貼近熱搜的結果"
}
return &types.CopyMissionInspirationData{
Label: parsed.Label,
SeedQuery: parsed.SeedQuery,
Brief: parsed.Brief,
TrendReason: parsed.TrendReason,
TrendKeywords: parsed.TrendKeywords,
Sources: sources,
WebSearchUsed: webSearchUsed,
Message: message,
}, nil
}