package persona import ( "context" "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" 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 } 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 } 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: post.SearchTag, TopicBrief: persona.Brief, Persona: persona.Persona, StyleProfile: persona.StyleProfile, OriginalText: post.Text, AuthorName: post.Author, }), }, }, }) 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, 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, ScanPostID: saved.ScanPostID, DraftType: saved.DraftType, 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 }