115 lines
4.2 KiB
TypeScript
115 lines
4.2 KiB
TypeScript
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<typeof parseProviderApiKeys>,
|
||
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<WorkspaceCapabilities> {
|
||
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,
|
||
};
|
||
} |