thread-master/old/backend/internal/library/personacopy/formula_draft.go

162 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package personacopy
import (
"context"
"fmt"
"strings"
libformula "haixun-backend/internal/library/formula"
"haixun-backend/internal/library/placement"
"haixun-backend/internal/library/style8d"
"haixun-backend/internal/library/websearch"
domai "haixun-backend/internal/model/ai/domain/usecase"
aiusecase "haixun-backend/internal/model/ai/usecase"
contentformuladomain "haixun-backend/internal/model/content_formula/domain/usecase"
copydraftentity "haixun-backend/internal/model/copy_draft/domain/entity"
copydraftdomain "haixun-backend/internal/model/copy_draft/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 FormulaDraftInput struct {
TenantID string
OwnerUID string
PersonaID string
AccountID string
FormulaID string
Topic string
Brief string
UseWebSearch bool
DraftCount int
}
type FormulaDraftDeps struct {
Persona personadomain.UseCase
ContentFormula contentformuladomain.UseCase
CopyDraft copydraftdomain.UseCase
ThreadsAccount threadsaccountdomain.UseCase
Placement placementusecase.UseCase
AI domai.UseCase
}
func RunFormulaDraft(ctx context.Context, deps FormulaDraftDeps, in FormulaDraftInput, progress ProgressFn) (int, error) {
if progress == nil {
progress = func(string, int) {}
}
topic := strings.TrimSpace(in.Topic)
if topic == "" {
return 0, fmt.Errorf("topic is required")
}
count := in.DraftCount
if count <= 0 {
count = 1
}
if count > 5 {
count = 5
}
progress("讀取人設與寫法公式…", 10)
persona, err := deps.Persona.Get(ctx, in.TenantID, in.OwnerUID, in.PersonaID)
if err != nil {
return 0, err
}
if !style8d.HasReady8D(persona.Persona, persona.StyleProfile) {
return 0, fmt.Errorf("請先完成人設 8D 對標分析")
}
formula, err := deps.ContentFormula.Get(ctx, in.TenantID, in.OwnerUID, in.AccountID, in.FormulaID)
if err != nil {
return 0, err
}
credential, err := deps.ThreadsAccount.ResolveMemberAiCredential(ctx, in.TenantID, in.OwnerUID)
if err != nil {
return 0, err
}
providerID, err := aiusecase.MapWorkerProvider(credential.Provider)
if err != nil {
return 0, err
}
aiReq := domai.GenerateRequest{
Provider: providerID,
Model: credential.Model,
Credential: domai.Credential{APIKey: credential.APIKey},
}
personaBlock := style8d.ResolvePersonaBlock(persona.Persona, persona.StyleProfile, persona.Brief)
researchNotes := ""
if in.UseWebSearch {
progress("搜尋網路參考資料…", 20)
researchNotes = searchFormulaNotes(ctx, deps, in.TenantID, in.OwnerUID, topic, in.Brief)
}
created := 0
for i := 0; i < count; i++ {
progress(fmt.Sprintf("呼叫 AI 產生草稿 %d/%d%s / %s…", i+1, count, credential.Provider, credential.Model), 35+(i*40/count))
generated, genErr := libformula.GenerateDraft(ctx, deps.AI, aiReq, libformula.GenerateInput{
Topic: topic,
Brief: in.Brief,
PersonaBlock: personaBlock,
ResearchNotes: researchNotes,
Formula: *formula,
})
if genErr != nil {
return created, genErr
}
if _, saveErr := deps.CopyDraft.Create(ctx, copydraftdomain.CreateRequest{
TenantID: in.TenantID,
OwnerUID: in.OwnerUID,
PersonaID: in.PersonaID,
FormulaID: formula.ID,
DraftType: copydraftentity.DraftTypeFormula,
Text: generated.Text,
TopicTag: generated.TopicTag,
Angle: generated.Angle,
Hook: generated.Hook,
Rationale: generated.Rationale,
ReferenceNotes: generated.StructureNotes,
Sources: []string{formula.Label},
}); saveErr != nil {
return created, saveErr
}
created++
}
progress(fmt.Sprintf("已產生 %d 篇草稿", created), 100)
return created, nil
}
func searchFormulaNotes(ctx context.Context, deps FormulaDraftDeps, tenantID, uid, topic, brief string) string {
research, err := deps.Placement.ResearchSettings(ctx, tenantID, uid)
if err != nil || !placement.WebSearchAvailable(research) {
return ""
}
memberCtx, err := deps.ThreadsAccount.ResolveMemberPlacementContext(ctx, tenantID, uid, research)
if err != nil {
return ""
}
webClient := websearch.New(memberCtx.WebSearchConfig())
if !webClient.Enabled() {
return ""
}
resp, err := webClient.Search(ctx, websearch.SearchOptions{
Query: topic + " " + strings.TrimSpace(brief),
Limit: 5,
Mode: websearch.ModeKnowledgeExpand,
})
if err != nil || len(resp.Results) == 0 {
return ""
}
var b strings.Builder
for _, snip := range resp.Results {
if snip.Title != "" {
b.WriteString("- ")
b.WriteString(snip.Title)
b.WriteString("\n")
}
if snip.Snippet != "" {
b.WriteString(snip.Snippet)
b.WriteString("\n")
}
}
return strings.TrimSpace(b.String())
}