89 lines
3.1 KiB
TypeScript
89 lines
3.1 KiB
TypeScript
import "server-only";
|
||
|
||
import { Prisma, PrismaClient } from "@prisma/client";
|
||
import { buildStyle8DPromptBlock } from "@/lib/types/style-profile";
|
||
|
||
const globalForPrisma = globalThis as unknown as {
|
||
prisma?: PrismaClient & { __schemaFingerprint?: string };
|
||
};
|
||
|
||
/** 與 schema 同步;dmmf 不可用時用固定字串讓 dev 快取仍能失效。 */
|
||
const SCHEMA_FINGERPRINT_FALLBACK =
|
||
"ProductProfile:accountId,brand,brandId,context,createdAt,id,label,matchTags,topics,updatedAt|Topic:accountId,active,brief,brandProfileId,createdAt,id,label,productContext,productProfileId,query,researchMap,selectedTags,topicGoal";
|
||
|
||
let schemaFingerprint: string | null = null;
|
||
|
||
function getSchemaFingerprint(): string {
|
||
if (schemaFingerprint) return schemaFingerprint;
|
||
try {
|
||
const models = Prisma.dmmf?.datamodel?.models;
|
||
if (models) {
|
||
const account = models.find((m) => m.name === "Account");
|
||
const product = models.find((m) => m.name === "ProductProfile");
|
||
const topic = models.find((m) => m.name === "Topic");
|
||
const productFields = product?.fields.map((f) => f.name).sort().join(",") ?? "";
|
||
const topicFields = topic?.fields.map((f) => f.name).sort().join(",") ?? "";
|
||
const accountFields = account?.fields.map((f) => f.name).sort().join(",") ?? "";
|
||
schemaFingerprint = `Account:${accountFields}|ProductProfile:${productFields}|Topic:${topicFields}`;
|
||
return schemaFingerprint;
|
||
}
|
||
} catch {
|
||
// Prisma.dmmf 僅在 server runtime 可用
|
||
}
|
||
schemaFingerprint = SCHEMA_FINGERPRINT_FALLBACK;
|
||
return schemaFingerprint;
|
||
}
|
||
|
||
function createPrismaClient() {
|
||
const fingerprint = getSchemaFingerprint();
|
||
const client = new PrismaClient({
|
||
log: process.env.NODE_ENV === "development" ? ["error", "warn"] : ["error"],
|
||
}) as PrismaClient & { __schemaFingerprint?: string };
|
||
client.__schemaFingerprint = fingerprint;
|
||
return client;
|
||
}
|
||
|
||
function isPrismaClientCurrent(
|
||
client: PrismaClient & { __schemaFingerprint?: string }
|
||
): boolean {
|
||
return (
|
||
"backgroundJob" in client &&
|
||
"user" in client &&
|
||
"productProfile" in client &&
|
||
"brandProfile" in client &&
|
||
client.__schemaFingerprint === getSchemaFingerprint()
|
||
);
|
||
}
|
||
|
||
function getPrismaClient() {
|
||
const cached = globalForPrisma.prisma;
|
||
if (cached && isPrismaClientCurrent(cached)) return cached;
|
||
|
||
const client = createPrismaClient();
|
||
if (process.env.NODE_ENV !== "production") {
|
||
globalForPrisma.prisma = client;
|
||
}
|
||
return client;
|
||
}
|
||
|
||
export const prisma = getPrismaClient();
|
||
|
||
export function resolvePersona(
|
||
settings: { persona: string | null },
|
||
account?: {
|
||
persona: string | null;
|
||
styleProfile?: string | null;
|
||
targetAudience?: string | null;
|
||
goals?: string | null;
|
||
} | null
|
||
) {
|
||
const blocks = [
|
||
account?.persona?.trim() || settings.persona?.trim(),
|
||
buildStyle8DPromptBlock(account?.styleProfile),
|
||
account?.targetAudience?.trim() ? `目標受眾:${account.targetAudience.trim()}` : null,
|
||
account?.goals?.trim() ? `經營目標:${account.goals.trim()}` : null,
|
||
].filter(Boolean);
|
||
|
||
return blocks.length > 0 ? blocks.join("\n\n") : null;
|
||
}
|