152 lines
4.5 KiB
Go
152 lines
4.5 KiB
Go
package placement
|
||
|
||
import (
|
||
"strings"
|
||
|
||
branddomain "haixun-backend/internal/model/brand/domain/usecase"
|
||
topicdomain "haixun-backend/internal/model/placement_topic/domain/usecase"
|
||
)
|
||
|
||
// PlacementTopicContext bundles brand, product, and user-entered topic fields for prompts.
|
||
type PlacementTopicContext struct {
|
||
BrandDisplayName string
|
||
TopicName string
|
||
SeedQuery string
|
||
Brief string
|
||
Goals string
|
||
TargetAudience string
|
||
ProductContext string
|
||
ProductBrief string
|
||
ProductLabel string
|
||
}
|
||
|
||
func PlacementTopicContextFromTopic(topic *topicdomain.TopicSummary, brand *branddomain.BrandSummary, productBrief string) PlacementTopicContext {
|
||
if topic == nil {
|
||
return PlacementTopicContextFromBrand(brand, productBrief)
|
||
}
|
||
merged := brandSummaryForTopic(topic, brand)
|
||
return PlacementTopicContextFromBrand(merged, productBrief)
|
||
}
|
||
|
||
func brandSummaryForTopic(topic *topicdomain.TopicSummary, brand *branddomain.BrandSummary) *branddomain.BrandSummary {
|
||
if brand == nil {
|
||
return nil
|
||
}
|
||
out := *brand
|
||
out.TopicName = strings.TrimSpace(topic.TopicName)
|
||
out.SeedQuery = strings.TrimSpace(topic.SeedQuery)
|
||
out.Brief = strings.TrimSpace(topic.Brief)
|
||
out.ProductID = strings.TrimSpace(topic.ProductID)
|
||
out.ResearchMap = topic.ResearchMap
|
||
return &out
|
||
}
|
||
|
||
func PlacementTopicContextFromBrand(brand *branddomain.BrandSummary, productBrief string) PlacementTopicContext {
|
||
if brand == nil {
|
||
return PlacementTopicContext{}
|
||
}
|
||
ctx := PlacementTopicContext{
|
||
BrandDisplayName: strings.TrimSpace(brand.DisplayName),
|
||
TopicName: strings.TrimSpace(brand.TopicName),
|
||
SeedQuery: strings.TrimSpace(brand.SeedQuery),
|
||
Brief: strings.TrimSpace(brand.Brief),
|
||
Goals: strings.TrimSpace(brand.Goals),
|
||
TargetAudience: strings.TrimSpace(brand.TargetAudience),
|
||
ProductContext: strings.TrimSpace(brand.ProductContext),
|
||
ProductBrief: strings.TrimSpace(productBrief),
|
||
ProductLabel: productLabelFromBrand(brand),
|
||
}
|
||
if ctx.ProductBrief == "" {
|
||
ctx.ProductBrief = strings.TrimSpace(brand.ProductBrief)
|
||
}
|
||
return ctx
|
||
}
|
||
|
||
func productLabelFromBrand(brand *branddomain.BrandSummary) string {
|
||
if brand == nil {
|
||
return ""
|
||
}
|
||
productID := strings.TrimSpace(brand.ProductID)
|
||
if productID == "" {
|
||
return ""
|
||
}
|
||
for _, item := range brand.Products {
|
||
if item.ID == productID {
|
||
return strings.TrimSpace(item.Label)
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func (c PlacementTopicContext) ToResearchMapInput() ResearchMapInput {
|
||
return ResearchMapInput{
|
||
BrandDisplayName: c.BrandDisplayName,
|
||
TopicName: c.TopicName,
|
||
SeedQuery: c.SeedQuery,
|
||
Brief: c.Brief,
|
||
Goals: c.Goals,
|
||
TargetAudience: c.TargetAudience,
|
||
ProductContext: c.ProductContext,
|
||
ProductBrief: c.ProductBrief,
|
||
ProductLabel: c.ProductLabel,
|
||
}
|
||
}
|
||
|
||
func (c PlacementTopicContext) WritePromptBlock(b *strings.Builder) {
|
||
writePromptSection(b, "品牌", c.BrandDisplayName)
|
||
writePromptSection(b, "置入產品", c.ProductDisplayName())
|
||
writePromptSection(b, "主題名稱", c.TopicName)
|
||
writePromptSection(b, "種子關鍵字", c.SeedQuery)
|
||
writePromptSection(b, "主題目標", c.Brief)
|
||
writePromptSection(b, "置入目標/備註", c.Goals)
|
||
writePromptSection(b, "已知受眾描述", c.TargetAudience)
|
||
productDetail := FormatProductContextForPrompt(c.ProductContext)
|
||
if productDetail == "" && c.ProductBrief != "" {
|
||
productDetail = c.ProductBrief
|
||
}
|
||
if productDetail != "" {
|
||
b.WriteString("【產品詳情】\n")
|
||
b.WriteString(productDetail)
|
||
b.WriteString("\n")
|
||
}
|
||
}
|
||
|
||
func (c PlacementTopicContext) ProductDisplayName() string {
|
||
fields := ParseProductContext(c.ProductContext)
|
||
if fields.Product != "" {
|
||
return fields.Product
|
||
}
|
||
return c.ProductLabel
|
||
}
|
||
|
||
func writePromptSection(b *strings.Builder, label, value string) {
|
||
value = strings.TrimSpace(value)
|
||
if value == "" {
|
||
return
|
||
}
|
||
b.WriteString("【")
|
||
b.WriteString(label)
|
||
b.WriteString("】")
|
||
b.WriteString(value)
|
||
b.WriteString("\n")
|
||
}
|
||
|
||
// PromptLines returns optional labeled lines for knowledge graph template vars.
|
||
func (c PlacementTopicContext) PromptLines() map[string]string {
|
||
productName := c.ProductDisplayName()
|
||
return map[string]string{
|
||
"brand_line": optionalPromptLine("品牌", c.BrandDisplayName),
|
||
"topic_line": optionalPromptLine("主題名稱", c.TopicName),
|
||
"product_line": optionalPromptLine("置入產品", productName),
|
||
"goals_line": optionalPromptLine("置入目標", c.Goals),
|
||
}
|
||
}
|
||
|
||
func optionalPromptLine(label, value string) string {
|
||
value = strings.TrimSpace(value)
|
||
if value == "" {
|
||
return ""
|
||
}
|
||
return label + ":" + value + "\n"
|
||
}
|