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