import "server-only"; import type { ProviderId } from "@/lib/ai/keys"; import { parseProviderApiKeys, resolveApiKey } from "@/lib/ai/keys"; import { getActiveAccountConnectionSettings } from "@/lib/account-connection-settings"; import { getActiveAccountProfile } from "@/lib/account-context"; import { getOrCreateSettingsForUser } from "@/lib/user-settings"; import { accountHasThreadsToken, isThreadsAppConfigured } from "@/lib/threads-api"; import { requireSessionUser } from "@/lib/auth/session"; import type { FeatureCapability, WorkspaceCapabilities } from "@/lib/capabilities/types"; export type { CapabilityFeature, FeatureCapability, WorkspaceCapabilities } from "@/lib/capabilities/types"; function cap( ready: boolean, label: string, options?: { reason?: string; setupHref?: string; setupLabel?: string } ): FeatureCapability { return { ready, label, ...options }; } function aiCapability( provider: string, keys: ReturnType, label: string ): FeatureCapability { const id = provider as ProviderId; const ready = !!resolveApiKey(id, keys); return cap(ready, label, ready ? undefined : { reason: `請先在設定頁填入 ${label} API key`, setupHref: "/settings", setupLabel: "前往 AI 設定", }); } export async function getWorkspaceCapabilities(): Promise { const user = await requireSessionUser(); const [account, connection, settings] = await Promise.all([ getActiveAccountProfile(), getActiveAccountConnectionSettings(), getOrCreateSettingsForUser(user.id), ]); const keys = parseProviderApiKeys(settings.providerApiKeys); const ai = aiCapability(settings.aiProvider, keys, "AI 模型"); const researchProvider = settings.researchAiProvider ?? settings.aiProvider; const research = aiCapability(researchProvider, keys, "研究地圖 AI"); const hasToken = accountHasThreadsToken(account); const appConfigured = isThreadsAppConfigured(); const threadsApi = cap(hasToken && appConfigured, "Threads 官方 API", { reason: !appConfigured ? "伺服器尚未設定 Threads App(THREADS_APP_ID / SECRET)" : !hasToken ? "請到連線設定綁定此經營帳號的 Threads OAuth" : undefined, setupHref: "/connections", setupLabel: "前往連線設定", }); const hasBrowserState = !!(account?.storageState?.trim()); const browserSession = cap(hasBrowserState, "Chrome 瀏覽器同步", { reason: "請到連線設定用 Chrome 擴充同步 Threads session", setupHref: "/connections", setupLabel: "前往連線設定", }); const canScanViaApi = connection.searchViaApi && threadsApi.ready; const canScanViaBrowser = connection.devMode && browserSession.ready; const scan = cap(canScanViaApi || canScanViaBrowser, "海巡搜尋", { reason: connection.searchViaApi ? !threadsApi.ready ? "已選 API 海巡,但帳號尚未綁定 Threads" : !connection.devMode ? "官方 API 未就緒,且未開啟瀏覽器備援" : "官方 API 與瀏覽器模式都未就緒" : connection.devMode ? "已開瀏覽器模式,但尚未同步 Chrome session" : "請到連線設定選「API 優先」或「Chrome 同步」", setupHref: "/connections", setupLabel: "設定海巡方式", }); const generate = { ...ai, label: "AI 生成" }; const analyze = { ...research, label: "主題分析" }; const viralAnalysis = { ...ai, label: "爆款分析" }; const outreach = { ...ai, label: "獲客留言" }; const canPublishViaApi = connection.publishViaApi && threadsApi.ready; const canPublishViaBrowser = connection.devMode && browserSession.ready; const publish = cap(canPublishViaApi || canPublishViaBrowser, "發布貼文", { reason: connection.publishViaApi ? "已選 API 發文,但帳號尚未綁定 Threads" : connection.devMode ? "已開瀏覽器發文,但尚未同步 Chrome session" : "請到連線設定設定發文方式", setupHref: "/connections", setupLabel: "設定發文方式", }); return { ai, research, threadsApi, browserSession, scan, analyze, generate, viralAnalysis, outreach, publish, }; }