thread-master/internal/logic/persona/generate_persona_copy_draft...

175 lines
5.1 KiB
Go

package persona
import (
"context"
"strings"
app "haixun-backend/internal/library/errors"
"haixun-backend/internal/library/errors/code"
"haixun-backend/internal/library/style8d"
libviral "haixun-backend/internal/library/viral"
domai "haixun-backend/internal/model/ai/domain/usecase"
aiusecase "haixun-backend/internal/model/ai/usecase"
copydraftusecase "haixun-backend/internal/model/copy_draft/domain/usecase"
"haixun-backend/internal/svc"
"haixun-backend/internal/types"
)
type GeneratePersonaCopyDraftLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGeneratePersonaCopyDraftLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GeneratePersonaCopyDraftLogic {
return &GeneratePersonaCopyDraftLogic{ctx: ctx, svcCtx: svcCtx}
}
func (l *GeneratePersonaCopyDraftLogic) GeneratePersonaCopyDraft(
req *types.GeneratePersonaCopyDraftHandlerReq,
) (*types.GeneratePersonaCopyDraftData, error) {
tenantID, uid, err := actorFrom(l.ctx)
if err != nil {
return nil, err
}
personaID := strings.TrimSpace(req.ID)
scanPostID := strings.TrimSpace(req.ScanPostID)
if scanPostID == "" {
return nil, app.For(code.Persona).InputMissingRequired("scan_post_id is required")
}
persona, err := l.svcCtx.Persona.Get(l.ctx, tenantID, uid, personaID)
if err != nil {
return nil, err
}
post, err := l.svcCtx.ScanPost.GetForPersona(l.ctx, tenantID, uid, personaID, scanPostID)
if err != nil {
return nil, err
}
topicLabel := strings.TrimSpace(post.SearchTag)
topicBrief := strings.TrimSpace(persona.Brief)
if missionID := strings.TrimSpace(post.CopyMissionID); missionID != "" {
if mission, missionErr := l.svcCtx.CopyMission.Get(l.ctx, tenantID, uid, personaID, missionID); missionErr == nil {
if label := strings.TrimSpace(mission.Label); label != "" {
topicLabel = label
}
if brief := strings.TrimSpace(mission.Brief); brief != "" {
topicBrief = brief
}
}
}
if !style8d.HasReady8D(persona.Persona, persona.StyleProfile) {
return nil, app.For(code.Persona).InputMissingRequired("請先完成人設 8D 對標分析")
}
personaBlock := style8d.ResolvePersonaBlock(persona.Persona, persona.StyleProfile, persona.Brief)
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
}
analysisText := ""
analyzeResult, analyzeErr := l.svcCtx.AI.GenerateText(l.ctx, domai.GenerateRequest{
Provider: providerID,
Model: credential.Model,
Credential: domai.Credential{
APIKey: credential.APIKey,
},
System: libviral.BuildAnalyzeViralSystemPrompt(),
Messages: []domai.Message{
{
Role: "user",
Content: libviral.BuildAnalyzeViralUserPrompt(libviral.AnalyzeViralInput{
PostText: post.Text,
AuthorName: post.Author,
LikeCount: post.LikeCount,
ReplyCount: post.ReplyCount,
SearchTag: post.SearchTag,
TopicLabel: topicLabel,
TopicBrief: topicBrief,
Persona: personaBlock,
}),
},
},
})
if analyzeErr == nil {
if parsed, parseErr := libviral.ParseAnalyzeViralOutput(analyzeResult.Text); parseErr == nil {
analysisText = libviral.FormatAnalysisForReplicate(parsed)
}
}
result, err := l.svcCtx.AI.GenerateText(l.ctx, domai.GenerateRequest{
Provider: providerID,
Model: credential.Model,
Credential: domai.Credential{
APIKey: credential.APIKey,
},
System: libviral.BuildSystemPrompt(),
Messages: []domai.Message{
{
Role: "user",
Content: libviral.BuildUserPrompt(libviral.ReplicateInput{
TopicLabel: topicLabel,
TopicBrief: topicBrief,
Persona: personaBlock,
StyleProfile: "",
OriginalText: post.Text,
AuthorName: post.Author,
StructureAnalysis: analysisText,
}),
},
},
})
if err != nil {
return nil, err
}
parsed, err := libviral.ParseReplicateOutput(result.Text)
if err != nil {
return nil, app.For(code.AI).SvcThirdParty("仿寫 LLM 回傳無法解析:" + err.Error())
}
saved, err := l.svcCtx.CopyDraft.Create(l.ctx, copydraftusecase.CreateRequest{
TenantID: tenantID,
OwnerUID: uid,
PersonaID: personaID,
CopyMissionID: post.CopyMissionID,
ScanPostID: scanPostID,
DraftType: "replicate",
Text: parsed.Text,
Angle: parsed.Angle,
Hook: parsed.Hook,
Rationale: parsed.Rationale,
ReferenceNotes: parsed.StructureNotes,
Sources: []string{post.Permalink},
})
if err != nil {
return nil, err
}
return &types.GeneratePersonaCopyDraftData{
Draft: types.CopyDraftData{
ID: saved.ID,
PersonaID: saved.PersonaID,
CopyMissionID: saved.CopyMissionID,
ScanPostID: saved.ScanPostID,
DraftType: saved.DraftType,
SortOrder: saved.SortOrder,
Text: saved.Text,
Angle: saved.Angle,
Hook: saved.Hook,
Rationale: saved.Rationale,
ReferenceNotes: saved.ReferenceNotes,
Sources: saved.Sources,
Status: saved.Status,
CreateAt: saved.CreateAt,
},
Message: "已產出仿寫草稿",
}, nil
}