chore: moved insights model to database package (#4575)

This commit is contained in:
Piyush Gupta
2025-01-15 13:17:05 +05:30
committed by GitHub
parent 8399391aaa
commit 2575b649a0
21 changed files with 114 additions and 255 deletions

3
.gitignore vendored
View File

@@ -35,9 +35,6 @@ yarn-error.log*
!packages/database/.env
!apps/web/.env
# Prisma generated files
packages/database/zod
# turbo
.turbo

View File

@@ -7,8 +7,11 @@ import { INSIGHTS_PER_PAGE } from "@formbricks/lib/constants";
import { validateInputs } from "@formbricks/lib/utils/validate";
import { ZId } from "@formbricks/types/common";
import { DatabaseError } from "@formbricks/types/errors";
import { TInsight } from "@formbricks/types/insights";
import { TSurveyQuestionId, ZSurveyQuestionId } from "@formbricks/types/surveys/types";
import {
TSurveyQuestionId,
TSurveyQuestionSummaryOpenText,
ZSurveyQuestionId,
} from "@formbricks/types/surveys/types";
export const getInsightsBySurveyIdQuestionId = reactCache(
async (
@@ -16,7 +19,7 @@ export const getInsightsBySurveyIdQuestionId = reactCache(
questionId: TSurveyQuestionId,
limit?: number,
offset?: number
): Promise<TInsight[]> =>
): Promise<TSurveyQuestionSummaryOpenText["insights"]> =>
cache(
async () => {
validateInputs([surveyId, ZId], [questionId, ZSurveyQuestionId]);

View File

@@ -2,7 +2,7 @@ import { createDocument } from "@/app/api/(internal)/insights/lib/document";
import { doesResponseHasAnyOpenTextAnswer } from "@/app/api/(internal)/insights/lib/utils";
import { documentCache } from "@/lib/cache/document";
import { insightCache } from "@/lib/cache/insight";
import { Prisma } from "@prisma/client";
import { Insight, InsightCategory, Prisma } from "@prisma/client";
import { embed } from "ai";
import { prisma } from "@formbricks/database";
import { embeddingsModel } from "@formbricks/lib/aiModels";
@@ -12,12 +12,6 @@ import { validateInputs } from "@formbricks/lib/utils/validate";
import { ZId } from "@formbricks/types/common";
import { TCreatedDocument } from "@formbricks/types/documents";
import { DatabaseError } from "@formbricks/types/errors";
import {
TInsight,
TInsightCategory,
TInsightCreateInput,
ZInsightCreateInput,
} from "@formbricks/types/insights";
import {
TSurvey,
TSurveyQuestionId,
@@ -25,6 +19,7 @@ import {
ZSurveyQuestions,
} from "@formbricks/types/surveys/types";
import { getContactAttributes } from "./contact-attribute";
import { TInsightCreateInput, TNearestInsights, ZInsightCreateInput } from "./types";
export const generateInsightsForSurveyResponsesConcept = async (
survey: Pick<TSurvey, "id" | "name" | "environmentId" | "questions">
@@ -328,23 +323,16 @@ export const getQuestionResponseReferenceId = (surveyId: string, questionId: TSu
return `${surveyId}-${questionId}`;
};
export const createInsight = async (insightGroupInput: TInsightCreateInput): Promise<TInsight> => {
export const createInsight = async (insightGroupInput: TInsightCreateInput): Promise<Insight> => {
validateInputs([insightGroupInput, ZInsightCreateInput]);
try {
// create document
const { vector, ...data } = insightGroupInput;
const prismaInsight = await prisma.insight.create({
const insight = await prisma.insight.create({
data,
});
const insight = {
...prismaInsight,
_count: {
documentInsights: 0,
},
};
// update document vector with the embedding
const vectorString = `[${insightGroupInput.vector.join(",")}]`;
await prisma.$executeRaw`
@@ -373,7 +361,7 @@ export const handleInsightAssignments = async (
insight: {
title: string;
description: string;
category: TInsightCategory;
category: InsightCategory;
}
) => {
try {
@@ -427,21 +415,15 @@ export const findNearestInsights = async (
vector: number[],
limit: number = 5,
threshold: number = 0.5
): Promise<TInsight[]> => {
): Promise<TNearestInsights[]> => {
validateInputs([environmentId, ZId]);
// Convert the embedding array to a JSON-like string representation
const vectorString = `[${vector.join(",")}]`;
// Execute raw SQL query to find nearest neighbors and exclude the vector column
const insights: TInsight[] = await prisma.$queryRaw`
const insights: TNearestInsights[] = await prisma.$queryRaw`
SELECT
id,
created_at AS "createdAt",
updated_at AS "updatedAt",
title,
description,
category,
"environmentId"
id
FROM "Insight" d
WHERE d."environmentId" = ${environmentId}
AND d."vector" <=> ${vectorString}::vector(512) <= ${threshold}

View File

@@ -0,0 +1,16 @@
import { Insight } from "@prisma/client";
import { z } from "zod";
import { ZInsight } from "@formbricks/database/zod/insights";
export const ZInsightCreateInput = ZInsight.pick({
environmentId: true,
title: true,
description: true,
category: true,
}).extend({
vector: z.array(z.number()).length(512),
});
export type TInsightCreateInput = z.infer<typeof ZInsightCreateInput>;
export type TNearestInsights = Pick<Insight, "id">;

View File

@@ -4,6 +4,7 @@ import { Prisma } from "@prisma/client";
import { embed, generateObject } from "ai";
import { z } from "zod";
import { prisma } from "@formbricks/database";
import { ZInsight } from "@formbricks/database/zod/insights";
import { embeddingsModel, llmModel } from "@formbricks/lib/aiModels";
import { validateInputs } from "@formbricks/lib/utils/validate";
import {
@@ -13,7 +14,6 @@ import {
ZDocumentSentiment,
} from "@formbricks/types/documents";
import { DatabaseError } from "@formbricks/types/errors";
import { ZInsightCategory } from "@formbricks/types/insights";
export const createDocumentAndAssignInsight = async (
surveyName: string,
@@ -38,7 +38,7 @@ export const createDocumentAndAssignInsight = async (
z.object({
title: z.string().describe("insight title, very specific"),
description: z.string().describe("very brief insight description"),
category: ZInsightCategory,
category: ZInsight.shape.category,
})
),
isSpam: z.boolean(),

View File

@@ -1,6 +1,7 @@
"use client";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { TInsightWithDocumentCount } from "@/modules/ee/insights/experience/types/insights";
import { Button } from "@/modules/ui/components/button";
import { Card, CardContent, CardFooter } from "@/modules/ui/components/card";
import {
@@ -16,7 +17,6 @@ import { useDeferredValue, useEffect, useState } from "react";
import Markdown from "react-markdown";
import { timeSince } from "@formbricks/lib/time";
import { TDocument, TDocumentFilterCriteria } from "@formbricks/types/documents";
import { TInsight } from "@formbricks/types/insights";
import { TUserLocale } from "@formbricks/types/user";
import CategoryBadge from "../../experience/components/category-select";
import SentimentSelect from "../sentiment-select";
@@ -25,7 +25,7 @@ import { getDocumentsByInsightIdAction, getDocumentsByInsightIdSurveyIdQuestionI
interface InsightSheetProps {
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
insight: TInsight | null;
insight: TInsightWithDocumentCount | null;
surveyId?: string;
questionId?: string;
handleFeedback: (feedback: "positive" | "negative") => void;

View File

@@ -4,18 +4,19 @@ import { InsightSheet } from "@/modules/ee/insights/components/insight-sheet";
import { Button } from "@/modules/ui/components/button";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/modules/ui/components/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/modules/ui/components/tabs";
import { Insight, InsightCategory } from "@prisma/client";
import { UserIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useCallback, useEffect, useState } from "react";
import formbricks from "@formbricks/js";
import { cn } from "@formbricks/lib/cn";
import { TDocumentFilterCriteria } from "@formbricks/types/documents";
import { TInsight, TInsightCategory } from "@formbricks/types/insights";
import { TSurveyQuestionSummaryOpenText } from "@formbricks/types/surveys/types";
import { TUserLocale } from "@formbricks/types/user";
import CategoryBadge from "../experience/components/category-select";
interface InsightViewProps {
insights: TInsight[];
insights: TSurveyQuestionSummaryOpenText["insights"];
questionId?: string;
surveyId?: string;
documentsFilter?: TDocumentFilterCriteria;
@@ -35,8 +36,10 @@ export const InsightView = ({
}: InsightViewProps) => {
const t = useTranslations();
const [isInsightSheetOpen, setIsInsightSheetOpen] = useState(true);
const [localInsights, setLocalInsights] = useState<TInsight[]>(insights);
const [currentInsight, setCurrentInsight] = useState<TInsight | null>(null);
const [localInsights, setLocalInsights] = useState<TSurveyQuestionSummaryOpenText["insights"]>(insights);
const [currentInsight, setCurrentInsight] = useState<
TSurveyQuestionSummaryOpenText["insights"][number] | null
>(null);
const [activeTab, setActiveTab] = useState<string>("all");
const [visibleInsights, setVisibleInsights] = useState(10);
@@ -61,9 +64,7 @@ export const InsightView = ({
if (filterValue === "all") {
setLocalInsights(insights);
} else {
setLocalInsights(
insights.filter((insight) => insight.category === (filterValue as TInsightCategory))
);
setLocalInsights(insights.filter((insight) => insight.category === (filterValue as InsightCategory)));
}
},
[insights]
@@ -88,13 +89,13 @@ export const InsightView = ({
setVisibleInsights((prevVisibleInsights) => Math.min(prevVisibleInsights + 10, insights.length));
};
const updateLocalInsight = (insightId: string, updates: Partial<TInsight>) => {
const updateLocalInsight = (insightId: string, updates: Partial<Insight>) => {
setLocalInsights((prevInsights) =>
prevInsights.map((insight) => (insight.id === insightId ? { ...insight, ...updates } : insight))
);
};
const onCategoryChange = async (insightId: string, newCategory: TInsight["category"]) => {
const onCategoryChange = async (insightId: string, newCategory: InsightCategory) => {
updateLocalInsight(insightId, { category: newCategory });
};

View File

@@ -9,9 +9,10 @@ import {
getProjectIdFromInsightId,
} from "@/lib/utils/helper";
import { checkAIPermission } from "@/modules/ee/insights/actions";
import { ZInsightFilterCriteria } from "@/modules/ee/insights/experience/types/insights";
import { z } from "zod";
import { ZInsight } from "@formbricks/database/zod/insights";
import { ZId } from "@formbricks/types/common";
import { ZInsight, ZInsightFilterCriteria } from "@formbricks/types/insights";
import { getInsights, updateInsight } from "./lib/insights";
import { getStats } from "./lib/stats";

View File

@@ -1,14 +1,14 @@
import { BadgeSelect, TBadgeSelectOption } from "@/modules/ui/components/badge-select";
import { InsightCategory } from "@prisma/client";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { TInsight } from "@formbricks/types/insights";
import { updateInsightAction } from "../actions";
interface CategoryBadgeProps {
category: TInsight["category"];
category: InsightCategory;
insightId: string;
onCategoryChange?: (insightId: string, category: TInsight["category"]) => void;
onCategoryChange?: (insightId: string, category: InsightCategory) => void;
}
const categoryOptions: TBadgeSelectOption[] = [
@@ -18,14 +18,14 @@ const categoryOptions: TBadgeSelectOption[] = [
{ text: "Other", type: "gray" },
];
const categoryMapping: Record<string, TInsight["category"]> = {
const categoryMapping: Record<string, InsightCategory> = {
Complaint: "complaint",
Request: "featureRequest",
Praise: "praise",
Other: "other",
};
const getCategoryIndex = (category: TInsight["category"]) => {
const getCategoryIndex = (category: InsightCategory) => {
switch (category) {
case "complaint":
return 0;
@@ -41,7 +41,7 @@ const getCategoryIndex = (category: TInsight["category"]) => {
const CategoryBadge = ({ category, insightId, onCategoryChange }: CategoryBadgeProps) => {
const [isUpdating, setIsUpdating] = useState(false);
const t = useTranslations();
const handleUpdateCategory = async (newCategory: TInsight["category"]) => {
const handleUpdateCategory = async (newCategory: InsightCategory) => {
setIsUpdating(true);
try {
await updateInsightAction({ insightId, data: { category: newCategory } });

View File

@@ -1,15 +1,19 @@
"use client";
import { InsightSheet } from "@/modules/ee/insights/components/insight-sheet";
import {
TInsightFilterCriteria,
TInsightWithDocumentCount,
} from "@/modules/ee/insights/experience/types/insights";
import { Button } from "@/modules/ui/components/button";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/modules/ui/components/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/modules/ui/components/tabs";
import { InsightCategory } from "@prisma/client";
import { UserIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useCallback, useEffect, useMemo, useState } from "react";
import formbricks from "@formbricks/js";
import { TDocumentFilterCriteria } from "@formbricks/types/documents";
import { TInsight, TInsightFilterCriteria } from "@formbricks/types/insights";
import { TUserLocale } from "@formbricks/types/user";
import { getEnvironmentInsightsAction } from "../actions";
import CategoryBadge from "./category-select";
@@ -31,11 +35,11 @@ export const InsightView = ({
locale,
}: InsightViewProps) => {
const t = useTranslations();
const [insights, setInsights] = useState<TInsight[]>([]);
const [insights, setInsights] = useState<TInsightWithDocumentCount[]>([]);
const [hasMore, setHasMore] = useState<boolean>(true);
const [isFetching, setIsFetching] = useState(false);
const [isInsightSheetOpen, setIsInsightSheetOpen] = useState(false);
const [currentInsight, setCurrentInsight] = useState<TInsight | null>(null);
const [currentInsight, setCurrentInsight] = useState<TInsightWithDocumentCount | null>(null);
const [activeTab, setActiveTab] = useState<string>("featureRequest");
const handleFeedback = (feedback: "positive" | "negative") => {
@@ -56,7 +60,7 @@ export const InsightView = ({
documentCreatedAt: {
min: statsFrom,
},
category: activeTab === "all" ? undefined : (activeTab as TInsight["category"]),
category: activeTab === "all" ? undefined : (activeTab as InsightCategory),
}),
[statsFrom, activeTab]
);

View File

@@ -1,5 +1,10 @@
import { insightCache } from "@/lib/cache/insight";
import { Prisma } from "@prisma/client";
import {
TInsightFilterCriteria,
TInsightWithDocumentCount,
ZInsightFilterCriteria,
} from "@/modules/ee/insights/experience/types/insights";
import { Insight, Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { cache } from "@formbricks/lib/cache";
@@ -8,7 +13,6 @@ import { responseCache } from "@formbricks/lib/response/cache";
import { validateInputs } from "@formbricks/lib/utils/validate";
import { ZId, ZOptionalNumber } from "@formbricks/types/common";
import { DatabaseError } from "@formbricks/types/errors";
import { TInsight, TInsightFilterCriteria, ZInsightFilterCriteria } from "@formbricks/types/insights";
export const getInsights = reactCache(
async (
@@ -16,7 +20,7 @@ export const getInsights = reactCache(
limit?: number,
offset?: number,
filterCriteria?: TInsightFilterCriteria
): Promise<TInsight[]> =>
): Promise<TInsightWithDocumentCount[]> =>
cache(
async () => {
validateInputs(
@@ -84,7 +88,7 @@ export const getInsights = reactCache(
)()
);
export const updateInsight = async (insightId: string, updates: Partial<TInsight>): Promise<void> => {
export const updateInsight = async (insightId: string, updates: Partial<Insight>): Promise<void> => {
try {
const updatedInsight = await prisma.insight.update({
where: { id: insightId },

View File

@@ -0,0 +1,21 @@
import { Insight } from "@prisma/client";
import { z } from "zod";
import { ZInsight } from "@formbricks/database/zod/insights";
export const ZInsightFilterCriteria = z.object({
documentCreatedAt: z
.object({
min: z.date().optional(),
max: z.date().optional(),
})
.optional(),
category: ZInsight.shape.category.optional(),
});
export type TInsightFilterCriteria = z.infer<typeof ZInsightFilterCriteria>;
export interface TInsightWithDocumentCount extends Insight {
_count: {
documentInsights: number;
};
}

View File

@@ -4,9 +4,7 @@
"version": "0.1.0",
"main": "./src/index.ts",
"files": [
"src",
"zod",
"zod-utils.ts"
"src"
],
"scripts": {
"clean": "rimraf .turbo node_modules",
@@ -37,7 +35,6 @@
"prisma-dbml-generator": "0.12.0",
"prisma-json-types-generator": "3.1.1",
"ts-node": "10.9.2",
"zod": "3.24.1",
"zod-prisma": "0.5.4"
"zod": "3.24.1"
}
}

View File

@@ -16,13 +16,6 @@ generator client {
// provider = "prisma-dbml-generator"
// }
generator zod {
provider = "zod-prisma"
output = "./zod"
imports = "./zod-utils"
relationModel = "default"
}
generator json {
provider = "prisma-json-types-generator"
}
@@ -117,20 +110,15 @@ model Response {
contactId String?
endingId String?
notes ResponseNote[]
/// @zod.custom(imports.ZResponseData)
/// [ResponseData]
data Json @default("{}")
/// @zod.custom(imports.ZResponseVariables)
/// [ResponseVariables]
variables Json @default("{}")
/// @zod.custom(imports.ZResponseTtc)
/// [ResponseTtc]
ttc Json @default("{}")
/// @zod.custom(imports.ZResponseMeta)
/// [ResponseMeta]
meta Json @default("{}")
tags TagsOnResponses[]
/// @zod.custom(imports.ZResponseContactAttributes)
/// [ResponseContactAttributes]
contactAttributes Json?
// singleUseId, used to prevent multiple responses
@@ -273,20 +261,15 @@ model Survey {
creator User? @relation(fields: [createdBy], references: [id])
createdBy String?
status SurveyStatus @default(draft)
/// @zod.custom(imports.ZSurveyWelcomeCard)
/// [SurveyWelcomeCard]
welcomeCard Json @default("{\"enabled\": false}")
/// @zod.custom(imports.ZSurveyQuestions)
/// [SurveyQuestions]
questions Json @default("[]")
/// @zod.custom(imports.ZSurveyEnding)
/// [SurveyEnding]
endings Json[] @default([])
thankYouCard Json? //deprecated
/// @zod.custom(imports.ZSurveyHiddenFields)
/// [SurveyHiddenFields]
hiddenFields Json @default("{\"enabled\": false}")
/// @zod.custom(imports.ZSurveyVariables)
/// [SurveyVariables]
variables Json @default("[]")
responses Response[]
@@ -294,7 +277,6 @@ model Survey {
recontactDays Int?
displayLimit Int?
triggers SurveyTrigger[]
/// @zod.custom(imports.ZSurveyInlineTriggers)
/// [SurveyInlineTriggers]
inlineTriggers Json?
attributeFilters SurveyAttributeFilter[]
@@ -304,25 +286,20 @@ model Survey {
delay Int @default(0)
runOnDate DateTime?
closeOnDate DateTime?
/// @zod.custom(imports.ZSurveyClosedMessage)
/// [SurveyClosedMessage]
surveyClosedMessage Json?
segmentId String?
segment Segment? @relation(fields: [segmentId], references: [id])
/// @zod.custom(imports.ZSurveyProjectOverwrites)
/// [SurveyProjectOverwrites]
projectOverwrites Json?
/// @zod.custom(imports.ZSurveyStyling)
/// [SurveyStyling]
styling Json?
/// @zod.custom(imports.ZSurveySingleUse)
/// [SurveySingleUse]
singleUse Json? @default("{\"enabled\": false, \"isEncrypted\": true}")
/// @zod.custom(imports.ZSurveyVerifyEmail)
/// [SurveyVerifyEmail]
verifyEmail Json? // deprecated
isVerifyEmailEnabled Boolean @default(false)
@@ -347,10 +324,8 @@ model SurveyFollowUp {
surveyId String
name String
/// [SurveyFollowUpTrigger]
/// @zod.custom(imports.ZSurveyFollowUpTrigger)
trigger Json
/// [SurveyFollowUpAction]
/// @zod.custom(imports.ZSurveyFollowUpAction)
action Json
}
@@ -368,7 +343,6 @@ model ActionClass {
description String?
type ActionType
key String?
/// @zod.custom(imports.ZActionClassNoCodeConfig)
/// [ActionClassNoCodeConfig]
noCodeConfig Json?
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
@@ -396,7 +370,6 @@ model Integration {
id String @id @default(cuid())
type IntegrationType
environmentId String
/// @zod.custom(imports.ZIntegrationConfig)
/// [IntegrationConfig]
config Json
environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
@@ -461,10 +434,8 @@ model Project {
environments Environment[]
brandColor String? // deprecated; use styling.brandColor instead
highlightBorderColor String? // deprecated
/// @zod.custom(imports.ZProjectStyling)
/// [Styling]
styling Json @default("{\"allowStyleOverwrite\":true}")
/// @zod.custom(imports.ZProjectConfig)
/// [ProjectConfig]
config Json @default("{}")
recontactDays Int @default(7)
@@ -474,7 +445,6 @@ model Project {
clickOutsideClose Boolean @default(true)
darkOverlay Boolean @default(false)
languages Language[]
/// @zod.custom(imports.ZLogo)
/// [Logo]
logo Json?
projectTeams ProjectTeam[]
@@ -490,10 +460,8 @@ model Organization {
name String
memberships Membership[]
projects Project[]
/// @zod.custom(imports.ZOrganizationBilling)
/// [OrganizationBilling]
billing Json
/// @zod.custom(imports.ZOrganizationWhitelabel)
/// [OrganizationWhitelabel]
whitelabel Json @default("{}")
invites Invite[]
@@ -640,11 +608,8 @@ model User {
invitesAccepted Invite[] @relation("inviteAcceptedBy")
role Role?
objective Objective?
/// @zod.custom(imports.ZUserNotificationSettings)
/// @zod.custom(imports.ZUserNotificationSettings)
/// [UserNotificationSettings]
notificationSettings Json @default("{}")
/// @zod.custom(imports.ZUserLocale)
/// [Locale]
locale String @default("en-US")
surveys Survey[]
@@ -667,7 +632,6 @@ model Segment {
title String
description String?
isPrivate Boolean @default(true)
/// @zod.custom(imports.ZSegmentFilters)
/// [SegmentFilter]
filters Json @default("[]")
environmentId String

View File

@@ -1,5 +1,5 @@
{
"exclude": ["node_modules", "dist", "zod"],
"exclude": ["node_modules", "dist"],
"extends": "@formbricks/config-typescript/node16.json",
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "tsup.config.ts"]
}

View File

@@ -1,27 +0,0 @@
/* eslint-disable import/no-relative-packages -- required for importing types */
import { z } from "zod";
export const ZActionProperties = z.record(z.string());
export { ZActionClassNoCodeConfig } from "../types/action-classes";
export { ZIntegrationConfig } from "../types/integration";
export { ZResponseData, ZResponseMeta, ZResponseTtc } from "../types/responses";
export {
ZSurveyWelcomeCard,
ZSurveyQuestions,
ZSurveyHiddenFields,
ZSurveyVariables,
ZSurveyClosedMessage,
ZSurveyProjectOverwrites,
ZSurveyStyling,
ZSurveySingleUse,
ZSurveyInlineTriggers,
ZSurveyEnding,
} from "../types/surveys/types";
export { ZSurveyFollowUpAction, ZSurveyFollowUpTrigger } from "./types/survey-follow-up";
export { ZSegmentFilters } from "../types/segment";
export { ZOrganizationBilling, ZOrganizationWhitelabel } from "../types/organizations";
export { ZUserNotificationSettings } from "../types/user";

View File

@@ -0,0 +1,12 @@
import { type Insight } from "@prisma/client";
import { z } from "zod";
export const ZInsight = z.object({
id: z.string().cuid2(),
createdAt: z.date(),
updatedAt: z.date(),
environmentId: z.string().cuid2(),
title: z.string(),
description: z.string(),
category: z.enum(["featureRequest", "complaint", "praise", "other"]),
}) satisfies z.ZodType<Insight>;

View File

@@ -1,6 +1,6 @@
import { z } from "zod";
import { ZInsight } from "@formbricks/database/zod/insights";
import { ZId } from "./common";
import { ZInsightCategory } from "./insights";
import { ZSurveyQuestionId } from "./surveys/types";
export const ZDocumentSentiment = z.enum(["positive", "negative", "neutral"]);
@@ -47,7 +47,7 @@ export const ZGenerateDocumentObjectSchema = z.object({
z.object({
title: z.string().describe("insight title, very specific"),
description: z.string().describe("very brief insight description"),
category: ZInsightCategory,
category: ZInsight.shape.category,
})
),
isSpam: z.boolean(),

View File

@@ -1,43 +0,0 @@
import { z } from "zod";
import { ZId } from "./common";
export const ZInsightCategory = z.enum(["featureRequest", "complaint", "praise", "other"]);
export type TInsightCategory = z.infer<typeof ZInsightCategory>;
export const ZInsight = z.object({
id: ZId,
createdAt: z.date(),
updatedAt: z.date(),
environmentId: ZId,
title: z.string(),
description: z.string(),
category: ZInsightCategory,
_count: z.object({
documentInsights: z.number(),
}),
});
export type TInsight = z.infer<typeof ZInsight>;
export const ZInsightCreateInput = z.object({
environmentId: ZId,
title: z.string(),
description: z.string(),
category: ZInsightCategory,
vector: z.array(z.number()).length(512),
});
export type TInsightCreateInput = z.infer<typeof ZInsightCreateInput>;
export const ZInsightFilterCriteria = z.object({
documentCreatedAt: z
.object({
min: z.date().optional(),
max: z.date().optional(),
})
.optional(),
category: ZInsightCategory.optional(),
});
export type TInsightFilterCriteria = z.infer<typeof ZInsightFilterCriteria>;

View File

@@ -1,10 +1,10 @@
/* eslint-disable no-new -- required for error */
import { type ZodIssue, z } from "zod";
import { ZSurveyFollowUp } from "@formbricks/database/types/survey-follow-up";
import { ZInsight } from "@formbricks/database/zod/insights";
import { ZActionClass, ZActionClassNoCodeConfig } from "../action-classes";
import { ZAllowedFileExtension, ZColor, ZId, ZPlacement, getZSafeUrl } from "../common";
import { ZContactAttributes } from "../contact-attribute";
import { ZInsight } from "../insights";
import { ZLanguage } from "../project";
import { ZSegment } from "../segment";
import { ZBaseStyling } from "../styling";
@@ -2371,7 +2371,13 @@ export const ZSurveyQuestionSummaryOpenText = z.object({
contactAttributes: ZContactAttributes.nullable(),
})
),
insights: z.array(ZInsight),
insights: z.array(
ZInsight.extend({
_count: z.object({
documentInsights: z.number(),
}),
})
),
insightsEnabled: z.boolean().optional(),
});

79
pnpm-lock.yaml generated
View File

@@ -831,9 +831,6 @@ importers:
zod:
specifier: 3.24.1
version: 3.24.1
zod-prisma:
specifier: 0.5.4
version: 0.5.4(decimal.js@10.4.3)(prisma@6.0.1)(zod@3.24.1)
packages/js:
dependencies:
@@ -3707,9 +3704,6 @@ packages:
prisma:
optional: true
'@prisma/debug@3.8.1':
resolution: {integrity: sha512-ft4VPTYME1UBJ7trfrBuF2w9jX1ipDy786T9fAEskNGb+y26gPDqz5fiEWc2kgHNeVdz/qTI/V3wXILRyEcgxQ==}
'@prisma/debug@5.0.0':
resolution: {integrity: sha512-3q/M/KqlQ01/HJXifU/zCNOHkoTWu24kGelMF/IBrRxm7njPqTTbwfnT1dh4JK+nuWM5/Dg1Lv00u2c0l7AHxg==}
@@ -3740,9 +3734,6 @@ packages:
'@prisma/fetch-engine@6.0.1':
resolution: {integrity: sha512-T36bWFVGeGYYSyYOj9d+O9G3sBC+pAyMC+jc45iSL63/Haq1GrYjQPgPMxrEj9m739taXrupoysRedQ+VyvM/Q==}
'@prisma/generator-helper@3.8.1':
resolution: {integrity: sha512-3zSy+XTEjmjLj6NO+/YPN1Cu7or3xA11TOoOnLRJ9G4pTT67RJXjK0L9Xy5n+3I0Xlb7xrWCgo8MvQQLMWzxPA==}
'@prisma/generator-helper@5.0.0':
resolution: {integrity: sha512-pufQ1mhoH6WzKNtzL79HZDoW4Ql3Lf8QEKVmBoW8e3Tdb50bxpYBYue5LBqp9vNW1xd1pgZO53cNiRfLX2d4Zg==}
@@ -5401,9 +5392,6 @@ packages:
svelte:
optional: true
'@ts-morph/common@0.12.3':
resolution: {integrity: sha512-4tUmeLyXJnJWvTFOKtcNJ1yh0a3SsTLi2MUoyj8iUNznFRN1ZquaNe7Oukqrnki2FzZkm0J9adCNLDZxUzvj+w==}
'@tsconfig/node10@1.0.11':
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
@@ -5449,9 +5437,6 @@ packages:
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
'@types/debug@4.1.7':
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
'@types/debug@4.1.8':
resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==}
@@ -6857,9 +6842,6 @@ packages:
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
code-block-writer@11.0.3:
resolution: {integrity: sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==}
codepage@1.15.0:
resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
engines: {node: '>=0.8'}
@@ -10567,9 +10549,6 @@ packages:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
parenthesis@3.1.8:
resolution: {integrity: sha512-KF/U8tk54BgQewkJPvB4s/US3VQY68BRDpH638+7O/n58TpnwiwnOtGIOsT2/i+M78s61BBpeC83STB88d8sqw==}
parse-asn1@5.1.7:
resolution: {integrity: sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==}
engines: {node: '>= 0.10'}
@@ -12501,9 +12480,6 @@ packages:
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
ts-morph@13.0.3:
resolution: {integrity: sha512-pSOfUMx8Ld/WUreoSzvMFQG5i9uEiWIsBYjpU9+TTASOeUa89j5HykomeqVULm1oqWtBdleI3KEFRLrlA3zGIw==}
ts-node@10.9.2:
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
hasBin: true
@@ -13349,18 +13325,6 @@ packages:
resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==}
engines: {node: '>= 10'}
zod-prisma@0.5.4:
resolution: {integrity: sha512-5Ca4Qd1a1jy1T/NqCEpbr0c+EsbjJfJ/7euEHob3zDvtUK2rTuD1Rc/vfzH8q8PtaR2TZbysD88NHmrLwpv3Xg==}
engines: {node: '>=14'}
hasBin: true
peerDependencies:
decimal.js: ^10.0.0
prisma: ^3.0.0
zod: ^3.0.0
peerDependenciesMeta:
decimal.js:
optional: true
zod-to-json-schema@3.24.1:
resolution: {integrity: sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==}
peerDependencies:
@@ -16886,12 +16850,6 @@ snapshots:
optionalDependencies:
prisma: 6.0.1
'@prisma/debug@3.8.1':
dependencies:
'@types/debug': 4.1.7
ms: 2.1.3
strip-ansi: 6.0.1
'@prisma/debug@5.0.0':
dependencies:
'@types/debug': 4.1.8
@@ -16948,13 +16906,6 @@ snapshots:
'@prisma/engines-version': 5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e
'@prisma/get-platform': 6.0.1
'@prisma/generator-helper@3.8.1':
dependencies:
'@prisma/debug': 3.8.1
'@types/cross-spawn': 6.0.2
chalk: 4.1.2
cross-spawn: 7.0.3
'@prisma/generator-helper@5.0.0':
dependencies:
'@prisma/debug': 5.0.0
@@ -19265,13 +19216,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@ts-morph/common@0.12.3':
dependencies:
fast-glob: 3.3.2
minimatch: 3.1.2
mkdirp: 1.0.4
path-browserify: 1.0.1
'@tsconfig/node10@1.0.11': {}
'@tsconfig/node12@1.0.11': {}
@@ -19323,10 +19267,6 @@ snapshots:
dependencies:
'@types/ms': 0.7.34
'@types/debug@4.1.7':
dependencies:
'@types/ms': 0.7.34
'@types/debug@4.1.8':
dependencies:
'@types/ms': 0.7.34
@@ -21023,8 +20963,6 @@ snapshots:
- '@types/react'
- '@types/react-dom'
code-block-writer@11.0.3: {}
codepage@1.15.0: {}
collapse-white-space@2.1.0: {}
@@ -25719,8 +25657,6 @@ snapshots:
dependencies:
callsites: 3.1.0
parenthesis@3.1.8: {}
parse-asn1@5.1.7:
dependencies:
asn1.js: 4.10.1
@@ -27967,11 +27903,6 @@ snapshots:
ts-interface-checker@0.1.13: {}
ts-morph@13.0.3:
dependencies:
'@ts-morph/common': 0.12.3
code-block-writer: 11.0.3
ts-node@10.9.2(@types/node@22.10.2)(typescript@5.7.2):
dependencies:
'@cspotcode/source-map-support': 0.8.1
@@ -28820,16 +28751,6 @@ snapshots:
compress-commons: 4.1.2
readable-stream: 3.6.2
zod-prisma@0.5.4(decimal.js@10.4.3)(prisma@6.0.1)(zod@3.24.1):
dependencies:
'@prisma/generator-helper': 3.8.1
parenthesis: 3.1.8
prisma: 6.0.1
ts-morph: 13.0.3
zod: 3.24.1
optionalDependencies:
decimal.js: 10.4.3
zod-to-json-schema@3.24.1(zod@3.24.1):
dependencies:
zod: 3.24.1