thread-master/frontend/src/lib/productContext.ts

87 lines
3.2 KiB
TypeScript
Raw Normal View History

2026-06-26 08:37:04 +00:00
export type CtaType = 'none' | 'link' | 'dm' | 'follow'
export type ProductContextFields = {
brand: string
product: string
features: string
placementTone?: string
ctaType?: CtaType
ctaUrl?: string
}
export const CTA_TYPE_OPTIONS: Array<{ value: CtaType; label: string }> = [
{ value: 'none', label: '(無)不主動帶 CTA' },
{ value: 'link', label: '連結(可填網址)' },
{ value: 'dm', label: '私訊' },
{ value: 'follow', label: '追蹤' },
]
export const EMPTY_PRODUCT_CONTEXT: ProductContextFields = {
brand: '',
product: '',
features: '',
placementTone: '',
ctaType: 'none',
ctaUrl: '',
}
function normalizeCtaType(value: unknown): CtaType {
return value === 'link' || value === 'dm' || value === 'follow' ? value : 'none'
}
export function parseProductContext(raw: string | null | undefined): ProductContextFields {
if (!raw?.trim()) return { ...EMPTY_PRODUCT_CONTEXT }
try {
const parsed = JSON.parse(raw) as Partial<ProductContextFields>
if (parsed && typeof parsed === 'object') {
return {
brand: parsed.brand?.trim() ?? '',
product: parsed.product?.trim() ?? '',
features: parsed.features?.trim() ?? '',
placementTone: parsed.placementTone?.trim() ?? '',
ctaType: normalizeCtaType(parsed.ctaType),
ctaUrl: parsed.ctaUrl?.trim() ?? '',
}
}
} catch {
return { ...EMPTY_PRODUCT_CONTEXT, features: raw.trim() }
}
return { ...EMPTY_PRODUCT_CONTEXT }
}
export function serializeProductContext(fields: ProductContextFields): string {
const brand = fields.brand.trim()
const product = fields.product.trim()
const features = fields.features.trim()
const placementTone = fields.placementTone?.trim() ?? ''
const ctaType = normalizeCtaType(fields.ctaType)
const ctaUrl = fields.ctaUrl?.trim() ?? ''
if (!brand && !product && !features && !placementTone && ctaType === 'none' && !ctaUrl) {
return ''
}
return JSON.stringify({ brand, product, features, placementTone, ctaType, ctaUrl })
}
export function hasProductContext(raw: string | null | undefined): boolean {
const fields = parseProductContext(raw)
return !!(fields.brand || fields.product || fields.features)
}
export function summarizeProductContext(raw: string | null | undefined): string | null {
const fields = parseProductContext(raw)
const parts = [fields.brand, fields.product, fields.features].filter(Boolean)
return parts.length > 0 ? parts.join(' · ') : null
}
export function formatProductContextForPrompt(raw: string | null | undefined): string {
const fields = parseProductContext(raw)
const lines: string[] = []
if (fields.brand) lines.push(`品牌:${fields.brand}`)
if (fields.product) lines.push(`產品:${fields.product}`)
if (fields.features) lines.push(`特色/能幫上忙的地方:${fields.features}`)
if (fields.placementTone) lines.push(`置入語氣偏好:${fields.placementTone}`)
if (fields.ctaType === 'link' && fields.ctaUrl) lines.push(`留言 CTA 連結:${fields.ctaUrl}`)
else if (fields.ctaType === 'dm') lines.push('留言 CTA引導私訊')
else if (fields.ctaType === 'follow') lines.push('留言 CTA引導追蹤')
return lines.join('\n')
}