2026-06-24 10:02:42 +00:00
|
|
|
|
package placement
|
|
|
|
|
|
|
2026-06-25 08:20:03 +00:00
|
|
|
|
import (
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
|
|
"haixun-backend/internal/library/websearch"
|
|
|
|
|
|
)
|
2026-06-24 10:02:42 +00:00
|
|
|
|
|
|
|
|
|
|
// 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
|
2026-06-25 08:20:03 +00:00
|
|
|
|
WebSearchProvider string
|
2026-06-24 10:02:42 +00:00
|
|
|
|
BraveAPIKey string
|
2026-06-25 08:20:03 +00:00
|
|
|
|
ExaAPIKey string
|
2026-06-24 10:02:42 +00:00
|
|
|
|
BraveCountry string
|
|
|
|
|
|
BraveSearchLang string
|
2026-06-25 08:20:03 +00:00
|
|
|
|
ExaUserLocation string
|
2026-06-24 10:02:42 +00:00
|
|
|
|
ApiConnected bool
|
|
|
|
|
|
BrowserConnected bool
|
|
|
|
|
|
ThreadsAPIAccessToken string
|
|
|
|
|
|
ScrapeReplies bool
|
|
|
|
|
|
RepliesPerPost int
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type ResearchSettings struct {
|
2026-06-25 08:20:03 +00:00
|
|
|
|
WebSearchProvider string
|
|
|
|
|
|
BraveAPIKey string
|
|
|
|
|
|
ExaAPIKey string
|
|
|
|
|
|
BraveCountry string
|
|
|
|
|
|
BraveSearchLang string
|
|
|
|
|
|
ExaUserLocation string
|
|
|
|
|
|
ExpandStrategy string
|
2026-06-24 10:02:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func BuildMemberContext(
|
|
|
|
|
|
tenantID, ownerUID, activeAccountID string,
|
|
|
|
|
|
prefs ConnectionPrefsInput,
|
|
|
|
|
|
apiConnected, browserConnected bool,
|
|
|
|
|
|
research ResearchSettings,
|
|
|
|
|
|
scrapeReplies bool,
|
|
|
|
|
|
repliesPerPost int,
|
|
|
|
|
|
) MemberContext {
|
|
|
|
|
|
mode := ParseSearchSourceMode(prefs.SearchSourceMode)
|
2026-06-25 08:20:03 +00:00
|
|
|
|
if prefs.DevMode && strings.TrimSpace(prefs.SearchSourceMode) == "" {
|
|
|
|
|
|
mode = SearchSourceCrawler
|
|
|
|
|
|
}
|
2026-06-24 10:02:42 +00:00
|
|
|
|
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"
|
|
|
|
|
|
}
|
2026-06-25 08:20:03 +00:00
|
|
|
|
userLocation := strings.TrimSpace(research.ExaUserLocation)
|
|
|
|
|
|
if userLocation == "" {
|
|
|
|
|
|
userLocation = "TW"
|
|
|
|
|
|
}
|
2026-06-24 10:02:42 +00:00
|
|
|
|
|
|
|
|
|
|
if repliesPerPost <= 0 {
|
|
|
|
|
|
repliesPerPost = 10
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return MemberContext{
|
2026-06-25 08:20:03 +00:00
|
|
|
|
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,
|
2026-06-24 10:02:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (c MemberContext) PayloadFields() map[string]any {
|
|
|
|
|
|
return map[string]any{
|
2026-06-25 08:20:03 +00:00
|
|
|
|
"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)
|
2026-06-24 10:02:42 +00:00
|
|
|
|
}
|
2026-06-25 08:20:03 +00:00
|
|
|
|
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))
|
2026-06-24 10:02:42 +00:00
|
|
|
|
}
|