package placement import ( "fmt" "strings" "haixun-backend/internal/library/websearch" ) // ConnectionPrefsInput mirrors persisted account connection prefs without importing threads_account. type ConnectionPrefsInput struct { DevMode bool SearchSourceMode string } // MemberContext is resolved per login member (email account) + active Threads operating account. type MemberContext struct { TenantID string OwnerUID string ActiveAccountID string DevMode bool SearchSourceMode SearchSourceMode AllowsThreadsAPI bool AllowsBrave bool AllowsCrawler bool WebSearchProvider string BraveAPIKey string ExaAPIKey string BraveCountry string BraveSearchLang string ExaUserLocation string ApiConnected bool BrowserConnected bool ThreadsAPIAccessToken string ScrapeReplies bool RepliesPerPost int } type ResearchSettings struct { WebSearchProvider string BraveAPIKey string ExaAPIKey string BraveCountry string BraveSearchLang string ExaUserLocation string ExpandStrategy string } func BuildMemberContext( tenantID, ownerUID, activeAccountID string, prefs ConnectionPrefsInput, apiConnected, browserConnected bool, research ResearchSettings, scrapeReplies bool, repliesPerPost int, ) MemberContext { mode := ParseSearchSourceMode(prefs.SearchSourceMode) if prefs.DevMode && strings.TrimSpace(prefs.SearchSourceMode) == "" { mode = SearchSourceCrawler } allowsCrawler := ModeAllowsCrawler(mode) allowsThreads := ModeAllowsThreadsAPI(mode) allowsBrave := ModeAllowsBrave(mode) country := strings.TrimSpace(research.BraveCountry) if country == "" { country = "tw" } lang := strings.TrimSpace(research.BraveSearchLang) if lang == "" { lang = "zh-hant" } userLocation := strings.TrimSpace(research.ExaUserLocation) if userLocation == "" { userLocation = "TW" } if repliesPerPost <= 0 { repliesPerPost = 10 } return MemberContext{ TenantID: tenantID, OwnerUID: ownerUID, ActiveAccountID: activeAccountID, DevMode: prefs.DevMode, SearchSourceMode: mode, AllowsThreadsAPI: allowsThreads, AllowsBrave: allowsBrave, AllowsCrawler: allowsCrawler, WebSearchProvider: string(websearch.ParseProvider(research.WebSearchProvider)), BraveAPIKey: strings.TrimSpace(research.BraveAPIKey), ExaAPIKey: strings.TrimSpace(research.ExaAPIKey), BraveCountry: country, BraveSearchLang: lang, ExaUserLocation: userLocation, ApiConnected: apiConnected, BrowserConnected: browserConnected, ScrapeReplies: scrapeReplies, RepliesPerPost: repliesPerPost, } } func (c MemberContext) PayloadFields() map[string]any { return map[string]any{ "tenant_id": c.TenantID, "owner_uid": c.OwnerUID, "threads_account_id": c.ActiveAccountID, "dev_mode": c.DevMode, "search_source_mode": string(c.SearchSourceMode), "allows_threads_api": c.AllowsThreadsAPI, "allows_brave": c.AllowsBrave, "allows_crawler": c.AllowsCrawler, "web_search_provider": c.WebSearchProvider, "brave_country": c.BraveCountry, "brave_search_lang": c.BraveSearchLang, "exa_user_location": c.ExaUserLocation, "api_connected": c.ApiConnected, "browser_connected": c.BrowserConnected, "scrape_replies": c.ScrapeReplies, "replies_per_post": c.RepliesPerPost, } } func (c MemberContext) WebSearchProviderEnum() websearch.Provider { return websearch.ParseProvider(c.WebSearchProvider) } func (c MemberContext) WebSearchAPIKey() string { if c.WebSearchProviderEnum() == websearch.ProviderExa { return strings.TrimSpace(c.ExaAPIKey) } return strings.TrimSpace(c.BraveAPIKey) } func (c MemberContext) WebSearchConfig() websearch.Config { return websearch.ConfigFromMember( c.BraveAPIKey, c.ExaAPIKey, c.WebSearchProvider, c.BraveCountry, c.BraveSearchLang, c.ExaUserLocation, ) } func (c MemberContext) WebSearchProviderLabel() string { return websearch.ProviderLabel(c.WebSearchProviderEnum()) } func (c MemberContext) WebSearchDiscoverChannel() DiscoverChannel { if c.WebSearchProviderEnum() == websearch.ProviderExa { return DiscoverExa } return DiscoverBrave } func MissingWebSearchKey(research ResearchSettings) bool { return strings.TrimSpace(websearch.ActiveAPIKey(websearch.ConfigFromMember( research.BraveAPIKey, research.ExaAPIKey, research.WebSearchProvider, research.BraveCountry, research.BraveSearchLang, research.ExaUserLocation, ))) == "" } func WebSearchKeyRequiredMessage(research ResearchSettings) string { provider := websearch.ParseProvider(research.WebSearchProvider) return fmt.Sprintf("請在設定頁設定 %s Search API key(跟隨此登入帳號)", websearch.ProviderLabel(provider)) }