haixunMaster/lib/capabilities.ts

115 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 AppTHREADS_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,
};
}