-
-
User
-
Response
-
Time
-
-
- {questionSummary.samples.slice(0, visibleResponses).map((response) => (
-
-
- {response.person ? (
-
-
-
- {getPersonIdentifier(response.person, response.personAttributes)}
-
-
- ) : (
-
- )}
-
-
- {response.value}
-
-
- {timeSince(new Date(response.updatedAt).toISOString())}
-
+ additionalInfo={
+ isAIEnabled && questionSummary.insightsEnabled === false ? (
+
- ))}
+ ) : undefined
+ }
+ />
+ {isInsightsEnabled && (
+
+
- {visibleResponses < questionSummary.samples.length && (
-
-
- Load more
-
-
- )}
+ )}
+
+
+ {activeTab === "insights" ? (
+
+ ) : activeTab === "responses" ? (
+ <>
+
+
+
+ User
+ Response
+ Time
+
+
+
+ {questionSummary.samples.slice(0, visibleResponses).map((response) => (
+
+
+ {response.person ? (
+
+
+
+ {getPersonIdentifier(response.person, response.personAttributes)}
+
+
+ ) : (
+
+ )}
+
+ {response.value}
+ {timeSince(new Date(response.updatedAt).toISOString())}
+
+ ))}
+
+
+ {visibleResponses < questionSummary.samples.length && (
+
+
+ Load more
+
+
+ )}
+ >
+ ) : null}
);
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/PictureChoiceSummary.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/PictureChoiceSummary.tsx
index e3e6c79a1b..052f72d215 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/PictureChoiceSummary.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/PictureChoiceSummary.tsx
@@ -3,6 +3,7 @@ import { TAttributeClass } from "@formbricks/types/attribute-classes";
import {
TI18nString,
TSurvey,
+ TSurveyQuestionId,
TSurveyQuestionSummaryPictureSelection,
TSurveyQuestionTypeEnum,
} from "@formbricks/types/surveys/types";
@@ -15,7 +16,7 @@ interface PictureChoiceSummaryProps {
survey: TSurvey;
attributeClasses: TAttributeClass[];
setFilter: (
- questionId: string,
+ questionId: TSurveyQuestionId,
label: TI18nString,
questionType: TSurveyQuestionTypeEnum,
filterValue: string,
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/QuestionSummaryHeader.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/QuestionSummaryHeader.tsx
index a4a8eafe76..5fc49bafb0 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/QuestionSummaryHeader.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/QuestionSummaryHeader.tsx
@@ -7,14 +7,14 @@ import { TSurvey, TSurveyQuestionSummary } from "@formbricks/types/surveys/types
interface HeadProps {
questionSummary: TSurveyQuestionSummary;
showResponses?: boolean;
- insights?: JSX.Element;
+ additionalInfo?: JSX.Element;
survey: TSurvey;
attributeClasses: TAttributeClass[];
}
export const QuestionSummaryHeader = ({
questionSummary,
- insights,
+ additionalInfo,
showResponses = true,
survey,
attributeClasses,
@@ -62,7 +62,7 @@ export const QuestionSummaryHeader = ({
{`${questionSummary.responseCount} Responses`}
)}
- {insights}
+ {additionalInfo}
{!questionSummary.question.required && (
Optional
)}
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/RatingSummary.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/RatingSummary.tsx
index 4c4f6fc557..55802feb91 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/RatingSummary.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/RatingSummary.tsx
@@ -5,6 +5,7 @@ import { TAttributeClass } from "@formbricks/types/attribute-classes";
import {
TI18nString,
TSurvey,
+ TSurveyQuestionId,
TSurveyQuestionSummaryRating,
TSurveyQuestionTypeEnum,
} from "@formbricks/types/surveys/types";
@@ -17,7 +18,7 @@ interface RatingSummaryProps {
survey: TSurvey;
attributeClasses: TAttributeClass[];
setFilter: (
- questionId: string,
+ questionId: TSurveyQuestionId,
label: TI18nString,
questionType: TSurveyQuestionTypeEnum,
filterValue: string,
@@ -44,7 +45,7 @@ export const RatingSummary = ({
questionSummary={questionSummary}
survey={survey}
attributeClasses={attributeClasses}
- insights={
+ additionalInfo={
{getIconBasedOnScale}
Overall: {questionSummary.average.toFixed(2)}
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ScrollToTop.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ScrollToTop.tsx
index e1f21bbecd..fa94f72118 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ScrollToTop.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/ScrollToTop.tsx
@@ -43,7 +43,7 @@ const ScrollToTop: React.FC
= ({ containerId }) => {
return (
↑
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryList.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryList.tsx
index 36d67a5071..51b545b6ab 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryList.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryList.tsx
@@ -25,7 +25,7 @@ import { toast } from "react-hot-toast";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TAttributeClass } from "@formbricks/types/attribute-classes";
import { TEnvironment } from "@formbricks/types/environment";
-import { TI18nString, TSurveySummary } from "@formbricks/types/surveys/types";
+import { TI18nString, TSurveyQuestionId, TSurveySummary } from "@formbricks/types/surveys/types";
import { TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
import { TSurvey } from "@formbricks/types/surveys/types";
import { EmptySpaceFiller } from "@formbricks/ui/components/EmptySpaceFiller";
@@ -39,6 +39,8 @@ interface SummaryListProps {
survey: TSurvey;
totalResponseCount: number;
attributeClasses: TAttributeClass[];
+ isAIEnabled: boolean;
+ documentsPerPage?: number;
}
export const SummaryList = ({
@@ -48,11 +50,13 @@ export const SummaryList = ({
survey,
totalResponseCount,
attributeClasses,
+ isAIEnabled,
+ documentsPerPage,
}: SummaryListProps) => {
const { setSelectedFilter, selectedFilter } = useResponseFilter();
const setFilter = (
- questionId: string,
+ questionId: TSurveyQuestionId,
label: TI18nString,
questionType: TSurveyQuestionTypeEnum,
filterValue: string,
@@ -126,6 +130,8 @@ export const SummaryList = ({
environmentId={environment.id}
survey={survey}
attributeClasses={attributeClasses}
+ isAIEnabled={isAIEnabled}
+ documentsPerPage={documentsPerPage}
/>
);
}
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryPage.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryPage.tsx
index 2b1d0215f9..b27cc84a62 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryPage.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryPage.tsx
@@ -48,6 +48,8 @@ interface SummaryPageProps {
user?: TUser;
totalResponseCount: number;
attributeClasses: TAttributeClass[];
+ isAIEnabled: boolean;
+ documentsPerPage?: number;
}
export const SummaryPage = ({
@@ -57,6 +59,8 @@ export const SummaryPage = ({
webAppUrl,
totalResponseCount,
attributeClasses,
+ isAIEnabled,
+ documentsPerPage,
}: SummaryPageProps) => {
const params = useParams();
const sharingKey = params.sharingKey as string;
@@ -176,6 +180,8 @@ export const SummaryPage = ({
environment={environment}
totalResponseCount={totalResponseCount}
attributeClasses={attributeClasses}
+ isAIEnabled={isAIEnabled}
+ documentsPerPage={documentsPerPage}
/>
>
);
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/insights.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/insights.ts
new file mode 100644
index 0000000000..583f36bd89
--- /dev/null
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/insights.ts
@@ -0,0 +1,74 @@
+import { documentCache } from "@/lib/cache/document";
+import { Prisma } from "@prisma/client";
+import { cache as reactCache } from "react";
+import { prisma } from "@formbricks/database";
+import { cache } from "@formbricks/lib/cache";
+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";
+
+export const getInsightsBySurveyIdQuestionId = reactCache(
+ (surveyId: string, questionId: TSurveyQuestionId, limit?: number, offset?: number): Promise =>
+ cache(
+ async () => {
+ validateInputs([surveyId, ZId], [questionId, ZSurveyQuestionId]);
+
+ limit = limit ?? INSIGHTS_PER_PAGE;
+ try {
+ const insights = await prisma.insight.findMany({
+ where: {
+ documentInsights: {
+ some: {
+ document: {
+ surveyId,
+ questionId,
+ },
+ },
+ },
+ },
+ include: {
+ _count: {
+ select: {
+ documentInsights: {
+ where: {
+ document: {
+ surveyId,
+ questionId,
+ },
+ },
+ },
+ },
+ },
+ },
+ orderBy: [
+ {
+ documentInsights: {
+ _count: "desc",
+ },
+ },
+ {
+ createdAt: "desc",
+ },
+ ],
+ take: limit ? limit : undefined,
+ skip: offset ? offset : undefined,
+ });
+
+ return insights;
+ } catch (error) {
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
+ throw new DatabaseError(error.message);
+ }
+
+ throw error;
+ }
+ },
+ [`getInsightsBySurveyIdQuestionId-${surveyId}-${limit}-${offset}`],
+ {
+ tags: [documentCache.tag.bySurveyId(surveyId)],
+ }
+ )()
+);
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/surveySummary.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/surveySummary.ts
new file mode 100644
index 0000000000..1a642588c0
--- /dev/null
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/surveySummary.ts
@@ -0,0 +1,916 @@
+import "server-only";
+import { getInsightsBySurveyIdQuestionId } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/insights";
+import { Prisma } from "@prisma/client";
+import { cache as reactCache } from "react";
+import { cache } from "@formbricks/lib/cache";
+import { displayCache } from "@formbricks/lib/display/cache";
+import { getDisplayCountBySurveyId } from "@formbricks/lib/display/service";
+import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
+import { responseCache } from "@formbricks/lib/response/cache";
+import { getResponseCountBySurveyId, getResponses } from "@formbricks/lib/response/service";
+import { surveyCache } from "@formbricks/lib/survey/cache";
+import { getSurvey } from "@formbricks/lib/survey/service";
+import { evaluateLogic, performActions } from "@formbricks/lib/surveyLogic/utils";
+import { validateInputs } from "@formbricks/lib/utils/validate";
+import { ZId } from "@formbricks/types/common";
+import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
+import {
+ TResponse,
+ TResponseData,
+ TResponseFilterCriteria,
+ TResponseVariables,
+ ZResponseFilterCriteria,
+} from "@formbricks/types/responses";
+import {
+ TSurvey,
+ TSurveyContactInfoQuestion,
+ TSurveyLanguage,
+ TSurveyMultipleChoiceQuestion,
+ TSurveyQuestion,
+ TSurveyQuestionId,
+ TSurveyQuestionSummaryAddress,
+ TSurveyQuestionSummaryDate,
+ TSurveyQuestionSummaryFileUpload,
+ TSurveyQuestionSummaryHiddenFields,
+ TSurveyQuestionSummaryMultipleChoice,
+ TSurveyQuestionSummaryOpenText,
+ TSurveyQuestionSummaryPictureSelection,
+ TSurveyQuestionSummaryRanking,
+ TSurveyQuestionSummaryRating,
+ TSurveyQuestionTypeEnum,
+ TSurveySummary,
+} from "@formbricks/types/surveys/types";
+import { convertFloatTo2Decimal } from "./utils";
+
+export const getSurveySummaryMeta = (
+ responses: TResponse[],
+ displayCount: number
+): TSurveySummary["meta"] => {
+ const completedResponses = responses.filter((response) => response.finished).length;
+
+ let ttcResponseCount = 0;
+ const ttcSum = responses.reduce((acc, response) => {
+ if (response.ttc?._total) {
+ ttcResponseCount++;
+ return acc + response.ttc._total;
+ }
+ return acc;
+ }, 0);
+ const responseCount = responses.length;
+
+ const startsPercentage = displayCount > 0 ? (responseCount / displayCount) * 100 : 0;
+ const completedPercentage = displayCount > 0 ? (completedResponses / displayCount) * 100 : 0;
+ const dropOffCount = responseCount - completedResponses;
+ const dropOffPercentage = responseCount > 0 ? (dropOffCount / responseCount) * 100 : 0;
+ const ttcAverage = ttcResponseCount > 0 ? ttcSum / ttcResponseCount : 0;
+
+ return {
+ displayCount: displayCount || 0,
+ totalResponses: responseCount,
+ startsPercentage: convertFloatTo2Decimal(startsPercentage),
+ completedResponses,
+ completedPercentage: convertFloatTo2Decimal(completedPercentage),
+ dropOffCount,
+ dropOffPercentage: convertFloatTo2Decimal(dropOffPercentage),
+ ttcAverage: convertFloatTo2Decimal(ttcAverage),
+ };
+};
+
+const evaluateLogicAndGetNextQuestionId = (
+ localSurvey: TSurvey,
+ data: TResponseData,
+ localVariables: TResponseVariables,
+ currentQuestionIndex: number,
+ currQuesTemp: TSurveyQuestion,
+ selectedLanguage: string | null
+): {
+ nextQuestionId: TSurveyQuestionId | undefined;
+ updatedSurvey: TSurvey;
+ updatedVariables: TResponseVariables;
+} => {
+ const questions = localSurvey.questions;
+
+ let updatedSurvey = { ...localSurvey };
+ let updatedVariables = { ...localVariables };
+
+ let firstJumpTarget: string | undefined;
+
+ if (currQuesTemp.logic && currQuesTemp.logic.length > 0) {
+ for (const logic of currQuesTemp.logic) {
+ if (evaluateLogic(localSurvey, data, localVariables, logic.conditions, selectedLanguage ?? "default")) {
+ const { jumpTarget, requiredQuestionIds, calculations } = performActions(
+ updatedSurvey,
+ logic.actions,
+ data,
+ updatedVariables
+ );
+
+ if (requiredQuestionIds.length > 0) {
+ updatedSurvey.questions = updatedSurvey.questions.map((q) =>
+ requiredQuestionIds.includes(q.id) ? { ...q, required: true } : q
+ );
+ }
+ updatedVariables = { ...updatedVariables, ...calculations };
+
+ if (jumpTarget && !firstJumpTarget) {
+ firstJumpTarget = jumpTarget;
+ }
+ }
+ }
+ }
+
+ // Return the first jump target if found, otherwise go to the next question
+ const nextQuestionId = firstJumpTarget || questions[currentQuestionIndex + 1]?.id || undefined;
+
+ return { nextQuestionId, updatedSurvey, updatedVariables };
+};
+
+export const getSurveySummaryDropOff = (
+ survey: TSurvey,
+ responses: TResponse[],
+ displayCount: number
+): TSurveySummary["dropOff"] => {
+ const initialTtc = survey.questions.reduce((acc: Record, question) => {
+ acc[question.id] = 0;
+ return acc;
+ }, {});
+
+ let totalTtc = { ...initialTtc };
+ let responseCounts = { ...initialTtc };
+
+ let dropOffArr = new Array(survey.questions.length).fill(0) as number[];
+ let impressionsArr = new Array(survey.questions.length).fill(0) as number[];
+ let dropOffPercentageArr = new Array(survey.questions.length).fill(0) as number[];
+
+ const surveyVariablesData = survey.variables?.reduce(
+ (acc, variable) => {
+ acc[variable.id] = variable.value;
+ return acc;
+ },
+ {} as Record
+ );
+
+ responses.forEach((response) => {
+ // Calculate total time-to-completion
+ Object.keys(totalTtc).forEach((questionId) => {
+ if (response.ttc && response.ttc[questionId]) {
+ totalTtc[questionId] += response.ttc[questionId];
+ responseCounts[questionId]++;
+ }
+ });
+
+ let localSurvey = structuredClone(survey);
+ let localResponseData: TResponseData = { ...response.data };
+ let localVariables: TResponseVariables = {
+ ...surveyVariablesData,
+ };
+
+ let currQuesIdx = 0;
+
+ while (currQuesIdx < localSurvey.questions.length) {
+ const currQues = localSurvey.questions[currQuesIdx];
+ if (!currQues) break;
+
+ // question is not answered and required
+ if (response.data[currQues.id] === undefined && currQues.required) {
+ dropOffArr[currQuesIdx]++;
+ impressionsArr[currQuesIdx]++;
+ break;
+ }
+
+ impressionsArr[currQuesIdx]++;
+
+ const { nextQuestionId, updatedSurvey, updatedVariables } = evaluateLogicAndGetNextQuestionId(
+ localSurvey,
+ localResponseData,
+ localVariables,
+ currQuesIdx,
+ currQues,
+ response.language
+ );
+
+ localSurvey = updatedSurvey;
+ localVariables = updatedVariables;
+
+ if (nextQuestionId) {
+ const nextQuesIdx = survey.questions.findIndex((q) => q.id === nextQuestionId);
+ if (!response.data[nextQuestionId] && !response.finished) {
+ dropOffArr[nextQuesIdx]++;
+ impressionsArr[nextQuesIdx]++;
+ break;
+ }
+ currQuesIdx = nextQuesIdx;
+ } else {
+ currQuesIdx++;
+ }
+ }
+ });
+
+ // Calculate the average time for each question
+ Object.keys(totalTtc).forEach((questionId) => {
+ totalTtc[questionId] =
+ responseCounts[questionId] > 0 ? totalTtc[questionId] / responseCounts[questionId] : 0;
+ });
+
+ if (!survey.welcomeCard.enabled) {
+ dropOffArr[0] = displayCount - impressionsArr[0];
+ if (impressionsArr[0] > displayCount) dropOffPercentageArr[0] = 0;
+
+ dropOffPercentageArr[0] =
+ impressionsArr[0] - displayCount >= 0
+ ? 0
+ : ((displayCount - impressionsArr[0]) / displayCount) * 100 || 0;
+
+ impressionsArr[0] = displayCount;
+ } else {
+ dropOffPercentageArr[0] = (dropOffArr[0] / impressionsArr[0]) * 100;
+ }
+
+ for (let i = 1; i < survey.questions.length; i++) {
+ if (impressionsArr[i] !== 0) {
+ dropOffPercentageArr[i] = (dropOffArr[i] / impressionsArr[i]) * 100;
+ }
+ }
+
+ const dropOff = survey.questions.map((question, index) => {
+ return {
+ questionId: question.id,
+ headline: getLocalizedValue(question.headline, "default"),
+ ttc: convertFloatTo2Decimal(totalTtc[question.id]) || 0,
+ impressions: impressionsArr[index] || 0,
+ dropOffCount: dropOffArr[index] || 0,
+ dropOffPercentage: convertFloatTo2Decimal(dropOffPercentageArr[index]) || 0,
+ };
+ });
+
+ return dropOff;
+};
+
+const getLanguageCode = (surveyLanguages: TSurveyLanguage[], languageCode: string | null) => {
+ if (!surveyLanguages?.length || !languageCode) return "default";
+ const language = surveyLanguages.find((surveyLanguage) => surveyLanguage.language.code === languageCode);
+ return language?.default ? "default" : language?.language.code || "default";
+};
+
+const checkForI18n = (response: TResponse, id: string, survey: TSurvey, languageCode: string) => {
+ const question = survey.questions.find((question) => question.id === id);
+
+ if (question?.type === "multipleChoiceMulti" || question?.type === "ranking") {
+ // Initialize an array to hold the choice values
+ let choiceValues = [] as string[];
+
+ (typeof response.data[id] === "string"
+ ? ([response.data[id]] as string[])
+ : (response.data[id] as string[])
+ )?.forEach((data) => {
+ choiceValues.push(
+ getLocalizedValue(
+ question.choices.find((choice) => choice.label[languageCode] === data)?.label,
+ "default"
+ ) || data
+ );
+ });
+
+ // Return the array of localized choice values of multiSelect multi questions
+ return choiceValues;
+ }
+
+ // Return the localized value of the choice fo multiSelect single question
+ const choice = (question as TSurveyMultipleChoiceQuestion)?.choices.find(
+ (choice) => choice.label[languageCode] === response.data[id]
+ );
+
+ return getLocalizedValue(choice?.label, "default") || response.data[id];
+};
+
+export const getQuestionSummary = async (
+ survey: TSurvey,
+ responses: TResponse[],
+ dropOff: TSurveySummary["dropOff"]
+): Promise => {
+ const VALUES_LIMIT = 50;
+ let summary: TSurveySummary["summary"] = [];
+
+ for (const question of survey.questions) {
+ switch (question.type) {
+ case TSurveyQuestionTypeEnum.OpenText: {
+ let values: TSurveyQuestionSummaryOpenText["samples"] = [];
+ responses.forEach((response) => {
+ const answer = response.data[question.id];
+ if (answer && typeof answer === "string") {
+ values.push({
+ id: response.id,
+ updatedAt: response.updatedAt,
+ value: answer,
+ person: response.person,
+ personAttributes: response.personAttributes,
+ });
+ }
+ });
+ const insights = await getInsightsBySurveyIdQuestionId(survey.id, question.id, 50);
+
+ summary.push({
+ type: question.type,
+ question,
+ responseCount: values.length,
+ samples: values.slice(0, VALUES_LIMIT),
+ insights,
+ insightsEnabled: question.insightsEnabled,
+ });
+
+ values;
+ break;
+ }
+ case TSurveyQuestionTypeEnum.MultipleChoiceSingle:
+ case TSurveyQuestionTypeEnum.MultipleChoiceMulti: {
+ let values: TSurveyQuestionSummaryMultipleChoice["choices"] = [];
+ // check last choice is others or not
+ const lastChoice = question.choices[question.choices.length - 1];
+ const isOthersEnabled = lastChoice.id === "other";
+
+ const questionChoices = question.choices.map((choice) => getLocalizedValue(choice.label, "default"));
+ if (isOthersEnabled) {
+ questionChoices.pop();
+ }
+
+ const choiceCountMap = questionChoices.reduce((acc: Record, choice) => {
+ acc[choice] = 0;
+ return acc;
+ }, {});
+
+ const otherValues: TSurveyQuestionSummaryMultipleChoice["choices"][number]["others"] = [];
+ responses.forEach((response) => {
+ const responseLanguageCode = getLanguageCode(survey.languages, response.language);
+
+ const answer =
+ responseLanguageCode === "default"
+ ? response.data[question.id]
+ : checkForI18n(response, question.id, survey, responseLanguageCode);
+
+ if (Array.isArray(answer)) {
+ answer.forEach((value) => {
+ if (questionChoices.includes(value)) {
+ choiceCountMap[value]++;
+ } else {
+ otherValues.push({
+ value,
+ person: response.person,
+ personAttributes: response.personAttributes,
+ });
+ }
+ });
+ } else if (typeof answer === "string") {
+ if (questionChoices.includes(answer)) {
+ choiceCountMap[answer]++;
+ } else {
+ otherValues.push({
+ value: answer,
+ person: response.person,
+ personAttributes: response.personAttributes,
+ });
+ }
+ }
+ });
+
+ Object.entries(choiceCountMap).map(([label, count]) => {
+ values.push({
+ value: label,
+ count,
+ percentage: responses.length > 0 ? convertFloatTo2Decimal((count / responses.length) * 100) : 0,
+ });
+ });
+
+ if (isOthersEnabled) {
+ values.push({
+ value: getLocalizedValue(lastChoice.label, "default") || "Other",
+ count: otherValues.length,
+ percentage: convertFloatTo2Decimal((otherValues.length / responses.length) * 100),
+ others: otherValues.slice(0, VALUES_LIMIT),
+ });
+ }
+ summary.push({
+ type: question.type,
+ question,
+ responseCount: responses.length,
+ choices: values,
+ });
+
+ values = [];
+ break;
+ }
+ case TSurveyQuestionTypeEnum.PictureSelection: {
+ let values: TSurveyQuestionSummaryPictureSelection["choices"] = [];
+ const choiceCountMap: Record = {};
+
+ question.choices.forEach((choice) => {
+ choiceCountMap[choice.id] = 0;
+ });
+ let totalResponseCount = 0;
+
+ responses.forEach((response) => {
+ const answer = response.data[question.id];
+ if (Array.isArray(answer)) {
+ answer.forEach((value) => {
+ totalResponseCount++;
+ choiceCountMap[value]++;
+ });
+ }
+ });
+
+ question.choices.forEach((choice) => {
+ values.push({
+ id: choice.id,
+ imageUrl: choice.imageUrl,
+ count: choiceCountMap[choice.id],
+ percentage:
+ totalResponseCount > 0
+ ? convertFloatTo2Decimal((choiceCountMap[choice.id] / totalResponseCount) * 100)
+ : 0,
+ });
+ });
+
+ summary.push({
+ type: question.type,
+ question,
+ responseCount: totalResponseCount,
+ choices: values,
+ });
+
+ values = [];
+ break;
+ }
+ case TSurveyQuestionTypeEnum.Rating: {
+ let values: TSurveyQuestionSummaryRating["choices"] = [];
+ const choiceCountMap: Record = {};
+ const range = question.range;
+
+ for (let i = 1; i <= range; i++) {
+ choiceCountMap[i] = 0;
+ }
+
+ let totalResponseCount = 0;
+ let totalRating = 0;
+ let dismissed = 0;
+
+ responses.forEach((response) => {
+ const answer = response.data[question.id];
+ if (typeof answer === "number") {
+ totalResponseCount++;
+ choiceCountMap[answer]++;
+ totalRating += answer;
+ } else if (response.ttc && response.ttc[question.id] > 0) {
+ dismissed++;
+ }
+ });
+
+ Object.entries(choiceCountMap).map(([label, count]) => {
+ values.push({
+ rating: parseInt(label),
+ count,
+ percentage:
+ totalResponseCount > 0 ? convertFloatTo2Decimal((count / totalResponseCount) * 100) : 0,
+ });
+ });
+
+ summary.push({
+ type: question.type,
+ question,
+ average: convertFloatTo2Decimal(totalRating / totalResponseCount) || 0,
+ responseCount: totalResponseCount,
+ choices: values,
+ dismissed: {
+ count: dismissed,
+ },
+ });
+
+ values = [];
+ break;
+ }
+ case TSurveyQuestionTypeEnum.NPS: {
+ const data = {
+ promoters: 0,
+ passives: 0,
+ detractors: 0,
+ dismissed: 0,
+ total: 0,
+ score: 0,
+ };
+
+ responses.forEach((response) => {
+ const value = response.data[question.id];
+ if (typeof value === "number") {
+ data.total++;
+ if (value >= 9) {
+ data.promoters++;
+ } else if (value >= 7) {
+ data.passives++;
+ } else {
+ data.detractors++;
+ }
+ } else if (response.ttc && response.ttc[question.id] > 0) {
+ data.total++;
+ data.dismissed++;
+ }
+ });
+
+ data.score =
+ data.total > 0
+ ? convertFloatTo2Decimal(((data.promoters - data.detractors) / data.total) * 100)
+ : 0;
+
+ summary.push({
+ type: question.type,
+ question,
+ responseCount: data.total,
+ total: data.total,
+ score: data.score,
+ promoters: {
+ count: data.promoters,
+ percentage: data.total > 0 ? convertFloatTo2Decimal((data.promoters / data.total) * 100) : 0,
+ },
+ passives: {
+ count: data.passives,
+ percentage: data.total > 0 ? convertFloatTo2Decimal((data.passives / data.total) * 100) : 0,
+ },
+ detractors: {
+ count: data.detractors,
+ percentage: data.total > 0 ? convertFloatTo2Decimal((data.detractors / data.total) * 100) : 0,
+ },
+ dismissed: {
+ count: data.dismissed,
+ percentage: data.total > 0 ? convertFloatTo2Decimal((data.dismissed / data.total) * 100) : 0,
+ },
+ });
+ break;
+ }
+ case TSurveyQuestionTypeEnum.CTA: {
+ const data = {
+ clicked: 0,
+ dismissed: 0,
+ };
+
+ responses.forEach((response) => {
+ const value = response.data[question.id];
+ if (value === "clicked") {
+ data.clicked++;
+ } else if (value === "dismissed") {
+ data.dismissed++;
+ }
+ });
+
+ const totalResponses = data.clicked + data.dismissed;
+ const idx = survey.questions.findIndex((q) => q.id === question.id);
+ const impressions = dropOff[idx].impressions;
+
+ summary.push({
+ type: question.type,
+ question,
+ impressionCount: impressions,
+ clickCount: data.clicked,
+ skipCount: data.dismissed,
+ responseCount: totalResponses,
+ ctr: {
+ count: data.clicked,
+ percentage: impressions > 0 ? convertFloatTo2Decimal((data.clicked / impressions) * 100) : 0,
+ },
+ });
+ break;
+ }
+ case TSurveyQuestionTypeEnum.Consent: {
+ const data = {
+ accepted: 0,
+ dismissed: 0,
+ };
+
+ responses.forEach((response) => {
+ const value = response.data[question.id];
+ if (value === "accepted") {
+ data.accepted++;
+ } else if (response.ttc && response.ttc[question.id] > 0) {
+ data.dismissed++;
+ }
+ });
+
+ const totalResponses = data.accepted + data.dismissed;
+
+ summary.push({
+ type: question.type,
+ question,
+ responseCount: totalResponses,
+ accepted: {
+ count: data.accepted,
+ percentage:
+ totalResponses > 0 ? convertFloatTo2Decimal((data.accepted / totalResponses) * 100) : 0,
+ },
+ dismissed: {
+ count: data.dismissed,
+ percentage:
+ totalResponses > 0 ? convertFloatTo2Decimal((data.dismissed / totalResponses) * 100) : 0,
+ },
+ });
+
+ break;
+ }
+ case TSurveyQuestionTypeEnum.Date: {
+ let values: TSurveyQuestionSummaryDate["samples"] = [];
+ responses.forEach((response) => {
+ const answer = response.data[question.id];
+ if (answer && typeof answer === "string") {
+ values.push({
+ id: response.id,
+ updatedAt: response.updatedAt,
+ value: answer,
+ person: response.person,
+ personAttributes: response.personAttributes,
+ });
+ }
+ });
+
+ summary.push({
+ type: question.type,
+ question,
+ responseCount: values.length,
+ samples: values.slice(0, VALUES_LIMIT),
+ });
+
+ values = [];
+ break;
+ }
+ case TSurveyQuestionTypeEnum.FileUpload: {
+ let values: TSurveyQuestionSummaryFileUpload["files"] = [];
+ responses.forEach((response) => {
+ const answer = response.data[question.id];
+ if (Array.isArray(answer)) {
+ values.push({
+ id: response.id,
+ updatedAt: response.updatedAt,
+ value: answer,
+ person: response.person,
+ personAttributes: response.personAttributes,
+ });
+ }
+ });
+
+ summary.push({
+ type: question.type,
+ question,
+ responseCount: values.length,
+ files: values.slice(0, VALUES_LIMIT),
+ });
+
+ values = [];
+ break;
+ }
+ case TSurveyQuestionTypeEnum.Cal: {
+ const data = {
+ booked: 0,
+ skipped: 0,
+ };
+
+ responses.forEach((response) => {
+ const value = response.data[question.id];
+ if (value === "booked") {
+ data.booked++;
+ } else if (response.ttc && response.ttc[question.id] > 0) {
+ data.skipped++;
+ }
+ });
+ const totalResponses = data.booked + data.skipped;
+
+ summary.push({
+ type: question.type,
+ question,
+ responseCount: totalResponses,
+ booked: {
+ count: data.booked,
+ percentage: totalResponses > 0 ? convertFloatTo2Decimal((data.booked / totalResponses) * 100) : 0,
+ },
+ skipped: {
+ count: data.skipped,
+ percentage:
+ totalResponses > 0 ? convertFloatTo2Decimal((data.skipped / totalResponses) * 100) : 0,
+ },
+ });
+
+ break;
+ }
+ case TSurveyQuestionTypeEnum.Matrix: {
+ const rows = question.rows.map((row) => getLocalizedValue(row, "default"));
+ const columns = question.columns.map((column) => getLocalizedValue(column, "default"));
+ let totalResponseCount = 0;
+
+ // Initialize count object
+ const countMap: Record = rows.reduce((acc, row) => {
+ acc[row] = columns.reduce((colAcc, col) => {
+ colAcc[col] = 0;
+ return colAcc;
+ }, {});
+ return acc;
+ }, {});
+
+ responses.forEach((response) => {
+ const selectedResponses = response.data[question.id] as Record;
+ const responseLanguageCode = getLanguageCode(survey.languages, response.language);
+ if (selectedResponses) {
+ totalResponseCount++;
+ question.rows.forEach((row) => {
+ const localizedRow = getLocalizedValue(row, responseLanguageCode);
+ const colValue = question.columns.find((column) => {
+ return getLocalizedValue(column, responseLanguageCode) === selectedResponses[localizedRow];
+ });
+ const colValueInDefaultLanguage = getLocalizedValue(colValue, "default");
+ if (colValueInDefaultLanguage && columns.includes(colValueInDefaultLanguage)) {
+ countMap[getLocalizedValue(row, "default")][colValueInDefaultLanguage] += 1;
+ }
+ });
+ }
+ });
+
+ const matrixSummary = rows.map((row) => {
+ let totalResponsesForRow = 0;
+ columns.forEach((col) => {
+ totalResponsesForRow += countMap[row][col];
+ });
+
+ const columnPercentages = columns.reduce((acc, col) => {
+ const count = countMap[row][col];
+ const percentage =
+ totalResponsesForRow > 0 ? ((count / totalResponsesForRow) * 100).toFixed(2) : "0.00";
+ acc[col] = percentage;
+ return acc;
+ }, {});
+
+ return { rowLabel: row, columnPercentages, totalResponsesForRow };
+ });
+
+ summary.push({
+ type: question.type,
+ question,
+ responseCount: totalResponseCount,
+ data: matrixSummary,
+ });
+ break;
+ }
+ case TSurveyQuestionTypeEnum.Address:
+ case TSurveyQuestionTypeEnum.ContactInfo: {
+ let values: TSurveyQuestionSummaryAddress["samples"] = [];
+ responses.forEach((response) => {
+ const answer = response.data[question.id];
+ if (Array.isArray(answer) && answer.length > 0) {
+ values.push({
+ id: response.id,
+ updatedAt: response.updatedAt,
+ value: answer,
+ person: response.person,
+ personAttributes: response.personAttributes,
+ });
+ }
+ });
+
+ summary.push({
+ type: question.type as TSurveyQuestionTypeEnum.ContactInfo,
+ question: question as TSurveyContactInfoQuestion,
+ responseCount: values.length,
+ samples: values.slice(0, VALUES_LIMIT),
+ });
+
+ values = [];
+ break;
+ }
+ case TSurveyQuestionTypeEnum.Ranking: {
+ let values: TSurveyQuestionSummaryRanking["choices"] = [];
+ const questionChoices = question.choices.map((choice) => getLocalizedValue(choice.label, "default"));
+ let totalResponseCount = 0;
+ const choiceRankSums: Record = {};
+ const choiceCountMap: Record = {};
+ questionChoices.forEach((choice) => {
+ choiceRankSums[choice] = 0;
+ choiceCountMap[choice] = 0;
+ });
+
+ responses.forEach((response) => {
+ const responseLanguageCode = getLanguageCode(survey.languages, response.language);
+
+ const answer =
+ responseLanguageCode === "default"
+ ? response.data[question.id]
+ : checkForI18n(response, question.id, survey, responseLanguageCode);
+
+ if (Array.isArray(answer)) {
+ totalResponseCount++;
+ answer.forEach((value, index) => {
+ const ranking = index + 1; // Calculate ranking based on index
+ if (questionChoices.includes(value)) {
+ choiceRankSums[value] += ranking;
+ choiceCountMap[value]++;
+ }
+ });
+ }
+ });
+
+ questionChoices.forEach((choice) => {
+ const count = choiceCountMap[choice];
+ const avgRanking = count > 0 ? choiceRankSums[choice] / count : 0;
+ values.push({
+ value: choice,
+ count,
+ avgRanking: convertFloatTo2Decimal(avgRanking),
+ });
+ });
+
+ summary.push({
+ type: question.type,
+ question,
+ responseCount: totalResponseCount,
+ choices: values,
+ });
+
+ break;
+ }
+ }
+ }
+
+ survey.hiddenFields?.fieldIds?.forEach((hiddenFieldId) => {
+ let values: TSurveyQuestionSummaryHiddenFields["samples"] = [];
+ responses.forEach((response) => {
+ const answer = response.data[hiddenFieldId];
+ if (answer && typeof answer === "string") {
+ values.push({
+ updatedAt: response.updatedAt,
+ value: answer,
+ person: response.person,
+ personAttributes: response.personAttributes,
+ });
+ }
+ });
+
+ summary.push({
+ type: "hiddenField",
+ id: hiddenFieldId,
+ responseCount: values.length,
+ samples: values.slice(0, VALUES_LIMIT),
+ });
+
+ values = [];
+ });
+
+ return summary;
+};
+
+export const getSurveySummary = reactCache(
+ (surveyId: string, filterCriteria?: TResponseFilterCriteria): Promise =>
+ cache(
+ async () => {
+ validateInputs([surveyId, ZId], [filterCriteria, ZResponseFilterCriteria.optional()]);
+
+ try {
+ const survey = await getSurvey(surveyId);
+ if (!survey) {
+ throw new ResourceNotFoundError("Survey", surveyId);
+ }
+
+ const batchSize = 3000;
+ const totalResponseCount = await getResponseCountBySurveyId(surveyId);
+ const filteredResponseCount = await getResponseCountBySurveyId(surveyId, filterCriteria);
+
+ const hasFilter = totalResponseCount !== filteredResponseCount;
+
+ const pages = Math.ceil(filteredResponseCount / batchSize);
+
+ const responsesArray = await Promise.all(
+ Array.from({ length: pages }, (_, i) => {
+ return getResponses(surveyId, batchSize, i * batchSize, filterCriteria);
+ })
+ );
+ const responses = responsesArray.flat();
+
+ const responseIds = hasFilter ? responses.map((response) => response.id) : [];
+
+ const displayCount = await getDisplayCountBySurveyId(surveyId, {
+ createdAt: filterCriteria?.createdAt,
+ ...(hasFilter && { responseIds }),
+ });
+
+ const dropOff = getSurveySummaryDropOff(survey, responses, displayCount);
+ const meta = getSurveySummaryMeta(responses, displayCount);
+ const questionWiseSummary = await getQuestionSummary(survey, responses, dropOff);
+
+ return { meta, dropOff, summary: questionWiseSummary };
+ } catch (error) {
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
+ throw new DatabaseError(error.message);
+ }
+
+ throw error;
+ }
+ },
+ [`getSurveySummary-${surveyId}-${JSON.stringify(filterCriteria)}`],
+ {
+ tags: [
+ surveyCache.tag.byId(surveyId),
+ responseCache.tag.bySurveyId(surveyId),
+ displayCache.tag.bySurveyId(surveyId),
+ ],
+ }
+ )()
+);
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils.ts
index 044b038339..59ba00c0d7 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils.ts
@@ -1,14 +1,18 @@
-import { TSurvey, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
+import { TSurvey, TSurveyQuestionId, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
export const convertFloatToNDecimal = (num: number, N: number = 2) => {
return Math.round(num * Math.pow(10, N)) / Math.pow(10, N);
};
+export const convertFloatTo2Decimal = (num: number) => {
+ return Math.round(num * 100) / 100;
+};
+
export const constructToastMessage = (
questionType: TSurveyQuestionTypeEnum,
filterValue: string,
survey: TSurvey,
- questionId: string,
+ questionId: TSurveyQuestionId,
filterComboBoxValue?: string | string[]
) => {
const questionIdx = survey.questions.findIndex((question) => question.id === questionId);
@@ -20,3 +24,12 @@ export const constructToastMessage = (
return `Added filter for responses where answer to question ${questionIdx + 1} ${filterValue} ${Array.isArray(filterComboBoxValue) ? filterComboBoxValue.join(",") : filterComboBoxValue}`;
}
};
+
+export const needsInsightsGeneration = (survey: TSurvey): boolean => {
+ const openTextQuestions = survey.questions.filter((question) => question.type === "openText");
+ const questionWithoutInsightsEnabled = openTextQuestions.some(
+ (question) => question.type === "openText" && typeof question.insightsEnabled === "undefined"
+ );
+
+ return openTextQuestions.length > 0 && questionWithoutInsightsEnabled;
+};
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/page.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/page.tsx
index fa76bdce4d..3ceacc7e87 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/page.tsx
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/page.tsx
@@ -1,11 +1,18 @@
import { SurveyAnalysisNavigation } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/components/SurveyAnalysisNavigation";
+import { EnableInsightsBanner } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/EnableInsightsBanner";
import { SummaryPage } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SummaryPage";
import { SurveyAnalysisCTA } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/components/SurveyAnalysisCTA";
+import { needsInsightsGeneration } from "@/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/utils";
+import { getIsAIEnabled } from "@/app/lib/utils";
import { getServerSession } from "next-auth";
import { notFound } from "next/navigation";
import { getAttributeClasses } from "@formbricks/lib/attributeClass/service";
import { authOptions } from "@formbricks/lib/authOptions";
-import { WEBAPP_URL } from "@formbricks/lib/constants";
+import {
+ DOCUMENTS_PER_PAGE,
+ MAX_RESPONSES_FOR_INSIGHT_GENERATION,
+ WEBAPP_URL,
+} from "@formbricks/lib/constants";
import { getEnvironment } from "@formbricks/lib/environment/service";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";
@@ -60,6 +67,11 @@ const Page = async ({ params }) => {
const totalResponseCount = await getResponseCountBySurveyId(params.surveyId);
const { isViewer } = getAccessFlags(currentUserMembership?.role);
+ // I took this out cause it's cloud only right?
+ // const { active: isEnterpriseEdition } = await getEnterpriseLicense();
+
+ const isAIEnabled = await getIsAIEnabled(organization);
+ const shouldGenerateInsights = needsInsightsGeneration(survey);
return (
@@ -74,6 +86,13 @@ const Page = async ({ params }) => {
user={user}
/>
}>
+ {isAIEnabled && shouldGenerateInsights && (
+
+ )}
{
user={user}
totalResponseCount={totalResponseCount}
attributeClasses={attributeClasses}
+ isAIEnabled={isAIEnabled}
+ documentsPerPage={DOCUMENTS_PER_PAGE}
/>
);
diff --git a/apps/web/app/(ee)/api/billing/stripe-webhook/lib/checkoutSessionCompleted.ts b/apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/checkoutSessionCompleted.ts
similarity index 100%
rename from apps/web/app/(ee)/api/billing/stripe-webhook/lib/checkoutSessionCompleted.ts
rename to apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/checkoutSessionCompleted.ts
diff --git a/apps/web/app/(ee)/api/billing/stripe-webhook/lib/constants.ts b/apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/constants.ts
similarity index 100%
rename from apps/web/app/(ee)/api/billing/stripe-webhook/lib/constants.ts
rename to apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/constants.ts
diff --git a/apps/web/app/(ee)/api/billing/stripe-webhook/lib/createCustomerPortalSession.ts b/apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/createCustomerPortalSession.ts
similarity index 100%
rename from apps/web/app/(ee)/api/billing/stripe-webhook/lib/createCustomerPortalSession.ts
rename to apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/createCustomerPortalSession.ts
diff --git a/apps/web/app/(ee)/api/billing/stripe-webhook/lib/createSubscription.ts b/apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/createSubscription.ts
similarity index 100%
rename from apps/web/app/(ee)/api/billing/stripe-webhook/lib/createSubscription.ts
rename to apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/createSubscription.ts
diff --git a/apps/web/app/(ee)/api/billing/stripe-webhook/lib/invoiceFinalized.ts b/apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/invoiceFinalized.ts
similarity index 100%
rename from apps/web/app/(ee)/api/billing/stripe-webhook/lib/invoiceFinalized.ts
rename to apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/invoiceFinalized.ts
diff --git a/apps/web/app/(ee)/api/billing/stripe-webhook/lib/isSubscriptionCancelled.ts b/apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/isSubscriptionCancelled.ts
similarity index 100%
rename from apps/web/app/(ee)/api/billing/stripe-webhook/lib/isSubscriptionCancelled.ts
rename to apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/isSubscriptionCancelled.ts
diff --git a/apps/web/app/(ee)/api/billing/stripe-webhook/lib/stripeWebhook.ts b/apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/stripeWebhook.ts
similarity index 100%
rename from apps/web/app/(ee)/api/billing/stripe-webhook/lib/stripeWebhook.ts
rename to apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/stripeWebhook.ts
diff --git a/apps/web/app/(ee)/api/billing/stripe-webhook/lib/subscriptionCreatedOrUpdated.ts b/apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/subscriptionCreatedOrUpdated.ts
similarity index 100%
rename from apps/web/app/(ee)/api/billing/stripe-webhook/lib/subscriptionCreatedOrUpdated.ts
rename to apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/subscriptionCreatedOrUpdated.ts
diff --git a/apps/web/app/(ee)/api/billing/stripe-webhook/lib/subscriptionDeleted.ts b/apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/subscriptionDeleted.ts
similarity index 100%
rename from apps/web/app/(ee)/api/billing/stripe-webhook/lib/subscriptionDeleted.ts
rename to apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/lib/subscriptionDeleted.ts
diff --git a/apps/web/app/(ee)/api/billing/stripe-webhook/route.ts b/apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/route.ts
similarity index 100%
rename from apps/web/app/(ee)/api/billing/stripe-webhook/route.ts
rename to apps/web/app/(ee)/(billing)/api/billing/stripe-webhook/route.ts
diff --git a/apps/web/app/(ee)/(billing)/environments/[environmentId]/layout.tsx b/apps/web/app/(ee)/(billing)/environments/[environmentId]/layout.tsx
new file mode 100644
index 0000000000..b264259515
--- /dev/null
+++ b/apps/web/app/(ee)/(billing)/environments/[environmentId]/layout.tsx
@@ -0,0 +1,3 @@
+import EnvLayout from "../../../../(app)/environments/[environmentId]/layout";
+
+export default EnvLayout;
diff --git a/apps/web/app/(ee)/environments/[environmentId]/settings/(organization)/billing/actions.ts b/apps/web/app/(ee)/(billing)/environments/[environmentId]/settings/(organization)/billing/actions.ts
similarity index 88%
rename from apps/web/app/(ee)/environments/[environmentId]/settings/(organization)/billing/actions.ts
rename to apps/web/app/(ee)/(billing)/environments/[environmentId]/settings/(organization)/billing/actions.ts
index 277646de9e..221bd4ce96 100644
--- a/apps/web/app/(ee)/environments/[environmentId]/settings/(organization)/billing/actions.ts
+++ b/apps/web/app/(ee)/(billing)/environments/[environmentId]/settings/(organization)/billing/actions.ts
@@ -1,8 +1,8 @@
"use server";
-import { createCustomerPortalSession } from "@/app/(ee)/api/billing/stripe-webhook/lib/createCustomerPortalSession";
-import { createSubscription } from "@/app/(ee)/api/billing/stripe-webhook/lib/createSubscription";
-import { isSubscriptionCancelled } from "@/app/(ee)/api/billing/stripe-webhook/lib/isSubscriptionCancelled";
+import { createCustomerPortalSession } from "@/app/(ee)/(billing)/api/billing/stripe-webhook/lib/createCustomerPortalSession";
+import { createSubscription } from "@/app/(ee)/(billing)/api/billing/stripe-webhook/lib/createSubscription";
+import { isSubscriptionCancelled } from "@/app/(ee)/(billing)/api/billing/stripe-webhook/lib/isSubscriptionCancelled";
import { z } from "zod";
import { authenticatedActionClient } from "@formbricks/lib/actionClient";
import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
diff --git a/apps/web/app/(ee)/environments/[environmentId]/settings/(organization)/billing/components/PricingTable.tsx b/apps/web/app/(ee)/(billing)/environments/[environmentId]/settings/(organization)/billing/components/PricingTable.tsx
similarity index 97%
rename from apps/web/app/(ee)/environments/[environmentId]/settings/(organization)/billing/components/PricingTable.tsx
rename to apps/web/app/(ee)/(billing)/environments/[environmentId]/settings/(organization)/billing/components/PricingTable.tsx
index 180f5aae54..494ff69195 100644
--- a/apps/web/app/(ee)/environments/[environmentId]/settings/(organization)/billing/components/PricingTable.tsx
+++ b/apps/web/app/(ee)/(billing)/environments/[environmentId]/settings/(organization)/billing/components/PricingTable.tsx
@@ -1,11 +1,11 @@
"use client";
-import { CLOUD_PRICING_DATA } from "@/app/(ee)/api/billing/stripe-webhook/lib/constants";
+import { CLOUD_PRICING_DATA } from "@/app/(ee)/(billing)/api/billing/stripe-webhook/lib/constants";
import {
isSubscriptionCancelledAction,
manageSubscriptionAction,
upgradePlanAction,
-} from "@/app/(ee)/environments/[environmentId]/settings/(organization)/billing/actions";
+} from "@/app/(ee)/(billing)/environments/[environmentId]/settings/(organization)/billing/actions";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
diff --git a/apps/web/app/(ee)/environments/[environmentId]/settings/(organization)/billing/layout.tsx b/apps/web/app/(ee)/(billing)/environments/[environmentId]/settings/(organization)/billing/layout.tsx
similarity index 100%
rename from apps/web/app/(ee)/environments/[environmentId]/settings/(organization)/billing/layout.tsx
rename to apps/web/app/(ee)/(billing)/environments/[environmentId]/settings/(organization)/billing/layout.tsx
diff --git a/apps/web/app/(ee)/environments/[environmentId]/settings/(organization)/billing/loading.tsx b/apps/web/app/(ee)/(billing)/environments/[environmentId]/settings/(organization)/billing/loading.tsx
similarity index 100%
rename from apps/web/app/(ee)/environments/[environmentId]/settings/(organization)/billing/loading.tsx
rename to apps/web/app/(ee)/(billing)/environments/[environmentId]/settings/(organization)/billing/loading.tsx
diff --git a/apps/web/app/(ee)/environments/[environmentId]/settings/(organization)/billing/page.tsx b/apps/web/app/(ee)/(billing)/environments/[environmentId]/settings/(organization)/billing/page.tsx
similarity index 100%
rename from apps/web/app/(ee)/environments/[environmentId]/settings/(organization)/billing/page.tsx
rename to apps/web/app/(ee)/(billing)/environments/[environmentId]/settings/(organization)/billing/page.tsx
diff --git a/apps/web/app/(ee)/environments/[environmentId]/layout.tsx b/apps/web/app/(ee)/environments/[environmentId]/layout.tsx
deleted file mode 100644
index 8aebc3a42c..0000000000
--- a/apps/web/app/(ee)/environments/[environmentId]/layout.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import { FormbricksClient } from "@/app/(app)/components/FormbricksClient";
-import { EnvironmentLayout } from "@/app/(app)/environments/[environmentId]/components/EnvironmentLayout";
-import EnvironmentStorageHandler from "@/app/(app)/environments/[environmentId]/components/EnvironmentStorageHandler";
-import { PosthogIdentify } from "@/app/(app)/environments/[environmentId]/components/PosthogIdentify";
-import { ResponseFilterProvider } from "@/app/(app)/environments/[environmentId]/components/ResponseFilterContext";
-import { getServerSession } from "next-auth";
-import { notFound, redirect } from "next/navigation";
-import { authOptions } from "@formbricks/lib/authOptions";
-import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth";
-import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
-import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
-import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
-import { getUser } from "@formbricks/lib/user/service";
-import { AuthorizationError } from "@formbricks/types/errors";
-import { ToasterClient } from "@formbricks/ui/components/ToasterClient";
-
-const EnvLayout = async ({ children, params }) => {
- const session = await getServerSession(authOptions);
- if (!session || !session.user) {
- return redirect(`/auth/login`);
- }
-
- const user = await getUser(session.user.id);
- if (!user) {
- throw new Error("User not found");
- }
-
- const hasAccess = await hasUserEnvironmentAccess(session.user.id, params.environmentId);
- if (!hasAccess) {
- throw new AuthorizationError("Not authorized");
- }
-
- const organization = await getOrganizationByEnvironmentId(params.environmentId);
- if (!organization) {
- throw new Error("Organization not found");
- }
- const product = await getProductByEnvironmentId(params.environmentId);
- if (!product) {
- throw new Error("Product not found");
- }
-
- const membership = await getMembershipByUserIdOrganizationId(session.user.id, organization.id);
- if (!membership) return notFound();
-
- return (
- <>
-
-
-
-
-
-
- {children}
-
-
- >
- );
-};
-
-export default EnvLayout;
diff --git a/apps/web/app/api/internal/csv-conversion/route.ts b/apps/web/app/api/(internal)/csv-conversion/route.ts
similarity index 100%
rename from apps/web/app/api/internal/csv-conversion/route.ts
rename to apps/web/app/api/(internal)/csv-conversion/route.ts
diff --git a/apps/web/app/api/internal/excel-conversion/route.ts b/apps/web/app/api/(internal)/excel-conversion/route.ts
similarity index 100%
rename from apps/web/app/api/internal/excel-conversion/route.ts
rename to apps/web/app/api/(internal)/excel-conversion/route.ts
diff --git a/apps/web/app/api/(internal)/insights/lib/document.ts b/apps/web/app/api/(internal)/insights/lib/document.ts
new file mode 100644
index 0000000000..bcbdeb9df0
--- /dev/null
+++ b/apps/web/app/api/(internal)/insights/lib/document.ts
@@ -0,0 +1,79 @@
+import { documentCache } from "@/lib/cache/document";
+import { Prisma } from "@prisma/client";
+import { embed, generateObject } from "ai";
+import { z } from "zod";
+import { prisma } from "@formbricks/database";
+import { embeddingsModel, llmModel } from "@formbricks/lib/aiModels";
+import { validateInputs } from "@formbricks/lib/utils/validate";
+import {
+ TDocument,
+ TDocumentCreateInput,
+ TGenerateDocumentObjectSchema,
+ ZDocumentCreateInput,
+ ZGenerateDocumentObjectSchema,
+} from "@formbricks/types/documents";
+import { DatabaseError } from "@formbricks/types/errors";
+
+export const createDocument = async (
+ surveyName: string,
+ documentInput: TDocumentCreateInput
+): Promise => {
+ validateInputs([surveyName, z.string()], [documentInput, ZDocumentCreateInput]);
+
+ try {
+ // Generate text embedding
+ const { embedding } = await embed({
+ model: embeddingsModel,
+ value: documentInput.text,
+ experimental_telemetry: { isEnabled: true },
+ });
+
+ // generate sentiment and insights
+ const { object } = await generateObject({
+ model: llmModel,
+ schema: ZGenerateDocumentObjectSchema,
+ system: `You are an XM researcher. You analyse a survey response (survey name, question headline & user answer) and generate insights from it. The insight title (1-3 words) should concicely answer the question, e.g. "What type of people do you think would most benefit" -> "Developers". You are very objective, for the insights split the feedback in the smallest parts possible and only use the feedback itself to draw conclusions. You must output at least one insight.`,
+ prompt: `Survey: ${surveyName}\n${documentInput.text}`,
+ temperature: 0,
+ experimental_telemetry: { isEnabled: true },
+ });
+
+ const sentiment = object.sentiment;
+ const isSpam = object.isSpam;
+
+ // create document
+ const prismaDocument = await prisma.document.create({
+ data: {
+ ...documentInput,
+ sentiment,
+ isSpam,
+ },
+ });
+
+ const document = {
+ ...prismaDocument,
+ vector: embedding,
+ };
+
+ // update document vector with the embedding
+ const vectorString = `[${embedding.join(",")}]`;
+ await prisma.$executeRaw`
+ UPDATE "Document"
+ SET "vector" = ${vectorString}::vector(512)
+ WHERE "id" = ${document.id};
+ `;
+
+ documentCache.revalidate({
+ id: document.id,
+ responseId: document.responseId,
+ questionId: document.questionId,
+ });
+
+ return { ...document, insights: object.insights, isSpam };
+ } catch (error) {
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
+ throw new DatabaseError(error.message);
+ }
+ throw error;
+ }
+};
diff --git a/apps/web/app/api/(internal)/insights/lib/insights.ts b/apps/web/app/api/(internal)/insights/lib/insights.ts
new file mode 100644
index 0000000000..07a4591acf
--- /dev/null
+++ b/apps/web/app/api/(internal)/insights/lib/insights.ts
@@ -0,0 +1,418 @@
+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 { embed } from "ai";
+import { prisma } from "@formbricks/database";
+import { embeddingsModel } from "@formbricks/lib/aiModels";
+import { getPromptText } from "@formbricks/lib/utils/ai";
+import { validateInputs } from "@formbricks/lib/utils/validate";
+import { ZId } from "@formbricks/types/common";
+import { DatabaseError } from "@formbricks/types/errors";
+import {
+ TInsight,
+ TInsightCategory,
+ TInsightCreateInput,
+ ZInsightCreateInput,
+} from "@formbricks/types/insights";
+import {
+ TSurveyQuestionId,
+ TSurveyQuestionTypeEnum,
+ TSurveyQuestions,
+ ZSurveyQuestions,
+} from "@formbricks/types/surveys/types";
+
+export const generateInsightsForSurveyResponsesConcept = async (surveyData: {
+ id: string;
+ name: string;
+ environmentId: string;
+ questions: TSurveyQuestions;
+}): Promise => {
+ const startTime = Date.now();
+ console.log(`Generating insights for survey responses: ${surveyData.id} at ${startTime}`);
+ const { id: surveyId, name, environmentId, questions } = surveyData;
+
+ validateInputs([surveyId, ZId], [environmentId, ZId], [questions, ZSurveyQuestions]);
+
+ try {
+ const openTextQuestionsWithInsights = questions.filter(
+ (question) => question.type === TSurveyQuestionTypeEnum.OpenText && question.insightsEnabled
+ );
+
+ const openTextQuestionIds = openTextQuestionsWithInsights.map((question) => question.id);
+
+ if (openTextQuestionIds.length === 0) {
+ return;
+ }
+
+ // Fetching responses
+ const batchSize = 200;
+ let skip = 0;
+ let rateLimit: number | undefined;
+ const spillover: { responseId: string; questionId: string; text: string }[] = [];
+ let allResponsesProcessed = false;
+
+ // Fetch the rate limit once, if not already set
+ if (rateLimit === undefined) {
+ const { rawResponse } = await embed({
+ model: embeddingsModel,
+ value: "Test",
+ experimental_telemetry: { isEnabled: true },
+ });
+
+ const rateLimitHeader = rawResponse?.headers?.["x-ratelimit-remaining-requests"];
+ rateLimit = rateLimitHeader ? parseInt(rateLimitHeader, 10) : undefined;
+ }
+
+ console.log(`Rate limit for embedding API: ${rateLimit}`);
+ while (!allResponsesProcessed || spillover.length > 0) {
+ // If there are any spillover documents from the previous iteration, prioritize them
+ let answersForDocumentCreation = [...spillover];
+ spillover.length = 0; // Empty the spillover array after moving contents
+
+ // Fetch new responses only if spillover is empty
+ if (answersForDocumentCreation.length === 0 && !allResponsesProcessed) {
+ const responses = await prisma.response.findMany({
+ where: {
+ surveyId,
+ documents: {
+ none: {},
+ },
+ finished: true,
+ },
+ select: {
+ id: true,
+ data: true,
+ },
+ take: batchSize,
+ skip,
+ });
+
+ if (responses.length === 0) {
+ allResponsesProcessed = true; // Mark as finished when no more responses are found
+ }
+
+ const responsesWithOpenTextAnswers = responses.filter((response) =>
+ doesResponseHasAnyOpenTextAnswer(openTextQuestionIds, response.data)
+ );
+
+ skip += batchSize - responsesWithOpenTextAnswers.length;
+
+ responsesWithOpenTextAnswers.forEach((response) => {
+ openTextQuestionsWithInsights.forEach((question) => {
+ const responseText = response.data[question.id] as string;
+ if (responseText) {
+ const text = getPromptText(question.headline.default, responseText);
+ answersForDocumentCreation.push({
+ responseId: response.id,
+ questionId: question.id,
+ text,
+ });
+ }
+ });
+ });
+ }
+
+ // Process documents only up to the rate limit
+ if (rateLimit !== undefined && rateLimit < answersForDocumentCreation.length) {
+ // Push excess documents to the spillover array
+ spillover.push(...answersForDocumentCreation.slice(rateLimit));
+ answersForDocumentCreation = answersForDocumentCreation.slice(0, rateLimit);
+ }
+
+ console.log(
+ `Processing ${answersForDocumentCreation.length} documents and spillover: ${spillover.length}`
+ );
+
+ const createDocumentPromises = answersForDocumentCreation.map((answer) => {
+ return createDocument(name, {
+ environmentId,
+ surveyId,
+ responseId: answer.responseId,
+ questionId: answer.questionId,
+ text: answer.text,
+ });
+ });
+
+ const createDocumentResults = await Promise.all(createDocumentPromises);
+ const createdDocuments = createDocumentResults.filter(Boolean);
+
+ for (const document of createdDocuments) {
+ if (document) {
+ const insightPromises: Promise[] = [];
+ const { insights, isSpam, id, environmentId } = document;
+ if (!isSpam) {
+ for (const insight of insights) {
+ if (typeof insight.title !== "string" || typeof insight.description !== "string") {
+ throw new Error("Insight title and description must be a string");
+ }
+
+ // Create or connect the insight
+ insightPromises.push(handleInsightAssignments(environmentId, id, insight));
+ }
+ await Promise.all(insightPromises);
+ }
+ }
+ }
+
+ documentCache.revalidate({
+ environmentId: environmentId,
+ surveyId: surveyId,
+ });
+ console.log(
+ `Processed ${createdDocuments.length} documents in ${(Date.now() - startTime) / 1000} seconds`
+ );
+ }
+
+ return;
+ } catch (error) {
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
+ throw new DatabaseError(error.message);
+ }
+
+ throw error;
+ }
+};
+
+export const generateInsightsForSurveyResponses = async (surveyData: {
+ id: string;
+ name: string;
+ environmentId: string;
+ questions: TSurveyQuestions;
+}): Promise => {
+ const { id: surveyId, name, environmentId, questions } = surveyData;
+
+ validateInputs([surveyId, ZId], [environmentId, ZId], [questions, ZSurveyQuestions]);
+ try {
+ const openTextQuestionsWithInsights = questions.filter(
+ (question) => question.type === TSurveyQuestionTypeEnum.OpenText && question.insightsEnabled
+ );
+
+ const openTextQuestionIds = openTextQuestionsWithInsights.map((question) => question.id);
+
+ if (openTextQuestionIds.length === 0) {
+ return;
+ }
+
+ // Fetching responses
+ const batchSize = 200;
+ let skip = 0;
+
+ const totalResponseCount = await prisma.response.count({
+ where: {
+ surveyId,
+ documents: {
+ none: {},
+ },
+ finished: true,
+ },
+ });
+
+ const pages = Math.ceil(totalResponseCount / batchSize);
+
+ for (let i = 0; i < pages; i++) {
+ const responses = await prisma.response.findMany({
+ where: {
+ surveyId,
+ documents: {
+ none: {},
+ },
+ finished: true,
+ },
+ select: {
+ id: true,
+ data: true,
+ },
+ take: batchSize,
+ skip,
+ });
+
+ const responsesWithOpenTextAnswers = responses.filter((response) =>
+ doesResponseHasAnyOpenTextAnswer(openTextQuestionIds, response.data)
+ );
+
+ skip += batchSize - responsesWithOpenTextAnswers.length;
+
+ const createDocumentPromises = responsesWithOpenTextAnswers.map((response) => {
+ return Promise.all(
+ openTextQuestionsWithInsights.map(async (question) => {
+ const responseText = response.data[question.id] as string;
+ if (!responseText) {
+ return;
+ }
+
+ const text = getPromptText(question.headline.default, responseText);
+
+ return await createDocument(name, {
+ environmentId,
+ surveyId,
+ responseId: response.id,
+ questionId: question.id,
+ text,
+ });
+ })
+ );
+ });
+
+ const createDocumentResults = await Promise.all(createDocumentPromises);
+ const createdDocuments = createDocumentResults.flat().filter(Boolean);
+
+ for (const document of createdDocuments) {
+ if (document) {
+ const insightPromises: Promise[] = [];
+ const { insights, isSpam, id, environmentId } = document;
+ if (!isSpam) {
+ for (const insight of insights) {
+ if (typeof insight.title !== "string" || typeof insight.description !== "string") {
+ throw new Error("Insight title and description must be a string");
+ }
+
+ // create or connect the insight
+ insightPromises.push(handleInsightAssignments(environmentId, id, insight));
+ }
+ await Promise.all(insightPromises);
+ }
+ }
+ }
+ documentCache.revalidate({
+ environmentId: environmentId,
+ surveyId: surveyId,
+ });
+ }
+ return;
+ } catch (error) {
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
+ throw new DatabaseError(error.message);
+ }
+
+ throw error;
+ }
+};
+
+export const getQuestionResponseReferenceId = (surveyId: string, questionId: TSurveyQuestionId) => {
+ return `${surveyId}-${questionId}`;
+};
+
+export const createInsight = async (insightGroupInput: TInsightCreateInput): Promise => {
+ validateInputs([insightGroupInput, ZInsightCreateInput]);
+
+ try {
+ // create document
+ const { vector, ...data } = insightGroupInput;
+ const prismaInsight = 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`
+ UPDATE "Insight"
+ SET "vector" = ${vectorString}::vector(512)
+ WHERE "id" = ${insight.id};
+ `;
+
+ insightCache.revalidate({
+ id: insight.id,
+ environmentId: insight.environmentId,
+ });
+
+ return insight;
+ } catch (error) {
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
+ throw new DatabaseError(error.message);
+ }
+ throw error;
+ }
+};
+
+export const handleInsightAssignments = async (
+ environmentId: string,
+ documentId: string,
+ insight: {
+ title: string;
+ description: string;
+ category: TInsightCategory;
+ }
+) => {
+ // create embedding for insight
+ const { embedding } = await embed({
+ model: embeddingsModel,
+ value: getInsightVectorText(insight.title, insight.description),
+ experimental_telemetry: { isEnabled: true },
+ });
+ // find close insight to merge it with
+ const nearestInsights = await findNearestInsights(environmentId, embedding, 1, 0.2);
+
+ if (nearestInsights.length > 0) {
+ // create a documentInsight with this insight
+ await prisma.documentInsight.create({
+ data: {
+ documentId,
+ insightId: nearestInsights[0].id,
+ },
+ });
+ documentCache.revalidate({
+ insightId: nearestInsights[0].id,
+ });
+ } else {
+ // create new insight and documentInsight
+ const newInsight = await createInsight({
+ environmentId: environmentId,
+ title: insight.title,
+ description: insight.description,
+ category: insight.category ?? "other",
+ vector: embedding,
+ });
+ // create a documentInsight with this insight
+ await prisma.documentInsight.create({
+ data: {
+ documentId,
+ insightId: newInsight.id,
+ },
+ });
+ documentCache.revalidate({
+ insightId: newInsight.id,
+ });
+ }
+};
+
+export const findNearestInsights = async (
+ environmentId: string,
+ vector: number[],
+ limit: number = 5,
+ threshold: number = 0.5
+): Promise => {
+ 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`
+ SELECT
+ id,
+ created_at AS "createdAt",
+ updated_at AS "updatedAt",
+ title,
+ description,
+ category,
+ "environmentId"
+ FROM "Insight" d
+ WHERE d."environmentId" = ${environmentId}
+ AND d."vector" <=> ${vectorString}::vector(512) <= ${threshold}
+ ORDER BY d."vector" <=> ${vectorString}::vector(512)
+ LIMIT ${limit};
+ `;
+
+ return insights;
+};
+
+export const getInsightVectorText = (title: string, description: string): string =>
+ `${title}: ${description}`;
diff --git a/apps/web/app/api/(internal)/insights/lib/utils.ts b/apps/web/app/api/(internal)/insights/lib/utils.ts
new file mode 100644
index 0000000000..bc66bc9221
--- /dev/null
+++ b/apps/web/app/api/(internal)/insights/lib/utils.ts
@@ -0,0 +1,135 @@
+import "server-only";
+import { prisma } from "@formbricks/database";
+import { CRON_SECRET, WEBAPP_URL } from "@formbricks/lib/constants";
+import { surveyCache } from "@formbricks/lib/survey/cache";
+import { doesSurveyHasOpenTextQuestion, getInsightsEnabled } from "@formbricks/lib/survey/utils";
+import { validateInputs } from "@formbricks/lib/utils/validate";
+import { ZId } from "@formbricks/types/common";
+import { ResourceNotFoundError } from "@formbricks/types/errors";
+import { TResponse } from "@formbricks/types/responses";
+import { TSurvey } from "@formbricks/types/surveys/types";
+
+export const generateInsightsForSurvey = (surveyId: string) => {
+ try {
+ return fetch(`${WEBAPP_URL}/api/insights`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "x-api-key": CRON_SECRET,
+ },
+ body: JSON.stringify({
+ surveyId,
+ }),
+ });
+ } catch (error) {
+ return {
+ ok: false,
+ error: new Error(`Error while generating insights for survey: ${error.message}`),
+ };
+ }
+};
+
+export const generateInsightsEnabledForSurveyQuestions = async (
+ surveyId: string
+): Promise<
+ | {
+ success: false;
+ }
+ | {
+ success: true;
+ survey: Pick;
+ }
+> => {
+ validateInputs([surveyId, ZId]);
+ try {
+ const survey = await prisma.survey.findUnique({
+ where: {
+ id: surveyId,
+ },
+ select: {
+ id: true,
+ name: true,
+ environmentId: true,
+ questions: true,
+ },
+ });
+
+ if (!survey) {
+ throw new ResourceNotFoundError("Survey", surveyId);
+ }
+
+ if (!doesSurveyHasOpenTextQuestion(survey.questions)) {
+ return { success: false };
+ }
+
+ const openTextQuestions = survey.questions.filter((question) => question.type === "openText");
+
+ const openTextQuestionsWithoutInsightsEnabled = openTextQuestions.filter(
+ (question) => question.type === "openText" && typeof question.insightsEnabled === "undefined"
+ );
+
+ if (openTextQuestionsWithoutInsightsEnabled.length === 0) {
+ return { success: false };
+ }
+
+ const insightsEnabledValues = await Promise.all(
+ openTextQuestions.map(async (question) => {
+ const insightsEnabled = await getInsightsEnabled(question);
+
+ return { id: question.id, insightsEnabled };
+ })
+ );
+
+ const insightsEnabledQuestionIds = insightsEnabledValues
+ .filter((value) => value.insightsEnabled)
+ .map((value) => value.id);
+
+ const updatedQuestions = survey.questions.map((question) => {
+ if (question.type === "openText") {
+ const areInsightsEnabled = insightsEnabledQuestionIds.includes(question.id);
+ return {
+ ...question,
+ insightsEnabled: areInsightsEnabled,
+ };
+ }
+
+ return question;
+ });
+
+ const updatedSurvey = await prisma.survey.update({
+ where: {
+ id: survey.id,
+ },
+ data: {
+ questions: updatedQuestions,
+ },
+ select: {
+ id: true,
+ name: true,
+ environmentId: true,
+ questions: true,
+ },
+ });
+
+ surveyCache.revalidate({ id: surveyId, environmentId: survey.environmentId });
+
+ if (insightsEnabledQuestionIds.length > 0) {
+ return { success: true, survey: updatedSurvey };
+ }
+
+ return { success: false };
+ } catch (error) {
+ console.error("Error generating insights for surveys:", error);
+ throw error;
+ }
+};
+
+export const doesResponseHasAnyOpenTextAnswer = (
+ openTextQuestionIds: string[],
+ response: TResponse["data"]
+): boolean => {
+ return openTextQuestionIds.some((questionId) => {
+ const answer = response[questionId];
+ return typeof answer === "string" && answer.length > 0;
+ });
+};
diff --git a/apps/web/app/api/(internal)/insights/route.ts b/apps/web/app/api/(internal)/insights/route.ts
new file mode 100644
index 0000000000..2c0481bf3d
--- /dev/null
+++ b/apps/web/app/api/(internal)/insights/route.ts
@@ -0,0 +1,53 @@
+// This function can run for a maximum of 300 seconds
+import {
+ // generateInsightsForSurveyResponses,
+ generateInsightsForSurveyResponsesConcept,
+} from "@/app/api/(internal)/insights/lib/insights";
+import { responses } from "@/app/lib/api/response";
+import { transformErrorToDetails } from "@/app/lib/api/validator";
+import { headers } from "next/headers";
+import { z } from "zod";
+import { CRON_SECRET } from "@formbricks/lib/constants";
+import { generateInsightsEnabledForSurveyQuestions } from "./lib/utils";
+
+export const maxDuration = 300; // This function can run for a maximum of 300 seconds
+
+const ZGenerateInsightsInput = z.object({
+ surveyId: z.string(),
+});
+
+export const POST = async (request: Request) => {
+ try {
+ // Check authentication
+ if (headers().get("x-api-key") !== CRON_SECRET) {
+ return responses.notAuthenticatedResponse();
+ }
+
+ const jsonInput = await request.json();
+ const inputValidation = ZGenerateInsightsInput.safeParse(jsonInput);
+
+ if (!inputValidation.success) {
+ console.error(inputValidation.error);
+ return responses.badRequestResponse(
+ "Fields are missing or incorrectly formatted",
+ transformErrorToDetails(inputValidation.error),
+ true
+ );
+ }
+
+ const { surveyId } = inputValidation.data;
+
+ const data = await generateInsightsEnabledForSurveyQuestions(surveyId);
+
+ if (!data.success) {
+ return responses.successResponse({ message: "No insights enabled questions found" });
+ }
+
+ // await generateInsightsForSurveyResponses(data.survey);
+ await generateInsightsForSurveyResponsesConcept(data.survey);
+
+ return responses.successResponse({ message: "Insights generated successfully" });
+ } catch (error) {
+ throw error;
+ }
+};
diff --git a/apps/web/app/api/(internal)/pipeline/lib/documents.ts b/apps/web/app/api/(internal)/pipeline/lib/documents.ts
new file mode 100644
index 0000000000..75c05e1537
--- /dev/null
+++ b/apps/web/app/api/(internal)/pipeline/lib/documents.ts
@@ -0,0 +1,107 @@
+import { handleInsightAssignments } from "@/app/api/(internal)/insights/lib/insights";
+import { documentCache } from "@/lib/cache/document";
+import { Prisma } from "@prisma/client";
+import { embed, generateObject } from "ai";
+import { z } from "zod";
+import { prisma } from "@formbricks/database";
+import { embeddingsModel, llmModel } from "@formbricks/lib/aiModels";
+import { validateInputs } from "@formbricks/lib/utils/validate";
+import {
+ TDocument,
+ TDocumentCreateInput,
+ ZDocumentCreateInput,
+ ZDocumentSentiment,
+} from "@formbricks/types/documents";
+import { DatabaseError } from "@formbricks/types/errors";
+import { ZInsightCategory } from "@formbricks/types/insights";
+
+export const createDocumentAndAssignInsight = async (
+ surveyName: string,
+ documentInput: TDocumentCreateInput
+): Promise => {
+ validateInputs([surveyName, z.string()], [documentInput, ZDocumentCreateInput]);
+
+ try {
+ // Generate text embedding
+ const { embedding } = await embed({
+ model: embeddingsModel,
+ value: documentInput.text,
+ experimental_telemetry: { isEnabled: true },
+ });
+
+ // generate sentiment and insights
+ const { object } = await generateObject({
+ model: llmModel,
+ schema: z.object({
+ sentiment: ZDocumentSentiment,
+ insights: z.array(
+ z.object({
+ title: z.string().describe("insight title, very specific"),
+ description: z.string().describe("very brief insight description"),
+ category: ZInsightCategory,
+ })
+ ),
+ isSpam: z.boolean(),
+ }),
+ system: `You are an XM researcher. You analyse a survey response (survey name, question headline & user answer) and generate insights from it. The insight title (1-3 words) should concicely answer the question, e.g. "What type of people do you think would most benefit" -> "Developers". You are very objective, for the insights split the feedback in the smallest parts possible and only use the feedback itself to draw conclusions. You must output at least one insight.`,
+ prompt: `Survey: ${surveyName}\n${documentInput.text}`,
+ temperature: 0,
+ experimental_telemetry: { isEnabled: true },
+ });
+
+ const sentiment = object.sentiment;
+ const isSpam = object.isSpam;
+ const insights = object.insights;
+
+ // create document
+ const prismaDocument = await prisma.document.create({
+ data: {
+ ...documentInput,
+ sentiment,
+ isSpam,
+ },
+ });
+
+ const document = {
+ ...prismaDocument,
+ vector: embedding,
+ };
+
+ // update document vector with the embedding
+ const vectorString = `[${embedding.join(",")}]`;
+ await prisma.$executeRaw`
+ UPDATE "Document"
+ SET "vector" = ${vectorString}::vector(512)
+ WHERE "id" = ${document.id};
+ `;
+
+ // connect or create the insights
+ const insightPromises: Promise[] = [];
+ if (!isSpam) {
+ for (const insight of insights) {
+ if (typeof insight.title !== "string" || typeof insight.description !== "string") {
+ throw new Error("Insight title and description must be a string");
+ }
+
+ // create or connect the insight
+ insightPromises.push(handleInsightAssignments(documentInput.environmentId, document.id, insight));
+ }
+ await Promise.all(insightPromises);
+ }
+
+ documentCache.revalidate({
+ id: document.id,
+ environmentId: document.environmentId,
+ surveyId: document.surveyId,
+ responseId: document.responseId,
+ questionId: document.questionId,
+ });
+
+ return document;
+ } catch (error) {
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
+ throw new DatabaseError(error.message);
+ }
+ throw error;
+ }
+};
diff --git a/apps/web/app/api/pipeline/lib/handleIntegrations.ts b/apps/web/app/api/(internal)/pipeline/lib/handleIntegrations.ts
similarity index 100%
rename from apps/web/app/api/pipeline/lib/handleIntegrations.ts
rename to apps/web/app/api/(internal)/pipeline/lib/handleIntegrations.ts
diff --git a/apps/web/app/api/pipeline/route.ts b/apps/web/app/api/(internal)/pipeline/route.ts
similarity index 74%
rename from apps/web/app/api/pipeline/route.ts
rename to apps/web/app/api/(internal)/pipeline/route.ts
index a824a140bf..4b93483dd9 100644
--- a/apps/web/app/api/pipeline/route.ts
+++ b/apps/web/app/api/(internal)/pipeline/route.ts
@@ -1,14 +1,18 @@
+import { createDocumentAndAssignInsight } from "@/app/api/(internal)/pipeline/lib/documents";
import { responses } from "@/app/lib/api/response";
import { transformErrorToDetails } from "@/app/lib/api/validator";
+import { getIsAIEnabled } from "@/app/lib/utils";
import { headers } from "next/headers";
import { prisma } from "@formbricks/database";
import { sendResponseFinishedEmail } from "@formbricks/email";
import { cache } from "@formbricks/lib/cache";
-import { CRON_SECRET } from "@formbricks/lib/constants";
+import { CRON_SECRET, IS_AI_CONFIGURED } from "@formbricks/lib/constants";
import { getIntegrations } from "@formbricks/lib/integration/service";
+import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
import { getResponseCountBySurveyId } from "@formbricks/lib/response/service";
import { getSurvey, updateSurvey } from "@formbricks/lib/survey/service";
import { convertDatesInObject } from "@formbricks/lib/time";
+import { getPromptText } from "@formbricks/lib/utils/ai";
import { webhookCache } from "@formbricks/lib/webhook/cache";
import { TPipelineTrigger, ZPipelineInput } from "@formbricks/types/pipelines";
import { TWebhook } from "@formbricks/types/webhooks";
@@ -139,6 +143,41 @@ export const POST = async (request: Request) => {
console.error("Promise rejected:", result.reason);
}
});
+
+ // generate embeddings for all open text question responses for all paid plans
+ const hasSurveyOpenTextQuestions = survey.questions.some((question) => question.type === "openText");
+ if (hasSurveyOpenTextQuestions) {
+ const isAICofigured = IS_AI_CONFIGURED;
+ if (hasSurveyOpenTextQuestions && isAICofigured) {
+ const organization = await getOrganizationByEnvironmentId(environmentId);
+ if (!organization) {
+ throw new Error("Organization not found");
+ }
+
+ const isAIEnabled = await getIsAIEnabled(organization);
+
+ if (isAIEnabled) {
+ for (const question of survey.questions) {
+ if (question.type === "openText" && question.insightsEnabled) {
+ const isQuestionAnswered =
+ response.data[question.id] !== undefined && response.data[question.id] !== "";
+ if (!isQuestionAnswered) {
+ continue;
+ }
+ const text = getPromptText(question.headline.default, response.data[question.id] as string);
+ // TODO: check if subheadline gives more context and better embeddings
+ await createDocumentAndAssignInsight(survey.name, {
+ environmentId,
+ surveyId,
+ responseId: response.id,
+ questionId: question.id,
+ text,
+ });
+ }
+ }
+ }
+ }
+ }
} else {
// Await webhook promises if no emails are sent (with allSettled to prevent early rejection)
const results = await Promise.allSettled(webhookPromises);
diff --git a/apps/web/app/lib/fetchFile.ts b/apps/web/app/lib/fetchFile.ts
index 414a128197..ddba2b4244 100755
--- a/apps/web/app/lib/fetchFile.ts
+++ b/apps/web/app/lib/fetchFile.ts
@@ -4,7 +4,7 @@ export const fetchFile = async (
) => {
const endpoint = filetype === "csv" ? "csv-conversion" : "excel-conversion";
- const response = await fetch(`/api/internal/${endpoint}`, {
+ const response = await fetch(`/api/${endpoint}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
diff --git a/apps/web/app/lib/utils.ts b/apps/web/app/lib/utils.ts
index a64262d1b0..3190568a8e 100644
--- a/apps/web/app/lib/utils.ts
+++ b/apps/web/app/lib/utils.ts
@@ -1,7 +1,27 @@
+import { getEnterpriseLicense } from "@formbricks/ee/lib/service";
+import { IS_AI_CONFIGURED, IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { TInvite } from "@formbricks/types/invites";
+import { TOrganization, TOrganizationBillingPlan } from "@formbricks/types/organizations";
export const isInviteExpired = (invite: TInvite) => {
const now = new Date();
const expiresAt = new Date(invite.expiresAt);
return now > expiresAt;
};
+
+export const getIsOrganizationAIReady = async (billingPlan: TOrganizationBillingPlan) => {
+ const { active: isEnterpriseEdition } = await getEnterpriseLicense();
+
+ // TODO: We'll remove the IS_FORMBRICKS_CLOUD check once we have the AI feature available for self-hosted customers
+ return Boolean(
+ IS_FORMBRICKS_CLOUD &&
+ IS_AI_CONFIGURED &&
+ isEnterpriseEdition &&
+ (billingPlan === "startup" || billingPlan === "scale" || billingPlan === "enterprise")
+ );
+};
+
+export const getIsAIEnabled = async (organization: TOrganization) => {
+ const isOrganizationAIReady = await getIsOrganizationAIReady(organization.billing.plan);
+ return Boolean(isOrganizationAIReady && organization.isAIEnabled);
+};
diff --git a/apps/web/app/share/[sharingKey]/(analysis)/summary/page.tsx b/apps/web/app/share/[sharingKey]/(analysis)/summary/page.tsx
index 5470a3257c..f3390a72f1 100644
--- a/apps/web/app/share/[sharingKey]/(analysis)/summary/page.tsx
+++ b/apps/web/app/share/[sharingKey]/(analysis)/summary/page.tsx
@@ -56,6 +56,7 @@ const Page = async ({ params }) => {
webAppUrl={WEBAPP_URL}
totalResponseCount={totalResponseCount}
attributeClasses={attributeClasses}
+ isAIEnabled={false} // Disable AI for sharing page for now
/>
diff --git a/apps/web/app/share/[sharingKey]/actions.ts b/apps/web/app/share/[sharingKey]/actions.ts
index 1d9add65a7..36735f0016 100644
--- a/apps/web/app/share/[sharingKey]/actions.ts
+++ b/apps/web/app/share/[sharingKey]/actions.ts
@@ -6,13 +6,13 @@ import {
getResponseCountBySurveyId,
getResponseFilteringValues,
getResponses,
- getSurveySummary,
} from "@formbricks/lib/response/service";
import { getSurveyIdByResultShareKey } from "@formbricks/lib/survey/service";
import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service";
import { ZId } from "@formbricks/types/common";
import { AuthorizationError } from "@formbricks/types/errors";
import { ZResponseFilterCriteria } from "@formbricks/types/responses";
+import { getSurveySummary } from "../../(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/surveySummary";
const ZGetResponsesBySurveySharingKeyAction = z.object({
sharingKey: z.string(),
diff --git a/apps/web/instrumentation.ts b/apps/web/instrumentation.ts
index f8a929ba42..f50fff0225 100644
--- a/apps/web/instrumentation.ts
+++ b/apps/web/instrumentation.ts
@@ -1,4 +1,20 @@
+import { registerOTel } from "@vercel/otel";
+import { LangfuseExporter } from "langfuse-vercel";
+import { env } from "@formbricks/lib/env";
+
export async function register() {
+ if (env.LANGFUSE_SECRET_KEY && env.LANGFUSE_PUBLIC_KEY && env.LANGFUSE_BASEURL) {
+ registerOTel({
+ serviceName: "formbricks-cloud-dev",
+ traceExporter: new LangfuseExporter({
+ debug: false,
+ secretKey: env.LANGFUSE_SECRET_KEY,
+ publicKey: env.LANGFUSE_PUBLIC_KEY,
+ baseUrl: env.LANGFUSE_BASEURL,
+ }),
+ });
+ }
+
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("./sentry.server.config");
}
diff --git a/apps/web/lib/cache/document.ts b/apps/web/lib/cache/document.ts
new file mode 100644
index 0000000000..65126dd9ce
--- /dev/null
+++ b/apps/web/lib/cache/document.ts
@@ -0,0 +1,66 @@
+import { revalidateTag } from "next/cache";
+import { TSurveyQuestionId } from "@formbricks/types/surveys/types";
+
+interface RevalidateProps {
+ id?: string;
+ environmentId?: string | null;
+ surveyId?: string | null;
+ responseId?: string | null;
+ questionId?: string | null;
+ insightId?: string | null;
+}
+
+export const documentCache = {
+ tag: {
+ byId(id: string) {
+ return `documents-${id}`;
+ },
+ byEnvironmentId(environmentId: string) {
+ return `environments-${environmentId}-documents`;
+ },
+ byResponseId(responseId: string) {
+ return `responses-${responseId}-documents`;
+ },
+ byResponseIdQuestionId(responseId: string, questionId: TSurveyQuestionId) {
+ return `responses-${responseId}-questions-${questionId}-documents`;
+ },
+ bySurveyId(surveyId: string) {
+ return `surveys-${surveyId}-documents`;
+ },
+ bySurveyIdQuestionId(surveyId: string, questionId: TSurveyQuestionId) {
+ return `surveys-${surveyId}-questions-${questionId}-documents`;
+ },
+ byInsightId(insightId: string) {
+ return `insights-${insightId}-documents`;
+ },
+ byInsightIdSurveyIdQuestionId(insightId: string, surveyId: string, questionId: TSurveyQuestionId) {
+ return `insights-${insightId}-surveys-${surveyId}-questions-${questionId}-documents`;
+ },
+ },
+ revalidate({ id, environmentId, surveyId, responseId, questionId, insightId }: RevalidateProps): void {
+ if (id) {
+ revalidateTag(this.tag.byId(id));
+ }
+ if (environmentId) {
+ revalidateTag(this.tag.byEnvironmentId(environmentId));
+ }
+ if (responseId) {
+ revalidateTag(this.tag.byResponseId(responseId));
+ }
+ if (surveyId) {
+ revalidateTag(this.tag.bySurveyId(surveyId));
+ }
+ if (responseId && questionId) {
+ revalidateTag(this.tag.byResponseIdQuestionId(responseId, questionId));
+ }
+ if (surveyId && questionId) {
+ revalidateTag(this.tag.bySurveyIdQuestionId(surveyId, questionId));
+ }
+ if (insightId) {
+ revalidateTag(this.tag.byInsightId(insightId));
+ }
+ if (insightId && surveyId && questionId) {
+ revalidateTag(this.tag.byInsightIdSurveyIdQuestionId(insightId, questionId));
+ }
+ },
+};
diff --git a/apps/web/lib/cache/insight.ts b/apps/web/lib/cache/insight.ts
new file mode 100644
index 0000000000..0affe5cb92
--- /dev/null
+++ b/apps/web/lib/cache/insight.ts
@@ -0,0 +1,25 @@
+import { revalidateTag } from "next/cache";
+
+interface RevalidateProps {
+ id?: string;
+ environmentId?: string;
+}
+
+export const insightCache = {
+ tag: {
+ byId(id: string) {
+ return `documentGroups-${id}`;
+ },
+ byEnvironmentId(environmentId: string) {
+ return `environments-${environmentId}-documentGroups`;
+ },
+ },
+ revalidate({ id, environmentId }: RevalidateProps): void {
+ if (id) {
+ revalidateTag(this.tag.byId(id));
+ }
+ if (environmentId) {
+ revalidateTag(this.tag.byEnvironmentId(environmentId));
+ }
+ },
+};
diff --git a/apps/web/modules/ee/insights/components/insight-sheet/actions.ts b/apps/web/modules/ee/insights/components/insight-sheet/actions.ts
new file mode 100644
index 0000000000..7d705762f8
--- /dev/null
+++ b/apps/web/modules/ee/insights/components/insight-sheet/actions.ts
@@ -0,0 +1,96 @@
+"use server";
+
+import { insightCache } from "@/lib/cache/insight";
+import {
+ getDocumentsByInsightId,
+ getDocumentsByInsightIdSurveyIdQuestionId,
+} from "@/modules/ee/insights/components/insight-sheet/lib/documents";
+import { z } from "zod";
+import { prisma } from "@formbricks/database";
+import { authenticatedActionClient } from "@formbricks/lib/actionClient";
+import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
+import { cache } from "@formbricks/lib/cache";
+import { getOrganizationIdFromEnvironmentId } from "@formbricks/lib/organization/utils";
+import { ZId } from "@formbricks/types/common";
+import { ZDocumentFilterCriteria } from "@formbricks/types/documents";
+import { ZSurveyQuestionId } from "@formbricks/types/surveys/types";
+
+const ZGetDocumentsByInsightIdSurveyIdQuestionIdAction = z.object({
+ insightId: ZId,
+ surveyId: ZId,
+ questionId: ZSurveyQuestionId,
+ limit: z.number().optional(),
+ offset: z.number().optional(),
+});
+
+const getOrganizationIdFromInsightId = async (insightId: string) =>
+ cache(
+ async () => {
+ const insight = await prisma.insight.findUnique({
+ where: {
+ id: insightId,
+ },
+ select: {
+ environmentId: true,
+ },
+ });
+
+ if (!insight) {
+ throw new Error("Insight not found");
+ }
+
+ return await getOrganizationIdFromEnvironmentId(insight.environmentId);
+ },
+ [`getInsight-${insightId}`],
+ {
+ tags: [insightCache.tag.byId(insightId)],
+ }
+ )();
+
+export const getDocumentsByInsightIdSurveyIdQuestionIdAction = authenticatedActionClient
+ .schema(ZGetDocumentsByInsightIdSurveyIdQuestionIdAction)
+ .action(async ({ ctx, parsedInput }) => {
+ const insight = await getOrganizationIdFromInsightId(parsedInput.insightId);
+
+ if (!insight) {
+ throw new Error("Insight not found");
+ }
+
+ await checkAuthorization({
+ userId: ctx.user.id,
+ organizationId: await getOrganizationIdFromInsightId(parsedInput.insightId),
+ rules: ["response", "read"],
+ });
+
+ return await getDocumentsByInsightIdSurveyIdQuestionId(
+ parsedInput.insightId,
+ parsedInput.surveyId,
+ parsedInput.questionId,
+ parsedInput.limit,
+ parsedInput.offset
+ );
+ });
+
+const ZGetDocumentsByInsightIdAction = z.object({
+ insightId: ZId,
+ limit: z.number().optional(),
+ offset: z.number().optional(),
+ filterCriteria: ZDocumentFilterCriteria.optional(),
+});
+
+export const getDocumentsByInsightIdAction = authenticatedActionClient
+ .schema(ZGetDocumentsByInsightIdAction)
+ .action(async ({ ctx, parsedInput }) => {
+ await checkAuthorization({
+ userId: ctx.user.id,
+ organizationId: await getOrganizationIdFromInsightId(parsedInput.insightId),
+ rules: ["response", "read"],
+ });
+
+ return await getDocumentsByInsightId(
+ parsedInput.insightId,
+ parsedInput.limit,
+ parsedInput.offset,
+ parsedInput.filterCriteria
+ );
+ });
diff --git a/apps/web/modules/ee/insights/components/insight-sheet/index.tsx b/apps/web/modules/ee/insights/components/insight-sheet/index.tsx
new file mode 100644
index 0000000000..b5d43ee173
--- /dev/null
+++ b/apps/web/modules/ee/insights/components/insight-sheet/index.tsx
@@ -0,0 +1,170 @@
+"use client";
+
+import { ThumbsDownIcon, ThumbsUpIcon } from "lucide-react";
+import { useCallback, useEffect, useState } from "react";
+import Markdown from "react-markdown";
+import { getFormattedErrorMessage } from "@formbricks/lib/actionClient/helper";
+import { timeSince } from "@formbricks/lib/time";
+import { TDocument, TDocumentFilterCriteria } from "@formbricks/types/documents";
+import { TInsight } from "@formbricks/types/insights";
+import { Badge } from "@formbricks/ui/components/Badge";
+import { Button } from "@formbricks/ui/components/Button";
+import { Card, CardContent, CardFooter } from "@formbricks/ui/components/Card";
+import {
+ Sheet,
+ SheetContent,
+ SheetDescription,
+ SheetHeader,
+ SheetTitle,
+} from "@formbricks/ui/components/Sheet";
+import { getDocumentsByInsightIdAction, getDocumentsByInsightIdSurveyIdQuestionIdAction } from "./actions";
+
+interface InsightSheetProps {
+ isOpen: boolean;
+ setIsOpen: (isOpen: boolean) => void;
+ insight: TInsight | null;
+ surveyId?: string;
+ questionId?: string;
+ handleFeedback: (feedback: "positive" | "negative") => void;
+ documentsFilter?: TDocumentFilterCriteria;
+ documentsPerPage?: number;
+}
+
+export const InsightSheet = ({
+ isOpen,
+ setIsOpen,
+ insight,
+ surveyId,
+ questionId,
+ handleFeedback,
+ documentsFilter,
+ documentsPerPage = 10,
+}: InsightSheetProps) => {
+ const [documents, setDocuments] = useState
([]);
+ const [page, setPage] = useState(1);
+ const [hasMore, setHasMore] = useState(true);
+
+ const fetchDocuments = useCallback(async () => {
+ if (!insight) return;
+
+ let documentsResponse;
+ if (questionId && surveyId) {
+ documentsResponse = await getDocumentsByInsightIdSurveyIdQuestionIdAction({
+ insightId: insight.id,
+ surveyId,
+ questionId,
+ limit: documentsPerPage,
+ offset: (page - 1) * documentsPerPage,
+ });
+ } else {
+ documentsResponse = await getDocumentsByInsightIdAction({
+ insightId: insight.id,
+ filterCriteria: documentsFilter,
+ limit: documentsPerPage,
+ offset: (page - 1) * documentsPerPage,
+ });
+ }
+
+ if (!documentsResponse?.data) {
+ const errorMessage = getFormattedErrorMessage(documentsResponse);
+ console.error(errorMessage);
+ return;
+ }
+
+ const fetchedDocuments = documentsResponse.data;
+
+ if (fetchedDocuments.length < documentsPerPage) {
+ setHasMore(false); // No more documents to fetch
+ }
+
+ setDocuments((prevDocuments) => [...prevDocuments, ...fetchedDocuments]);
+ }, [insight, page, surveyId, questionId, documentsFilter]);
+
+ useEffect(() => {
+ if (isOpen) {
+ setDocuments([]);
+ setPage(1);
+ setHasMore(true);
+ }
+ if (insight) {
+ fetchDocuments();
+ }
+ }, [fetchDocuments, isOpen]);
+
+ const handleFeedbackClick = (feedback: "positive" | "negative") => {
+ setIsOpen(false);
+ handleFeedback(feedback);
+ };
+
+ const loadMoreDocuments = () => {
+ if (hasMore) {
+ setPage((prevPage) => prevPage + 1);
+ }
+ };
+
+ if (!insight) {
+ return null;
+ }
+
+ return (
+ setIsOpen(v)}>
+
+
+
+ {insight.title}
+ {insight.category === "complaint" ? (
+
+ ) : insight.category === "featureRequest" ? (
+
+ ) : insight.category === "praise" ? (
+
+ ) : null}
+
+ {insight.description}
+
+
Did you find this insight helpful?
+
handleFeedbackClick("positive")}
+ />
+ handleFeedbackClick("negative")}
+ />
+
+
+
+
+ {documents.map((document) => (
+
+
+ {document.text}
+
+
+
+ Sentiment:{" "}
+ {document.sentiment === "positive" ? (
+
+ ) : document.sentiment === "neutral" ? (
+
+ ) : document.sentiment === "negative" ? (
+
+ ) : null}
+
+ {timeSince(new Date(document.createdAt).toISOString())}
+
+
+ ))}
+
+
+ {hasMore && (
+
+
+ Load more
+
+
+ )}
+
+
+ );
+};
diff --git a/apps/web/modules/ee/insights/components/insight-sheet/lib/documents.ts b/apps/web/modules/ee/insights/components/insight-sheet/lib/documents.ts
new file mode 100644
index 0000000000..f99f11405a
--- /dev/null
+++ b/apps/web/modules/ee/insights/components/insight-sheet/lib/documents.ts
@@ -0,0 +1,126 @@
+import { documentCache } from "@/lib/cache/document";
+import { insightCache } from "@/lib/cache/insight";
+import { Prisma } from "@prisma/client";
+import { cache as reactCache } from "react";
+import { z } from "zod";
+import { prisma } from "@formbricks/database";
+import { cache } from "@formbricks/lib/cache";
+import { DOCUMENTS_PER_PAGE } from "@formbricks/lib/constants";
+import { validateInputs } from "@formbricks/lib/utils/validate";
+import { ZId } from "@formbricks/types/common";
+import { TDocument, TDocumentFilterCriteria, ZDocumentFilterCriteria } from "@formbricks/types/documents";
+import { DatabaseError } from "@formbricks/types/errors";
+import { TSurveyQuestionId, ZSurveyQuestionId } from "@formbricks/types/surveys/types";
+
+export const getDocumentsByInsightId = reactCache(
+ (
+ insightId: string,
+ limit?: number,
+ offset?: number,
+ filterCriteria?: TDocumentFilterCriteria
+ ): Promise =>
+ cache(
+ async () => {
+ validateInputs(
+ [insightId, ZId],
+ [limit, z.number().optional()],
+ [offset, z.number().optional()],
+ [filterCriteria, ZDocumentFilterCriteria.optional()]
+ );
+
+ limit = limit ?? DOCUMENTS_PER_PAGE;
+ try {
+ const documents = await prisma.document.findMany({
+ where: {
+ documentInsights: {
+ some: {
+ insightId,
+ },
+ },
+ createdAt: {
+ gte: filterCriteria?.createdAt?.min,
+ lte: filterCriteria?.createdAt?.max,
+ },
+ },
+ orderBy: [
+ {
+ createdAt: "desc",
+ },
+ ],
+ take: limit ? limit : undefined,
+ skip: offset ? offset : undefined,
+ });
+
+ return documents;
+ } catch (error) {
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
+ throw new DatabaseError(error.message);
+ }
+
+ throw error;
+ }
+ },
+ [`getDocumentsByInsightId-${insightId}-${limit}-${offset}`],
+ {
+ tags: [documentCache.tag.byInsightId(insightId), insightCache.tag.byId(insightId)],
+ }
+ )()
+);
+
+export const getDocumentsByInsightIdSurveyIdQuestionId = reactCache(
+ (
+ insightId: string,
+ surveyId: string,
+ questionId: TSurveyQuestionId,
+ limit?: number,
+ offset?: number
+ ): Promise =>
+ cache(
+ async () => {
+ validateInputs(
+ [insightId, ZId],
+ [surveyId, ZId],
+ [questionId, ZSurveyQuestionId],
+ [limit, z.number().optional()],
+ [offset, z.number().optional()]
+ );
+
+ limit = limit ?? DOCUMENTS_PER_PAGE;
+ try {
+ const documents = await prisma.document.findMany({
+ where: {
+ questionId,
+ surveyId,
+ documentInsights: {
+ some: {
+ insightId,
+ },
+ },
+ },
+ orderBy: [
+ {
+ createdAt: "desc",
+ },
+ ],
+ take: limit ? limit : undefined,
+ skip: offset ? offset : undefined,
+ });
+
+ return documents;
+ } catch (error) {
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
+ throw new DatabaseError(error.message);
+ }
+
+ throw error;
+ }
+ },
+ [`getDocumentsByInsightIdSurveyIdQuestionId-${insightId}-${surveyId}-${questionId}-${limit}-${offset}`],
+ {
+ tags: [
+ documentCache.tag.byInsightIdSurveyIdQuestionId(insightId, surveyId, questionId),
+ insightCache.tag.byId(insightId),
+ ],
+ }
+ )()
+);
diff --git a/apps/web/modules/ee/insights/components/insights-view.tsx b/apps/web/modules/ee/insights/components/insights-view.tsx
new file mode 100644
index 0000000000..2ba6f8b6a3
--- /dev/null
+++ b/apps/web/modules/ee/insights/components/insights-view.tsx
@@ -0,0 +1,171 @@
+"use client";
+
+import { InsightSheet } from "@/modules/ee/insights/components/insight-sheet";
+import { UserIcon } from "lucide-react";
+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 { Badge } from "@formbricks/ui/components/Badge";
+import { Button } from "@formbricks/ui/components/Button";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@formbricks/ui/components/Table";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@formbricks/ui/components/Tabs";
+
+interface InsightViewProps {
+ insights: TInsight[];
+ questionId?: string;
+ surveyId?: string;
+ documentsFilter?: TDocumentFilterCriteria;
+ isFetching?: boolean;
+ documentsPerPage?: number;
+}
+
+export const InsightView = ({
+ insights,
+ questionId,
+ surveyId,
+ documentsFilter,
+ isFetching,
+ documentsPerPage,
+}: InsightViewProps) => {
+ const [isInsightSheetOpen, setIsInsightSheetOpen] = useState(true);
+ const [localInsights, setLocalInsights] = useState(insights);
+ const [currentInsight, setCurrentInsight] = useState(null);
+ const [activeTab, setActiveTab] = useState("all");
+ const [visibleInsights, setVisibleInsights] = useState(10);
+
+ const handleFeedback = (feedback: "positive" | "negative") => {
+ formbricks.track("AI Insight Feedback", {
+ hiddenFields: {
+ feedbackSentiment: feedback,
+ insightId: currentInsight?.id,
+ insightTitle: currentInsight?.title,
+ insightDescription: currentInsight?.description,
+ insightCategory: currentInsight?.category,
+ environmentId: currentInsight?.environmentId,
+ surveyId,
+ questionId,
+ },
+ });
+ };
+
+ const handleFilterSelect = useCallback(
+ (filterValue: string) => {
+ setActiveTab(filterValue);
+ if (filterValue === "all") {
+ setLocalInsights(insights);
+ } else {
+ setLocalInsights(
+ insights.filter((insight) => insight.category === (filterValue as TInsightCategory))
+ );
+ }
+ },
+ [insights]
+ );
+
+ useEffect(() => {
+ handleFilterSelect(activeTab);
+ }, [insights]);
+
+ const handleLoadMore = () => {
+ setVisibleInsights((prevVisibleInsights) => Math.min(prevVisibleInsights + 10, insights.length));
+ };
+
+ return (
+
+
+
+ All
+ Complaint
+ Feature Request
+ Praise
+ Other
+
+
+
+
+
+ #
+ Title
+ Description
+ Category
+
+
+
+ {isFetching ? null : insights.length === 0 ? (
+
+
+
+ No insights found. Collect more survey responses or enable insights for your existing
+ surveys to get started.
+
+
+
+ ) : localInsights.length === 0 ? (
+
+
+ No insights found for this filter.
+
+
+ ) : (
+ localInsights.slice(0, visibleInsights).map((insight) => (
+ {
+ setCurrentInsight(insight);
+ setIsInsightSheetOpen(true);
+ }}>
+
+ {insight._count.documentInsights}
+
+ {insight.title}
+ {insight.description}
+
+ {insight.category === "complaint" ? (
+
+ ) : insight.category === "featureRequest" ? (
+
+ ) : insight.category === "praise" ? (
+
+ ) : insight.category === "other" ? (
+
+ ) : null}
+
+
+ ))
+ )}
+
+
+
+
+
+ {visibleInsights < localInsights.length && (
+
+
+ Load more
+
+
+ )}
+
+
+
+ );
+};
diff --git a/apps/web/modules/ee/insights/experience/actions.ts b/apps/web/modules/ee/insights/experience/actions.ts
new file mode 100644
index 0000000000..039163bd99
--- /dev/null
+++ b/apps/web/modules/ee/insights/experience/actions.ts
@@ -0,0 +1,51 @@
+"use server";
+
+import { z } from "zod";
+import { authenticatedActionClient } from "@formbricks/lib/actionClient";
+import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
+import { getOrganizationIdFromEnvironmentId } from "@formbricks/lib/organization/utils";
+import { ZId } from "@formbricks/types/common";
+import { ZInsightFilterCriteria } from "@formbricks/types/insights";
+import { getInsights } from "./lib/insights";
+import { getStats } from "./lib/stats";
+
+const ZGetEnvironmentInsightsAction = z.object({
+ environmentId: ZId,
+ limit: z.number().optional(),
+ offset: z.number().optional(),
+ insightsFilter: ZInsightFilterCriteria.optional(),
+});
+
+export const getEnvironmentInsightsAction = authenticatedActionClient
+ .schema(ZGetEnvironmentInsightsAction)
+ .action(async ({ ctx, parsedInput }) => {
+ await checkAuthorization({
+ userId: ctx.user.id,
+ organizationId: await getOrganizationIdFromEnvironmentId(parsedInput.environmentId),
+ rules: ["response", "read"],
+ });
+
+ return await getInsights(
+ parsedInput.environmentId,
+ parsedInput.limit,
+ parsedInput.offset,
+ parsedInput.insightsFilter
+ );
+ });
+
+const ZGetStatsAction = z.object({
+ environmentId: ZId,
+ statsFrom: z.date().optional(),
+});
+
+export const getStatsAction = authenticatedActionClient
+ .schema(ZGetStatsAction)
+ .action(async ({ ctx, parsedInput }) => {
+ await checkAuthorization({
+ userId: ctx.user.id,
+ organizationId: await getOrganizationIdFromEnvironmentId(parsedInput.environmentId),
+ rules: ["response", "read"],
+ });
+
+ return await getStats(parsedInput.environmentId, parsedInput.statsFrom);
+ });
diff --git a/apps/web/modules/ee/insights/experience/components/dashboard.tsx b/apps/web/modules/ee/insights/experience/components/dashboard.tsx
new file mode 100644
index 0000000000..6d8cd45856
--- /dev/null
+++ b/apps/web/modules/ee/insights/experience/components/dashboard.tsx
@@ -0,0 +1,71 @@
+"use client";
+
+import { Greeting } from "@/modules/ee/insights/experience/components/greeting";
+import { InsightsCard } from "@/modules/ee/insights/experience/components/insights-card";
+import { ExperiencePageStats } from "@/modules/ee/insights/experience/components/stats";
+import { TemplatesCard } from "@/modules/ee/insights/experience/components/templates-card";
+import { getDateFromTimeRange } from "@/modules/ee/insights/experience/lib/utils";
+import { TStatsPeriod } from "@/modules/ee/insights/experience/types/stats";
+import { useState } from "react";
+import { TEnvironment } from "@formbricks/types/environment";
+import { TProduct } from "@formbricks/types/product";
+import { TUser } from "@formbricks/types/user";
+import { ToggleGroup, ToggleGroupItem } from "@formbricks/ui/components/ToggleGroup";
+
+interface DashboardProps {
+ user: TUser;
+ environment: TEnvironment;
+ product: TProduct;
+ insightsPerPage: number;
+ documentsPerPage: number;
+}
+
+export const Dashboard = ({
+ environment,
+ product,
+ user,
+ insightsPerPage,
+ documentsPerPage,
+}: DashboardProps) => {
+ const [statsPeriod, setStatsPeriod] = useState("week");
+ const statsFrom = getDateFromTimeRange(statsPeriod);
+ return (
+
+
+ value && setStatsPeriod(value as TStatsPeriod)}>
+
+ Today
+
+
+ This week
+
+
+ This month
+
+
+ This quarter
+
+
+ All time
+
+
+
+
+
+
+ );
+};
diff --git a/apps/web/modules/ee/insights/experience/components/greeting.tsx b/apps/web/modules/ee/insights/experience/components/greeting.tsx
new file mode 100644
index 0000000000..994f6df7ed
--- /dev/null
+++ b/apps/web/modules/ee/insights/experience/components/greeting.tsx
@@ -0,0 +1,24 @@
+"use client";
+
+import { H1 } from "@formbricks/ui/components/Typography";
+
+interface GreetingProps {
+ userName: string;
+}
+
+export const Greeting = ({ userName }: GreetingProps) => {
+ function getGreeting() {
+ const hour = new Date().getHours();
+ if (hour < 12) return "☀️ Good morning";
+ if (hour < 18) return "🌤️ Good afternoon";
+ return "🌙 Good evening";
+ }
+
+ const greeting = getGreeting();
+
+ return (
+
+ {greeting}, {userName}
+
+ );
+};
diff --git a/apps/web/modules/ee/insights/experience/components/insight-loading.tsx b/apps/web/modules/ee/insights/experience/components/insight-loading.tsx
new file mode 100644
index 0000000000..c6e91235c7
--- /dev/null
+++ b/apps/web/modules/ee/insights/experience/components/insight-loading.tsx
@@ -0,0 +1,28 @@
+export const InsightLoading = () => {
+ return (
+
+ );
+};
diff --git a/apps/web/modules/ee/insights/experience/components/insight-view.tsx b/apps/web/modules/ee/insights/experience/components/insight-view.tsx
new file mode 100644
index 0000000000..5b6daa9b3e
--- /dev/null
+++ b/apps/web/modules/ee/insights/experience/components/insight-view.tsx
@@ -0,0 +1,197 @@
+"use client";
+
+import { InsightSheet } from "@/modules/ee/insights/components/insight-sheet";
+import { UserIcon } from "lucide-react";
+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 { Badge } from "@formbricks/ui/components/Badge";
+import { Button } from "@formbricks/ui/components/Button";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@formbricks/ui/components/Table";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@formbricks/ui/components/Tabs";
+import { getEnvironmentInsightsAction } from "../actions";
+import { InsightLoading } from "./insight-loading";
+
+interface InsightViewProps {
+ statsFrom?: Date;
+ environmentId: string;
+ documentsPerPage: number;
+ insightsPerPage: number;
+}
+
+export const InsightView = ({
+ statsFrom,
+ environmentId,
+ insightsPerPage,
+ documentsPerPage,
+}: InsightViewProps) => {
+ const [insights, setInsights] = useState([]);
+ const [hasMore, setHasMore] = useState(true);
+ const [isFetching, setIsFetching] = useState(true);
+ const [isInsightSheetOpen, setIsInsightSheetOpen] = useState(false);
+ const [currentInsight, setCurrentInsight] = useState(null);
+ const [activeTab, setActiveTab] = useState("all");
+
+ const handleFeedback = (feedback: "positive" | "negative") => {
+ formbricks.track("AI Insight Feedback", {
+ hiddenFields: {
+ feedbackSentiment: feedback,
+ insightId: currentInsight?.id,
+ insightTitle: currentInsight?.title,
+ insightDescription: currentInsight?.description,
+ insightCategory: currentInsight?.category,
+ environmentId: currentInsight?.environmentId,
+ },
+ });
+ };
+
+ const insightsFilter: TInsightFilterCriteria = useMemo(
+ () => ({
+ documentCreatedAt: {
+ min: statsFrom,
+ },
+ category: activeTab === "all" ? undefined : (activeTab as TInsight["category"]),
+ }),
+ [statsFrom, activeTab]
+ );
+
+ const documentsFilter: TDocumentFilterCriteria = useMemo(
+ () => ({
+ createdAt: {
+ min: statsFrom,
+ },
+ }),
+ [statsFrom]
+ );
+
+ useEffect(() => {
+ const fetchInitialInsights = async () => {
+ setIsFetching(true);
+ setInsights([]);
+ const res = await getEnvironmentInsightsAction({
+ environmentId,
+ limit: insightsPerPage,
+ offset: 0,
+ insightsFilter,
+ });
+ if (res?.data) {
+ setInsights(res.data);
+ setHasMore(res.data.length >= insightsPerPage);
+ setIsFetching(false);
+ }
+ };
+
+ fetchInitialInsights();
+ }, [environmentId, insightsPerPage, insightsFilter]);
+
+ const fetchNextPage = useCallback(async () => {
+ if (!hasMore) return;
+ setIsFetching(true);
+ const res = await getEnvironmentInsightsAction({
+ environmentId,
+ limit: insightsPerPage,
+ offset: insights.length,
+ insightsFilter,
+ });
+ if (res?.data) {
+ setInsights((prevInsights) => [...prevInsights, ...(res.data || [])]);
+ setHasMore(res.data.length >= insightsPerPage);
+ setIsFetching(false);
+ }
+ }, [environmentId, insights, insightsPerPage, insightsFilter, hasMore]);
+
+ const handleFilterSelect = (value: string) => {
+ setActiveTab(value);
+ };
+
+ return (
+
+
+
+ All
+ Complaint
+ Feature Request
+ Praise
+ Other
+
+
+
+
+
+ #
+ Title
+ Description
+ Category
+
+
+
+ {insights.length === 0 && !isFetching ? (
+
+
+
+ No insights found. Collect more survey responses or enable insights for your existing
+ surveys to get started.
+
+
+
+ ) : (
+ insights.map((insight) => (
+ {
+ setCurrentInsight(insight);
+ setIsInsightSheetOpen(true);
+ }}>
+
+ {insight._count.documentInsights}
+
+ {insight.title}
+ {insight.description}
+
+ {insight.category === "complaint" ? (
+
+ ) : insight.category === "featureRequest" ? (
+
+ ) : insight.category === "praise" ? (
+
+ ) : (
+
+ )}
+
+
+ ))
+ )}
+
+
+ {isFetching && }
+
+
+
+ {hasMore && !isFetching && (
+
+
+ Load more
+
+
+ )}
+
+
+
+ );
+};
diff --git a/apps/web/modules/ee/insights/experience/components/insights-card.tsx b/apps/web/modules/ee/insights/experience/components/insights-card.tsx
new file mode 100644
index 0000000000..7cd9c0b044
--- /dev/null
+++ b/apps/web/modules/ee/insights/experience/components/insights-card.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@formbricks/ui/components/Card";
+import { InsightView } from "./insight-view";
+
+interface InsightsCardProps {
+ environmentId: string;
+ insightsPerPage: number;
+ productName: string;
+ statsFrom?: Date;
+ documentsPerPage: number;
+}
+
+export const InsightsCard = ({
+ statsFrom,
+ environmentId,
+ productName,
+ insightsPerPage: insightsLimit,
+ documentsPerPage,
+}: InsightsCardProps) => {
+ return (
+
+
+ Insights for {productName}
+ All the insights generated from responses across all your surveys
+
+
+
+
+
+ );
+};
diff --git a/apps/web/modules/ee/insights/experience/components/stats.tsx b/apps/web/modules/ee/insights/experience/components/stats.tsx
new file mode 100644
index 0000000000..6fac478bb6
--- /dev/null
+++ b/apps/web/modules/ee/insights/experience/components/stats.tsx
@@ -0,0 +1,104 @@
+"use client";
+
+import { getStatsAction } from "@/modules/ee/insights/experience/actions";
+import { TStats } from "@/modules/ee/insights/experience/types/stats";
+import { ActivityIcon, GaugeIcon, InboxIcon, MessageCircleIcon } from "lucide-react";
+import { useEffect, useState } from "react";
+import toast from "react-hot-toast";
+import { getFormattedErrorMessage } from "@formbricks/lib/actionClient/helper";
+import { Badge } from "@formbricks/ui/components/Badge";
+import { Card, CardContent, CardHeader, CardTitle } from "@formbricks/ui/components/Card";
+import { cn } from "@formbricks/ui/lib/utils";
+
+interface ExperiencePageStatsProps {
+ statsFrom?: Date;
+ environmentId: string;
+}
+
+export const ExperiencePageStats = ({ statsFrom, environmentId }: ExperiencePageStatsProps) => {
+ const [stats, setStats] = useState({
+ activeSurveys: 0,
+ newResponses: 0,
+ analysedFeedbacks: 0,
+ });
+ const [isLoading, setIsLoading] = useState(true);
+
+ useEffect(() => {
+ const getData = async () => {
+ setIsLoading(true);
+ const getStatsResponse = await getStatsAction({ environmentId, statsFrom });
+
+ if (getStatsResponse?.data) {
+ setStats(getStatsResponse.data);
+ } else {
+ const errorMessage = getFormattedErrorMessage(getStatsResponse);
+ toast.error(errorMessage);
+ }
+ setIsLoading(false);
+ };
+
+ getData();
+ }, [environmentId, statsFrom]);
+
+ const statsData = [
+ {
+ key: "sentimentScore",
+ title: "Sentiment Score",
+ value: stats.sentimentScore ? `${Math.floor(stats.sentimentScore * 100)}%` : "-",
+ icon: GaugeIcon,
+ width: "w-20",
+ },
+ {
+ key: "activeSurveys",
+ title: "Active Surveys",
+ value: stats.activeSurveys,
+ icon: MessageCircleIcon,
+ width: "w-10",
+ },
+ {
+ key: "newResponses",
+ title: "New Responses",
+ value: stats.newResponses,
+ icon: InboxIcon,
+ width: "w-10",
+ },
+ {
+ key: "analysedFeedbacks",
+ title: "Analysed Feedbacks",
+ value: stats.analysedFeedbacks,
+ icon: ActivityIcon,
+ width: "w-10",
+ },
+ ];
+
+ return (
+
+ {statsData.map((stat, index) => (
+
+
+ {stat.title}
+
+
+
+
+ {isLoading ? (
+
+ ) : (
+ (stat.value ?? "-")
+ )}
+
+ {stat.key === "sentimentScore" && stats.overallSentiment && (
+
+ {stats.overallSentiment === "positive" ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+
+ ))}
+
+ );
+};
diff --git a/apps/web/modules/ee/insights/experience/components/templates-card.tsx b/apps/web/modules/ee/insights/experience/components/templates-card.tsx
new file mode 100644
index 0000000000..a3c45b1e7a
--- /dev/null
+++ b/apps/web/modules/ee/insights/experience/components/templates-card.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import { TEnvironment } from "@formbricks/types/environment";
+import { TProduct } from "@formbricks/types/product";
+import { TTemplateFilter } from "@formbricks/types/templates";
+import { TUser } from "@formbricks/types/user";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@formbricks/ui/components/Card";
+import { TemplateList } from "@formbricks/ui/components/TemplateList";
+
+interface TemplatesCardProps {
+ environment: TEnvironment;
+ product: TProduct;
+ user: TUser;
+ prefilledFilters: TTemplateFilter[];
+}
+
+export const TemplatesCard = ({ environment, product, user, prefilledFilters }: TemplatesCardProps) => {
+ return (
+
+
+ Measure your customer experience
+ Choose a template or start from scratch
+
+
+
+
+
+
+ );
+};
diff --git a/apps/web/modules/ee/insights/experience/lib/insights.ts b/apps/web/modules/ee/insights/experience/lib/insights.ts
new file mode 100644
index 0000000000..694597b1a0
--- /dev/null
+++ b/apps/web/modules/ee/insights/experience/lib/insights.ts
@@ -0,0 +1,84 @@
+import { insightCache } from "@/lib/cache/insight";
+import { Prisma } from "@prisma/client";
+import { cache as reactCache } from "react";
+import { prisma } from "@formbricks/database";
+import { cache } from "@formbricks/lib/cache";
+import { INSIGHTS_PER_PAGE } from "@formbricks/lib/constants";
+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(
+ (
+ environmentId: string,
+ limit?: number,
+ offset?: number,
+ filterCriteria?: TInsightFilterCriteria
+ ): Promise =>
+ cache(
+ async () => {
+ validateInputs(
+ [environmentId, ZId],
+ [limit, ZOptionalNumber],
+ [offset, ZOptionalNumber],
+ [filterCriteria, ZInsightFilterCriteria.optional()]
+ );
+
+ limit = limit ?? INSIGHTS_PER_PAGE;
+ try {
+ const insights = await prisma.insight.findMany({
+ where: {
+ environmentId,
+ documentInsights: {
+ some: {
+ document: {
+ createdAt: {
+ gte: filterCriteria?.documentCreatedAt?.min,
+ lte: filterCriteria?.documentCreatedAt?.max,
+ },
+ },
+ },
+ },
+ category: filterCriteria?.category,
+ },
+ include: {
+ _count: {
+ select: {
+ documentInsights: {
+ where: {
+ document: {
+ createdAt: {
+ gte: filterCriteria?.documentCreatedAt?.min,
+ lte: filterCriteria?.documentCreatedAt?.max,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ orderBy: [
+ {
+ createdAt: "desc",
+ },
+ ],
+ take: limit ? limit : undefined,
+ skip: offset ? offset : undefined,
+ });
+
+ return insights;
+ } catch (error) {
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
+ throw new DatabaseError(error.message);
+ }
+
+ throw error;
+ }
+ },
+ [`experience-getInsights-${environmentId}-${limit}-${offset}-${JSON.stringify(filterCriteria)}`],
+ {
+ tags: [insightCache.tag.byEnvironmentId(environmentId)],
+ }
+ )()
+);
diff --git a/apps/web/modules/ee/insights/experience/lib/stats.ts b/apps/web/modules/ee/insights/experience/lib/stats.ts
new file mode 100644
index 0000000000..fa3b195db0
--- /dev/null
+++ b/apps/web/modules/ee/insights/experience/lib/stats.ts
@@ -0,0 +1,104 @@
+import "server-only";
+import { documentCache } from "@/lib/cache/document";
+import { Prisma } from "@prisma/client";
+import { cache as reactCache } from "react";
+import { prisma } from "@formbricks/database";
+import { cache } from "@formbricks/lib/cache";
+import { responseCache } from "@formbricks/lib/response/cache";
+import { validateInputs } from "@formbricks/lib/utils/validate";
+import { ZId } from "@formbricks/types/common";
+import { DatabaseError } from "@formbricks/types/errors";
+import { TStats } from "../types/stats";
+
+export const getStats = reactCache(
+ (environmentId: string, statsFrom?: Date): Promise =>
+ cache(
+ async () => {
+ validateInputs([environmentId, ZId]);
+ try {
+ const groupedResponesPromise = prisma.response.groupBy({
+ by: ["surveyId"],
+ _count: {
+ surveyId: true,
+ },
+ where: {
+ survey: {
+ environmentId,
+ },
+ createdAt: {
+ gte: statsFrom,
+ },
+ },
+ });
+
+ const groupedSentimentsPromise = prisma.document.groupBy({
+ by: ["sentiment"],
+ _count: {
+ sentiment: true,
+ },
+ where: {
+ environmentId,
+ createdAt: {
+ gte: statsFrom,
+ },
+ },
+ });
+
+ const [groupedRespones, groupedSentiments] = await Promise.all([
+ groupedResponesPromise,
+ groupedSentimentsPromise,
+ ]);
+
+ const activeSurveys = groupedRespones.length;
+
+ const newResponses = groupedRespones.reduce((acc, { _count }) => acc + _count.surveyId, 0);
+
+ const sentimentCounts = groupedSentiments.reduce(
+ (acc, { sentiment, _count }) => {
+ acc[sentiment] = _count.sentiment;
+ return acc;
+ },
+ {
+ positive: 0,
+ negative: 0,
+ neutral: 0,
+ }
+ );
+
+ // analysed feedbacks is the sum of all the sentiments
+ const analysedFeedbacks = Object.values(sentimentCounts).reduce((acc, count) => acc + count, 0);
+
+ // the sentiment score is the ratio of positive to total (positive + negative) sentiment counts. For this we ignore neutral sentiment counts.
+ let sentimentScore: number = 0,
+ overallSentiment: TStats["overallSentiment"];
+
+ if (sentimentCounts.positive || sentimentCounts.negative) {
+ sentimentScore = sentimentCounts.positive / (sentimentCounts.positive + sentimentCounts.negative);
+
+ overallSentiment = sentimentScore > 0.5 ? "positive" : "negative";
+ }
+
+ return {
+ newResponses,
+ activeSurveys,
+ analysedFeedbacks,
+ sentimentScore,
+ overallSentiment,
+ };
+ } catch (error) {
+ if (error instanceof Prisma.PrismaClientKnownRequestError) {
+ console.error(error);
+ throw new DatabaseError(error.message);
+ }
+ throw error;
+ }
+ },
+ [`stats-${environmentId}-${statsFrom?.toDateString()}`],
+ {
+ tags: [
+ responseCache.tag.byEnvironmentId(environmentId),
+ documentCache.tag.byEnvironmentId(environmentId),
+ ],
+ }
+ )()
+);
diff --git a/apps/web/modules/ee/insights/experience/lib/utils.ts b/apps/web/modules/ee/insights/experience/lib/utils.ts
new file mode 100644
index 0000000000..7821dfa049
--- /dev/null
+++ b/apps/web/modules/ee/insights/experience/lib/utils.ts
@@ -0,0 +1,18 @@
+import { TStatsPeriod } from "@/modules/ee/insights/experience/types/stats";
+
+export const getDateFromTimeRange = (timeRange: TStatsPeriod): Date | undefined => {
+ if (timeRange === "all") {
+ return new Date(0);
+ }
+ const now = new Date();
+ switch (timeRange) {
+ case "day":
+ return new Date(now.getTime() - 1000 * 60 * 60 * 24);
+ case "week":
+ return new Date(now.getTime() - 1000 * 60 * 60 * 24 * 7);
+ case "month":
+ return new Date(now.getTime() - 1000 * 60 * 60 * 24 * 30);
+ case "quarter":
+ return new Date(now.getTime() - 1000 * 60 * 60 * 24 * 90);
+ }
+};
diff --git a/apps/web/modules/ee/insights/experience/page.tsx b/apps/web/modules/ee/insights/experience/page.tsx
new file mode 100644
index 0000000000..0ce71e705d
--- /dev/null
+++ b/apps/web/modules/ee/insights/experience/page.tsx
@@ -0,0 +1,59 @@
+import { getIsAIEnabled } from "@/app/lib/utils";
+import { Dashboard } from "@/modules/ee/insights/experience/components/dashboard";
+import { getServerSession } from "next-auth";
+import { notFound } from "next/navigation";
+import { authOptions } from "@formbricks/lib/authOptions";
+import { DOCUMENTS_PER_PAGE, INSIGHTS_PER_PAGE } from "@formbricks/lib/constants";
+import { getEnvironment } from "@formbricks/lib/environment/service";
+import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
+import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
+import { getUser } from "@formbricks/lib/user/service";
+import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
+
+export const ExperiencePage = async ({ params }) => {
+ const session = await getServerSession(authOptions);
+ if (!session) {
+ throw new Error("Session not found");
+ }
+
+ const user = await getUser(session.user.id);
+ if (!user) {
+ throw new Error("User not found");
+ }
+
+ const [environment, product, organization] = await Promise.all([
+ getEnvironment(params.environmentId),
+ getProductByEnvironmentId(params.environmentId),
+ getOrganizationByEnvironmentId(params.environmentId),
+ ]);
+
+ if (!environment) {
+ throw new Error("Environment not found");
+ }
+
+ if (!product) {
+ throw new Error("Product not found");
+ }
+
+ if (!organization) {
+ throw new Error("Organization not found");
+ }
+
+ const isAIEnabled = await getIsAIEnabled(organization);
+
+ if (!isAIEnabled) {
+ notFound();
+ }
+
+ return (
+
+
+
+ );
+};
diff --git a/apps/web/modules/ee/insights/experience/types/stats.ts b/apps/web/modules/ee/insights/experience/types/stats.ts
new file mode 100644
index 0000000000..93ae2e7307
--- /dev/null
+++ b/apps/web/modules/ee/insights/experience/types/stats.ts
@@ -0,0 +1,14 @@
+import { z } from "zod";
+
+export const ZStats = z.object({
+ sentimentScore: z.number().optional(),
+ overallSentiment: z.enum(["positive", "negative"]).optional(),
+ activeSurveys: z.number(),
+ newResponses: z.number(),
+ analysedFeedbacks: z.number(),
+});
+
+export type TStats = z.infer;
+
+export const ZStatsPeriod = z.enum(["all", "day", "week", "month", "quarter"]);
+export type TStatsPeriod = z.infer;
diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs
index d472d502e7..566cf2fdd8 100644
--- a/apps/web/next.config.mjs
+++ b/apps/web/next.config.mjs
@@ -21,6 +21,7 @@ const nextConfig = {
poweredByHeader: false,
experimental: {
serverComponentsExternalPackages: ["@aws-sdk"],
+ instrumentationHook: true,
staleTimes: {
dynamic: 0,
},
diff --git a/apps/web/package.json b/apps/web/package.json
index 8ee64517af..29468c349b 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -28,12 +28,16 @@
"@formbricks/ui": "workspace:*",
"@hookform/resolvers": "3.9.0",
"@json2csv/node": "7.0.6",
+ "@opentelemetry/api-logs": "0.53.0",
+ "@opentelemetry/instrumentation": "0.53.0",
+ "@opentelemetry/sdk-logs": "0.53.0",
"@paralleldrive/cuid2": "2.2.2",
"@radix-ui/react-collapsible": "1.1.1",
"@react-email/components": "0.0.25",
"@sentry/nextjs": "8.34.0",
"@tanstack/react-table": "8.20.5",
"@vercel/og": "0.6.3",
+ "@vercel/otel": "1.10.0",
"@vercel/speed-insights": "1.0.12",
"bcryptjs": "2.4.3",
"dotenv": "16.4.5",
@@ -43,6 +47,7 @@
"googleapis": "144.0.0",
"jiti": "2.3.3",
"jsonwebtoken": "9.0.2",
+ "langfuse-vercel": "3.27.0",
"lodash": "4.17.21",
"lru-cache": "11.0.1",
"lucide-react": "0.452.0",
diff --git a/apps/web/playwright/organization.spec.ts b/apps/web/playwright/organization.spec.ts
index 6d65223a33..15eb8d6744 100644
--- a/apps/web/playwright/organization.spec.ts
+++ b/apps/web/playwright/organization.spec.ts
@@ -10,6 +10,10 @@ test.describe("Invite, accept and remove organization member", async () => {
// let inviteLink: string;
test("Invite organization member", async ({ page }) => {
+ page.on("console", (msg) => {
+ console.log(msg);
+ });
+
await signUpAndLogin(page, name, email, name);
await finishOnboarding(page, "link");
@@ -24,7 +28,7 @@ test.describe("Invite, accept and remove organization member", async () => {
await expect(dropdownInnerContentWrapper).toBeVisible();
await page.getByRole("link", { name: "Organization" }).click();
- await page.waitForURL(/\/environments\/[^/]+\/settings\/members/);
+ await page.waitForURL(/\/environments\/[^/]+\/settings\/general/);
await page.locator('[data-testid="members-loading-card"]:first-child').waitFor({ state: "hidden" });
diff --git a/apps/web/playwright/survey.spec.ts b/apps/web/playwright/survey.spec.ts
index 990c03faa6..73b4c8086f 100644
--- a/apps/web/playwright/survey.spec.ts
+++ b/apps/web/playwright/survey.spec.ts
@@ -717,7 +717,8 @@ test.describe("Testing Survey with advanced logic", async () => {
await page.getByRole("button", { name: "Close" }).click();
await page.getByRole("link").filter({ hasText: "Responses" }).click();
- await expect(page.getByRole("table")).toBeVisible();
+ await page.waitForSelector("#response-table");
+
await expect(page.getByRole("cell", { name: "score" })).toBeVisible();
await page.waitForTimeout(5000);
await expect(page.getByRole("cell", { name: "32", exact: true })).toBeVisible();
diff --git a/docker-compose.yml b/docker-compose.yml
index 2927e26bfe..7f5ff5d652 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -16,6 +16,7 @@ x-nextauth-secret: &nextauth_secret
# Encryption key
# You can use: `openssl rand -hex 32` to generate one
+
x-cron-secret: &cron_secret
# Set the below to use it instead of API Key for the API & use as an auth for cronjobs
# You can use: $(openssl rand -hex 32) to generate a secure one
@@ -72,7 +73,7 @@ x-next-public-sentry-dsn: &next_public_sentry_dsn # Enable Sentry Error Tracking
services:
postgres:
restart: always
- image: postgres:15-alpine
+ image: pgvector/pgvector:pg17
volumes:
- postgres:/var/lib/postgresql/data
environment:
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index aff5d94b90..f00b7d6e91 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -163,7 +163,7 @@ x-environment: &environment
services:
postgres:
restart: always
- image: postgres:15-alpine
+ image: pgvector/pgvector:pg17
volumes:
- postgres:/var/lib/postgresql/data
environment:
diff --git a/packages/config-typescript/base.json b/packages/config-typescript/base.json
index b34de39a12..0965f8ab2f 100644
--- a/packages/config-typescript/base.json
+++ b/packages/config-typescript/base.json
@@ -14,7 +14,8 @@
"noUnusedParameters": true,
"preserveWatchOutput": true,
"skipLibCheck": true,
- "strict": true
+ "strict": true,
+ "strictNullChecks": true
},
"exclude": ["node_modules", "dist"]
}
diff --git a/packages/database/data-migrations/20240807120500_cta_consent_dismissed_inconsistency/data-migration.ts b/packages/database/data-migrations/20240807120500_cta_consent_dismissed_inconsistency/data-migration.ts
index faf2279840..9b7758f6af 100644
--- a/packages/database/data-migrations/20240807120500_cta_consent_dismissed_inconsistency/data-migration.ts
+++ b/packages/database/data-migrations/20240807120500_cta_consent_dismissed_inconsistency/data-migration.ts
@@ -2,7 +2,11 @@
/* eslint-disable no-console -- logging is allowed in migration scripts */
import { PrismaClient } from "@prisma/client";
-import { type TSurveyQuestion, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
+import {
+ type TSurveyQuestion,
+ type TSurveyQuestionId,
+ TSurveyQuestionTypeEnum,
+} from "@formbricks/types/surveys/types";
const prisma = new PrismaClient();
@@ -55,7 +59,7 @@ async function runMigration(): Promise {
responses.map(async (response) => {
const updatedData = { ...response.data };
- ctaOrConsentQuestionIds.forEach((questionId: string) => {
+ ctaOrConsentQuestionIds.forEach((questionId: TSurveyQuestionId) => {
if (updatedData[questionId] && updatedData[questionId] === "dismissed") {
updatedData[questionId] = "";
}
diff --git a/packages/database/docker-compose.yml b/packages/database/docker-compose.yml
index 0a4bdade92..f4ca536d67 100644
--- a/packages/database/docker-compose.yml
+++ b/packages/database/docker-compose.yml
@@ -1,9 +1,8 @@
-version: "3.3"
services:
postgres:
- image: postgres:15-alpine
+ image: pgvector/pgvector:pg17
volumes:
- - formbricks-postgres:/var/lib/postgresql/data
+ - postgres:/var/lib/postgresql/data
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
@@ -21,5 +20,5 @@ services:
- 1025:1025 # smtp server
volumes:
- formbricks-postgres:
+ postgres:
driver: local
diff --git a/packages/database/migrations/20241017124431_add_documents_and_insights/migration.sql b/packages/database/migrations/20241017124431_add_documents_and_insights/migration.sql
new file mode 100644
index 0000000000..993e023674
--- /dev/null
+++ b/packages/database/migrations/20241017124431_add_documents_and_insights/migration.sql
@@ -0,0 +1,77 @@
+-- CreateExtension
+CREATE EXTENSION IF NOT EXISTS "vector";
+
+-- CreateEnum
+CREATE TYPE "InsightCategory" AS ENUM ('featureRequest', 'complaint', 'praise', 'other');
+
+-- CreateEnum
+CREATE TYPE "Sentiment" AS ENUM ('positive', 'negative', 'neutral');
+
+-- AlterTable
+ALTER TABLE "Organization" ADD COLUMN "isAIEnabled" BOOLEAN NOT NULL DEFAULT false;
+
+-- CreateTable
+CREATE TABLE "Insight" (
+ "id" TEXT NOT NULL,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updated_at" TIMESTAMP(3) NOT NULL,
+ "environmentId" TEXT NOT NULL,
+ "category" "InsightCategory" NOT NULL,
+ "title" TEXT NOT NULL,
+ "description" TEXT NOT NULL,
+ "vector" vector(512),
+
+ CONSTRAINT "Insight_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "DocumentInsight" (
+ "documentId" TEXT NOT NULL,
+ "insightId" TEXT NOT NULL,
+
+ CONSTRAINT "DocumentInsight_pkey" PRIMARY KEY ("documentId","insightId")
+);
+
+-- CreateTable
+CREATE TABLE "Document" (
+ "id" TEXT NOT NULL,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updated_at" TIMESTAMP(3) NOT NULL,
+ "environmentId" TEXT NOT NULL,
+ "surveyId" TEXT,
+ "responseId" TEXT,
+ "questionId" TEXT,
+ "sentiment" "Sentiment" NOT NULL,
+ "isSpam" BOOLEAN NOT NULL,
+ "text" TEXT NOT NULL,
+ "vector" vector(512),
+
+ CONSTRAINT "Document_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE INDEX "DocumentInsight_insightId_idx" ON "DocumentInsight"("insightId");
+
+-- CreateIndex
+CREATE INDEX "Document_created_at_idx" ON "Document"("created_at");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Document_responseId_questionId_key" ON "Document"("responseId", "questionId");
+
+-- AddForeignKey
+ALTER TABLE "Insight" ADD CONSTRAINT "Insight_environmentId_fkey" FOREIGN KEY ("environmentId") REFERENCES "Environment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "DocumentInsight" ADD CONSTRAINT "DocumentInsight_documentId_fkey" FOREIGN KEY ("documentId") REFERENCES "Document"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "DocumentInsight" ADD CONSTRAINT "DocumentInsight_insightId_fkey" FOREIGN KEY ("insightId") REFERENCES "Insight"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Document" ADD CONSTRAINT "Document_environmentId_fkey" FOREIGN KEY ("environmentId") REFERENCES "Environment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Document" ADD CONSTRAINT "Document_surveyId_fkey" FOREIGN KEY ("surveyId") REFERENCES "Survey"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "Document" ADD CONSTRAINT "Document_responseId_fkey" FOREIGN KEY ("responseId") REFERENCES "Response"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/packages/database/schema.prisma b/packages/database/schema.prisma
index 3342dca9ed..accaf33f50 100644
--- a/packages/database/schema.prisma
+++ b/packages/database/schema.prisma
@@ -2,12 +2,14 @@
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
- provider = "postgresql"
- url = env("DATABASE_URL")
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+ extensions = [pgvector(map: "vector")]
}
generator client {
- provider = "prisma-client-js"
+ provider = "prisma-client-js"
+ previewFeatures = ["postgresqlExtensions"]
}
// generator dbml {
@@ -134,6 +136,7 @@ model Response {
// singleUseId, used to prevent multiple responses
singleUseId String?
language String?
+ documents Document[]
displayId String? @unique
display Display? @relation(fields: [displayId], references: [id])
@@ -329,6 +332,7 @@ model Survey {
displayPercentage Decimal?
languages SurveyLanguage[]
showLanguageSwitch Boolean?
+ documents Document[]
@@index([environmentId, updatedAt])
@@index([segmentId])
@@ -403,6 +407,8 @@ model Environment {
tags Tag[]
segments Segment[]
integration Integration[]
+ documents Document[]
+ insights Insight[]
@@index([productId])
}
@@ -457,6 +463,7 @@ model Organization {
/// [TeamBilling]
billing Json
invites Invite[]
+ isAIEnabled Boolean @default(false)
}
enum MembershipRole {
@@ -647,3 +654,60 @@ model SurveyLanguage {
@@index([surveyId])
@@index([languageId])
}
+
+enum InsightCategory {
+ featureRequest
+ complaint
+ praise
+ other
+}
+
+model Insight {
+ id String @id @default(cuid())
+ createdAt DateTime @default(now()) @map(name: "created_at")
+ updatedAt DateTime @updatedAt @map(name: "updated_at")
+ environmentId String
+ environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
+ category InsightCategory
+ title String
+ description String
+ vector Unsupported("vector(512)")?
+ documentInsights DocumentInsight[]
+}
+
+model DocumentInsight {
+ documentId String
+ document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
+ insightId String
+ insight Insight @relation(fields: [insightId], references: [id], onDelete: Cascade)
+
+ @@id([documentId, insightId])
+ @@index([insightId])
+}
+
+enum Sentiment {
+ positive
+ negative
+ neutral
+}
+
+model Document {
+ id String @id @default(cuid())
+ createdAt DateTime @default(now()) @map(name: "created_at")
+ updatedAt DateTime @updatedAt @map(name: "updated_at")
+ environmentId String
+ environment Environment @relation(fields: [environmentId], references: [id], onDelete: Cascade)
+ surveyId String?
+ survey Survey? @relation(fields: [surveyId], references: [id], onDelete: Cascade)
+ responseId String?
+ response Response? @relation(fields: [responseId], references: [id], onDelete: Cascade)
+ questionId String?
+ sentiment Sentiment
+ isSpam Boolean
+ text String
+ vector Unsupported("vector(512)")?
+ documentInsights DocumentInsight[]
+
+ @@unique([responseId, questionId])
+ @@index([createdAt])
+}
diff --git a/packages/ee/multi-language/components/multi-language-card.tsx b/packages/ee/multi-language/components/multi-language-card.tsx
index e6db53af09..695858633d 100644
--- a/packages/ee/multi-language/components/multi-language-card.tsx
+++ b/packages/ee/multi-language/components/multi-language-card.tsx
@@ -8,7 +8,7 @@ import { useEffect, useMemo, useState } from "react";
import { cn } from "@formbricks/lib/cn";
import { addMultiLanguageLabels, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import type { TLanguage, TProduct } from "@formbricks/types/product";
-import type { TSurvey, TSurveyLanguage } from "@formbricks/types/surveys/types";
+import type { TSurvey, TSurveyLanguage, TSurveyQuestionId } from "@formbricks/types/surveys/types";
import { AdvancedOptionToggle } from "@formbricks/ui/components/AdvancedOptionToggle";
import { Button } from "@formbricks/ui/components/Button";
import { ConfirmationModal } from "@formbricks/ui/components/ConfirmationModal";
@@ -22,8 +22,8 @@ interface MultiLanguageCardProps {
localSurvey: TSurvey;
product: TProduct;
setLocalSurvey: (survey: TSurvey) => void;
- activeQuestionId: string | null;
- setActiveQuestionId: (questionId: string | null) => void;
+ activeQuestionId: TSurveyQuestionId | null;
+ setActiveQuestionId: (questionId: TSurveyQuestionId | null) => void;
isMultiLanguageAllowed?: boolean;
isFormbricksCloud: boolean;
setSelectedLanguageCode: (language: string) => void;
diff --git a/packages/ee/multi-language/components/secondary-language-select.tsx b/packages/ee/multi-language/components/secondary-language-select.tsx
index e11e1ef710..29d0aac9ed 100644
--- a/packages/ee/multi-language/components/secondary-language-select.tsx
+++ b/packages/ee/multi-language/components/secondary-language-select.tsx
@@ -1,12 +1,12 @@
import type { TLanguage, TProduct } from "@formbricks/types/product";
-import type { TSurvey } from "@formbricks/types/surveys/types";
+import type { TSurvey, TSurveyQuestionId } from "@formbricks/types/surveys/types";
import { LanguageToggle } from "./language-toggle";
interface SecondaryLanguageSelectProps {
product: TProduct;
defaultLanguage: TLanguage;
setSelectedLanguageCode: (languageCode: string) => void;
- setActiveQuestionId: (questionId: string) => void;
+ setActiveQuestionId: (questionId: TSurveyQuestionId) => void;
localSurvey: TSurvey;
updateSurveyLanguages: (language: TLanguage) => void;
}
diff --git a/packages/ee/package.json b/packages/ee/package.json
index 8ef89a7972..9e7473ae68 100644
--- a/packages/ee/package.json
+++ b/packages/ee/package.json
@@ -20,6 +20,8 @@
"@types/react": "18.3.11"
},
"dependencies": {
+ "ai": "^3.4.9",
+ "@ai-sdk/azure": "^0.0.17",
"@formbricks/database": "workspace:*",
"@formbricks/lib": "workspace:*",
"@paralleldrive/cuid2": "2.2.2",
diff --git a/packages/lib/.eslintrc.js b/packages/lib/.eslintrc.cjs
similarity index 100%
rename from packages/lib/.eslintrc.js
rename to packages/lib/.eslintrc.cjs
diff --git a/packages/lib/aiModels.ts b/packages/lib/aiModels.ts
new file mode 100644
index 0000000000..2c814be408
--- /dev/null
+++ b/packages/lib/aiModels.ts
@@ -0,0 +1,14 @@
+import { createAzure } from "@ai-sdk/azure";
+import { env } from "./env";
+
+export const llmModel = createAzure({
+ resourceName: env.AI_AZURE_LLM_RESSOURCE_NAME, // Azure resource name
+ apiKey: env.AI_AZURE_LLM_API_KEY, // Azure API key
+})(env.AI_AZURE_LLM_DEPLOYMENT_ID || "llm");
+
+export const embeddingsModel = createAzure({
+ resourceName: env.AI_AZURE_EMBEDDINGS_RESSOURCE_NAME, // Azure resource name
+ apiKey: env.AI_AZURE_EMBEDDINGS_API_KEY, // Azure API key
+}).embedding(env.AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID || "embeddings", {
+ dimensions: 512,
+});
diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts
index 0cbeeeaf6f..eeb2f1dd5b 100644
--- a/packages/lib/constants.ts
+++ b/packages/lib/constants.ts
@@ -78,6 +78,9 @@ export const ITEMS_PER_PAGE = 30;
export const SURVEYS_PER_PAGE = 12;
export const RESPONSES_PER_PAGE = 20;
export const TEXT_RESPONSES_PER_PAGE = 5;
+export const INSIGHTS_PER_PAGE = 10;
+export const DOCUMENTS_PER_PAGE = 10;
+export const MAX_RESPONSES_FOR_INSIGHT_GENERATION = 500;
export const DEFAULT_ORGANIZATION_ID = env.DEFAULT_ORGANIZATION_ID;
export const DEFAULT_ORGANIZATION_ROLE = env.DEFAULT_ORGANIZATION_ROLE;
@@ -211,3 +214,12 @@ export const BILLING_LIMITS = {
MIU: 30000,
},
} as const;
+
+export const IS_AI_CONFIGURED = Boolean(
+ env.AI_AZURE_EMBEDDINGS_API_KEY &&
+ env.AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID &&
+ env.AI_AZURE_EMBEDDINGS_RESSOURCE_NAME &&
+ env.AI_AZURE_LLM_API_KEY &&
+ env.AI_AZURE_LLM_DEPLOYMENT_ID &&
+ env.AI_AZURE_LLM_RESSOURCE_NAME
+);
diff --git a/packages/lib/env.ts b/packages/lib/env.ts
index 65da94dfdf..2341aa8cd8 100644
--- a/packages/lib/env.ts
+++ b/packages/lib/env.ts
@@ -7,6 +7,12 @@ export const env = createEnv({
* Will throw if you access these variables on the client.
*/
server: {
+ AI_AZURE_EMBEDDINGS_API_KEY: z.string().optional(),
+ AI_AZURE_LLM_API_KEY: z.string().optional(),
+ AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID: z.string().optional(),
+ AI_AZURE_LLM_DEPLOYMENT_ID: z.string().optional(),
+ AI_AZURE_EMBEDDINGS_RESSOURCE_NAME: z.string().optional(),
+ AI_AZURE_LLM_RESSOURCE_NAME: z.string().optional(),
AIRTABLE_CLIENT_ID: z.string().optional(),
AZUREAD_CLIENT_ID: z.string().optional(),
AZUREAD_CLIENT_SECRET: z.string().optional(),
@@ -87,6 +93,9 @@ export const env = createEnv({
VERCEL_URL: z.string().optional(),
WEBAPP_URL: z.string().url().optional(),
UNSPLASH_ACCESS_KEY: z.string().optional(),
+ LANGFUSE_SECRET_KEY: z.string().optional(),
+ LANGFUSE_PUBLIC_KEY: z.string().optional(),
+ LANGFUSE_BASEURL: z.string().optional(),
},
/*
@@ -113,6 +122,15 @@ export const env = createEnv({
* 💡 You'll get type errors if not all variables from `server` & `client` are included here.
*/
runtimeEnv: {
+ AI_AZURE_EMBEDDINGS_API_KEY: process.env.AI_AZURE_EMBEDDINGS_API_KEY,
+ AI_AZURE_LLM_API_KEY: process.env.AI_AZURE_LLM_API_KEY,
+ AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID: process.env.AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID,
+ AI_AZURE_LLM_DEPLOYMENT_ID: process.env.AI_AZURE_LLM_DEPLOYMENT_ID,
+ AI_AZURE_EMBEDDINGS_RESSOURCE_NAME: process.env.AI_AZURE_EMBEDDINGS_RESSOURCE_NAME,
+ AI_AZURE_LLM_RESSOURCE_NAME: process.env.AI_AZURE_LLM_RESSOURCE_NAME,
+ LANGFUSE_SECRET_KEY: process.env.LANGFUSE_SECRET_KEY,
+ LANGFUSE_PUBLIC_KEY: process.env.LANGFUSE_PUBLIC_KEY,
+ LANGFUSE_BASEURL: process.env.LANGFUSE_BASEURL,
AIRTABLE_CLIENT_ID: process.env.AIRTABLE_CLIENT_ID,
AZUREAD_CLIENT_ID: process.env.AZUREAD_CLIENT_ID,
AZUREAD_CLIENT_SECRET: process.env.AZUREAD_CLIENT_SECRET,
diff --git a/packages/lib/organization/service.ts b/packages/lib/organization/service.ts
index 5b8f8ea1bc..be08853926 100644
--- a/packages/lib/organization/service.ts
+++ b/packages/lib/organization/service.ts
@@ -20,12 +20,13 @@ import { updateUser } from "../user/service";
import { validateInputs } from "../utils/validate";
import { organizationCache } from "./cache";
-export const select = {
+export const select: Prisma.OrganizationSelect = {
id: true,
createdAt: true,
updatedAt: true,
name: true,
billing: true,
+ isAIEnabled: true,
};
export const getOrganizationsTag = (organizationId: string) => `organizations-${organizationId}`;
@@ -200,13 +201,13 @@ export const updateOrganization = async (
});
// revalidate cache for environments
- updatedOrganization?.products.forEach((product) => {
- product.environments.forEach(async (environment) => {
+ for (const product of updatedOrganization.products) {
+ for (const environment of product.environments) {
organizationCache.revalidate({
environmentId: environment.id,
});
- });
- });
+ }
+ }
const organization = {
...updatedOrganization,
diff --git a/packages/lib/package.json b/packages/lib/package.json
index 2b0afd756d..192285d566 100644
--- a/packages/lib/package.json
+++ b/packages/lib/package.json
@@ -5,6 +5,7 @@
"version": "0.0.0",
"main": "./index.ts",
"types": "./index.ts",
+ "type": "module",
"scripts": {
"clean": "rimraf .turbo node_modules .next coverage",
"lint": "eslint . --ext .ts,.js,.tsx,.jsx",
diff --git a/packages/lib/response/service.ts b/packages/lib/response/service.ts
index 21a3036b0c..e0c6c71e9c 100644
--- a/packages/lib/response/service.ts
+++ b/packages/lib/response/service.ts
@@ -3,8 +3,7 @@ import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { TAttributes } from "@formbricks/types/attributes";
-import { ZOptionalNumber, ZString } from "@formbricks/types/common";
-import { ZId } from "@formbricks/types/common";
+import { ZId, ZOptionalNumber, ZString } from "@formbricks/types/common";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
import { TPerson } from "@formbricks/types/people";
import {
@@ -16,13 +15,12 @@ import {
ZResponseInput,
ZResponseUpdateInput,
} from "@formbricks/types/responses";
-import { TSurvey, TSurveyQuestionTypeEnum, TSurveySummary } from "@formbricks/types/surveys/types";
+import { TSurvey, TSurveyQuestionTypeEnum } from "@formbricks/types/surveys/types";
import { TTag } from "@formbricks/types/tags";
import { getAttributes } from "../attribute/service";
import { cache } from "../cache";
import { IS_FORMBRICKS_CLOUD, ITEMS_PER_PAGE, WEBAPP_URL } from "../constants";
-import { displayCache } from "../display/cache";
-import { deleteDisplay, getDisplayCountBySurveyId } from "../display/service";
+import { deleteDisplay } from "../display/service";
import { getMonthlyOrganizationResponseCount, getOrganizationByEnvironmentId } from "../organization/service";
import { createPerson, getPersonByUserId } from "../person/service";
import { sendPlanLimitsReachedEventToPosthogWeekly } from "../posthogServer";
@@ -38,14 +36,11 @@ import {
buildWhereClause,
calculateTtcTotal,
extractSurveyDetails,
- getQuestionWiseSummary,
getResponseHiddenFields,
getResponseMeta,
getResponsePersonAttributes,
getResponsesFileName,
getResponsesJson,
- getSurveySummaryDropOff,
- getSurveySummaryMeta,
} from "./utils";
const RESPONSES_PER_PAGE = 10;
@@ -512,60 +507,6 @@ export const getResponses = reactCache(
)()
);
-export const getSurveySummary = reactCache(
- (surveyId: string, filterCriteria?: TResponseFilterCriteria): Promise =>
- cache(
- async () => {
- validateInputs([surveyId, ZId], [filterCriteria, ZResponseFilterCriteria.optional()]);
-
- try {
- const survey = await getSurvey(surveyId);
- if (!survey) {
- throw new ResourceNotFoundError("Survey", surveyId);
- }
-
- const batchSize = 3000;
- const totalResponseCount = await getResponseCountBySurveyId(surveyId);
- const filteredResponseCount = await getResponseCountBySurveyId(surveyId, filterCriteria);
-
- const hasFilter = totalResponseCount !== filteredResponseCount;
-
- const pages = Math.ceil(filteredResponseCount / batchSize);
-
- const responsesArray = await Promise.all(
- Array.from({ length: pages }, (_, i) => {
- return getResponses(surveyId, batchSize, i * batchSize, filterCriteria);
- })
- );
- const responses = responsesArray.flat();
-
- const responseIds = hasFilter ? responses.map((response) => response.id) : [];
-
- const displayCount = await getDisplayCountBySurveyId(surveyId, {
- createdAt: filterCriteria?.createdAt,
- ...(hasFilter && { responseIds }),
- });
-
- const dropOff = getSurveySummaryDropOff(survey, responses, displayCount);
- const meta = getSurveySummaryMeta(responses, displayCount);
- const questionWiseSummary = getQuestionWiseSummary(survey, responses, dropOff);
-
- return { meta, dropOff, summary: questionWiseSummary };
- } catch (error) {
- if (error instanceof Prisma.PrismaClientKnownRequestError) {
- throw new DatabaseError(error.message);
- }
-
- throw error;
- }
- },
- [`getSurveySummary-${surveyId}-${JSON.stringify(filterCriteria)}`],
- {
- tags: [responseCache.tag.bySurveyId(surveyId), displayCache.tag.bySurveyId(surveyId)],
- }
- )()
-);
-
export const getResponseDownloadUrl = async (
surveyId: string,
format: "csv" | "xlsx",
diff --git a/packages/lib/response/tests/__mocks__/data.mock.ts b/packages/lib/response/tests/__mocks__/data.mock.ts
index 89e63565b8..5bb413d1dc 100644
--- a/packages/lib/response/tests/__mocks__/data.mock.ts
+++ b/packages/lib/response/tests/__mocks__/data.mock.ts
@@ -384,6 +384,8 @@ export const mockSurveySummaryOutput = {
},
summary: [
{
+ insights: undefined,
+ insightsEnabled: undefined,
question: {
headline: { default: "Question Text", de: "Fragetext" },
id: "ars2tjk8hsi8oqk1uac00mo8",
diff --git a/packages/lib/response/tests/response.test.ts b/packages/lib/response/tests/response.test.ts
index 8a81872273..9ca9755691 100644
--- a/packages/lib/response/tests/response.test.ts
+++ b/packages/lib/response/tests/response.test.ts
@@ -23,6 +23,7 @@ import { testInputValidation } from "vitestSetup";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
import { TResponse, TResponseFilterCriteria, TResponseInput } from "@formbricks/types/responses";
import { TTag } from "@formbricks/types/tags";
+import { getSurveySummary } from "../../../../apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/lib/surveySummary";
import { selectPerson } from "../../person/service";
import {
mockAttributeClass,
@@ -39,7 +40,6 @@ import {
getResponses,
getResponsesByEnvironmentId,
getResponsesByPersonId,
- getSurveySummary,
updateResponse,
} from "../service";
import { buildWhereClause } from "../utils";
diff --git a/packages/lib/response/utils.ts b/packages/lib/response/utils.ts
index 098996a6a7..980d28e5fa 100644
--- a/packages/lib/response/utils.ts
+++ b/packages/lib/response/utils.ts
@@ -2,36 +2,15 @@ import "server-only";
import { Prisma } from "@prisma/client";
import {
TResponse,
- TResponseData,
TResponseFilterCriteria,
TResponseHiddenFieldsFilter,
TResponseTtc,
- TResponseVariables,
TSurveyMetaFieldFilter,
TSurveyPersonAttributes,
} from "@formbricks/types/responses";
-import {
- TSurvey,
- TSurveyContactInfoQuestion,
- TSurveyLanguage,
- TSurveyMultipleChoiceQuestion,
- TSurveyQuestion,
- TSurveyQuestionSummaryAddress,
- TSurveyQuestionSummaryDate,
- TSurveyQuestionSummaryFileUpload,
- TSurveyQuestionSummaryHiddenFields,
- TSurveyQuestionSummaryMultipleChoice,
- TSurveyQuestionSummaryOpenText,
- TSurveyQuestionSummaryPictureSelection,
- TSurveyQuestionSummaryRanking,
- TSurveyQuestionSummaryRating,
- TSurveyQuestionTypeEnum,
- TSurveySummary,
-} from "@formbricks/types/surveys/types";
+import { TSurvey } from "@formbricks/types/surveys/types";
import { getLocalizedValue } from "../i18n/utils";
-import { structuredClone } from "../pollyfills/structuredClone";
import { processResponseData } from "../responses";
-import { evaluateLogic, performActions } from "../surveyLogic/utils";
import { getTodaysDateTimeFormatted } from "../time";
import { sanitizeString } from "../utils/strings";
@@ -574,817 +553,6 @@ export const getResponsesJson = (
return jsonData;
};
-const convertFloatTo2Decimal = (num: number) => {
- return Math.round(num * 100) / 100;
-};
-
-export const getSurveySummaryMeta = (
- responses: TResponse[],
- displayCount: number
-): TSurveySummary["meta"] => {
- const completedResponses = responses.filter((response) => response.finished).length;
-
- let ttcResponseCount = 0;
- const ttcSum = responses.reduce((acc, response) => {
- if (response.ttc?._total) {
- ttcResponseCount++;
- return acc + response.ttc._total;
- }
- return acc;
- }, 0);
- const responseCount = responses.length;
-
- const startsPercentage = displayCount > 0 ? (responseCount / displayCount) * 100 : 0;
- const completedPercentage = displayCount > 0 ? (completedResponses / displayCount) * 100 : 0;
- const dropOffCount = responseCount - completedResponses;
- const dropOffPercentage = responseCount > 0 ? (dropOffCount / responseCount) * 100 : 0;
- const ttcAverage = ttcResponseCount > 0 ? ttcSum / ttcResponseCount : 0;
-
- return {
- displayCount: displayCount || 0,
- totalResponses: responseCount,
- startsPercentage: convertFloatTo2Decimal(startsPercentage),
- completedResponses,
- completedPercentage: convertFloatTo2Decimal(completedPercentage),
- dropOffCount,
- dropOffPercentage: convertFloatTo2Decimal(dropOffPercentage),
- ttcAverage: convertFloatTo2Decimal(ttcAverage),
- };
-};
-
-export const getSurveySummaryDropOff = (
- survey: TSurvey,
- responses: TResponse[],
- displayCount: number
-): TSurveySummary["dropOff"] => {
- const initialTtc = survey.questions.reduce((acc: Record, question) => {
- acc[question.id] = 0;
- return acc;
- }, {});
-
- let totalTtc = { ...initialTtc };
- let responseCounts = { ...initialTtc };
-
- let dropOffArr = new Array(survey.questions.length).fill(0) as number[];
- let impressionsArr = new Array(survey.questions.length).fill(0) as number[];
- let dropOffPercentageArr = new Array(survey.questions.length).fill(0) as number[];
-
- const surveyVariablesData = survey.variables?.reduce(
- (acc, variable) => {
- acc[variable.id] = variable.value;
- return acc;
- },
- {} as Record
- );
-
- responses.forEach((response) => {
- // Calculate total time-to-completion
- Object.keys(totalTtc).forEach((questionId) => {
- if (response.ttc && response.ttc[questionId]) {
- totalTtc[questionId] += response.ttc[questionId];
- responseCounts[questionId]++;
- }
- });
-
- let localSurvey = structuredClone(survey);
- let localResponseData: TResponseData = { ...response.data };
- let localVariables: TResponseVariables = {
- ...surveyVariablesData,
- };
-
- let currQuesIdx = 0;
-
- while (currQuesIdx < localSurvey.questions.length) {
- const currQues = localSurvey.questions[currQuesIdx];
- if (!currQues) break;
-
- // question is not answered and required
- if (response.data[currQues.id] === undefined && currQues.required) {
- dropOffArr[currQuesIdx]++;
- impressionsArr[currQuesIdx]++;
- break;
- }
-
- impressionsArr[currQuesIdx]++;
-
- const { nextQuestionId, updatedSurvey, updatedVariables } = evaluateLogicAndGetNextQuestionId(
- localSurvey,
- localResponseData,
- localVariables,
- currQuesIdx,
- currQues,
- response.language
- );
-
- localSurvey = updatedSurvey;
- localVariables = updatedVariables;
-
- if (nextQuestionId) {
- const nextQuesIdx = survey.questions.findIndex((q) => q.id === nextQuestionId);
- if (!response.data[nextQuestionId] && !response.finished) {
- dropOffArr[nextQuesIdx]++;
- impressionsArr[nextQuesIdx]++;
- break;
- }
- currQuesIdx = nextQuesIdx;
- } else {
- currQuesIdx++;
- }
- }
- });
-
- // Calculate the average time for each question
- Object.keys(totalTtc).forEach((questionId) => {
- totalTtc[questionId] =
- responseCounts[questionId] > 0 ? totalTtc[questionId] / responseCounts[questionId] : 0;
- });
-
- if (!survey.welcomeCard.enabled) {
- dropOffArr[0] = displayCount - impressionsArr[0];
- if (impressionsArr[0] > displayCount) dropOffPercentageArr[0] = 0;
-
- dropOffPercentageArr[0] =
- impressionsArr[0] - displayCount >= 0
- ? 0
- : ((displayCount - impressionsArr[0]) / displayCount) * 100 || 0;
-
- impressionsArr[0] = displayCount;
- } else {
- dropOffPercentageArr[0] = (dropOffArr[0] / impressionsArr[0]) * 100;
- }
-
- for (let i = 1; i < survey.questions.length; i++) {
- if (impressionsArr[i] !== 0) {
- dropOffPercentageArr[i] = (dropOffArr[i] / impressionsArr[i]) * 100;
- }
- }
-
- const dropOff = survey.questions.map((question, index) => {
- return {
- questionId: question.id,
- headline: getLocalizedValue(question.headline, "default"),
- ttc: convertFloatTo2Decimal(totalTtc[question.id]) || 0,
- impressions: impressionsArr[index] || 0,
- dropOffCount: dropOffArr[index] || 0,
- dropOffPercentage: convertFloatTo2Decimal(dropOffPercentageArr[index]) || 0,
- };
- });
-
- return dropOff;
-};
-
-const evaluateLogicAndGetNextQuestionId = (
- localSurvey: TSurvey,
- data: TResponseData,
- localVariables: TResponseVariables,
- currentQuestionIndex: number,
- currQuesTemp: TSurveyQuestion,
- selectedLanguage: string | null
-): { nextQuestionId: string | undefined; updatedSurvey: TSurvey; updatedVariables: TResponseVariables } => {
- const questions = localSurvey.questions;
-
- let updatedSurvey = { ...localSurvey };
- let updatedVariables = { ...localVariables };
-
- let firstJumpTarget: string | undefined;
-
- if (currQuesTemp.logic && currQuesTemp.logic.length > 0) {
- for (const logic of currQuesTemp.logic) {
- if (evaluateLogic(localSurvey, data, localVariables, logic.conditions, selectedLanguage ?? "default")) {
- const { jumpTarget, requiredQuestionIds, calculations } = performActions(
- updatedSurvey,
- logic.actions,
- data,
- updatedVariables
- );
-
- if (requiredQuestionIds.length > 0) {
- updatedSurvey.questions = updatedSurvey.questions.map((q) =>
- requiredQuestionIds.includes(q.id) ? { ...q, required: true } : q
- );
- }
- updatedVariables = { ...updatedVariables, ...calculations };
-
- if (jumpTarget && !firstJumpTarget) {
- firstJumpTarget = jumpTarget;
- }
- }
- }
- }
-
- // Return the first jump target if found, otherwise go to the next question
- const nextQuestionId = firstJumpTarget || questions[currentQuestionIndex + 1]?.id || undefined;
-
- return { nextQuestionId, updatedSurvey, updatedVariables };
-};
-
-const getLanguageCode = (surveyLanguages: TSurveyLanguage[], languageCode: string | null) => {
- if (!surveyLanguages?.length || !languageCode) return "default";
- const language = surveyLanguages.find((surveyLanguage) => surveyLanguage.language.code === languageCode);
- return language?.default ? "default" : language?.language.code || "default";
-};
-
-const checkForI18n = (response: TResponse, id: string, survey: TSurvey, languageCode: string) => {
- const question = survey.questions.find((question) => question.id === id);
-
- if (question?.type === "multipleChoiceMulti" || question?.type === "ranking") {
- // Initialize an array to hold the choice values
- let choiceValues = [] as string[];
-
- (typeof response.data[id] === "string"
- ? ([response.data[id]] as string[])
- : (response.data[id] as string[])
- )?.forEach((data) => {
- choiceValues.push(
- getLocalizedValue(
- question.choices.find((choice) => choice.label[languageCode] === data)?.label,
- "default"
- ) || data
- );
- });
-
- // Return the array of localized choice values of multiSelect multi questions
- return choiceValues;
- }
-
- // Return the localized value of the choice fo multiSelect single question
- const choice = (question as TSurveyMultipleChoiceQuestion)?.choices.find(
- (choice) => choice.label[languageCode] === response.data[id]
- );
-
- return getLocalizedValue(choice?.label, "default") || response.data[id];
-};
-
-export const getQuestionWiseSummary = (
- survey: TSurvey,
- responses: TResponse[],
- dropOff: TSurveySummary["dropOff"]
-): TSurveySummary["summary"] => {
- const VALUES_LIMIT = 50;
- let summary: TSurveySummary["summary"] = [];
-
- survey.questions.forEach((question, idx) => {
- switch (question.type) {
- case TSurveyQuestionTypeEnum.OpenText: {
- let values: TSurveyQuestionSummaryOpenText["samples"] = [];
- responses.forEach((response) => {
- const answer = response.data[question.id];
- if (answer && typeof answer === "string") {
- values.push({
- id: response.id,
- updatedAt: response.updatedAt,
- value: answer,
- person: response.person,
- personAttributes: response.personAttributes,
- });
- }
- });
-
- summary.push({
- type: question.type,
- question,
- responseCount: values.length,
- samples: values.slice(0, VALUES_LIMIT),
- });
-
- values = [];
- break;
- }
- case TSurveyQuestionTypeEnum.MultipleChoiceSingle:
- case TSurveyQuestionTypeEnum.MultipleChoiceMulti: {
- let values: TSurveyQuestionSummaryMultipleChoice["choices"] = [];
- // check last choice is others or not
- const lastChoice = question.choices[question.choices.length - 1];
- const isOthersEnabled = lastChoice.id === "other";
-
- const questionChoices = question.choices.map((choice) => getLocalizedValue(choice.label, "default"));
- if (isOthersEnabled) {
- questionChoices.pop();
- }
-
- const choiceCountMap = questionChoices.reduce((acc: Record, choice) => {
- acc[choice] = 0;
- return acc;
- }, {});
-
- const otherValues: TSurveyQuestionSummaryMultipleChoice["choices"][number]["others"] = [];
- responses.forEach((response) => {
- const responseLanguageCode = getLanguageCode(survey.languages, response.language);
-
- const answer =
- responseLanguageCode === "default"
- ? response.data[question.id]
- : checkForI18n(response, question.id, survey, responseLanguageCode);
-
- if (Array.isArray(answer)) {
- answer.forEach((value) => {
- if (questionChoices.includes(value)) {
- choiceCountMap[value]++;
- } else {
- otherValues.push({
- value,
- person: response.person,
- personAttributes: response.personAttributes,
- });
- }
- });
- } else if (typeof answer === "string") {
- if (questionChoices.includes(answer)) {
- choiceCountMap[answer]++;
- } else {
- otherValues.push({
- value: answer,
- person: response.person,
- personAttributes: response.personAttributes,
- });
- }
- }
- });
-
- Object.entries(choiceCountMap).map(([label, count]) => {
- values.push({
- value: label,
- count,
- percentage: responses.length > 0 ? convertFloatTo2Decimal((count / responses.length) * 100) : 0,
- });
- });
-
- if (isOthersEnabled) {
- values.push({
- value: getLocalizedValue(lastChoice.label, "default") || "Other",
- count: otherValues.length,
- percentage: convertFloatTo2Decimal((otherValues.length / responses.length) * 100),
- others: otherValues.slice(0, VALUES_LIMIT),
- });
- }
- summary.push({
- type: question.type,
- question,
- responseCount: responses.length,
- choices: values,
- });
-
- values = [];
- break;
- }
- case TSurveyQuestionTypeEnum.PictureSelection: {
- let values: TSurveyQuestionSummaryPictureSelection["choices"] = [];
- const choiceCountMap: Record = {};
-
- question.choices.forEach((choice) => {
- choiceCountMap[choice.id] = 0;
- });
- let totalResponseCount = 0;
-
- responses.forEach((response) => {
- const answer = response.data[question.id];
- if (Array.isArray(answer)) {
- answer.forEach((value) => {
- totalResponseCount++;
- choiceCountMap[value]++;
- });
- }
- });
-
- question.choices.forEach((choice) => {
- values.push({
- id: choice.id,
- imageUrl: choice.imageUrl,
- count: choiceCountMap[choice.id],
- percentage:
- totalResponseCount > 0
- ? convertFloatTo2Decimal((choiceCountMap[choice.id] / totalResponseCount) * 100)
- : 0,
- });
- });
-
- summary.push({
- type: question.type,
- question,
- responseCount: totalResponseCount,
- choices: values,
- });
-
- values = [];
- break;
- }
- case TSurveyQuestionTypeEnum.Rating: {
- let values: TSurveyQuestionSummaryRating["choices"] = [];
- const choiceCountMap: Record = {};
- const range = question.range;
-
- for (let i = 1; i <= range; i++) {
- choiceCountMap[i] = 0;
- }
-
- let totalResponseCount = 0;
- let totalRating = 0;
- let dismissed = 0;
-
- responses.forEach((response) => {
- const answer = response.data[question.id];
- if (typeof answer === "number") {
- totalResponseCount++;
- choiceCountMap[answer]++;
- totalRating += answer;
- } else if (response.ttc && response.ttc[question.id] > 0) {
- dismissed++;
- }
- });
-
- Object.entries(choiceCountMap).map(([label, count]) => {
- values.push({
- rating: parseInt(label),
- count,
- percentage:
- totalResponseCount > 0 ? convertFloatTo2Decimal((count / totalResponseCount) * 100) : 0,
- });
- });
-
- summary.push({
- type: question.type,
- question,
- average: convertFloatTo2Decimal(totalRating / totalResponseCount) || 0,
- responseCount: totalResponseCount,
- choices: values,
- dismissed: {
- count: dismissed,
- },
- });
-
- values = [];
- break;
- }
- case TSurveyQuestionTypeEnum.NPS: {
- const data = {
- promoters: 0,
- passives: 0,
- detractors: 0,
- dismissed: 0,
- total: 0,
- score: 0,
- };
-
- responses.forEach((response) => {
- const value = response.data[question.id];
- if (typeof value === "number") {
- data.total++;
- if (value >= 9) {
- data.promoters++;
- } else if (value >= 7) {
- data.passives++;
- } else {
- data.detractors++;
- }
- } else if (response.ttc && response.ttc[question.id] > 0) {
- data.total++;
- data.dismissed++;
- }
- });
-
- data.score =
- data.total > 0
- ? convertFloatTo2Decimal(((data.promoters - data.detractors) / data.total) * 100)
- : 0;
-
- summary.push({
- type: question.type,
- question,
- responseCount: data.total,
- total: data.total,
- score: data.score,
- promoters: {
- count: data.promoters,
- percentage: data.total > 0 ? convertFloatTo2Decimal((data.promoters / data.total) * 100) : 0,
- },
- passives: {
- count: data.passives,
- percentage: data.total > 0 ? convertFloatTo2Decimal((data.passives / data.total) * 100) : 0,
- },
- detractors: {
- count: data.detractors,
- percentage: data.total > 0 ? convertFloatTo2Decimal((data.detractors / data.total) * 100) : 0,
- },
- dismissed: {
- count: data.dismissed,
- percentage: data.total > 0 ? convertFloatTo2Decimal((data.dismissed / data.total) * 100) : 0,
- },
- });
- break;
- }
- case TSurveyQuestionTypeEnum.CTA: {
- const data = {
- clicked: 0,
- dismissed: 0,
- };
-
- responses.forEach((response) => {
- const value = response.data[question.id];
- if (value === "clicked") {
- data.clicked++;
- } else if (value === "dismissed") {
- data.dismissed++;
- }
- });
-
- const totalResponses = data.clicked + data.dismissed;
- const impressions = dropOff[idx].impressions;
-
- summary.push({
- type: question.type,
- question,
- impressionCount: impressions,
- clickCount: data.clicked,
- skipCount: data.dismissed,
- responseCount: totalResponses,
- ctr: {
- count: data.clicked,
- percentage: impressions > 0 ? convertFloatTo2Decimal((data.clicked / impressions) * 100) : 0,
- },
- });
- break;
- }
- case TSurveyQuestionTypeEnum.Consent: {
- const data = {
- accepted: 0,
- dismissed: 0,
- };
-
- responses.forEach((response) => {
- const value = response.data[question.id];
- if (value === "accepted") {
- data.accepted++;
- } else if (response.ttc && response.ttc[question.id] > 0) {
- data.dismissed++;
- }
- });
-
- const totalResponses = data.accepted + data.dismissed;
-
- summary.push({
- type: question.type,
- question,
- responseCount: totalResponses,
- accepted: {
- count: data.accepted,
- percentage:
- totalResponses > 0 ? convertFloatTo2Decimal((data.accepted / totalResponses) * 100) : 0,
- },
- dismissed: {
- count: data.dismissed,
- percentage:
- totalResponses > 0 ? convertFloatTo2Decimal((data.dismissed / totalResponses) * 100) : 0,
- },
- });
-
- break;
- }
- case TSurveyQuestionTypeEnum.Date: {
- let values: TSurveyQuestionSummaryDate["samples"] = [];
- responses.forEach((response) => {
- const answer = response.data[question.id];
- if (answer && typeof answer === "string") {
- values.push({
- id: response.id,
- updatedAt: response.updatedAt,
- value: answer,
- person: response.person,
- personAttributes: response.personAttributes,
- });
- }
- });
-
- summary.push({
- type: question.type,
- question,
- responseCount: values.length,
- samples: values.slice(0, VALUES_LIMIT),
- });
-
- values = [];
- break;
- }
- case TSurveyQuestionTypeEnum.FileUpload: {
- let values: TSurveyQuestionSummaryFileUpload["files"] = [];
- responses.forEach((response) => {
- const answer = response.data[question.id];
- if (Array.isArray(answer)) {
- values.push({
- id: response.id,
- updatedAt: response.updatedAt,
- value: answer,
- person: response.person,
- personAttributes: response.personAttributes,
- });
- }
- });
-
- summary.push({
- type: question.type,
- question,
- responseCount: values.length,
- files: values.slice(0, VALUES_LIMIT),
- });
-
- values = [];
- break;
- }
- case TSurveyQuestionTypeEnum.Cal: {
- const data = {
- booked: 0,
- skipped: 0,
- };
-
- responses.forEach((response) => {
- const value = response.data[question.id];
- if (value === "booked") {
- data.booked++;
- } else if (response.ttc && response.ttc[question.id] > 0) {
- data.skipped++;
- }
- });
- const totalResponses = data.booked + data.skipped;
-
- summary.push({
- type: question.type,
- question,
- responseCount: totalResponses,
- booked: {
- count: data.booked,
- percentage: totalResponses > 0 ? convertFloatTo2Decimal((data.booked / totalResponses) * 100) : 0,
- },
- skipped: {
- count: data.skipped,
- percentage:
- totalResponses > 0 ? convertFloatTo2Decimal((data.skipped / totalResponses) * 100) : 0,
- },
- });
-
- break;
- }
- case TSurveyQuestionTypeEnum.Matrix: {
- const rows = question.rows.map((row) => getLocalizedValue(row, "default"));
- const columns = question.columns.map((column) => getLocalizedValue(column, "default"));
- let totalResponseCount = 0;
-
- // Initialize count object
- const countMap: Record = rows.reduce((acc, row) => {
- acc[row] = columns.reduce((colAcc, col) => {
- colAcc[col] = 0;
- return colAcc;
- }, {});
- return acc;
- }, {});
-
- responses.forEach((response) => {
- const selectedResponses = response.data[question.id] as Record;
- const responseLanguageCode = getLanguageCode(survey.languages, response.language);
- if (selectedResponses) {
- totalResponseCount++;
- question.rows.forEach((row) => {
- const localizedRow = getLocalizedValue(row, responseLanguageCode);
- const colValue = question.columns.find((column) => {
- return getLocalizedValue(column, responseLanguageCode) === selectedResponses[localizedRow];
- });
- const colValueInDefaultLanguage = getLocalizedValue(colValue, "default");
- if (colValueInDefaultLanguage && columns.includes(colValueInDefaultLanguage)) {
- countMap[getLocalizedValue(row, "default")][colValueInDefaultLanguage] += 1;
- }
- });
- }
- });
-
- const matrixSummary = rows.map((row) => {
- let totalResponsesForRow = 0;
- columns.forEach((col) => {
- totalResponsesForRow += countMap[row][col];
- });
-
- const columnPercentages = columns.reduce((acc, col) => {
- const count = countMap[row][col];
- const percentage =
- totalResponsesForRow > 0 ? ((count / totalResponsesForRow) * 100).toFixed(2) : "0.00";
- acc[col] = percentage;
- return acc;
- }, {});
-
- return { rowLabel: row, columnPercentages, totalResponsesForRow };
- });
-
- summary.push({
- type: question.type,
- question,
- responseCount: totalResponseCount,
- data: matrixSummary,
- });
- break;
- }
- case TSurveyQuestionTypeEnum.Address:
- case TSurveyQuestionTypeEnum.ContactInfo: {
- let values: TSurveyQuestionSummaryAddress["samples"] = [];
- responses.forEach((response) => {
- const answer = response.data[question.id];
- if (Array.isArray(answer) && answer.length > 0) {
- values.push({
- id: response.id,
- updatedAt: response.updatedAt,
- value: answer,
- person: response.person,
- personAttributes: response.personAttributes,
- });
- }
- });
-
- summary.push({
- type: question.type as TSurveyQuestionTypeEnum.ContactInfo,
- question: question as TSurveyContactInfoQuestion,
- responseCount: values.length,
- samples: values.slice(0, VALUES_LIMIT),
- });
-
- values = [];
- break;
- }
- case TSurveyQuestionTypeEnum.Ranking: {
- let values: TSurveyQuestionSummaryRanking["choices"] = [];
- const questionChoices = question.choices.map((choice) => getLocalizedValue(choice.label, "default"));
- let totalResponseCount = 0;
- const choiceRankSums: Record = {};
- const choiceCountMap: Record = {};
- questionChoices.forEach((choice) => {
- choiceRankSums[choice] = 0;
- choiceCountMap[choice] = 0;
- });
-
- responses.forEach((response) => {
- const responseLanguageCode = getLanguageCode(survey.languages, response.language);
-
- const answer =
- responseLanguageCode === "default"
- ? response.data[question.id]
- : checkForI18n(response, question.id, survey, responseLanguageCode);
-
- if (Array.isArray(answer)) {
- totalResponseCount++;
- answer.forEach((value, index) => {
- const ranking = index + 1; // Calculate ranking based on index
- if (questionChoices.includes(value)) {
- choiceRankSums[value] += ranking;
- choiceCountMap[value]++;
- }
- });
- }
- });
-
- questionChoices.forEach((choice) => {
- const count = choiceCountMap[choice];
- const avgRanking = count > 0 ? choiceRankSums[choice] / count : 0;
- values.push({
- value: choice,
- count,
- avgRanking: convertFloatTo2Decimal(avgRanking),
- });
- });
-
- summary.push({
- type: question.type,
- question,
- responseCount: totalResponseCount,
- choices: values,
- });
-
- break;
- }
- }
- });
-
- survey.hiddenFields?.fieldIds?.forEach((hiddenFieldId) => {
- let values: TSurveyQuestionSummaryHiddenFields["samples"] = [];
- responses.forEach((response) => {
- const answer = response.data[hiddenFieldId];
- if (answer && typeof answer === "string") {
- values.push({
- updatedAt: response.updatedAt,
- value: answer,
- person: response.person,
- personAttributes: response.personAttributes,
- });
- }
- });
-
- summary.push({
- type: "hiddenField",
- id: hiddenFieldId,
- responseCount: values.length,
- samples: values.slice(0, VALUES_LIMIT),
- });
-
- values = [];
- });
-
- return summary;
-};
-
export const getResponsePersonAttributes = (
responses: Pick[]
): TSurveyPersonAttributes => {
diff --git a/packages/lib/survey/service.ts b/packages/lib/survey/service.ts
index d26c50b7d6..348cf9b70c 100644
--- a/packages/lib/survey/service.ts
+++ b/packages/lib/survey/service.ts
@@ -15,6 +15,7 @@ import {
TSurvey,
TSurveyCreateInput,
TSurveyFilterCriteria,
+ TSurveyQuestions,
ZSurvey,
ZSurveyCreateInput,
} from "@formbricks/types/surveys/types";
@@ -27,7 +28,10 @@ import { ITEMS_PER_PAGE } from "../constants";
import { displayCache } from "../display/cache";
import { getDisplaysByPersonId } from "../display/service";
import { getEnvironment } from "../environment/service";
-import { subscribeOrganizationMembersToSurveyResponses } from "../organization/service";
+import {
+ getOrganizationByEnvironmentId,
+ subscribeOrganizationMembersToSurveyResponses,
+} from "../organization/service";
import { personCache } from "../person/cache";
import { getPerson } from "../person/service";
import { structuredClone } from "../pollyfills/structuredClone";
@@ -38,10 +42,18 @@ import { responseCache } from "../response/cache";
import { getResponsesByPersonId } from "../response/service";
import { segmentCache } from "../segment/cache";
import { createSegment, deleteSegment, evaluateSegment, getSegment, updateSegment } from "../segment/service";
+import { getIsAIEnabled } from "../utils/ai";
import { diffInDays } from "../utils/datetime";
import { validateInputs } from "../utils/validate";
import { surveyCache } from "./cache";
-import { anySurveyHasFilters, buildOrderByClause, buildWhereClause, transformPrismaSurvey } from "./utils";
+import {
+ anySurveyHasFilters,
+ buildOrderByClause,
+ buildWhereClause,
+ doesSurveyHasOpenTextQuestion,
+ getInsightsEnabled,
+ transformPrismaSurvey,
+} from "./utils";
interface TriggerUpdate {
create?: Array<{ actionClassId: string }>;
@@ -537,6 +549,68 @@ export const updateSurvey = async (updatedSurvey: TSurvey): Promise =>
return rest;
});
+ const organization = await getOrganizationByEnvironmentId(environmentId);
+ if (!organization) {
+ throw new ResourceNotFoundError("Organization", null);
+ }
+
+ //AI Insights
+ const isAIEnabled = await getIsAIEnabled(organization.billing.plan);
+ if (isAIEnabled) {
+ if (doesSurveyHasOpenTextQuestion(data.questions ?? [])) {
+ const openTextQuestions = data.questions?.filter((question) => question.type === "openText") ?? [];
+ const currentSurveyOpenTextQuestions = currentSurvey.questions?.filter(
+ (question) => question.type === "openText"
+ );
+
+ // find the questions that have been updated or added
+ const questionsToCheckForInsights: TSurveyQuestions = [];
+
+ for (const question of openTextQuestions) {
+ const existingQuestion = currentSurveyOpenTextQuestions?.find((ques) => ques.id === question.id);
+ const isExistingQuestion = !!existingQuestion;
+
+ if (isExistingQuestion && question.headline.default === existingQuestion.headline.default) {
+ continue;
+ } else {
+ questionsToCheckForInsights.push(question);
+ }
+ }
+
+ const insightsEnabledValues = await Promise.all(
+ questionsToCheckForInsights.map(async (question) => {
+ const insightsEnabled = await getInsightsEnabled(question);
+
+ return { id: question.id, insightsEnabled };
+ })
+ );
+
+ data.questions = data.questions?.map((question) => {
+ const index = insightsEnabledValues.findIndex((item) => item.id === question.id);
+ if (index !== -1) {
+ return {
+ ...question,
+ insightsEnabled: insightsEnabledValues[index].insightsEnabled,
+ };
+ }
+
+ return question;
+ });
+ }
+ } else {
+ // check if an existing question got changed that had insights enabled
+ const insightsEnabledOpenTextQuestions = currentSurvey.questions?.filter(
+ (question) => question.type === "openText" && question.insightsEnabled !== undefined
+ );
+ // if question headline changed, remove insightsEnabled
+ for (const question of insightsEnabledOpenTextQuestions) {
+ const updatedQuestion = data.questions?.find((q) => q.id === question.id);
+ if (updatedQuestion && updatedQuestion.headline.default !== question.headline.default) {
+ updatedQuestion.insightsEnabled = undefined;
+ }
+ }
+ }
+
surveyData.updatedAt = new Date();
data = {
@@ -680,7 +754,7 @@ export const createSurvey = async (
const actionClasses = await getActionClasses(parsedEnvironmentId);
// @ts-expect-error
- const data: Omit = {
+ let data: Omit = {
...restSurveyBody,
// TODO: Create with attributeFilters
triggers: restSurveyBody.triggers
@@ -697,6 +771,38 @@ export const createSurvey = async (
};
}
+ const organization = await getOrganizationByEnvironmentId(parsedEnvironmentId);
+ if (!organization) {
+ throw new ResourceNotFoundError("Organization", null);
+ }
+
+ //AI Insights
+ const isAIEnabled = await getIsAIEnabled(organization.billing.plan);
+ if (isAIEnabled) {
+ if (doesSurveyHasOpenTextQuestion(data.questions ?? [])) {
+ const openTextQuestions = data.questions?.filter((question) => question.type === "openText") ?? [];
+ const insightsEnabledValues = await Promise.all(
+ openTextQuestions.map(async (question) => {
+ const insightsEnabled = await getInsightsEnabled(question);
+
+ return { id: question.id, insightsEnabled };
+ })
+ );
+
+ data.questions = data.questions?.map((question) => {
+ const index = insightsEnabledValues.findIndex((item) => item.id === question.id);
+ if (index !== -1) {
+ return {
+ ...question,
+ insightsEnabled: insightsEnabledValues[index].insightsEnabled,
+ };
+ }
+
+ return question;
+ });
+ }
+ }
+
const survey = await prisma.survey.create({
data: {
...data,
diff --git a/packages/lib/survey/tests/survey.test.ts b/packages/lib/survey/tests/survey.test.ts
index 5827d88156..f9b9110891 100644
--- a/packages/lib/survey/tests/survey.test.ts
+++ b/packages/lib/survey/tests/survey.test.ts
@@ -288,6 +288,7 @@ describe("Tests for updateSurvey", () => {
describe("Happy Path", () => {
it("Updates a survey successfully", async () => {
prisma.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
+ prisma.organization.findFirst.mockResolvedValueOnce(mockOrganizationOutput);
prisma.survey.update.mockResolvedValueOnce(mockSurveyOutput);
const updatedSurvey = await updateSurvey(updateSurveyInput);
expect(updatedSurvey).toEqual(mockTransformedSurveyOutput);
@@ -311,6 +312,7 @@ describe("Tests for updateSurvey", () => {
clientVersion: "0.0.1",
});
prisma.survey.findUnique.mockResolvedValueOnce(mockSurveyOutput);
+ prisma.organization.findFirst.mockResolvedValueOnce(mockOrganizationOutput);
prisma.survey.update.mockRejectedValue(errToThrow);
await expect(updateSurvey(updateSurveyInput)).rejects.toThrow(DatabaseError);
});
diff --git a/packages/lib/survey/utils.ts b/packages/lib/survey/utils.ts
index e1106b233a..f6e281d10b 100644
--- a/packages/lib/survey/utils.ts
+++ b/packages/lib/survey/utils.ts
@@ -1,7 +1,15 @@
import "server-only";
import { Prisma } from "@prisma/client";
+import { generateObject } from "ai";
+import { z } from "zod";
import { TSegment } from "@formbricks/types/segment";
-import { TSurvey, TSurveyFilterCriteria } from "@formbricks/types/surveys/types";
+import {
+ TSurvey,
+ TSurveyFilterCriteria,
+ TSurveyQuestion,
+ TSurveyQuestions,
+} from "@formbricks/types/surveys/types";
+import { llmModel } from "../aiModels";
export const transformPrismaSurvey = (surveyPrisma: any): TSurvey => {
let segment: TSegment | null = null;
@@ -86,3 +94,20 @@ export const anySurveyHasFilters = (surveys: TSurvey[]): boolean => {
return false;
});
};
+
+export const doesSurveyHasOpenTextQuestion = (questions: TSurveyQuestions): boolean => {
+ return questions.some((question) => question.type === "openText");
+};
+
+export const getInsightsEnabled = async (question: TSurveyQuestion): Promise => {
+ const { object } = await generateObject({
+ model: llmModel,
+ schema: z.object({
+ insightsEnabled: z.boolean(),
+ }),
+ prompt: `We extract insights (e.g. feature requests, complaints, other) from survey questions. Can we find them in this question?: ${question.headline.default}`,
+ experimental_telemetry: { isEnabled: true },
+ });
+
+ return object.insightsEnabled;
+};
diff --git a/packages/lib/utils/ai.ts b/packages/lib/utils/ai.ts
new file mode 100644
index 0000000000..c7b90a0754
--- /dev/null
+++ b/packages/lib/utils/ai.ts
@@ -0,0 +1,15 @@
+import { TOrganizationBillingPlan } from "@formbricks/types/organizations";
+import { IS_AI_CONFIGURED, IS_FORMBRICKS_CLOUD } from "../constants";
+
+export const getPromptText = (questionHeadline: string, response: string) => {
+ return `**${questionHeadline}**\n${response}`;
+};
+
+export const getIsAIEnabled = async (billingPlan: TOrganizationBillingPlan) => {
+ // This is a temporary workaround to enable AI without checking the ee license validity, as the ee package is not available in the lib package.(but the billing plan check suffices the license check).
+ return Boolean(
+ IS_AI_CONFIGURED &&
+ IS_FORMBRICKS_CLOUD &&
+ (billingPlan === "startup" || billingPlan === "scale" || billingPlan === "enterprise")
+ );
+};
diff --git a/packages/lib/vite.config.ts b/packages/lib/vite.config.ts
index 6f770ba7a7..961fa69cc7 100644
--- a/packages/lib/vite.config.ts
+++ b/packages/lib/vite.config.ts
@@ -1,7 +1,9 @@
+import tsconfigPaths from "vite-tsconfig-paths";
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
setupFiles: ["./vitestSetup.ts"],
},
+ plugins: [tsconfigPaths()],
});
diff --git a/packages/surveys/src/components/general/Headline.tsx b/packages/surveys/src/components/general/Headline.tsx
index a0402c531d..aa55a1d89f 100644
--- a/packages/surveys/src/components/general/Headline.tsx
+++ b/packages/surveys/src/components/general/Headline.tsx
@@ -1,6 +1,8 @@
+import { TSurveyQuestionId } from "@formbricks/types/surveys/types";
+
interface HeadlineProps {
headline?: string;
- questionId: string;
+ questionId: TSurveyQuestionId;
required?: boolean;
alignTextCenter?: boolean;
}
diff --git a/packages/surveys/src/components/general/HtmlBody.tsx b/packages/surveys/src/components/general/HtmlBody.tsx
index 5e194b0281..e124e4e1c3 100644
--- a/packages/surveys/src/components/general/HtmlBody.tsx
+++ b/packages/surveys/src/components/general/HtmlBody.tsx
@@ -1,9 +1,10 @@
import { cn } from "@/lib/utils";
import { useEffect, useState } from "react";
+import { TSurveyQuestionId } from "@formbricks/types/surveys/types";
interface HtmlBodyProps {
htmlString?: string;
- questionId: string;
+ questionId: TSurveyQuestionId;
}
export const HtmlBody = ({ htmlString, questionId }: HtmlBodyProps) => {
diff --git a/packages/surveys/src/components/general/ProgressBar.tsx b/packages/surveys/src/components/general/ProgressBar.tsx
index 4a89abca7c..861c8235ee 100644
--- a/packages/surveys/src/components/general/ProgressBar.tsx
+++ b/packages/surveys/src/components/general/ProgressBar.tsx
@@ -1,11 +1,11 @@
import { calculateElementIdx } from "@/lib/utils";
import { useCallback, useMemo } from "preact/hooks";
-import { TSurvey } from "@formbricks/types/surveys/types";
+import { TSurvey, TSurveyQuestionId } from "@formbricks/types/surveys/types";
import { Progress } from "./Progress";
interface ProgressBarProps {
survey: TSurvey;
- questionId: string;
+ questionId: TSurveyQuestionId;
}
export const ProgressBar = ({ survey, questionId }: ProgressBarProps) => {
diff --git a/packages/surveys/src/components/general/QuestionConditional.tsx b/packages/surveys/src/components/general/QuestionConditional.tsx
index fb455e1ca0..c57c8df2ca 100644
--- a/packages/surveys/src/components/general/QuestionConditional.tsx
+++ b/packages/surveys/src/components/general/QuestionConditional.tsx
@@ -20,6 +20,7 @@ import { TUploadFileConfig } from "@formbricks/types/storage";
import {
TSurveyQuestion,
TSurveyQuestionChoice,
+ TSurveyQuestionId,
TSurveyQuestionTypeEnum,
} from "@formbricks/types/surveys/types";
@@ -39,7 +40,7 @@ interface QuestionConditionalProps {
setTtc: (ttc: TResponseTtc) => void;
surveyId: string;
autoFocusEnabled: boolean;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
}
export const QuestionConditional = ({
diff --git a/packages/surveys/src/components/general/Subheader.tsx b/packages/surveys/src/components/general/Subheader.tsx
index 2551a1d54a..b6e1ed5a49 100644
--- a/packages/surveys/src/components/general/Subheader.tsx
+++ b/packages/surveys/src/components/general/Subheader.tsx
@@ -1,6 +1,8 @@
+import { TSurveyQuestionId } from "@formbricks/types/surveys/types";
+
interface SubheaderProps {
subheader?: string;
- questionId: string;
+ questionId: TSurveyQuestionId;
}
export const Subheader = ({ subheader, questionId }: SubheaderProps) => {
diff --git a/packages/surveys/src/components/general/Survey.tsx b/packages/surveys/src/components/general/Survey.tsx
index 3d2059164c..7b473fc220 100644
--- a/packages/surveys/src/components/general/Survey.tsx
+++ b/packages/surveys/src/components/general/Survey.tsx
@@ -19,10 +19,10 @@ import type {
TResponseTtc,
TResponseVariables,
} from "@formbricks/types/responses";
-import { TSurvey } from "@formbricks/types/surveys/types";
+import { TSurvey, TSurveyQuestionId } from "@formbricks/types/surveys/types";
interface VariableStackEntry {
- questionId: string;
+ questionId: TSurveyQuestionId;
variables: TResponseVariables;
}
@@ -198,7 +198,7 @@ export const Survey = ({
}));
};
- const pushVariableState = (questionId: string) => {
+ const pushVariableState = (questionId: TSurveyQuestionId) => {
setVariableStack((prevStack) => [...prevStack, { questionId, variables: { ...currentVariables } }]);
};
@@ -215,7 +215,7 @@ export const Survey = ({
const evaluateLogicAndGetNextQuestionId = (
data: TResponseData
- ): { nextQuestionId: string | undefined; calculatedVariables: TResponseVariables } => {
+ ): { nextQuestionId: TSurveyQuestionId | undefined; calculatedVariables: TResponseVariables } => {
const questions = survey.questions;
const firstEndingId = survey.endings.length > 0 ? survey.endings[0].id : undefined;
@@ -318,7 +318,10 @@ export const Survey = ({
setQuestionId(prevQuestionId);
};
- const getQuestionPrefillData = (questionId: string, offset: number): TResponseDataValue | undefined => {
+ const getQuestionPrefillData = (
+ questionId: TSurveyQuestionId,
+ offset: number
+ ): TResponseDataValue | undefined => {
if (offset === 0 && prefillResponseData) {
return prefillResponseData[questionId];
}
diff --git a/packages/surveys/src/components/questions/AddressQuestion.tsx b/packages/surveys/src/components/questions/AddressQuestion.tsx
index 04390773e4..98f2e210e5 100644
--- a/packages/surveys/src/components/questions/AddressQuestion.tsx
+++ b/packages/surveys/src/components/questions/AddressQuestion.tsx
@@ -10,7 +10,7 @@ import { useMemo, useRef, useState } from "preact/hooks";
import { useCallback } from "react";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
-import type { TSurveyAddressQuestion } from "@formbricks/types/surveys/types";
+import type { TSurveyAddressQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
interface AddressQuestionProps {
question: TSurveyAddressQuestion;
@@ -23,7 +23,7 @@ interface AddressQuestionProps {
languageCode: string;
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
autoFocusEnabled: boolean;
}
diff --git a/packages/surveys/src/components/questions/CTAQuestion.tsx b/packages/surveys/src/components/questions/CTAQuestion.tsx
index 2450bc98b9..dbb8d388be 100644
--- a/packages/surveys/src/components/questions/CTAQuestion.tsx
+++ b/packages/surveys/src/components/questions/CTAQuestion.tsx
@@ -9,7 +9,7 @@ import { useState } from "react";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TResponseData } from "@formbricks/types/responses";
import { TResponseTtc } from "@formbricks/types/responses";
-import type { TSurveyCTAQuestion } from "@formbricks/types/surveys/types";
+import type { TSurveyCTAQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
interface CTAQuestionProps {
question: TSurveyCTAQuestion;
@@ -23,7 +23,7 @@ interface CTAQuestionProps {
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
autoFocusEnabled: boolean;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
}
export const CTAQuestion = ({
diff --git a/packages/surveys/src/components/questions/CalQuestion.tsx b/packages/surveys/src/components/questions/CalQuestion.tsx
index 53b319848d..52450ef3d4 100644
--- a/packages/surveys/src/components/questions/CalQuestion.tsx
+++ b/packages/surveys/src/components/questions/CalQuestion.tsx
@@ -10,7 +10,7 @@ import { useCallback, useState } from "preact/hooks";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TResponseData } from "@formbricks/types/responses";
import { TResponseTtc } from "@formbricks/types/responses";
-import { TSurveyCalQuestion } from "@formbricks/types/surveys/types";
+import { TSurveyCalQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
interface CalQuestionProps {
question: TSurveyCalQuestion;
@@ -24,7 +24,7 @@ interface CalQuestionProps {
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
autoFocusEnabled: boolean;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
}
export const CalQuestion = ({
diff --git a/packages/surveys/src/components/questions/ConsentQuestion.tsx b/packages/surveys/src/components/questions/ConsentQuestion.tsx
index 08116d82ba..bde52a2857 100644
--- a/packages/surveys/src/components/questions/ConsentQuestion.tsx
+++ b/packages/surveys/src/components/questions/ConsentQuestion.tsx
@@ -8,7 +8,7 @@ import { getUpdatedTtc, useTtc } from "@/lib/ttc";
import { useCallback, useState } from "preact/hooks";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
-import type { TSurveyConsentQuestion } from "@formbricks/types/surveys/types";
+import type { TSurveyConsentQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
interface ConsentQuestionProps {
question: TSurveyConsentQuestion;
@@ -22,7 +22,7 @@ interface ConsentQuestionProps {
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
autoFocusEnabled: boolean;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
}
export const ConsentQuestion = ({
diff --git a/packages/surveys/src/components/questions/ContactInfoQuestion.tsx b/packages/surveys/src/components/questions/ContactInfoQuestion.tsx
index c99f1f18ab..a1d7f890ab 100644
--- a/packages/surveys/src/components/questions/ContactInfoQuestion.tsx
+++ b/packages/surveys/src/components/questions/ContactInfoQuestion.tsx
@@ -9,7 +9,7 @@ import { getUpdatedTtc, useTtc } from "@/lib/ttc";
import { useCallback, useMemo, useRef, useState } from "preact/hooks";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
-import type { TSurveyContactInfoQuestion } from "@formbricks/types/surveys/types";
+import type { TSurveyContactInfoQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
interface ContactInfoQuestionProps {
question: TSurveyContactInfoQuestion;
@@ -23,7 +23,7 @@ interface ContactInfoQuestionProps {
languageCode: string;
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
autoFocusEnabled: boolean;
}
diff --git a/packages/surveys/src/components/questions/DateQuestion.tsx b/packages/surveys/src/components/questions/DateQuestion.tsx
index 1059b13492..e7a92ac9ea 100644
--- a/packages/surveys/src/components/questions/DateQuestion.tsx
+++ b/packages/surveys/src/components/questions/DateQuestion.tsx
@@ -11,7 +11,7 @@ import DatePicker from "react-date-picker";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { getMonthName, getOrdinalDate } from "@formbricks/lib/utils/datetime";
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
-import type { TSurveyDateQuestion } from "@formbricks/types/surveys/types";
+import type { TSurveyDateQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
import "../../styles/date-picker.css";
interface DateQuestionProps {
@@ -27,7 +27,7 @@ interface DateQuestionProps {
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
autoFocusEnabled: boolean;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
}
const CalendarIcon = () => (
diff --git a/packages/surveys/src/components/questions/FileUploadQuestion.tsx b/packages/surveys/src/components/questions/FileUploadQuestion.tsx
index 07b4739989..b4ae6360c3 100644
--- a/packages/surveys/src/components/questions/FileUploadQuestion.tsx
+++ b/packages/surveys/src/components/questions/FileUploadQuestion.tsx
@@ -8,7 +8,7 @@ import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TJsFileUploadParams } from "@formbricks/types/js";
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
import { TUploadFileConfig } from "@formbricks/types/storage";
-import type { TSurveyFileUploadQuestion } from "@formbricks/types/surveys/types";
+import type { TSurveyFileUploadQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
import { BackButton } from "../buttons/BackButton";
import { FileInput } from "../general/FileInput";
import { Subheader } from "../general/Subheader";
@@ -27,7 +27,7 @@ interface FileUploadQuestionProps {
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
autoFocusEnabled: boolean;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
}
export const FileUploadQuestion = ({
diff --git a/packages/surveys/src/components/questions/MatrixQuestion.tsx b/packages/surveys/src/components/questions/MatrixQuestion.tsx
index 10e5b9fc36..e7558fe047 100644
--- a/packages/surveys/src/components/questions/MatrixQuestion.tsx
+++ b/packages/surveys/src/components/questions/MatrixQuestion.tsx
@@ -10,7 +10,7 @@ import { JSX } from "preact";
import { useCallback, useMemo, useState } from "preact/hooks";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
-import type { TI18nString, TSurveyMatrixQuestion } from "@formbricks/types/surveys/types";
+import type { TI18nString, TSurveyMatrixQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
interface MatrixQuestionProps {
question: TSurveyMatrixQuestion;
@@ -23,7 +23,7 @@ interface MatrixQuestionProps {
languageCode: string;
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
}
export const MatrixQuestion = ({
diff --git a/packages/surveys/src/components/questions/MultipleChoiceMultiQuestion.tsx b/packages/surveys/src/components/questions/MultipleChoiceMultiQuestion.tsx
index 1ac3b24e09..ab817ff1ae 100644
--- a/packages/surveys/src/components/questions/MultipleChoiceMultiQuestion.tsx
+++ b/packages/surveys/src/components/questions/MultipleChoiceMultiQuestion.tsx
@@ -9,7 +9,7 @@ import { cn, getShuffledChoicesIds } from "@/lib/utils";
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
-import type { TSurveyMultipleChoiceQuestion } from "@formbricks/types/surveys/types";
+import type { TSurveyMultipleChoiceQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
interface MultipleChoiceMultiProps {
question: TSurveyMultipleChoiceQuestion;
@@ -23,7 +23,7 @@ interface MultipleChoiceMultiProps {
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
autoFocusEnabled: boolean;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
}
export const MultipleChoiceMultiQuestion = ({
diff --git a/packages/surveys/src/components/questions/MultipleChoiceSingleQuestion.tsx b/packages/surveys/src/components/questions/MultipleChoiceSingleQuestion.tsx
index 41702b4707..51864d16fb 100644
--- a/packages/surveys/src/components/questions/MultipleChoiceSingleQuestion.tsx
+++ b/packages/surveys/src/components/questions/MultipleChoiceSingleQuestion.tsx
@@ -9,7 +9,7 @@ import { cn, getShuffledChoicesIds } from "@/lib/utils";
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
-import type { TSurveyMultipleChoiceQuestion } from "@formbricks/types/surveys/types";
+import type { TSurveyMultipleChoiceQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
interface MultipleChoiceSingleProps {
question: TSurveyMultipleChoiceQuestion;
@@ -23,7 +23,7 @@ interface MultipleChoiceSingleProps {
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
autoFocusEnabled: boolean;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
}
export const MultipleChoiceSingleQuestion = ({
diff --git a/packages/surveys/src/components/questions/NPSQuestion.tsx b/packages/surveys/src/components/questions/NPSQuestion.tsx
index c9b865694d..2d388e09cd 100644
--- a/packages/surveys/src/components/questions/NPSQuestion.tsx
+++ b/packages/surveys/src/components/questions/NPSQuestion.tsx
@@ -9,7 +9,7 @@ import { cn } from "@/lib/utils";
import { useState } from "preact/hooks";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
-import type { TSurveyNPSQuestion } from "@formbricks/types/surveys/types";
+import type { TSurveyNPSQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
interface NPSQuestionProps {
question: TSurveyNPSQuestion;
@@ -23,7 +23,7 @@ interface NPSQuestionProps {
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
autoFocusEnabled: boolean;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
}
export const NPSQuestion = ({
diff --git a/packages/surveys/src/components/questions/OpenTextQuestion.tsx b/packages/surveys/src/components/questions/OpenTextQuestion.tsx
index 1c4b9b8609..84fe388526 100644
--- a/packages/surveys/src/components/questions/OpenTextQuestion.tsx
+++ b/packages/surveys/src/components/questions/OpenTextQuestion.tsx
@@ -10,7 +10,7 @@ import { useCallback } from "react";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TResponseData } from "@formbricks/types/responses";
import { TResponseTtc } from "@formbricks/types/responses";
-import type { TSurveyOpenTextQuestion } from "@formbricks/types/surveys/types";
+import type { TSurveyOpenTextQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
interface OpenTextQuestionProps {
question: TSurveyOpenTextQuestion;
@@ -25,7 +25,7 @@ interface OpenTextQuestionProps {
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
autoFocusEnabled: boolean;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
}
export const OpenTextQuestion = ({
diff --git a/packages/surveys/src/components/questions/PictureSelectionQuestion.tsx b/packages/surveys/src/components/questions/PictureSelectionQuestion.tsx
index 98b6bc72f7..63d467c23b 100644
--- a/packages/surveys/src/components/questions/PictureSelectionQuestion.tsx
+++ b/packages/surveys/src/components/questions/PictureSelectionQuestion.tsx
@@ -9,7 +9,7 @@ import { cn } from "@/lib/utils";
import { useEffect, useState } from "preact/hooks";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
-import type { TSurveyPictureSelectionQuestion } from "@formbricks/types/surveys/types";
+import type { TSurveyPictureSelectionQuestion, TSurveyQuestionId } from "@formbricks/types/surveys/types";
interface PictureSelectionProps {
question: TSurveyPictureSelectionQuestion;
@@ -23,7 +23,7 @@ interface PictureSelectionProps {
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
autoFocusEnabled: boolean;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
}
export const PictureSelectionQuestion = ({
diff --git a/packages/surveys/src/components/questions/RankingQuestion.tsx b/packages/surveys/src/components/questions/RankingQuestion.tsx
index 35f101e337..ba840ed95c 100644
--- a/packages/surveys/src/components/questions/RankingQuestion.tsx
+++ b/packages/surveys/src/components/questions/RankingQuestion.tsx
@@ -10,7 +10,11 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useCallback, useMemo, useState } from "preact/hooks";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
-import type { TSurveyQuestionChoice, TSurveyRankingQuestion } from "@formbricks/types/surveys/types";
+import type {
+ TSurveyQuestionChoice,
+ TSurveyQuestionId,
+ TSurveyRankingQuestion,
+} from "@formbricks/types/surveys/types";
interface RankingQuestionProps {
question: TSurveyRankingQuestion;
@@ -24,7 +28,7 @@ interface RankingQuestionProps {
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
autoFocusEnabled: boolean;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
}
export const RankingQuestion = ({
diff --git a/packages/surveys/src/components/questions/RatingQuestion.tsx b/packages/surveys/src/components/questions/RatingQuestion.tsx
index 353b4ade53..5c25b1add2 100644
--- a/packages/surveys/src/components/questions/RatingQuestion.tsx
+++ b/packages/surveys/src/components/questions/RatingQuestion.tsx
@@ -8,7 +8,7 @@ import { cn } from "@/lib/utils";
import { useEffect, useState } from "preact/hooks";
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { TResponseData, TResponseTtc } from "@formbricks/types/responses";
-import type { TSurveyRatingQuestion } from "@formbricks/types/surveys/types";
+import type { TSurveyQuestionId, TSurveyRatingQuestion } from "@formbricks/types/surveys/types";
import {
ConfusedFace,
FrowningFace,
@@ -35,7 +35,7 @@ interface RatingQuestionProps {
ttc: TResponseTtc;
setTtc: (ttc: TResponseTtc) => void;
autoFocusEnabled: boolean;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
}
export const RatingQuestion = ({
diff --git a/packages/surveys/src/components/wrappers/StackedCardsContainer.tsx b/packages/surveys/src/components/wrappers/StackedCardsContainer.tsx
index 4d6eea1858..801e544969 100644
--- a/packages/surveys/src/components/wrappers/StackedCardsContainer.tsx
+++ b/packages/surveys/src/components/wrappers/StackedCardsContainer.tsx
@@ -2,18 +2,18 @@ import { cn } from "@/lib/utils";
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
import { TProductStyling } from "@formbricks/types/product";
import { TCardArrangementOptions } from "@formbricks/types/styling";
-import { TSurvey, TSurveyStyling } from "@formbricks/types/surveys/types";
+import { TSurvey, TSurveyQuestionId, TSurveyStyling } from "@formbricks/types/surveys/types";
// offset = 0 -> Current question card
// offset < 0 -> Question cards that are already answered
// offset > 0 -> Question that aren't answered yet
interface StackedCardsContainerProps {
cardArrangement: TCardArrangementOptions;
- currentQuestionId: string;
+ currentQuestionId: TSurveyQuestionId;
survey: TSurvey;
getCardContent: (questionIdxTemp: number, offset: number) => JSX.Element | undefined;
styling: TProductStyling | TSurveyStyling;
- setQuestionId: (questionId: string) => void;
+ setQuestionId: (questionId: TSurveyQuestionId) => void;
shouldResetQuestionId?: boolean;
fullSizeCards: boolean;
}
diff --git a/packages/surveys/src/lib/ttc.ts b/packages/surveys/src/lib/ttc.ts
index c184cdfc1d..c2a47b0e74 100644
--- a/packages/surveys/src/lib/ttc.ts
+++ b/packages/surveys/src/lib/ttc.ts
@@ -1,7 +1,8 @@
import { useEffect } from "react";
import { TResponseTtc } from "@formbricks/types/responses";
+import { TSurveyQuestionId } from "@formbricks/types/surveys/types";
-export const getUpdatedTtc = (ttc: TResponseTtc, questionId: string, time: number) => {
+export const getUpdatedTtc = (ttc: TResponseTtc, questionId: TSurveyQuestionId, time: number) => {
// Check if the question ID already exists
if (ttc.hasOwnProperty(questionId)) {
return {
@@ -18,7 +19,7 @@ export const getUpdatedTtc = (ttc: TResponseTtc, questionId: string, time: numbe
};
export const useTtc = (
- questionId: string,
+ questionId: TSurveyQuestionId,
ttc: TResponseTtc,
setTtc: (ttc: TResponseTtc) => void,
startTime: number,
diff --git a/packages/types/document-insights.ts b/packages/types/document-insights.ts
new file mode 100644
index 0000000000..7356d0f3fb
--- /dev/null
+++ b/packages/types/document-insights.ts
@@ -0,0 +1,9 @@
+import { z } from "zod";
+import { ZId } from "./common";
+
+export const ZDocumentInsight = z.object({
+ documentId: ZId,
+ insightId: ZId,
+});
+
+export type TDocumentInsight = z.infer;
diff --git a/packages/types/documents.ts b/packages/types/documents.ts
new file mode 100644
index 0000000000..1c046e74a6
--- /dev/null
+++ b/packages/types/documents.ts
@@ -0,0 +1,56 @@
+import { z } from "zod";
+import { ZId } from "./common";
+import { ZInsightCategory } from "./insights";
+import { ZSurveyQuestionId } from "./surveys/types";
+
+export const ZDocumentSentiment = z.enum(["positive", "negative", "neutral"]);
+
+export type TDocumentSentiment = z.infer;
+
+export const ZDocument = z.object({
+ id: ZId,
+ createdAt: z.date(),
+ updatedAt: z.date(),
+ environmentId: ZId,
+ responseId: ZId.nullable(),
+ questionId: ZSurveyQuestionId.nullable(),
+ sentiment: ZDocumentSentiment,
+ text: z.string(),
+});
+
+export type TDocument = z.infer;
+
+export const ZDocumentCreateInput = z.object({
+ environmentId: ZId,
+ surveyId: ZId,
+ responseId: ZId,
+ questionId: ZSurveyQuestionId,
+ text: z.string(),
+});
+
+export type TDocumentCreateInput = z.infer;
+
+export const ZDocumentFilterCriteria = z.object({
+ createdAt: z
+ .object({
+ min: z.date().optional(),
+ max: z.date().optional(),
+ })
+ .optional(),
+});
+
+export type TDocumentFilterCriteria = z.infer;
+
+export const ZGenerateDocumentObjectSchema = z.object({
+ sentiment: ZDocumentSentiment,
+ insights: z.array(
+ z.object({
+ title: z.string().describe("insight title, very specific"),
+ description: z.string().describe("very brief insight description"),
+ category: ZInsightCategory,
+ })
+ ),
+ isSpam: z.boolean(),
+});
+
+export type TGenerateDocumentObjectSchema = z.infer;
diff --git a/packages/types/insights.ts b/packages/types/insights.ts
new file mode 100644
index 0000000000..69b250f70a
--- /dev/null
+++ b/packages/types/insights.ts
@@ -0,0 +1,43 @@
+import { z } from "zod";
+import { ZId } from "./common";
+
+export const ZInsightCategory = z.enum(["featureRequest", "complaint", "praise", "other"]);
+
+export type TInsightCategory = z.infer;
+
+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;
+
+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;
+
+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;
diff --git a/packages/types/organizations.ts b/packages/types/organizations.ts
index 48295aaf84..990a8235da 100644
--- a/packages/types/organizations.ts
+++ b/packages/types/organizations.ts
@@ -39,6 +39,7 @@ export const ZOrganization = z.object({
message: "Organization name must be at least 1 character long",
}),
billing: ZOrganizationBilling,
+ isAIEnabled: z.boolean().default(false),
});
export const ZOrganizationCreateInput = z.object({
@@ -51,6 +52,7 @@ export type TOrganizationCreateInput = z.infer;
export const ZOrganizationUpdateInput = z.object({
name: z.string(),
billing: ZOrganizationBilling.optional(),
+ isAIEnabled: z.boolean().optional(),
});
export type TOrganizationUpdateInput = z.infer;
diff --git a/packages/types/surveys/types.ts b/packages/types/surveys/types.ts
index e26ccc1eef..dcd67c58b7 100644
--- a/packages/types/surveys/types.ts
+++ b/packages/types/surveys/types.ts
@@ -2,6 +2,7 @@ import { type ZodIssue, z } from "zod";
import { ZActionClass, ZActionClassNoCodeConfig } from "../action-classes";
import { ZAttributes } from "../attributes";
import { ZAllowedFileExtension, ZColor, ZId, ZPlacement } from "../common";
+import { ZInsight } from "../insights";
import { ZLanguage } from "../product";
import { ZSegment } from "../segment";
import { ZBaseStyling } from "../styling";
@@ -473,6 +474,7 @@ export const ZSurveyOpenTextQuestion = ZSurveyQuestionBase.extend({
placeholder: ZI18nString.optional(),
longAnswer: z.boolean().optional(),
inputType: ZSurveyOpenTextQuestionInputType.optional().default("text"),
+ insightsEnabled: z.boolean().default(false).optional(),
});
export type TSurveyOpenTextQuestion = z.infer;
@@ -2094,6 +2096,8 @@ export const ZSurveyQuestionSummaryOpenText = z.object({
personAttributes: ZAttributes.nullable(),
})
),
+ insights: z.array(ZInsight),
+ insightsEnabled: z.boolean().optional(),
});
export type TSurveyQuestionSummaryOpenText = z.infer;
@@ -2430,6 +2434,8 @@ export const ZSurveySummary = z.object({
summary: z.array(z.union([ZSurveyQuestionSummary, ZSurveyQuestionSummaryHiddenFields])),
});
+export type TSurveySummary = z.infer;
+
export const ZSurveyFilterCriteria = z.object({
name: z.string().optional(),
status: z.array(ZSurveyStatus).optional(),
@@ -2468,7 +2474,6 @@ const ZSortOption = z.object({
});
export type TSortOption = z.infer;
-export type TSurveySummary = z.infer;
export const ZSurveyRecallItem = z.object({
id: z.string(),
diff --git a/packages/types/surveys/validation.ts b/packages/types/surveys/validation.ts
index f7ca9ddd4b..35ffae9038 100644
--- a/packages/types/surveys/validation.ts
+++ b/packages/types/surveys/validation.ts
@@ -7,6 +7,7 @@ import type {
TSurveyLanguage,
TSurveyLogicAction,
TSurveyQuestion,
+ TSurveyQuestionId,
} from "./types";
export const FORBIDDEN_IDS = [
@@ -191,7 +192,7 @@ export const findQuestionsWithCyclicLogic = (questions: TSurveyQuestion[]): stri
const recStack: Record = {};
const cyclicQuestions = new Set();
- const checkForCyclicLogic = (questionId: string): boolean => {
+ const checkForCyclicLogic = (questionId: TSurveyQuestionId): boolean => {
if (!visited[questionId]) {
visited[questionId] = true;
recStack[questionId] = true;
diff --git a/packages/ui/components/Card/index.tsx b/packages/ui/components/Card/index.tsx
index 5a09020567..819aa673ce 100644
--- a/packages/ui/components/Card/index.tsx
+++ b/packages/ui/components/Card/index.tsx
@@ -1,65 +1,52 @@
-import { Button } from "../Button";
+import * as React from "react";
+import { cn } from "@formbricks/lib/cn";
-interface CardProps {
- connectText?: string;
- connectHref?: string;
- connectNewTab?: boolean;
- docsText?: string;
- docsHref?: string;
- docsNewTab?: boolean;
- label: string;
- description: string;
- icon?: React.ReactNode;
- connected?: boolean;
- statusText?: string;
-}
-
-export type { CardProps };
-
-export const Card: React.FC = ({
- connectText,
- connectHref,
- connectNewTab,
- docsText,
- docsHref,
- docsNewTab,
- label,
- description,
- icon,
- connected,
- statusText,
-}) => (
-
- {connected != undefined && statusText != undefined && (
-
- {connected === true ? (
-
-
-
-
- ) : (
-
-
-
- )}
- {statusText}
-
- )}
-
- {icon &&
{icon}
}
-
{label}
-
{description}
-
- {connectHref && (
-
- {connectText}
-
- )}
- {docsHref && (
-
- {docsText}
-
- )}
-
-
+const Card = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ )
);
+Card.displayName = "Card";
+
+const CardHeader = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ )
+);
+CardHeader.displayName = "CardHeader";
+
+const CardTitle = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ )
+);
+CardTitle.displayName = "CardTitle";
+
+const CardDescription = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ )
+);
+CardDescription.displayName = "CardDescription";
+
+const CardContent = React.forwardRef>(
+ ({ className, ...props }, ref) =>
+);
+CardContent.displayName = "CardContent";
+
+const CardFooter = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ )
+);
+CardFooter.displayName = "CardFooter";
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
diff --git a/packages/ui/components/Card/stories.tsx b/packages/ui/components/Card/stories.tsx
index ce7434755a..645e1d5fd2 100644
--- a/packages/ui/components/Card/stories.tsx
+++ b/packages/ui/components/Card/stories.tsx
@@ -3,7 +3,7 @@ import { BellRing } from "lucide-react";
import { Card } from "./index";
const meta = {
- title: "ui/Card",
+ title: "ui/IntegrationCard",
component: Card,
tags: ["autodocs"],
parameters: {
diff --git a/packages/ui/components/CardArrangementTabs/index.tsx b/packages/ui/components/CardArrangementTabs/index.tsx
index 2729594751..104b1c9628 100644
--- a/packages/ui/components/CardArrangementTabs/index.tsx
+++ b/packages/ui/components/CardArrangementTabs/index.tsx
@@ -1,6 +1,6 @@
import { TCardArrangementOptions } from "@formbricks/types/styling";
import { TSurveyType } from "@formbricks/types/surveys/types";
-import { Tabs } from "../Tabs";
+import { StylingTabs } from "../StylingTabs";
import { CasualCardArrangementIcon } from "../icons/CasualCardArrangementIcon";
import { SimpleCardsArrangementIcon } from "../icons/SimpleCardArrangementIcon";
import { StraightCardArrangementIcon } from "../icons/StraightCardArrangementIcon";
@@ -36,7 +36,7 @@ export const CardArrangementTabs = ({
return (
-
{
handleCardArrangementChange(value);
diff --git a/packages/ui/components/IntegrationCard/index.tsx b/packages/ui/components/IntegrationCard/index.tsx
new file mode 100644
index 0000000000..5a09020567
--- /dev/null
+++ b/packages/ui/components/IntegrationCard/index.tsx
@@ -0,0 +1,65 @@
+import { Button } from "../Button";
+
+interface CardProps {
+ connectText?: string;
+ connectHref?: string;
+ connectNewTab?: boolean;
+ docsText?: string;
+ docsHref?: string;
+ docsNewTab?: boolean;
+ label: string;
+ description: string;
+ icon?: React.ReactNode;
+ connected?: boolean;
+ statusText?: string;
+}
+
+export type { CardProps };
+
+export const Card: React.FC = ({
+ connectText,
+ connectHref,
+ connectNewTab,
+ docsText,
+ docsHref,
+ docsNewTab,
+ label,
+ description,
+ icon,
+ connected,
+ statusText,
+}) => (
+
+ {connected != undefined && statusText != undefined && (
+
+ {connected === true ? (
+
+
+
+
+ ) : (
+
+
+
+ )}
+ {statusText}
+
+ )}
+
+ {icon &&
{icon}
}
+
{label}
+
{description}
+
+ {connectHref && (
+
+ {connectText}
+
+ )}
+ {docsHref && (
+
+ {docsText}
+
+ )}
+
+
+);
diff --git a/packages/ui/components/IntegrationCard/stories.tsx b/packages/ui/components/IntegrationCard/stories.tsx
new file mode 100644
index 0000000000..645e1d5fd2
--- /dev/null
+++ b/packages/ui/components/IntegrationCard/stories.tsx
@@ -0,0 +1,69 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { BellRing } from "lucide-react";
+import { Card } from "./index";
+
+const meta = {
+ title: "ui/IntegrationCard",
+ component: Card,
+ tags: ["autodocs"],
+ parameters: {
+ docs: {
+ description: {
+ component: `The **card** component is used to display a card with a label, description, and optional icon. It can also display a status and buttons for connecting and viewing documentation.`,
+ },
+ },
+ argTypes: {
+ icon: { control: "text" },
+ },
+ },
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Primary: Story = {
+ args: {
+ label: "Card Label",
+ description: "This is the description of the card.",
+ connectText: "Connect",
+ connectHref: "#",
+ connectNewTab: false,
+ docsText: "Docs",
+ docsHref: "#",
+ docsNewTab: false,
+ connected: true,
+ statusText: "Connected",
+ },
+};
+
+export const Disconnected: Story = {
+ args: {
+ label: "Card Label",
+ description: "This is the description of the card.",
+ connectText: "Connect",
+ connectHref: "#",
+ connectNewTab: false,
+ docsText: "Docs",
+ docsHref: "#",
+ docsNewTab: false,
+ connected: false,
+ statusText: "Disconnected",
+ },
+};
+
+export const WithIcon: Story = {
+ args: {
+ label: "Card Label",
+ description: "This is the description of the card.",
+ connectText: "Connect",
+ connectHref: "#",
+ connectNewTab: false,
+ docsText: "Docs",
+ docsHref: "#",
+ docsNewTab: false,
+ connected: true,
+ statusText: "Connected",
+ icon: ,
+ },
+};
diff --git a/packages/ui/components/OptionCard/index.tsx b/packages/ui/components/OptionCard/index.tsx
index 86e5c8e005..8050605592 100644
--- a/packages/ui/components/OptionCard/index.tsx
+++ b/packages/ui/components/OptionCard/index.tsx
@@ -12,9 +12,9 @@ interface PathwayOptionProps {
}
const sizeClasses = {
- sm: "rounded-lg max-w-xs border border-slate-200 shadow-card-sm transition-all duration-150",
- md: "rounded-xl max-w-xs border border-slate-200 shadow-card-md transition-all duration-300",
- lg: "rounded-2xl max-w-sm border border-slate-200 shadow-card-lg transition-all duration-500",
+ sm: "rounded-lg max-w-xs min-w-40 border border-slate-200 shadow-card-sm transition-all duration-150",
+ md: "rounded-xl max-w-xs min-w-40 border border-slate-200 shadow-card-md transition-all duration-300",
+ lg: "rounded-2xl max-w-sm min-w-40 border border-slate-200 shadow-card-lg transition-all duration-500",
};
export const OptionCard: React.FC = ({
diff --git a/packages/ui/components/PreviewSurvey/index.tsx b/packages/ui/components/PreviewSurvey/index.tsx
index f5623d7d34..90767a5836 100644
--- a/packages/ui/components/PreviewSurvey/index.tsx
+++ b/packages/ui/components/PreviewSurvey/index.tsx
@@ -8,7 +8,7 @@ import { TJsFileUploadParams } from "@formbricks/types/js";
import type { TProduct } from "@formbricks/types/product";
import { TProductStyling } from "@formbricks/types/product";
import { TUploadFileConfig } from "@formbricks/types/storage";
-import { TSurvey, TSurveyStyling } from "@formbricks/types/surveys/types";
+import { TSurvey, TSurveyQuestionId, TSurveyStyling } from "@formbricks/types/surveys/types";
import { ClientLogo } from "../ClientLogo";
import { MediaBackground } from "../MediaBackground";
import { ResetProgressButton } from "../ResetProgressButton";
@@ -144,7 +144,7 @@ export const PreviewSurvey = ({
}, [product.styling, survey.styling]);
const updateQuestionId = useCallback(
- (newQuestionId: string) => {
+ (newQuestionId: TSurveyQuestionId) => {
if (
!newQuestionId ||
newQuestionId === "hidden" ||
diff --git a/packages/ui/components/QuestionFormInput/components/RecallItemSelect.tsx b/packages/ui/components/QuestionFormInput/components/RecallItemSelect.tsx
index 41e8e73cb6..64978da97b 100644
--- a/packages/ui/components/QuestionFormInput/components/RecallItemSelect.tsx
+++ b/packages/ui/components/QuestionFormInput/components/RecallItemSelect.tsx
@@ -21,6 +21,7 @@ import {
TSurvey,
TSurveyHiddenFields,
TSurveyQuestion,
+ TSurveyQuestionId,
TSurveyRecallItem,
} from "@formbricks/types/surveys/types";
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "../../DropdownMenu";
@@ -40,7 +41,7 @@ const questionIconMapping = {
interface RecallItemSelectProps {
localSurvey: TSurvey;
- questionId: string;
+ questionId: TSurveyQuestionId;
addRecallItem: (question: TSurveyRecallItem) => void;
setShowRecallItemSelect: (show: boolean) => void;
recallItems: TSurveyRecallItem[];
diff --git a/packages/ui/components/SecondaryNavigation/index.tsx b/packages/ui/components/SecondaryNavigation/index.tsx
index e3dcf51b43..da69ce2035 100644
--- a/packages/ui/components/SecondaryNavigation/index.tsx
+++ b/packages/ui/components/SecondaryNavigation/index.tsx
@@ -2,7 +2,13 @@ import Link from "next/link";
import { cn } from "@formbricks/lib/cn";
interface SecondaryNavbarProps {
- navigation: { id: string; label: string; href: string; icon?: React.ReactNode; hidden?: boolean }[];
+ navigation: {
+ id: string;
+ label: string;
+ href?: string;
+ onClick?: (event: React.MouseEvent) => void;
+ hidden?: boolean;
+ }[];
activeId: string;
loading?: boolean;
}
@@ -15,37 +21,70 @@ export const SecondaryNavigation = ({ navigation, activeId, loading, ...props }:
{loading ? (
<>
{navigation.map((navElem) => (
-
- {navElem.label}
-
+
+
+ {navElem.label}
+
+
+
))}
>
) : (
<>
{navigation.map((navElem) => (
-
+ {navElem.href ? (
+
+ {navElem.label}
+
+ ) : (
+
+ {navElem.label}
+
)}
- aria-current={navElem.id === activeId ? "page" : undefined}>
- {navElem.label}
-
+
+
))}
>
)}
diff --git a/packages/ui/components/Separator/index.tsx b/packages/ui/components/Separator/index.tsx
new file mode 100644
index 0000000000..d5c3f45098
--- /dev/null
+++ b/packages/ui/components/Separator/index.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import * as SeparatorPrimitive from "@radix-ui/react-separator";
+import * as React from "react";
+import { cn } from "../../lib/utils";
+
+const Separator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
+
+));
+Separator.displayName = SeparatorPrimitive.Root.displayName;
+
+export { Separator };
diff --git a/packages/ui/components/Sheet/index.tsx b/packages/ui/components/Sheet/index.tsx
new file mode 100644
index 0000000000..191f571cfd
--- /dev/null
+++ b/packages/ui/components/Sheet/index.tsx
@@ -0,0 +1,119 @@
+"use client";
+
+import * as SheetPrimitive from "@radix-ui/react-dialog";
+import { type VariantProps, cva } from "class-variance-authority";
+import { X } from "lucide-react";
+import * as React from "react";
+import { cn } from "@formbricks/lib/cn";
+
+const Sheet = SheetPrimitive.Root;
+
+const SheetTrigger = SheetPrimitive.Trigger;
+
+const SheetClose = SheetPrimitive.Close;
+
+const SheetPortal = SheetPrimitive.Portal;
+
+const SheetOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
+
+const sheetVariants = cva(
+ "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
+ {
+ variants: {
+ side: {
+ top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
+ bottom:
+ "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
+ left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
+ right:
+ "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
+ },
+ },
+ defaultVariants: {
+ side: "right",
+ },
+ }
+);
+
+interface SheetContentProps
+ extends React.ComponentPropsWithoutRef,
+ VariantProps {}
+
+const SheetContent = React.forwardRef, SheetContentProps>(
+ ({ side = "right", className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+ )
+);
+SheetContent.displayName = SheetPrimitive.Content.displayName;
+
+const SheetHeader = ({ className, ...props }: React.HTMLAttributes) => (
+
+);
+SheetHeader.displayName = "SheetHeader";
+
+const SheetFooter = ({ className, ...props }: React.HTMLAttributes) => (
+
+);
+SheetFooter.displayName = "SheetFooter";
+
+const SheetTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+SheetTitle.displayName = SheetPrimitive.Title.displayName;
+
+const SheetDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+SheetDescription.displayName = SheetPrimitive.Description.displayName;
+
+export {
+ Sheet,
+ SheetPortal,
+ SheetOverlay,
+ SheetTrigger,
+ SheetClose,
+ SheetContent,
+ SheetHeader,
+ SheetFooter,
+ SheetTitle,
+ SheetDescription,
+};
diff --git a/packages/ui/components/StylingTabs/index.tsx b/packages/ui/components/StylingTabs/index.tsx
new file mode 100644
index 0000000000..29c4198757
--- /dev/null
+++ b/packages/ui/components/StylingTabs/index.tsx
@@ -0,0 +1,78 @@
+import React, { useState } from "react";
+import { cn } from "@formbricks/lib/cn";
+import { Label } from "../Label";
+
+interface Option {
+ value: T;
+ label: string;
+ icon?: React.ReactNode;
+}
+
+interface StylingTabsProps {
+ id: string;
+ options: Option[];
+ defaultSelected?: T;
+ onChange: (value: T) => void;
+ className?: string;
+ tabsContainerClassName?: string;
+
+ label?: string;
+ subLabel?: string;
+}
+
+export const StylingTabs = ({
+ id,
+ options,
+ defaultSelected,
+ onChange,
+ className,
+ tabsContainerClassName,
+ label,
+ subLabel,
+}: StylingTabsProps) => {
+ const [selectedOption, setSelectedOption] = useState(defaultSelected);
+
+ const handleChange = (event: React.ChangeEvent) => {
+ const value = event.target.value as T;
+ setSelectedOption(value);
+ onChange(value);
+ };
+
+ return (
+
+ <>
+ {label &&
{label} }
+ {subLabel &&
{subLabel}
}
+ >
+
+
+ {options.map((option) => (
+
+
+ {option.label}
+ {option.icon}
+
+ ))}
+
+
+ );
+};
diff --git a/packages/ui/components/Tabs/index.tsx b/packages/ui/components/Tabs/index.tsx
index 99605536f0..8d56818025 100644
--- a/packages/ui/components/Tabs/index.tsx
+++ b/packages/ui/components/Tabs/index.tsx
@@ -1,78 +1,54 @@
-import React, { useState } from "react";
+"use client";
+
+import * as TabsPrimitive from "@radix-ui/react-tabs";
+import * as React from "react";
import { cn } from "@formbricks/lib/cn";
-import { Label } from "../Label";
-interface Option {
- value: T;
- label: string;
- icon?: React.ReactNode;
-}
+const Tabs = TabsPrimitive.Root;
-interface TabsProps {
- id: string;
- options: Option[];
- defaultSelected?: T;
- onChange: (value: T) => void;
- className?: string;
- tabsContainerClassName?: string;
+const TabsList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsList.displayName = TabsPrimitive.List.displayName;
- label?: string;
- subLabel?: string;
-}
+const TabsTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
-export const Tabs = ({
- id,
- options,
- defaultSelected,
- onChange,
- className,
- tabsContainerClassName,
- label,
- subLabel,
-}: TabsProps) => {
- const [selectedOption, setSelectedOption] = useState(defaultSelected);
+const TabsContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsContent.displayName = TabsPrimitive.Content.displayName;
- const handleChange = (event: React.ChangeEvent) => {
- const value = event.target.value as T;
- setSelectedOption(value);
- onChange(value);
- };
-
- return (
-
- <>
- {label &&
{label} }
- {subLabel &&
{subLabel}
}
- >
-
-
- {options.map((option) => (
-
-
- {option.label}
- {option.icon}
-
- ))}
-
-
- );
-};
+export { Tabs, TabsList, TabsTrigger, TabsContent };
diff --git a/packages/ui/components/TemplateList/components/StartFromScratchTemplate.tsx b/packages/ui/components/TemplateList/components/StartFromScratchTemplate.tsx
index c3fe3eb5c0..e2d01b6e03 100644
--- a/packages/ui/components/TemplateList/components/StartFromScratchTemplate.tsx
+++ b/packages/ui/components/TemplateList/components/StartFromScratchTemplate.tsx
@@ -13,6 +13,7 @@ interface StartFromScratchTemplateProps {
product: TProduct;
createSurvey: (template: TTemplate) => void;
loading: boolean;
+ noPreview?: boolean;
}
export const StartFromScratchTemplate = ({
@@ -22,11 +23,16 @@ export const StartFromScratchTemplate = ({
product,
createSurvey,
loading,
+ noPreview,
}: StartFromScratchTemplateProps) => {
return (
{
+ if (noPreview) {
+ createSurvey(customSurvey);
+ return;
+ }
const newTemplate = replacePresetPlaceholders(customSurvey, product);
onTemplateClick(newTemplate);
setActiveTemplate(newTemplate);
diff --git a/packages/ui/components/TemplateList/components/Template.tsx b/packages/ui/components/TemplateList/components/Template.tsx
index 0c81caa4fd..47811a24af 100644
--- a/packages/ui/components/TemplateList/components/Template.tsx
+++ b/packages/ui/components/TemplateList/components/Template.tsx
@@ -14,6 +14,7 @@ interface TemplateProps {
createSurvey: (template: TTemplate) => void;
loading: boolean;
selectedFilter: TTemplateFilter[];
+ noPreview?: boolean;
}
export const Template = ({
@@ -25,11 +26,16 @@ export const Template = ({
createSurvey,
loading,
selectedFilter,
+ noPreview,
}: TemplateProps) => {
return (
{
const newTemplate = replacePresetPlaceholders(template, product);
+ if (noPreview) {
+ createSurvey(newTemplate);
+ return;
+ }
onTemplateClick(newTemplate);
setActiveTemplate(newTemplate);
}}
diff --git a/packages/ui/components/TemplateList/index.tsx b/packages/ui/components/TemplateList/index.tsx
index 6e1c0bfd6d..0f673d1819 100644
--- a/packages/ui/components/TemplateList/index.tsx
+++ b/packages/ui/components/TemplateList/index.tsx
@@ -20,17 +20,21 @@ interface TemplateListProps {
environment: TEnvironment;
product: TProduct;
templateSearch?: string;
+ showFilters?: boolean;
prefilledFilters: TTemplateFilter[];
onTemplateClick?: (template: TTemplate) => void;
+ noPreview?: boolean; // single click to create survey
}
export const TemplateList = ({
user,
product,
environment,
+ showFilters = true,
templateSearch,
prefilledFilters,
onTemplateClick = () => {},
+ noPreview,
}: TemplateListProps) => {
const router = useRouter();
const [activeTemplate, setActiveTemplate] = useState
(null);
@@ -101,8 +105,8 @@ export const TemplateList = ({
}, [selectedFilter, templateSearch]);
return (
-
- {!templateSearch && (
+
+ {showFilters && !templateSearch && (
{(process.env.NODE_ENV === "development" ? [...filteredTemplates] : filteredTemplates).map(
(template: TTemplate) => {
@@ -132,6 +137,7 @@ export const TemplateList = ({
createSurvey={createSurvey}
loading={loading}
selectedFilter={selectedFilter}
+ noPreview={noPreview}
/>
);
}
diff --git a/packages/ui/components/Textarea/index.tsx b/packages/ui/components/Textarea/index.tsx
new file mode 100644
index 0000000000..bb2e662a96
--- /dev/null
+++ b/packages/ui/components/Textarea/index.tsx
@@ -0,0 +1,20 @@
+import * as React from "react";
+import { cn } from "../../lib/utils";
+
+export interface TextareaProps extends React.TextareaHTMLAttributes {}
+
+const Textarea = React.forwardRef(({ className, ...props }, ref) => {
+ return (
+
+ );
+});
+Textarea.displayName = "Textarea";
+
+export { Textarea };
diff --git a/packages/ui/components/ToggleGroup/index.tsx b/packages/ui/components/ToggleGroup/index.tsx
new file mode 100644
index 0000000000..607da83ec8
--- /dev/null
+++ b/packages/ui/components/ToggleGroup/index.tsx
@@ -0,0 +1,52 @@
+"use client";
+
+import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
+import { type VariantProps } from "class-variance-authority";
+import * as React from "react";
+import { cn } from "@formbricks/lib/cn";
+import { toggleVariants } from "./toggle";
+
+const ToggleGroupContext = React.createContext>({
+ size: "default",
+ variant: "default",
+});
+
+const ToggleGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & VariantProps
+>(({ className, variant, size, children, ...props }, ref) => (
+
+ {children}
+
+));
+
+ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
+
+const ToggleGroupItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & VariantProps
+>(({ className, children, variant, size, ...props }, ref) => {
+ const context = React.useContext(ToggleGroupContext);
+
+ return (
+
+ {children}
+
+ );
+});
+
+ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
+
+export { ToggleGroup, ToggleGroupItem };
diff --git a/packages/ui/components/ToggleGroup/toggle.tsx b/packages/ui/components/ToggleGroup/toggle.tsx
new file mode 100644
index 0000000000..5a206a6b5d
--- /dev/null
+++ b/packages/ui/components/ToggleGroup/toggle.tsx
@@ -0,0 +1,39 @@
+"use client";
+
+import * as TogglePrimitive from "@radix-ui/react-toggle";
+import { type VariantProps, cva } from "class-variance-authority";
+import * as React from "react";
+import { cn } from "@formbricks/lib/cn";
+
+const toggleVariants = cva(
+ "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-white transition-colors hover:bg-slate-200 hover:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-slate-200 data-[state=on]:text-slate-900 dark:ring-offset-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-400 dark:focus-visible:ring-slate-300 dark:data-[state=on]:bg-slate-800 dark:data-[state=on]:text-slate-50",
+ {
+ variants: {
+ variant: {
+ default: "bg-transparent",
+ outline:
+ "border border-slate-300 bg-transparent hover:bg-slate-200 hover:text-slate-900 dark:border-slate-800 dark:hover:bg-slate-800 dark:hover:text-slate-50",
+ },
+ size: {
+ default: "h-10 px-3",
+ sm: "h-9 px-2.5",
+ lg: "h-11 px-5",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+);
+
+const Toggle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & VariantProps
+>(({ className, variant, size, ...props }, ref) => (
+
+));
+
+Toggle.displayName = TogglePrimitive.Root.displayName;
+
+export { Toggle, toggleVariants };
diff --git a/packages/ui/components/Typography/index.tsx b/packages/ui/components/Typography/index.tsx
new file mode 100644
index 0000000000..534fd4995e
--- /dev/null
+++ b/packages/ui/components/Typography/index.tsx
@@ -0,0 +1,155 @@
+import React, { forwardRef } from "react";
+import { cn } from "../../lib/utils";
+
+const H1 = forwardRef>((props, ref) => {
+ return (
+
+ {props.children}
+
+ );
+});
+
+H1.displayName = "H1";
+export { H1 };
+
+const H2 = forwardRef>((props, ref) => {
+ return (
+
+ {props.children}
+
+ );
+});
+
+H2.displayName = "H2";
+export { H2 };
+
+const H3 = forwardRef>((props, ref) => {
+ return (
+
+ {props.children}
+
+ );
+});
+
+H3.displayName = "H3";
+export { H3 };
+
+const H4 = forwardRef>((props, ref) => {
+ return (
+
+ {props.children}
+
+ );
+});
+
+H4.displayName = "H4";
+export { H4 };
+
+const Lead = forwardRef>((props, ref) => {
+ return (
+
+ {props.children}
+
+ );
+});
+
+Lead.displayName = "Lead";
+export { Lead };
+
+const P = forwardRef>((props, ref) => {
+ return (
+
+ {props.children}
+
+ );
+});
+
+P.displayName = "P";
+export { P };
+
+const Large = forwardRef>((props, ref) => {
+ return (
+
+ {props.children}
+
+ );
+});
+
+Large.displayName = "Large";
+export { Large };
+
+const Small = forwardRef>((props, ref) => {
+ return (
+
+ {props.children}
+
+ );
+});
+
+Small.displayName = "Small";
+export { Small };
+
+const Muted = forwardRef>((props, ref) => {
+ return (
+
+ {props.children}
+
+ );
+});
+
+Muted.displayName = "Muted";
+export { Muted };
+
+const InlineCode = forwardRef>((props, ref) => {
+ return (
+
+ {props.children}
+
+ );
+});
+
+InlineCode.displayName = "InlineCode";
+export { InlineCode };
+
+const List = forwardRef>((props, ref) => {
+ return (
+ li]:mt-2", props.className)}>
+ {props.children}
+
+ );
+});
+
+List.displayName = "List";
+export { List };
+
+const Quote = forwardRef>((props, ref) => {
+ return (
+
+ {props.children}
+
+ );
+});
+
+Quote.displayName = "Quote";
+export { Quote };
diff --git a/packages/ui/package.json b/packages/ui/package.json
index e0ba155b12..d123b2f12f 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -35,9 +35,13 @@
"@radix-ui/react-popover": "1.1.1",
"@radix-ui/react-radio-group": "1.2.0",
"@radix-ui/react-select": "2.1.1",
+ "@radix-ui/react-separator": "1.1.0",
"@radix-ui/react-slider": "1.2.0",
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-switch": "1.1.0",
+ "@radix-ui/react-tabs": "1.1.1",
+ "@radix-ui/react-toggle": "1.1.0",
+ "@radix-ui/react-toggle-group": "1.1.0",
"@radix-ui/react-tooltip": "1.1.2",
"@tailwindcss/forms": "0.5.9",
"@tailwindcss/typography": "0.5.13",
diff --git a/packages/ui/tailwind.config.js b/packages/ui/tailwind.config.js
index 4205af0666..40f98f21db 100644
--- a/packages/ui/tailwind.config.js
+++ b/packages/ui/tailwind.config.js
@@ -4,7 +4,7 @@ module.exports = {
"./app/**/*.{js,ts,jsx,tsx}", // Note the addition of the `app` directory.
"./pages/**/*.{js,ts,jsx,tsx}",
// include packages if not transpiling
- "../../packages/ui/**/*.{ts,tsx}",
+ "../../packages/ui/components/**/*.{ts,tsx}",
],
theme: {
extend: {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 689ca46215..5b0cd1c6c2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -78,7 +78,7 @@ importers:
version: link:../../packages/react-native
expo:
specifier: 51.0.26
- version: 51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13)
+ version: 51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13)
expo-status-bar:
specifier: 1.12.1
version: 1.12.1
@@ -87,10 +87,10 @@ importers:
version: 18.3.1
react-native:
specifier: 0.74.4
- version: 0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1)
+ version: 0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1)
react-native-webview:
specifier: 13.8.6
- version: 13.8.6(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)
+ version: 13.8.6(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)
devDependencies:
'@babel/core':
specifier: 7.25.2
@@ -130,19 +130,19 @@ importers:
version: 2.1.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@headlessui/tailwindcss':
specifier: 0.2.1
- version: 0.2.1(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)))
+ version: 0.2.1(tailwindcss@3.4.13(ts-node@10.9.2(typescript@5.4.5)))
'@mapbox/rehype-prism':
specifier: 0.9.0
version: 0.9.0
'@mdx-js/loader':
specifier: 3.0.1
- version: 3.0.1(webpack@5.95.0(@swc/core@1.3.101(@swc/helpers@0.5.13)))
+ version: 3.0.1(webpack@5.95.0)
'@mdx-js/react':
specifier: 3.0.1
version: 3.0.1(@types/react@18.3.11)(react@18.3.1)
'@next/mdx':
specifier: 14.2.15
- version: 14.2.15(@mdx-js/loader@3.0.1(webpack@5.95.0(@swc/core@1.3.101(@swc/helpers@0.5.13))))(@mdx-js/react@3.0.1(@types/react@18.3.11)(react@18.3.1))
+ version: 14.2.15(@mdx-js/loader@3.0.1(webpack@5.95.0))(@mdx-js/react@3.0.1(@types/react@18.3.11)(react@18.3.1))
'@paralleldrive/cuid2':
specifier: 2.2.2
version: 2.2.2
@@ -151,13 +151,13 @@ importers:
version: 2.2.1
'@tailwindcss/typography':
specifier: 0.5.15
- version: 0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)))
+ version: 0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(typescript@5.4.5)))
acorn:
specifier: 8.12.1
version: 8.12.1
autoprefixer:
specifier: 10.4.20
- version: 10.4.20(postcss@8.4.41)
+ version: 10.4.20(postcss@8.4.47)
clsx:
specifier: 2.1.1
version: 2.1.1
@@ -247,7 +247,7 @@ importers:
version: 1.2.1
tailwindcss:
specifier: 3.4.13
- version: 3.4.13(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5))
+ version: 3.4.13(ts-node@10.9.2(typescript@5.4.5))
unist-util-filter:
specifier: 5.0.1
version: 5.0.1
@@ -315,7 +315,7 @@ importers:
version: 8.3.5(@storybook/test@8.3.5(storybook@8.3.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.4.5)
'@storybook/react-vite':
specifier: 8.3.5
- version: 8.3.5(@preact/preset-vite@2.9.0(@babel/core@7.25.2)(preact@10.23.2)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6)))(@storybook/test@8.3.5(storybook@8.3.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.24.0)(storybook@8.3.5)(typescript@5.4.5)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6))(webpack-sources@3.2.3)
+ version: 8.3.5(@preact/preset-vite@2.9.0(@babel/core@7.25.2)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6)))(@storybook/test@8.3.5(storybook@8.3.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.24.0)(storybook@8.3.5)(typescript@5.4.5)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6))(webpack-sources@3.2.3)
'@storybook/test':
specifier: 8.3.5
version: 8.3.5(storybook@8.3.5)
@@ -342,7 +342,7 @@ importers:
version: 8.3.5
tsup:
specifier: 8.3.0
- version: 8.3.0(@microsoft/api-extractor@7.43.0(@types/node@22.3.0))(@swc/core@1.3.101(@swc/helpers@0.5.13))(jiti@2.3.3)(postcss@8.4.47)(tsx@4.16.5)(typescript@5.4.5)(yaml@2.5.1)
+ version: 8.3.0(@microsoft/api-extractor@7.43.0(@types/node@22.3.0))(@swc/core@1.3.101)(jiti@2.3.3)(postcss@8.4.47)(tsx@4.16.5)(typescript@5.4.5)(yaml@2.5.1)
vite:
specifier: 5.4.8
version: 5.4.8(@types/node@22.3.0)(terser@5.31.6)
@@ -397,6 +397,15 @@ importers:
'@json2csv/node':
specifier: 7.0.6
version: 7.0.6
+ '@opentelemetry/api-logs':
+ specifier: 0.53.0
+ version: 0.53.0
+ '@opentelemetry/instrumentation':
+ specifier: 0.53.0
+ version: 0.53.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-logs':
+ specifier: 0.53.0
+ version: 0.53.0(@opentelemetry/api@1.9.0)
'@paralleldrive/cuid2':
specifier: 2.2.2
version: 2.2.2
@@ -408,16 +417,19 @@ importers:
version: 0.0.25(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@sentry/nextjs':
specifier: 8.34.0
- version: 8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.95.0)
+ version: 8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@14.2.15(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.95.0)
'@tanstack/react-table':
specifier: 8.20.5
version: 8.20.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@vercel/og':
specifier: 0.6.3
version: 0.6.3
+ '@vercel/otel':
+ specifier: 1.10.0
+ version: 1.10.0(@opentelemetry/api-logs@0.53.0)(@opentelemetry/api@1.9.0)(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-logs@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-metrics@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))
'@vercel/speed-insights':
specifier: 1.0.12
- version: 1.0.12(next@14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
+ version: 1.0.12(next@14.2.15(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(svelte@4.2.19)(vue@3.5.11(typescript@5.4.5))
bcryptjs:
specifier: 2.4.3
version: 2.4.3
@@ -442,6 +454,9 @@ importers:
jsonwebtoken:
specifier: 9.0.2
version: 9.0.2
+ langfuse-vercel:
+ specifier: 3.27.0
+ version: 3.27.0(ai@3.4.9(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.5.11(typescript@5.4.5))(zod@3.23.8))
lodash:
specifier: 4.17.21
version: 4.17.21
@@ -456,10 +471,10 @@ importers:
version: 4.0.4
next:
specifier: 14.2.15
- version: 14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ version: 14.2.15(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-safe-action:
specifier: 7.9.3
- version: 7.9.3(next@14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8)
+ version: 7.9.3(next@14.2.15(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8)
optional:
specifier: 0.1.4
version: 0.1.4
@@ -514,7 +529,7 @@ importers:
version: link:../../packages/config-eslint
'@neshca/cache-handler':
specifier: 1.7.4
- version: 1.7.4(next@14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(redis@4.7.0)
+ version: 1.7.4(next@14.2.15(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(redis@4.7.0)
'@types/bcryptjs':
specifier: 2.4.6
version: 2.4.6
@@ -574,7 +589,7 @@ importers:
version: 8.0.0(eslint@8.57.0)(typescript@5.4.5)
'@vercel/style-guide':
specifier: 6.0.0
- version: 6.0.0(@next/eslint-plugin-next@14.2.5)(eslint@8.57.0)(prettier@3.3.3)(typescript@5.4.5)(vitest@2.0.5(@types/node@22.3.0)(jsdom@24.1.3)(terser@5.31.6))
+ version: 6.0.0(@next/eslint-plugin-next@14.2.5)(eslint@8.57.0)(prettier@3.3.3)(typescript@5.4.5)(vitest@2.0.5)
eslint-config-next:
specifier: 14.2.5
version: 14.2.5(eslint@8.57.0)(typescript@5.4.5)
@@ -598,13 +613,13 @@ importers:
devDependencies:
'@trivago/prettier-plugin-sort-imports':
specifier: 4.3.0
- version: 4.3.0(prettier@3.3.3)
+ version: 4.3.0(@vue/compiler-sfc@3.5.11)(prettier@3.3.3)
prettier:
specifier: 3.3.3
version: 3.3.3
prettier-plugin-tailwindcss:
specifier: 0.6.6
- version: 0.6.6(@trivago/prettier-plugin-sort-imports@4.3.0(prettier@3.3.3))(prettier@3.3.3)
+ version: 0.6.6(@trivago/prettier-plugin-sort-imports@4.3.0(@vue/compiler-sfc@3.5.11)(prettier@3.3.3))(prettier@3.3.3)
packages/config-typescript:
devDependencies:
@@ -656,7 +671,7 @@ importers:
version: 3.1.1(prisma@5.20.0)(typescript@5.4.5)
ts-node:
specifier: 10.9.2
- version: 10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)
+ version: 10.9.2(@swc/core@1.3.101)(@types/node@22.3.0)(typescript@5.4.5)
zod:
specifier: 3.23.8
version: 3.23.8
@@ -666,6 +681,9 @@ importers:
packages/ee:
dependencies:
+ '@ai-sdk/azure':
+ specifier: ^0.0.17
+ version: 0.0.17(zod@3.23.8)
'@formbricks/database':
specifier: workspace:*
version: link:../database
@@ -678,6 +696,9 @@ importers:
'@radix-ui/react-collapsible':
specifier: 1.1.0
version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ ai:
+ specifier: ^3.4.9
+ version: 3.4.9(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.5.11(typescript@5.4.5))(zod@3.23.8)
https-proxy-agent:
specifier: 7.0.5
version: 7.0.5
@@ -763,7 +784,7 @@ importers:
version: 18.3.11
react-email:
specifier: 2.1.6
- version: 2.1.6(@opentelemetry/api@1.9.0)(@swc/helpers@0.5.13)(eslint@8.57.0)(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5))
+ version: 2.1.6(@opentelemetry/api@1.9.0)(@swc/helpers@0.5.13)(eslint@8.57.0)(ts-node@10.9.2)
packages/js:
dependencies:
@@ -906,7 +927,7 @@ importers:
version: 16.4.5
ts-node:
specifier: 10.9.2
- version: 10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)
+ version: 10.9.2(@swc/core@1.3.101)(@types/node@22.3.0)(typescript@5.4.5)
vitest:
specifier: 2.0.5
version: 2.0.5(@types/node@22.3.0)(jsdom@24.1.3)(terser@5.31.6)
@@ -918,7 +939,7 @@ importers:
dependencies:
react-native-webview:
specifier: '>=13.0.0'
- version: 13.8.6(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)
+ version: 13.8.6(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)
devDependencies:
'@formbricks/api':
specifier: workspace:*
@@ -934,7 +955,7 @@ importers:
version: link:../types
'@react-native-async-storage/async-storage':
specifier: 1.23.1
- version: 1.23.1(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))
+ version: 1.23.1(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))
'@types/react':
specifier: 18.3.11
version: 18.3.11
@@ -943,7 +964,7 @@ importers:
version: 18.3.1
react-native:
specifier: 0.74.5
- version: 0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1)
+ version: 0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1)
terser:
specifier: 5.31.3
version: 5.31.3
@@ -1001,7 +1022,7 @@ importers:
version: 14.2.3
tailwindcss:
specifier: 3.4.10
- version: 3.4.10(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5))
+ version: 3.4.10(ts-node@10.9.2(@types/node@22.3.0)(typescript@5.4.5))
terser:
specifier: 5.31.6
version: 5.31.6
@@ -1081,6 +1102,9 @@ importers:
'@radix-ui/react-select':
specifier: 2.1.1
version: 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-separator':
+ specifier: 1.1.0
+ version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-slider':
specifier: 1.2.0
version: 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -1090,15 +1114,24 @@ importers:
'@radix-ui/react-switch':
specifier: 1.1.0
version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-tabs':
+ specifier: 1.1.1
+ version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-toggle':
+ specifier: 1.1.0
+ version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-toggle-group':
+ specifier: 1.1.0
+ version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-tooltip':
specifier: 1.1.2
version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tailwindcss/forms':
specifier: 0.5.9
- version: 0.5.9(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)))
+ version: 0.5.9(tailwindcss@3.4.13(ts-node@10.9.2))
'@tailwindcss/typography':
specifier: 0.5.13
- version: 0.5.13(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)))
+ version: 0.5.13(tailwindcss@3.4.13(ts-node@10.9.2))
autoprefixer:
specifier: 10.4.20
version: 10.4.20(postcss@8.4.41)
@@ -1146,7 +1179,7 @@ importers:
version: 2.5.2
tailwindcss:
specifier: 3.4.13
- version: 3.4.13(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5))
+ version: 3.4.13(ts-node@10.9.2(typescript@5.4.5))
devDependencies:
'@formbricks/config-typescript':
specifier: workspace:*
@@ -1184,6 +1217,92 @@ packages:
'@adobe/css-tools@4.4.0':
resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==}
+ '@ai-sdk/azure@0.0.17':
+ resolution: {integrity: sha512-SXp3vJ1KpF5nsJCuKQVzGvk0JiIX/p6ZSNNvBfv0REsamn+Jlo7h8mLaDjbJH7cRQB+ozViP0yP4XekliIuzKw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.0.0
+
+ '@ai-sdk/openai@0.0.40':
+ resolution: {integrity: sha512-9Iq1UaBHA5ZzNv6j3govuKGXrbrjuWvZIgWNJv4xzXlDMHu9P9hnqlBr/Aiay54WwCuTVNhTzAUTfFgnTs2kbQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.0.0
+
+ '@ai-sdk/provider-utils@1.0.20':
+ resolution: {integrity: sha512-ngg/RGpnA00eNOWEtXHenpX1MsM2QshQh4QJFjUfwcqHpM5kTfG7je7Rc3HcEDP+OkRVv2GF+X4fC1Vfcnl8Ow==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.0.0
+ peerDependenciesMeta:
+ zod:
+ optional: true
+
+ '@ai-sdk/provider-utils@1.0.5':
+ resolution: {integrity: sha512-XfOawxk95X3S43arn2iQIFyWGMi0DTxsf9ETc6t7bh91RPWOOPYN1tsmS5MTKD33OGJeaDQ/gnVRzXUCRBrckQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.0.0
+ peerDependenciesMeta:
+ zod:
+ optional: true
+
+ '@ai-sdk/provider@0.0.14':
+ resolution: {integrity: sha512-gaQ5Y033nro9iX1YUjEDFDRhmMcEiCk56LJdIUbX5ozEiCNCfpiBpEqrjSp/Gp5RzBS2W0BVxfG7UGW6Ezcrzg==}
+ engines: {node: '>=18'}
+
+ '@ai-sdk/provider@0.0.24':
+ resolution: {integrity: sha512-XMsNGJdGO+L0cxhhegtqZ8+T6nn4EoShS819OvCgI2kLbYTIvk0GWFGD0AXJmxkxs3DrpsJxKAFukFR7bvTkgQ==}
+ engines: {node: '>=18'}
+
+ '@ai-sdk/react@0.0.62':
+ resolution: {integrity: sha512-1asDpxgmeHWL0/EZPCLENxfOHT+0jce0z/zasRhascodm2S6f6/KZn5doLG9jdmarcb+GjMjFmmwyOVXz3W1xg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ react: ^18 || ^19
+ zod: ^3.0.0
+ peerDependenciesMeta:
+ react:
+ optional: true
+ zod:
+ optional: true
+
+ '@ai-sdk/solid@0.0.49':
+ resolution: {integrity: sha512-KnfWTt640cS1hM2fFIba8KHSPLpOIWXtEm28pNCHTvqasVKlh2y/zMQANTwE18pF2nuXL9P9F5/dKWaPsaEzQw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ solid-js: ^1.7.7
+ peerDependenciesMeta:
+ solid-js:
+ optional: true
+
+ '@ai-sdk/svelte@0.0.51':
+ resolution: {integrity: sha512-aIZJaIds+KpCt19yUDCRDWebzF/17GCY7gN9KkcA2QM6IKRO5UmMcqEYja0ZmwFQPm1kBZkF2njhr8VXis2mAw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ svelte: ^3.0.0 || ^4.0.0
+ peerDependenciesMeta:
+ svelte:
+ optional: true
+
+ '@ai-sdk/ui-utils@0.0.46':
+ resolution: {integrity: sha512-ZG/wneyJG+6w5Nm/hy1AKMuRgjPQToAxBsTk61c9sVPUTaxo+NNjM2MhXQMtmsja2N5evs8NmHie+ExEgpL3cA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ zod: ^3.0.0
+ peerDependenciesMeta:
+ zod:
+ optional: true
+
+ '@ai-sdk/vue@0.0.54':
+ resolution: {integrity: sha512-Ltu6gbuii8Qlp3gg7zdwdnHdS4M8nqKDij2VVO1223VOtIFwORFJzKqpfx44U11FW8z2TPVBYN+FjkyVIcN2hg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ vue: ^3.3.4
+ peerDependenciesMeta:
+ vue:
+ optional: true
+
'@algolia/autocomplete-core@1.17.4':
resolution: {integrity: sha512-H1CAzj43RDeC4Vq9FW2JLtRDNxhjRG/aPX69nbNrKbYzX9P0YohxrEj3kJ9G+e20y0L0pYboAOeU6wgbKJ6gOA==}
@@ -1460,8 +1579,8 @@ packages:
resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==}
engines: {node: '>=6.9.0'}
- '@babel/compat-data@7.25.7':
- resolution: {integrity: sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw==}
+ '@babel/compat-data@7.25.8':
+ resolution: {integrity: sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==}
engines: {node: '>=6.9.0'}
'@babel/core@7.24.5':
@@ -1472,8 +1591,8 @@ packages:
resolution: {integrity: sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==}
engines: {node: '>=6.9.0'}
- '@babel/eslint-parser@7.25.7':
- resolution: {integrity: sha512-B+BO9x86VYsQHimucBAL1fxTJKF4wyKY6ZVzee9QgzdZOUfs3BaR6AQrgoGrRI+7IFS1wUz/VyQ+SoBcSpdPbw==}
+ '@babel/eslint-parser@7.25.8':
+ resolution: {integrity: sha512-Po3VLMN7fJtv0nsOjBDSbO1J71UhzShE9MuOSkWEV9IZQXzhZklYtzKZ8ZD/Ij3a0JBv1AG3Ny2L3jvAHQVOGg==}
engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0}
peerDependencies:
'@babel/core': ^7.11.0
@@ -1606,8 +1725,8 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
- '@babel/parser@7.25.7':
- resolution: {integrity: sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==}
+ '@babel/parser@7.25.8':
+ resolution: {integrity: sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -1661,8 +1780,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-proposal-export-default-from@7.25.7':
- resolution: {integrity: sha512-Egdiuy7pLTyaPkIr6rItNyFVbblTmx3VgqY+72KiS9BzcA+SMyrS9zSumQeSANo8uE3Kax0ZUMkpNh0Q+mbNwg==}
+ '@babel/plugin-proposal-export-default-from@7.25.8':
+ resolution: {integrity: sha512-5SLPHA/Gk7lNdaymtSVS9jH77Cs7yuHTR3dYj+9q+M7R7tNLXhNuvnmOfafRIzpWL+dtMibuu1I4ofrc768Gkw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1720,17 +1839,6 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-syntax-class-properties@7.12.13':
- resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-class-static-block@7.14.5':
- resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
'@babel/plugin-syntax-decorators@7.25.7':
resolution: {integrity: sha512-oXduHo642ZhstLVYTe2z2GSJIruU0c/W3/Ghr6A5yGMsVrvdnxO1z+3pbTcT7f3/Clnt+1z8D/w1r1f1SHaCHw==}
engines: {node: '>=6.9.0'}
@@ -1748,11 +1856,6 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-syntax-export-namespace-from@7.8.3':
- resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
'@babel/plugin-syntax-flow@7.25.7':
resolution: {integrity: sha512-fyoj6/YdVtlv2ROig/J0fP7hh/wNO1MJGm1NR70Pg7jbkF+jOUL9joorqaCOQh06Y+LfgTagHzC8KqZ3MF782w==}
engines: {node: '>=6.9.0'}
@@ -1771,16 +1874,6 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-syntax-import-meta@7.10.4':
- resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-json-strings@7.8.3':
- resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
'@babel/plugin-syntax-jsx@7.25.7':
resolution: {integrity: sha512-ruZOnKO+ajVL/MVx+PwNBPOkrnXTXoWMtte1MBpegfCArhqOe3Bj52avVj1huLLxNKYKXYaSxZ2F+woK1ekXfw==}
engines: {node: '>=6.9.0'}
@@ -1817,18 +1910,6 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-syntax-private-property-in-object@7.14.5':
- resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
- '@babel/plugin-syntax-top-level-await@7.14.5':
- resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==}
- engines: {node: '>=6.9.0'}
- peerDependencies:
- '@babel/core': ^7.0.0-0
-
'@babel/plugin-syntax-typescript@7.25.7':
resolution: {integrity: sha512-rR+5FDjpCHqqZN2bzZm18bVYGaejGq5ZkpVCJLXor/+zlSrSoc4KWcHI0URVWjl/68Dyr1uwZUz/1njycEAv9g==}
engines: {node: '>=6.9.0'}
@@ -1847,8 +1928,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-async-generator-functions@7.25.7':
- resolution: {integrity: sha512-4B6OhTrwYKHYYgcwErvZjbmH9X5TxQBsaBHdzEIB4l71gR5jh/tuHGlb9in47udL2+wVUcOz5XXhhfhVJwEpEg==}
+ '@babel/plugin-transform-async-generator-functions@7.25.8':
+ resolution: {integrity: sha512-9ypqkozyzpG+HxlH4o4gdctalFGIjjdufzo7I2XPda0iBnZ6a+FO0rIEQcdSPXp02CkvGsII1exJhmROPQd5oA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1877,8 +1958,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-class-static-block@7.25.7':
- resolution: {integrity: sha512-rvUUtoVlkDWtDWxGAiiQj0aNktTPn3eFynBcMC2IhsXweehwgdI9ODe+XjWw515kEmv22sSOTp/rxIRuTiB7zg==}
+ '@babel/plugin-transform-class-static-block@7.25.8':
+ resolution: {integrity: sha512-e82gl3TCorath6YLf9xUwFehVvjvfqFhdOo4+0iVIVju+6XOi5XHkqB3P2AXnSwoeTX0HBoXq5gJFtvotJzFnQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.12.0
@@ -1919,8 +2000,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0
- '@babel/plugin-transform-dynamic-import@7.25.7':
- resolution: {integrity: sha512-UvcLuual4h7/GfylKm2IAA3aph9rwvAM2XBA0uPKU3lca+Maai4jBjjEVUS568ld6kJcgbouuumCBhMd/Yz17w==}
+ '@babel/plugin-transform-dynamic-import@7.25.8':
+ resolution: {integrity: sha512-gznWY+mr4ZQL/EWPcbBQUP3BXS5FwZp8RUOw06BaRn8tQLzN4XLIxXejpHN9Qo8x8jjBmAAKp6FoS51AgkSA/A==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1931,8 +2012,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-export-namespace-from@7.25.7':
- resolution: {integrity: sha512-h3MDAP5l34NQkkNulsTNyjdaR+OiB0Im67VU//sFupouP8Q6m9Spy7l66DcaAQxtmCqGdanPByLsnwFttxKISQ==}
+ '@babel/plugin-transform-export-namespace-from@7.25.8':
+ resolution: {integrity: sha512-sPtYrduWINTQTW7FtOy99VCTWp4H23UX7vYcut7S4CIMEXU+54zKX9uCoGkLsWXteyaMXzVHgzWbLfQ1w4GZgw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1955,8 +2036,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-json-strings@7.25.7':
- resolution: {integrity: sha512-Ot43PrL9TEAiCe8C/2erAjXMeVSnE/BLEx6eyrKLNFCCw5jvhTHKyHxdI1pA0kz5njZRYAnMO2KObGqOCRDYSA==}
+ '@babel/plugin-transform-json-strings@7.25.8':
+ resolution: {integrity: sha512-4OMNv7eHTmJ2YXs3tvxAfa/I43di+VcF+M4Wt66c88EAED1RoGaf1D64cL5FkRpNL+Vx9Hds84lksWvd/wMIdA==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -1967,8 +2048,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-logical-assignment-operators@7.25.7':
- resolution: {integrity: sha512-iImzbA55BjiovLyG2bggWS+V+OLkaBorNvc/yJoeeDQGztknRnDdYfp2d/UPmunZYEnZi6Lg8QcTmNMHOB0lGA==}
+ '@babel/plugin-transform-logical-assignment-operators@7.25.8':
+ resolution: {integrity: sha512-f5W0AhSbbI+yY6VakT04jmxdxz+WsID0neG7+kQZbCOjuyJNdL5Nn4WIBm4hRpKnUcO9lP0eipUhFN12JpoH8g==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -2015,20 +2096,20 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-nullish-coalescing-operator@7.25.7':
- resolution: {integrity: sha512-FbuJ63/4LEL32mIxrxwYaqjJxpbzxPVQj5a+Ebrc8JICV6YX8nE53jY+K0RZT3um56GoNWgkS2BQ/uLGTjtwfw==}
+ '@babel/plugin-transform-nullish-coalescing-operator@7.25.8':
+ resolution: {integrity: sha512-Z7WJJWdQc8yCWgAmjI3hyC+5PXIubH9yRKzkl9ZEG647O9szl9zvmKLzpbItlijBnVhTUf1cpyWBsZ3+2wjWPQ==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-numeric-separator@7.25.7':
- resolution: {integrity: sha512-8CbutzSSh4hmD+jJHIA8vdTNk15kAzOnFLVVgBSMGr28rt85ouT01/rezMecks9pkU939wDInImwCKv4ahU4IA==}
+ '@babel/plugin-transform-numeric-separator@7.25.8':
+ resolution: {integrity: sha512-rm9a5iEFPS4iMIy+/A/PiS0QN0UyjPIeVvbU5EMZFKJZHt8vQnasbpo3T3EFcxzCeYO0BHfc4RqooCZc51J86Q==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-object-rest-spread@7.25.7':
- resolution: {integrity: sha512-1JdVKPhD7Y5PvgfFy0Mv2brdrolzpzSoUq2pr6xsR+m+3viGGeHEokFKsCgOkbeFOQxfB1Vt2F0cPJLRpFI4Zg==}
+ '@babel/plugin-transform-object-rest-spread@7.25.8':
+ resolution: {integrity: sha512-LkUu0O2hnUKHKE7/zYOIjByMa4VRaV2CD/cdGz0AxU9we+VA3kDDggKEzI0Oz1IroG+6gUP6UmWEHBMWZU316g==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -2039,14 +2120,14 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-optional-catch-binding@7.25.7':
- resolution: {integrity: sha512-m9obYBA39mDPN7lJzD5WkGGb0GO54PPLXsbcnj1Hyeu8mSRz7Gb4b1A6zxNX32ZuUySDK4G6it8SDFWD1nCnqg==}
+ '@babel/plugin-transform-optional-catch-binding@7.25.8':
+ resolution: {integrity: sha512-EbQYweoMAHOn7iJ9GgZo14ghhb9tTjgOc88xFgYngifx7Z9u580cENCV159M4xDh3q/irbhSjZVpuhpC2gKBbg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-optional-chaining@7.25.7':
- resolution: {integrity: sha512-h39agClImgPWg4H8mYVAbD1qP9vClFbEjqoJmt87Zen8pjqK8FTPUwrOXAvqu5soytwxrLMd2fx2KSCp2CHcNg==}
+ '@babel/plugin-transform-optional-chaining@7.25.8':
+ resolution: {integrity: sha512-q05Bk7gXOxpTHoQ8RSzGSh/LHVB9JEIkKnk3myAWwZHnYiTGYtbdrYkIsS8Xyh4ltKf7GNUSgzs/6P2bJtBAQg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -2063,8 +2144,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-private-property-in-object@7.25.7':
- resolution: {integrity: sha512-LzA5ESzBy7tqj00Yjey9yWfs3FKy4EmJyKOSWld144OxkTji81WWnUT8nkLUn+imN/zHL8ZQlOu/MTUAhHaX3g==}
+ '@babel/plugin-transform-private-property-in-object@7.25.8':
+ resolution: {integrity: sha512-8Uh966svuB4V8RHHg0QJOB32QK287NBksJOByoKmHMp1TAobNniNalIkI2i5IPj5+S9NYCG4VIjbEuiSN8r+ow==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -2189,8 +2270,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0
- '@babel/preset-env@7.25.7':
- resolution: {integrity: sha512-Gibz4OUdyNqqLj+7OAvBZxOD7CklCtMA5/j0JgUEwOnaRULsPDXmic2iKxL2DX2vQduPR5wH2hjZas/Vr/Oc0g==}
+ '@babel/preset-env@7.25.8':
+ resolution: {integrity: sha512-58T2yulDHMN8YMUxiLq5YmWUnlDCyY1FsHM+v12VMx+1/FlrUj5tY50iDCpofFQEM8fMYOaY9YRvym2jcjn1Dg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -2244,8 +2325,8 @@ packages:
resolution: {integrity: sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==}
engines: {node: '>=6.9.0'}
- '@babel/types@7.25.7':
- resolution: {integrity: sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==}
+ '@babel/types@7.25.8':
+ resolution: {integrity: sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==}
engines: {node: '>=6.9.0'}
'@base2/pretty-print-object@1.0.1':
@@ -3751,6 +3832,12 @@ packages:
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
+ '@opentelemetry/sdk-logs@0.53.0':
+ resolution: {integrity: sha512-dhSisnEgIj/vJZXZV6f6KcTnyLDx/VuQ6l3ejuZpMpPlh9S1qMHiZU9NMmOkVkwwHkMy3G6mEBwdP23vUZVr4g==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ '@opentelemetry/api': '>=1.4.0 <1.10.0'
+
'@opentelemetry/sdk-metrics@1.26.0':
resolution: {integrity: sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ==}
engines: {node: '>=14'}
@@ -4346,6 +4433,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-separator@1.1.0':
+ resolution: {integrity: sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/react-slider@1.2.0':
resolution: {integrity: sha512-dAHCDA4/ySXROEPaRtaMV5WHL8+JB/DbtyTbJjYkY0RXmKMO2Ln8DFZhywG5/mVQ4WqHDBc8smc14yPXPqZHYA==}
peerDependencies:
@@ -4390,6 +4490,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-tabs@1.1.1':
+ resolution: {integrity: sha512-3GBUDmP2DvzmtYLMsHmpA1GtR46ZDZ+OreXM/N+kkQJOPIgytFWWTfDQmBQKBvaFS0Vno0FktdbVzN28KGrMdw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/react-toggle-group@1.1.0':
resolution: {integrity: sha512-PpTJV68dZU2oqqgq75Uzto5o/XfOVgkrJ9rulVmfTKxWp3HfUjHE6CP/WLRR4AzPX9HWxw7vFow2me85Yu+Naw==}
peerDependencies:
@@ -5848,6 +5961,9 @@ packages:
'@types/debug@4.1.8':
resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==}
+ '@types/diff-match-patch@1.0.36':
+ resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==}
+
'@types/doctrine@0.0.9':
resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==}
@@ -6302,6 +6418,18 @@ packages:
resolution: {integrity: sha512-aoCrC9FqkeA+WEEb9CwSmjD0rGlFeNqbUsI41JPmKWR9Hx6FFn86tvH96O5HZMF6VAXTGHxa3nPH3BokROpdgA==}
engines: {node: '>=16'}
+ '@vercel/otel@1.10.0':
+ resolution: {integrity: sha512-bv1FXbFZlFbB89vyA2P9/kr6eZ42bMtXgqBJpgi+8yOrZU8rkg9wMi0TL//AgAf/qKtaryDm1sbCnkLHgCI3PQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@opentelemetry/api': ^1.7.0
+ '@opentelemetry/api-logs': '>=0.46.0 && <1.0.0'
+ '@opentelemetry/instrumentation': '>=0.46.0 && <1.0.0'
+ '@opentelemetry/resources': ^1.19.0
+ '@opentelemetry/sdk-logs': '>=0.46.0 && <1.0.0'
+ '@opentelemetry/sdk-metrics': ^1.19.0
+ '@opentelemetry/sdk-trace-base': ^1.19.0
+
'@vercel/speed-insights@1.0.12':
resolution: {integrity: sha512-ZGQ+a7bcfWJD2VYEp2R1LHvRAMyyaFBYytZXsfnbOMkeOvzGNVxUL7aVUvisIrTZjXTSsxG45DKX7yiw6nq2Jw==}
peerDependencies:
@@ -6388,6 +6516,12 @@ packages:
'@vue/compiler-dom@3.5.11':
resolution: {integrity: sha512-pyGf8zdbDDRkBrEzf8p7BQlMKNNF5Fk/Cf/fQ6PiUz9at4OaUfyXW0dGJTo2Vl1f5U9jSLCNf0EZJEogLXoeew==}
+ '@vue/compiler-sfc@3.5.11':
+ resolution: {integrity: sha512-gsbBtT4N9ANXXepprle+X9YLg2htQk1sqH/qGJ/EApl+dgpUBdTv3yP7YlR535uHZY3n6XaR0/bKo0BgwwDniw==}
+
+ '@vue/compiler-ssr@3.5.11':
+ resolution: {integrity: sha512-P4+GPjOuC2aFTk1Z4WANvEhyOykcvEd5bIj2KVNGKGfM745LaXGr++5njpdBTzVz5pZifdlR1kpYSJJpIlSePA==}
+
'@vue/language-core@1.8.27':
resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==}
peerDependencies:
@@ -6396,6 +6530,20 @@ packages:
typescript:
optional: true
+ '@vue/reactivity@3.5.11':
+ resolution: {integrity: sha512-Nqo5VZEn8MJWlCce8XoyVqHZbd5P2NH+yuAaFzuNSR96I+y1cnuUiq7xfSG+kyvLSiWmaHTKP1r3OZY4mMD50w==}
+
+ '@vue/runtime-core@3.5.11':
+ resolution: {integrity: sha512-7PsxFGqwfDhfhh0OcDWBG1DaIQIVOLgkwA5q6MtkPiDFjp5gohVnJEahSktwSFLq7R5PtxDKy6WKURVN1UDbzA==}
+
+ '@vue/runtime-dom@3.5.11':
+ resolution: {integrity: sha512-GNghjecT6IrGf0UhuYmpgaOlN7kxzQBhxWEn08c/SQDxv1yy4IXI1bn81JgEpQ4IXjRxWtPyI8x0/7TF5rPfYQ==}
+
+ '@vue/server-renderer@3.5.11':
+ resolution: {integrity: sha512-cVOwYBxR7Wb1B1FoxYvtjJD8X/9E5nlH4VSkJy2uMA1MzYNdzAAB//l8nrmN9py/4aP+3NjWukf9PZ3TeWULaA==}
+ peerDependencies:
+ vue: 3.5.11
+
'@vue/shared@3.5.11':
resolution: {integrity: sha512-W8GgysJVnFo81FthhzurdRAWP/byq3q2qIw70e0JWblzVhjgOMiC2GyovXrZTFQJnFVryYaKGP3Tc9vYzYm6PQ==}
@@ -6523,6 +6671,27 @@ packages:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'}
+ ai@3.4.9:
+ resolution: {integrity: sha512-wmVzpIHNGjCEjIJ/3945a/DIkz+gwObjC767ZRgO8AmtIZMO5KqvqNr7n2KF+gQrCPCMC8fM1ICQFXSvBZnBlA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ openai: ^4.42.0
+ react: ^18 || ^19
+ sswr: ^2.1.0
+ svelte: ^3.0.0 || ^4.0.0
+ zod: ^3.0.0
+ peerDependenciesMeta:
+ openai:
+ optional: true
+ react:
+ optional: true
+ sswr:
+ optional: true
+ svelte:
+ optional: true
+ zod:
+ optional: true
+
ajv-keywords@3.5.2:
resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
peerDependencies:
@@ -7251,6 +7420,9 @@ packages:
code-block-writer@11.0.3:
resolution: {integrity: sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==}
+ code-red@1.0.4:
+ resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==}
+
codepage@1.15.0:
resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
engines: {node: '>=0.8'}
@@ -7503,6 +7675,10 @@ packages:
resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==}
engines: {node: '>=8.0.0'}
+ css-tree@2.3.1:
+ resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
+ engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
+
css-what@6.1.0:
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
engines: {node: '>= 6'}
@@ -7706,6 +7882,9 @@ packages:
didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+ diff-match-patch@1.0.5:
+ resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==}
+
diff@4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
@@ -8248,6 +8427,10 @@ packages:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
+ eventsource-parser@1.1.2:
+ resolution: {integrity: sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==}
+ engines: {node: '>=14.18'}
+
evp_bytestokey@1.0.3:
resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==}
@@ -9540,6 +9723,9 @@ packages:
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+ json-schema@0.4.0:
+ resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
+
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
@@ -9555,6 +9741,11 @@ packages:
jsonc-parser@3.3.1:
resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
+ jsondiffpatch@0.6.0:
+ resolution: {integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+
jsonfile@4.0.0:
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
@@ -9599,6 +9790,20 @@ packages:
kolorist@1.8.0:
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
+ langfuse-core@3.27.0:
+ resolution: {integrity: sha512-0GSQFMpQ4lFFIBIO3+7qyOHTnIXRuU93zbXxw8RvSv4kadhOjTLsxAhSs2y7kK3bpFkPHVBiQAp4Doo/FZjXtA==}
+ engines: {node: '>=18'}
+
+ langfuse-vercel@3.27.0:
+ resolution: {integrity: sha512-vHEznWtGe3cqNJJMuQpMZQY/GBOYYBmQIzqgSTleI0cjZMoSKwRK46non4hA6n3w4pHWNb6gvYu8+3/OvRcnDQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ ai: '>=3.2.44'
+
+ langfuse@3.27.0:
+ resolution: {integrity: sha512-zYQae1ebHkZkSjGhDP64RtZrzMAadnUk5fcW63UHovKTSNDtlU6ed2h5rPSaKFv3SiXiHjhL8E5pnTh/wHhuvQ==}
+ engines: {node: '>=18'}
+
language-subtag-registry@0.3.23:
resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
@@ -9730,6 +9935,9 @@ packages:
resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==}
engines: {node: '>=8.9.0'}
+ locate-character@3.0.0:
+ resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
+
locate-path@3.0.0:
resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
engines: {node: '>=6'}
@@ -9997,6 +10205,9 @@ packages:
mdn-data@2.0.14:
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
+ mdn-data@2.0.30:
+ resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
+
mdurl@2.0.0:
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
@@ -10382,6 +10593,10 @@ packages:
muggle-string@0.3.1:
resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==}
+ mustache@4.2.0:
+ resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
+ hasBin: true
+
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
@@ -10391,6 +10606,11 @@ packages:
react: '*'
react-dom: '*'
+ nanoid@3.3.6:
+ resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
nanoid@3.3.7:
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -11950,6 +12170,9 @@ packages:
search-insights@2.17.2:
resolution: {integrity: sha512-zFNpOpUO+tY2D85KrxJ+aqwnIfdEGi06UH2+xEb+Bp9Mwznmauqc9djbnBibJO5mpfUPPa8st6Sx65+vbeO45g==}
+ secure-json-parse@2.7.0:
+ resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
+
selderee@0.11.0:
resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==}
@@ -12217,6 +12440,11 @@ packages:
resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ sswr@2.1.0:
+ resolution: {integrity: sha512-Cqc355SYlTAaUt8iDPaC/4DPPXK925PePLMxyBKuWd5kKc5mwsG3nT9+Mq2tyguL5s7b4Jg+IRMpTRsNTAfpSQ==}
+ peerDependencies:
+ svelte: ^4.0.0 || ^5.0.0-next.0
+
stack-generator@2.0.10:
resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==}
@@ -12448,6 +12676,23 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
+ svelte@4.2.19:
+ resolution: {integrity: sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==}
+ engines: {node: '>=16'}
+
+ swr@2.2.5:
+ resolution: {integrity: sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==}
+ peerDependencies:
+ react: ^16.11.0 || ^17.0.0 || ^18.0.0
+
+ swrev@4.0.0:
+ resolution: {integrity: sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==}
+
+ swrv@1.0.4:
+ resolution: {integrity: sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==}
+ peerDependencies:
+ vue: '>=3.2.26 < 4'
+
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
@@ -13240,6 +13485,14 @@ packages:
peerDependencies:
typescript: '*'
+ vue@3.5.11:
+ resolution: {integrity: sha512-/8Wurrd9J3lb72FTQS7gRMNQD4nztTtKPmuDuPuhqXmmpD6+skVjAeahNpVzsuky6Sy9gy7wn8UadqPtt9SQIg==}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
w3c-xmlserializer@5.0.0:
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
engines: {node: '>=18'}
@@ -13565,6 +13818,11 @@ packages:
decimal.js:
optional: true
+ zod-to-json-schema@3.23.2:
+ resolution: {integrity: sha512-uSt90Gzc/tUfyNqxnjlfBs8W6WSGpNBv0rVsNxP/BVSMHMKGdthPYff4xtCHYloJGM0CFxFsb3NbC0eqPhfImw==}
+ peerDependencies:
+ zod: ^3.23.3
+
zod-validation-error@2.1.0:
resolution: {integrity: sha512-VJh93e2wb4c3tWtGgTa0OF/dTt/zoPCPzXq4V11ZjxmEAFaPi/Zss1xIZdEB5RD8GD00U0/iVXgqkF77RV7pdQ==}
engines: {node: '>=18.0.0'}
@@ -13596,6 +13854,91 @@ snapshots:
'@adobe/css-tools@4.4.0': {}
+ '@ai-sdk/azure@0.0.17(zod@3.23.8)':
+ dependencies:
+ '@ai-sdk/openai': 0.0.40(zod@3.23.8)
+ '@ai-sdk/provider': 0.0.14
+ '@ai-sdk/provider-utils': 1.0.5(zod@3.23.8)
+ zod: 3.23.8
+
+ '@ai-sdk/openai@0.0.40(zod@3.23.8)':
+ dependencies:
+ '@ai-sdk/provider': 0.0.14
+ '@ai-sdk/provider-utils': 1.0.5(zod@3.23.8)
+ zod: 3.23.8
+
+ '@ai-sdk/provider-utils@1.0.20(zod@3.23.8)':
+ dependencies:
+ '@ai-sdk/provider': 0.0.24
+ eventsource-parser: 1.1.2
+ nanoid: 3.3.6
+ secure-json-parse: 2.7.0
+ optionalDependencies:
+ zod: 3.23.8
+
+ '@ai-sdk/provider-utils@1.0.5(zod@3.23.8)':
+ dependencies:
+ '@ai-sdk/provider': 0.0.14
+ eventsource-parser: 1.1.2
+ nanoid: 3.3.6
+ secure-json-parse: 2.7.0
+ optionalDependencies:
+ zod: 3.23.8
+
+ '@ai-sdk/provider@0.0.14':
+ dependencies:
+ json-schema: 0.4.0
+
+ '@ai-sdk/provider@0.0.24':
+ dependencies:
+ json-schema: 0.4.0
+
+ '@ai-sdk/react@0.0.62(react@18.3.1)(zod@3.23.8)':
+ dependencies:
+ '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8)
+ '@ai-sdk/ui-utils': 0.0.46(zod@3.23.8)
+ swr: 2.2.5(react@18.3.1)
+ optionalDependencies:
+ react: 18.3.1
+ zod: 3.23.8
+
+ '@ai-sdk/solid@0.0.49(zod@3.23.8)':
+ dependencies:
+ '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8)
+ '@ai-sdk/ui-utils': 0.0.46(zod@3.23.8)
+ transitivePeerDependencies:
+ - zod
+
+ '@ai-sdk/svelte@0.0.51(svelte@4.2.19)(zod@3.23.8)':
+ dependencies:
+ '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8)
+ '@ai-sdk/ui-utils': 0.0.46(zod@3.23.8)
+ sswr: 2.1.0(svelte@4.2.19)
+ optionalDependencies:
+ svelte: 4.2.19
+ transitivePeerDependencies:
+ - zod
+
+ '@ai-sdk/ui-utils@0.0.46(zod@3.23.8)':
+ dependencies:
+ '@ai-sdk/provider': 0.0.24
+ '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8)
+ json-schema: 0.4.0
+ secure-json-parse: 2.7.0
+ zod-to-json-schema: 3.23.2(zod@3.23.8)
+ optionalDependencies:
+ zod: 3.23.8
+
+ '@ai-sdk/vue@0.0.54(vue@3.5.11(typescript@5.4.5))(zod@3.23.8)':
+ dependencies:
+ '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8)
+ '@ai-sdk/ui-utils': 0.0.46(zod@3.23.8)
+ swrv: 1.0.4(vue@3.5.11(typescript@5.4.5))
+ optionalDependencies:
+ vue: 3.5.11(typescript@5.4.5)
+ transitivePeerDependencies:
+ - zod
+
'@algolia/autocomplete-core@1.17.4(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)(search-insights@2.17.2)':
dependencies:
'@algolia/autocomplete-plugin-algolia-insights': 1.17.4(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)(search-insights@2.17.2)
@@ -14270,7 +14613,7 @@ snapshots:
'@babel/highlight': 7.25.7
picocolors: 1.1.0
- '@babel/compat-data@7.25.7': {}
+ '@babel/compat-data@7.25.8': {}
'@babel/core@7.24.5':
dependencies:
@@ -14283,7 +14626,7 @@ snapshots:
'@babel/parser': 7.24.5
'@babel/template': 7.25.7
'@babel/traverse': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
convert-source-map: 2.0.0
debug: 4.3.7
gensync: 1.0.0-beta.2
@@ -14300,10 +14643,10 @@ snapshots:
'@babel/helper-compilation-targets': 7.25.7
'@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.2)
'@babel/helpers': 7.25.7
- '@babel/parser': 7.25.7
+ '@babel/parser': 7.25.8
'@babel/template': 7.25.7
'@babel/traverse': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
convert-source-map: 2.0.0
debug: 4.3.7
gensync: 1.0.0-beta.2
@@ -14312,7 +14655,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@babel/eslint-parser@7.25.7(@babel/core@7.25.2)(eslint@8.57.0)':
+ '@babel/eslint-parser@7.25.8(@babel/core@7.25.2)(eslint@8.57.0)':
dependencies:
'@babel/core': 7.25.2
'@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1
@@ -14328,7 +14671,7 @@ snapshots:
'@babel/generator@7.2.0':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
jsesc: 2.5.2
lodash: 4.17.21
source-map: 0.5.7
@@ -14336,25 +14679,25 @@ snapshots:
'@babel/generator@7.25.7':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
'@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.25
jsesc: 3.0.2
'@babel/helper-annotate-as-pure@7.25.7':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
'@babel/helper-builder-binary-assignment-operator-visitor@7.25.7':
dependencies:
'@babel/traverse': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
transitivePeerDependencies:
- supports-color
'@babel/helper-compilation-targets@7.25.7':
dependencies:
- '@babel/compat-data': 7.25.7
+ '@babel/compat-data': 7.25.8
'@babel/helper-validator-option': 7.25.7
browserslist: 4.24.0
lru-cache: 5.1.1
@@ -14393,28 +14736,28 @@ snapshots:
'@babel/helper-environment-visitor@7.24.7':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
'@babel/helper-function-name@7.24.7':
dependencies:
'@babel/template': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
'@babel/helper-hoist-variables@7.24.7':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
'@babel/helper-member-expression-to-functions@7.25.7':
dependencies:
'@babel/traverse': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
transitivePeerDependencies:
- supports-color
'@babel/helper-module-imports@7.25.7':
dependencies:
'@babel/traverse': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
transitivePeerDependencies:
- supports-color
@@ -14440,7 +14783,7 @@ snapshots:
'@babel/helper-optimise-call-expression@7.25.7':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
'@babel/helper-plugin-utils@7.25.7': {}
@@ -14465,20 +14808,20 @@ snapshots:
'@babel/helper-simple-access@7.25.7':
dependencies:
'@babel/traverse': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
transitivePeerDependencies:
- supports-color
'@babel/helper-skip-transparent-expression-wrappers@7.25.7':
dependencies:
'@babel/traverse': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
transitivePeerDependencies:
- supports-color
'@babel/helper-split-export-declaration@7.24.7':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
'@babel/helper-string-parser@7.25.7': {}
@@ -14490,14 +14833,14 @@ snapshots:
dependencies:
'@babel/template': 7.25.7
'@babel/traverse': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
transitivePeerDependencies:
- supports-color
'@babel/helpers@7.25.7':
dependencies:
'@babel/template': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
'@babel/highlight@7.25.7':
dependencies:
@@ -14508,11 +14851,11 @@ snapshots:
'@babel/parser@7.24.5':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
- '@babel/parser@7.25.7':
+ '@babel/parser@7.25.8':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
'@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.7(@babel/core@7.25.2)':
dependencies:
@@ -14537,7 +14880,7 @@ snapshots:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
'@babel/helper-skip-transparent-expression-wrappers': 7.25.7
- '@babel/plugin-transform-optional-chaining': 7.25.7(@babel/core@7.25.2)
+ '@babel/plugin-transform-optional-chaining': 7.25.8(@babel/core@7.25.2)
transitivePeerDependencies:
- supports-color
@@ -14576,11 +14919,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@babel/plugin-proposal-export-default-from@7.25.7(@babel/core@7.25.2)':
+ '@babel/plugin-proposal-export-default-from@7.25.8(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-syntax-export-default-from': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.25.2)':
dependencies:
@@ -14602,7 +14944,7 @@ snapshots:
'@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.25.2)':
dependencies:
- '@babel/compat-data': 7.25.7
+ '@babel/compat-data': 7.25.8
'@babel/core': 7.25.2
'@babel/helper-compilation-targets': 7.25.7
'@babel/helper-plugin-utils': 7.25.7
@@ -14633,16 +14975,6 @@ snapshots:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.25.2)':
- dependencies:
- '@babel/core': 7.25.2
- '@babel/helper-plugin-utils': 7.25.7
-
- '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.25.2)':
- dependencies:
- '@babel/core': 7.25.2
- '@babel/helper-plugin-utils': 7.25.7
-
'@babel/plugin-syntax-decorators@7.25.7(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
@@ -14658,11 +14990,6 @@ snapshots:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.25.2)':
- dependencies:
- '@babel/core': 7.25.2
- '@babel/helper-plugin-utils': 7.25.7
-
'@babel/plugin-syntax-flow@7.25.7(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
@@ -14678,16 +15005,6 @@ snapshots:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.25.2)':
- dependencies:
- '@babel/core': 7.25.2
- '@babel/helper-plugin-utils': 7.25.7
-
- '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.25.2)':
- dependencies:
- '@babel/core': 7.25.2
- '@babel/helper-plugin-utils': 7.25.7
-
'@babel/plugin-syntax-jsx@7.25.7(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
@@ -14723,16 +15040,6 @@ snapshots:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.25.2)':
- dependencies:
- '@babel/core': 7.25.2
- '@babel/helper-plugin-utils': 7.25.7
-
- '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.25.2)':
- dependencies:
- '@babel/core': 7.25.2
- '@babel/helper-plugin-utils': 7.25.7
-
'@babel/plugin-syntax-typescript@7.25.7(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
@@ -14749,12 +15056,11 @@ snapshots:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-transform-async-generator-functions@7.25.7(@babel/core@7.25.2)':
+ '@babel/plugin-transform-async-generator-functions@7.25.8(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
'@babel/helper-remap-async-to-generator': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.25.2)
'@babel/traverse': 7.25.7
transitivePeerDependencies:
- supports-color
@@ -14786,12 +15092,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-class-static-block@7.25.7(@babel/core@7.25.2)':
+ '@babel/plugin-transform-class-static-block@7.25.8(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
'@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.2)
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.25.2)
transitivePeerDependencies:
- supports-color
@@ -14835,11 +15140,10 @@ snapshots:
'@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.2)
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-transform-dynamic-import@7.25.7(@babel/core@7.25.2)':
+ '@babel/plugin-transform-dynamic-import@7.25.8(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.25.2)
'@babel/plugin-transform-exponentiation-operator@7.25.7(@babel/core@7.25.2)':
dependencies:
@@ -14849,11 +15153,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-export-namespace-from@7.25.7(@babel/core@7.25.2)':
+ '@babel/plugin-transform-export-namespace-from@7.25.8(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.25.2)
'@babel/plugin-transform-flow-strip-types@7.25.7(@babel/core@7.25.2)':
dependencies:
@@ -14878,22 +15181,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-json-strings@7.25.7(@babel/core@7.25.2)':
+ '@babel/plugin-transform-json-strings@7.25.8(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.25.2)
'@babel/plugin-transform-literals@7.25.7(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-transform-logical-assignment-operators@7.25.7(@babel/core@7.25.2)':
+ '@babel/plugin-transform-logical-assignment-operators@7.25.8(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.25.2)
'@babel/plugin-transform-member-expression-literals@7.25.7(@babel/core@7.25.2)':
dependencies:
@@ -14946,24 +15247,21 @@ snapshots:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-transform-nullish-coalescing-operator@7.25.7(@babel/core@7.25.2)':
+ '@babel/plugin-transform-nullish-coalescing-operator@7.25.8(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.2)
- '@babel/plugin-transform-numeric-separator@7.25.7(@babel/core@7.25.2)':
+ '@babel/plugin-transform-numeric-separator@7.25.8(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.25.2)
- '@babel/plugin-transform-object-rest-spread@7.25.7(@babel/core@7.25.2)':
+ '@babel/plugin-transform-object-rest-spread@7.25.8(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
'@babel/helper-compilation-targets': 7.25.7
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.25.2)
'@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-object-super@7.25.7(@babel/core@7.25.2)':
@@ -14974,18 +15272,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-optional-catch-binding@7.25.7(@babel/core@7.25.2)':
+ '@babel/plugin-transform-optional-catch-binding@7.25.8(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.25.2)
- '@babel/plugin-transform-optional-chaining@7.25.7(@babel/core@7.25.2)':
+ '@babel/plugin-transform-optional-chaining@7.25.8(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
'@babel/helper-skip-transparent-expression-wrappers': 7.25.7
- '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.25.2)
transitivePeerDependencies:
- supports-color
@@ -15002,13 +15298,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-private-property-in-object@7.25.7(@babel/core@7.25.2)':
+ '@babel/plugin-transform-private-property-in-object@7.25.8(@babel/core@7.25.2)':
dependencies:
'@babel/core': 7.25.2
'@babel/helper-annotate-as-pure': 7.25.7
'@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.2)
'@babel/helper-plugin-utils': 7.25.7
- '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.25.2)
transitivePeerDependencies:
- supports-color
@@ -15046,7 +15341,7 @@ snapshots:
'@babel/helper-module-imports': 7.25.7
'@babel/helper-plugin-utils': 7.25.7
'@babel/plugin-syntax-jsx': 7.25.7(@babel/core@7.25.2)
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
transitivePeerDependencies:
- supports-color
@@ -15141,9 +15436,9 @@ snapshots:
'@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.2)
'@babel/helper-plugin-utils': 7.25.7
- '@babel/preset-env@7.25.7(@babel/core@7.25.2)':
+ '@babel/preset-env@7.25.8(@babel/core@7.25.2)':
dependencies:
- '@babel/compat-data': 7.25.7
+ '@babel/compat-data': 7.25.8
'@babel/core': 7.25.2
'@babel/helper-compilation-targets': 7.25.7
'@babel/helper-plugin-utils': 7.25.7
@@ -15154,45 +15449,30 @@ snapshots:
'@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.25.2)
- '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.25.2)
- '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.25.2)
- '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.25.2)
- '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.25.2)
- '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.25.2)
'@babel/plugin-syntax-import-assertions': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-syntax-import-attributes': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.25.2)
- '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.25.2)
- '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.25.2)
- '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.2)
- '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.25.2)
- '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.25.2)
- '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.25.2)
- '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.25.2)
- '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.25.2)
- '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.25.2)
'@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.25.2)
'@babel/plugin-transform-arrow-functions': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-async-generator-functions': 7.25.7(@babel/core@7.25.2)
+ '@babel/plugin-transform-async-generator-functions': 7.25.8(@babel/core@7.25.2)
'@babel/plugin-transform-async-to-generator': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-block-scoped-functions': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-block-scoping': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-class-properties': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-class-static-block': 7.25.7(@babel/core@7.25.2)
+ '@babel/plugin-transform-class-static-block': 7.25.8(@babel/core@7.25.2)
'@babel/plugin-transform-classes': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-computed-properties': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-destructuring': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-dotall-regex': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-duplicate-keys': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-dynamic-import': 7.25.7(@babel/core@7.25.2)
+ '@babel/plugin-transform-dynamic-import': 7.25.8(@babel/core@7.25.2)
'@babel/plugin-transform-exponentiation-operator': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-export-namespace-from': 7.25.7(@babel/core@7.25.2)
+ '@babel/plugin-transform-export-namespace-from': 7.25.8(@babel/core@7.25.2)
'@babel/plugin-transform-for-of': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-function-name': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-json-strings': 7.25.7(@babel/core@7.25.2)
+ '@babel/plugin-transform-json-strings': 7.25.8(@babel/core@7.25.2)
'@babel/plugin-transform-literals': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-logical-assignment-operators': 7.25.7(@babel/core@7.25.2)
+ '@babel/plugin-transform-logical-assignment-operators': 7.25.8(@babel/core@7.25.2)
'@babel/plugin-transform-member-expression-literals': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-modules-amd': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-modules-commonjs': 7.25.7(@babel/core@7.25.2)
@@ -15200,15 +15480,15 @@ snapshots:
'@babel/plugin-transform-modules-umd': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-named-capturing-groups-regex': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-new-target': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-nullish-coalescing-operator': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-numeric-separator': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-object-rest-spread': 7.25.7(@babel/core@7.25.2)
+ '@babel/plugin-transform-nullish-coalescing-operator': 7.25.8(@babel/core@7.25.2)
+ '@babel/plugin-transform-numeric-separator': 7.25.8(@babel/core@7.25.2)
+ '@babel/plugin-transform-object-rest-spread': 7.25.8(@babel/core@7.25.2)
'@babel/plugin-transform-object-super': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-optional-catch-binding': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-optional-chaining': 7.25.7(@babel/core@7.25.2)
+ '@babel/plugin-transform-optional-catch-binding': 7.25.8(@babel/core@7.25.2)
+ '@babel/plugin-transform-optional-chaining': 7.25.8(@babel/core@7.25.2)
'@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-private-methods': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-private-property-in-object': 7.25.7(@babel/core@7.25.2)
+ '@babel/plugin-transform-private-property-in-object': 7.25.8(@babel/core@7.25.2)
'@babel/plugin-transform-property-literals': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-regenerator': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-reserved-words': 7.25.7(@babel/core@7.25.2)
@@ -15241,7 +15521,7 @@ snapshots:
dependencies:
'@babel/core': 7.25.2
'@babel/helper-plugin-utils': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
esutils: 2.0.3
'@babel/preset-react@7.25.7(@babel/core@7.25.2)':
@@ -15283,8 +15563,8 @@ snapshots:
'@babel/template@7.25.7':
dependencies:
'@babel/code-frame': 7.25.7
- '@babel/parser': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/parser': 7.25.8
+ '@babel/types': 7.25.8
'@babel/traverse@7.23.2':
dependencies:
@@ -15294,8 +15574,8 @@ snapshots:
'@babel/helper-function-name': 7.24.7
'@babel/helper-hoist-variables': 7.24.7
'@babel/helper-split-export-declaration': 7.24.7
- '@babel/parser': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/parser': 7.25.8
+ '@babel/types': 7.25.8
debug: 4.3.7
globals: 11.12.0
transitivePeerDependencies:
@@ -15305,9 +15585,9 @@ snapshots:
dependencies:
'@babel/code-frame': 7.25.7
'@babel/generator': 7.25.7
- '@babel/parser': 7.25.7
+ '@babel/parser': 7.25.8
'@babel/template': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
debug: 4.3.7
globals: 11.12.0
transitivePeerDependencies:
@@ -15318,7 +15598,7 @@ snapshots:
'@babel/helper-validator-identifier': 7.25.7
to-fast-properties: 2.0.0
- '@babel/types@7.25.7':
+ '@babel/types@7.25.8':
dependencies:
'@babel/helper-string-parser': 7.25.7
'@babel/helper-validator-identifier': 7.25.7
@@ -16060,8 +16340,8 @@ snapshots:
dependencies:
'@babel/core': 7.25.2
'@babel/generator': 7.25.7
- '@babel/parser': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/parser': 7.25.8
+ '@babel/types': 7.25.8
'@expo/config': 9.0.3
'@expo/env': 0.3.0
'@expo/json-file': 8.3.3
@@ -16198,9 +16478,9 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
- '@headlessui/tailwindcss@0.2.1(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)))':
+ '@headlessui/tailwindcss@0.2.1(tailwindcss@3.4.13(ts-node@10.9.2(typescript@5.4.5)))':
dependencies:
- tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5))
+ tailwindcss: 3.4.13(ts-node@10.9.2(typescript@5.4.5))
'@hookform/resolvers@3.9.0(react-hook-form@7.53.0(react@18.3.1))':
dependencies:
@@ -16580,11 +16860,11 @@ snapshots:
refractor: 3.6.0
unist-util-visit: 2.0.3
- '@mdx-js/loader@3.0.1(webpack@5.95.0(@swc/core@1.3.101(@swc/helpers@0.5.13)))':
+ '@mdx-js/loader@3.0.1(webpack@5.95.0)':
dependencies:
'@mdx-js/mdx': 3.0.1
source-map: 0.7.4
- webpack: 5.95.0(@swc/core@1.3.101(@swc/helpers@0.5.13))
+ webpack: 5.95.0
transitivePeerDependencies:
- supports-color
@@ -16657,11 +16937,11 @@ snapshots:
'@microsoft/tsdoc@0.14.2': {}
- '@neshca/cache-handler@1.7.4(next@14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(redis@4.7.0)':
+ '@neshca/cache-handler@1.7.4(next@14.2.15(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(redis@4.7.0)':
dependencies:
cluster-key-slot: 1.1.2
lru-cache: 10.4.3
- next: 14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ next: 14.2.15(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
redis: 4.7.0
'@next/env@13.5.7': {}
@@ -16674,11 +16954,11 @@ snapshots:
dependencies:
glob: 10.3.10
- '@next/mdx@14.2.15(@mdx-js/loader@3.0.1(webpack@5.95.0(@swc/core@1.3.101(@swc/helpers@0.5.13))))(@mdx-js/react@3.0.1(@types/react@18.3.11)(react@18.3.1))':
+ '@next/mdx@14.2.15(@mdx-js/loader@3.0.1(webpack@5.95.0))(@mdx-js/react@3.0.1(@types/react@18.3.11)(react@18.3.1))':
dependencies:
source-map: 0.7.4
optionalDependencies:
- '@mdx-js/loader': 3.0.1(webpack@5.95.0(@swc/core@1.3.101(@swc/helpers@0.5.13)))
+ '@mdx-js/loader': 3.0.1(webpack@5.95.0)
'@mdx-js/react': 3.0.1(@types/react@18.3.11)(react@18.3.1)
'@next/swc-darwin-arm64@14.1.4':
@@ -17004,6 +17284,13 @@ snapshots:
'@opentelemetry/core': 1.26.0(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.27.0
+ '@opentelemetry/sdk-logs@0.53.0(@opentelemetry/api@1.9.0)':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/api-logs': 0.53.0
+ '@opentelemetry/core': 1.26.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/resources': 1.26.0(@opentelemetry/api@1.9.0)
+
'@opentelemetry/sdk-metrics@1.26.0(@opentelemetry/api@1.9.0)':
dependencies:
'@opentelemetry/api': 1.9.0
@@ -17926,6 +18213,15 @@ snapshots:
'@types/react': 18.3.11
'@types/react-dom': 18.3.0
+ '@radix-ui/react-separator@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.11
+ '@types/react-dom': 18.3.0
+
'@radix-ui/react-slider@1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/number': 1.1.0
@@ -17982,6 +18278,22 @@ snapshots:
'@types/react': 18.3.11
'@types/react-dom': 18.3.0
+ '@radix-ui/react-tabs@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.0
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.11)(react@18.3.1)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.11)(react@18.3.1)
+ '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.11
+ '@types/react-dom': 18.3.0
+
'@radix-ui/react-toggle-group@1.1.0(@types/react-dom@18.3.0)(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.0
@@ -17997,6 +18309,21 @@ snapshots:
'@types/react': 18.2.47
'@types/react-dom': 18.3.0
+ '@radix-ui/react-toggle-group@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.0
+ '@radix-ui/react-context': 1.1.0(@types/react@18.3.11)(react@18.3.1)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.11)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-toggle': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.11
+ '@types/react-dom': 18.3.0
+
'@radix-ui/react-toggle@1.1.0(@types/react-dom@18.3.0)(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.0
@@ -18008,6 +18335,17 @@ snapshots:
'@types/react': 18.2.47
'@types/react-dom': 18.3.0
+ '@radix-ui/react-toggle@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.0
+ '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.11)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.11
+ '@types/react-dom': 18.3.0
+
'@radix-ui/react-tooltip@1.1.1(@types/react-dom@18.3.0)(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.0
@@ -18327,10 +18665,10 @@ snapshots:
dependencies:
react: 18.3.1
- '@react-native-async-storage/async-storage@1.23.1(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))':
+ '@react-native-async-storage/async-storage@1.23.1(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))':
dependencies:
merge-options: 3.0.4
- react-native: 0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1)
+ react-native: 0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1)
'@react-native-community/cli-clean@13.6.9(encoding@0.1.13)':
dependencies:
@@ -18483,26 +18821,26 @@ snapshots:
'@react-native/assets-registry@0.74.87': {}
- '@react-native/babel-plugin-codegen@0.74.86(@babel/preset-env@7.25.7(@babel/core@7.25.2))':
+ '@react-native/babel-plugin-codegen@0.74.86(@babel/preset-env@7.25.8(@babel/core@7.25.2))':
dependencies:
- '@react-native/codegen': 0.74.86(@babel/preset-env@7.25.7(@babel/core@7.25.2))
+ '@react-native/codegen': 0.74.86(@babel/preset-env@7.25.8(@babel/core@7.25.2))
transitivePeerDependencies:
- '@babel/preset-env'
- supports-color
- '@react-native/babel-plugin-codegen@0.74.87(@babel/preset-env@7.25.7(@babel/core@7.25.2))':
+ '@react-native/babel-plugin-codegen@0.74.87(@babel/preset-env@7.25.8(@babel/core@7.25.2))':
dependencies:
- '@react-native/codegen': 0.74.87(@babel/preset-env@7.25.7(@babel/core@7.25.2))
+ '@react-native/codegen': 0.74.87(@babel/preset-env@7.25.8(@babel/core@7.25.2))
transitivePeerDependencies:
- '@babel/preset-env'
- supports-color
- '@react-native/babel-preset@0.74.86(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))':
+ '@react-native/babel-preset@0.74.86(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))':
dependencies:
'@babel/core': 7.25.2
'@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.25.2)
'@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.25.2)
- '@babel/plugin-proposal-export-default-from': 7.25.7(@babel/core@7.25.2)
+ '@babel/plugin-proposal-export-default-from': 7.25.8(@babel/core@7.25.2)
'@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.25.2)
'@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.25.2)
'@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.25.2)
@@ -18527,7 +18865,7 @@ snapshots:
'@babel/plugin-transform-named-capturing-groups-regex': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-private-methods': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-private-property-in-object': 7.25.7(@babel/core@7.25.2)
+ '@babel/plugin-transform-private-property-in-object': 7.25.8(@babel/core@7.25.2)
'@babel/plugin-transform-react-display-name': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-react-jsx': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-react-jsx-self': 7.25.7(@babel/core@7.25.2)
@@ -18539,19 +18877,19 @@ snapshots:
'@babel/plugin-transform-typescript': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-unicode-regex': 7.25.7(@babel/core@7.25.2)
'@babel/template': 7.25.7
- '@react-native/babel-plugin-codegen': 0.74.86(@babel/preset-env@7.25.7(@babel/core@7.25.2))
+ '@react-native/babel-plugin-codegen': 0.74.86(@babel/preset-env@7.25.8(@babel/core@7.25.2))
babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.25.2)
react-refresh: 0.14.2
transitivePeerDependencies:
- '@babel/preset-env'
- supports-color
- '@react-native/babel-preset@0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))':
+ '@react-native/babel-preset@0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))':
dependencies:
'@babel/core': 7.25.2
'@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.25.2)
'@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.25.2)
- '@babel/plugin-proposal-export-default-from': 7.25.7(@babel/core@7.25.2)
+ '@babel/plugin-proposal-export-default-from': 7.25.8(@babel/core@7.25.2)
'@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.25.2)
'@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.25.2)
'@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.25.2)
@@ -18576,7 +18914,7 @@ snapshots:
'@babel/plugin-transform-named-capturing-groups-regex': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-private-methods': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-private-property-in-object': 7.25.7(@babel/core@7.25.2)
+ '@babel/plugin-transform-private-property-in-object': 7.25.8(@babel/core@7.25.2)
'@babel/plugin-transform-react-display-name': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-react-jsx': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-react-jsx-self': 7.25.7(@babel/core@7.25.2)
@@ -18588,45 +18926,45 @@ snapshots:
'@babel/plugin-transform-typescript': 7.25.7(@babel/core@7.25.2)
'@babel/plugin-transform-unicode-regex': 7.25.7(@babel/core@7.25.2)
'@babel/template': 7.25.7
- '@react-native/babel-plugin-codegen': 0.74.87(@babel/preset-env@7.25.7(@babel/core@7.25.2))
+ '@react-native/babel-plugin-codegen': 0.74.87(@babel/preset-env@7.25.8(@babel/core@7.25.2))
babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.25.2)
react-refresh: 0.14.2
transitivePeerDependencies:
- '@babel/preset-env'
- supports-color
- '@react-native/codegen@0.74.86(@babel/preset-env@7.25.7(@babel/core@7.25.2))':
+ '@react-native/codegen@0.74.86(@babel/preset-env@7.25.8(@babel/core@7.25.2))':
dependencies:
- '@babel/parser': 7.25.7
- '@babel/preset-env': 7.25.7(@babel/core@7.25.2)
+ '@babel/parser': 7.25.8
+ '@babel/preset-env': 7.25.8(@babel/core@7.25.2)
glob: 7.2.3
hermes-parser: 0.19.1
invariant: 2.2.4
- jscodeshift: 0.14.0(@babel/preset-env@7.25.7(@babel/core@7.25.2))
+ jscodeshift: 0.14.0(@babel/preset-env@7.25.8(@babel/core@7.25.2))
mkdirp: 0.5.6
nullthrows: 1.1.1
transitivePeerDependencies:
- supports-color
- '@react-native/codegen@0.74.87(@babel/preset-env@7.25.7(@babel/core@7.25.2))':
+ '@react-native/codegen@0.74.87(@babel/preset-env@7.25.8(@babel/core@7.25.2))':
dependencies:
- '@babel/parser': 7.25.7
- '@babel/preset-env': 7.25.7(@babel/core@7.25.2)
+ '@babel/parser': 7.25.8
+ '@babel/preset-env': 7.25.8(@babel/core@7.25.2)
glob: 7.2.3
hermes-parser: 0.19.1
invariant: 2.2.4
- jscodeshift: 0.14.0(@babel/preset-env@7.25.7(@babel/core@7.25.2))
+ jscodeshift: 0.14.0(@babel/preset-env@7.25.8(@babel/core@7.25.2))
mkdirp: 0.5.6
nullthrows: 1.1.1
transitivePeerDependencies:
- supports-color
- '@react-native/community-cli-plugin@0.74.86(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13)':
+ '@react-native/community-cli-plugin@0.74.86(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13)':
dependencies:
'@react-native-community/cli-server-api': 13.6.9(encoding@0.1.13)
'@react-native-community/cli-tools': 13.6.9(encoding@0.1.13)
'@react-native/dev-middleware': 0.74.86(encoding@0.1.13)
- '@react-native/metro-babel-transformer': 0.74.86(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))
+ '@react-native/metro-babel-transformer': 0.74.86(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))
chalk: 4.1.2
execa: 5.1.1
metro: 0.80.12
@@ -18643,12 +18981,12 @@ snapshots:
- supports-color
- utf-8-validate
- '@react-native/community-cli-plugin@0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13)':
+ '@react-native/community-cli-plugin@0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13)':
dependencies:
'@react-native-community/cli-server-api': 13.6.9(encoding@0.1.13)
'@react-native-community/cli-tools': 13.6.9(encoding@0.1.13)
'@react-native/dev-middleware': 0.74.87(encoding@0.1.13)
- '@react-native/metro-babel-transformer': 0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))
+ '@react-native/metro-babel-transformer': 0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))
chalk: 4.1.2
execa: 5.1.1
metro: 0.80.12
@@ -18742,20 +19080,20 @@ snapshots:
'@react-native/js-polyfills@0.74.87': {}
- '@react-native/metro-babel-transformer@0.74.86(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))':
+ '@react-native/metro-babel-transformer@0.74.86(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))':
dependencies:
'@babel/core': 7.25.2
- '@react-native/babel-preset': 0.74.86(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))
+ '@react-native/babel-preset': 0.74.86(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))
hermes-parser: 0.19.1
nullthrows: 1.1.1
transitivePeerDependencies:
- '@babel/preset-env'
- supports-color
- '@react-native/metro-babel-transformer@0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))':
+ '@react-native/metro-babel-transformer@0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))':
dependencies:
'@babel/core': 7.25.2
- '@react-native/babel-preset': 0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))
+ '@react-native/babel-preset': 0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))
hermes-parser: 0.19.1
nullthrows: 1.1.1
transitivePeerDependencies:
@@ -18768,21 +19106,21 @@ snapshots:
'@react-native/normalize-colors@0.74.87': {}
- '@react-native/virtualized-lists@0.74.86(@types/react@18.3.11)(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)':
+ '@react-native/virtualized-lists@0.74.86(@types/react@18.3.11)(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)':
dependencies:
invariant: 2.2.4
nullthrows: 1.1.1
react: 18.3.1
- react-native: 0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1)
+ react-native: 0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.11
- '@react-native/virtualized-lists@0.74.87(@types/react@18.3.11)(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)':
+ '@react-native/virtualized-lists@0.74.87(@types/react@18.3.11)(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)':
dependencies:
invariant: 2.2.4
nullthrows: 1.1.1
react: 18.3.1
- react-native: 0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1)
+ react-native: 0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.11
@@ -19065,7 +19403,7 @@ snapshots:
'@sentry/types': 8.34.0
'@sentry/utils': 8.34.0
- '@sentry/nextjs@8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.95.0)':
+ '@sentry/nextjs@8.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(encoding@0.1.13)(next@14.2.15(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.95.0)':
dependencies:
'@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.27.0
@@ -19080,7 +19418,7 @@ snapshots:
'@sentry/vercel-edge': 8.34.0
'@sentry/webpack-plugin': 2.22.3(encoding@0.1.13)(webpack@5.95.0)
chalk: 3.0.0
- next: 14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ next: 14.2.15(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
resolve: 1.22.8
rollup: 3.29.5
stacktrace-parser: 0.1.10
@@ -19678,7 +20016,7 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
- '@storybook/builder-vite@8.3.5(@preact/preset-vite@2.9.0(@babel/core@7.25.2)(preact@10.23.2)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6)))(storybook@8.3.5)(typescript@5.4.5)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6))(webpack-sources@3.2.3)':
+ '@storybook/builder-vite@8.3.5(@preact/preset-vite@2.9.0(@babel/core@7.25.2)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6)))(storybook@8.3.5)(typescript@5.4.5)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6))(webpack-sources@3.2.3)':
dependencies:
'@storybook/csf-plugin': 8.3.5(storybook@8.3.5)(webpack-sources@3.2.3)
'@types/find-cache-dir': 3.2.1
@@ -19765,11 +20103,11 @@ snapshots:
react-dom: 18.3.1(react@18.3.1)
storybook: 8.3.5
- '@storybook/react-vite@8.3.5(@preact/preset-vite@2.9.0(@babel/core@7.25.2)(preact@10.23.2)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6)))(@storybook/test@8.3.5(storybook@8.3.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.24.0)(storybook@8.3.5)(typescript@5.4.5)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6))(webpack-sources@3.2.3)':
+ '@storybook/react-vite@8.3.5(@preact/preset-vite@2.9.0(@babel/core@7.25.2)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6)))(@storybook/test@8.3.5(storybook@8.3.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.24.0)(storybook@8.3.5)(typescript@5.4.5)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6))(webpack-sources@3.2.3)':
dependencies:
'@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.4.5)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6))
'@rollup/pluginutils': 5.1.2(rollup@4.24.0)
- '@storybook/builder-vite': 8.3.5(@preact/preset-vite@2.9.0(@babel/core@7.25.2)(preact@10.23.2)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6)))(storybook@8.3.5)(typescript@5.4.5)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6))(webpack-sources@3.2.3)
+ '@storybook/builder-vite': 8.3.5(@preact/preset-vite@2.9.0(@babel/core@7.25.2)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6)))(storybook@8.3.5)(typescript@5.4.5)(vite@5.4.8(@types/node@22.3.0)(terser@5.31.6))(webpack-sources@3.2.3)
'@storybook/react': 8.3.5(@storybook/test@8.3.5(storybook@8.3.5))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.5)(typescript@5.4.5)
find-up: 5.0.0
magic-string: 0.30.11
@@ -19916,26 +20254,26 @@ snapshots:
optionalDependencies:
typescript: 5.4.5
- '@tailwindcss/forms@0.5.9(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)))':
+ '@tailwindcss/forms@0.5.9(tailwindcss@3.4.13(ts-node@10.9.2))':
dependencies:
mini-svg-data-uri: 1.4.4
- tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5))
+ tailwindcss: 3.4.13(ts-node@10.9.2(typescript@5.4.5))
- '@tailwindcss/typography@0.5.13(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)))':
+ '@tailwindcss/typography@0.5.13(tailwindcss@3.4.13(ts-node@10.9.2))':
dependencies:
lodash.castarray: 4.4.0
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
postcss-selector-parser: 6.0.10
- tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5))
+ tailwindcss: 3.4.13(ts-node@10.9.2(typescript@5.4.5))
- '@tailwindcss/typography@0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)))':
+ '@tailwindcss/typography@0.5.15(tailwindcss@3.4.13(ts-node@10.9.2(typescript@5.4.5)))':
dependencies:
lodash.castarray: 4.4.0
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
postcss-selector-parser: 6.0.10
- tailwindcss: 3.4.13(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5))
+ tailwindcss: 3.4.13(ts-node@10.9.2(typescript@5.4.5))
'@tanstack/react-table@8.20.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
@@ -19978,15 +20316,17 @@ snapshots:
dependencies:
'@testing-library/dom': 10.4.0
- '@trivago/prettier-plugin-sort-imports@4.3.0(prettier@3.3.3)':
+ '@trivago/prettier-plugin-sort-imports@4.3.0(@vue/compiler-sfc@3.5.11)(prettier@3.3.3)':
dependencies:
'@babel/generator': 7.17.7
- '@babel/parser': 7.25.7
+ '@babel/parser': 7.25.8
'@babel/traverse': 7.23.2
'@babel/types': 7.17.0
javascript-natural-sort: 0.7.1
lodash: 4.17.21
prettier: 3.3.3
+ optionalDependencies:
+ '@vue/compiler-sfc': 3.5.11
transitivePeerDependencies:
- supports-color
@@ -20015,24 +20355,24 @@ snapshots:
'@types/babel__core@7.20.5':
dependencies:
- '@babel/parser': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/parser': 7.25.8
+ '@babel/types': 7.25.8
'@types/babel__generator': 7.6.8
'@types/babel__template': 7.4.4
'@types/babel__traverse': 7.20.6
'@types/babel__generator@7.6.8':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
'@types/babel__template@7.4.4':
dependencies:
- '@babel/parser': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/parser': 7.25.8
+ '@babel/types': 7.25.8
'@types/babel__traverse@7.20.6':
dependencies:
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
'@types/bcryptjs@2.4.6': {}
@@ -20071,6 +20411,8 @@ snapshots:
dependencies:
'@types/ms': 0.7.34
+ '@types/diff-match-patch@1.0.36': {}
+
'@types/doctrine@0.0.9': {}
'@types/dompurify@3.0.5':
@@ -20643,15 +20985,27 @@ snapshots:
satori: 0.10.9
yoga-wasm-web: 0.3.3
- '@vercel/speed-insights@1.0.12(next@14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
- optionalDependencies:
- next: 14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- react: 18.3.1
+ '@vercel/otel@1.10.0(@opentelemetry/api-logs@0.53.0)(@opentelemetry/api@1.9.0)(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-logs@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-metrics@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))':
+ dependencies:
+ '@opentelemetry/api': 1.9.0
+ '@opentelemetry/api-logs': 0.53.0
+ '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/resources': 1.26.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-logs': 0.53.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-metrics': 1.26.0(@opentelemetry/api@1.9.0)
+ '@opentelemetry/sdk-trace-base': 1.26.0(@opentelemetry/api@1.9.0)
- '@vercel/style-guide@6.0.0(@next/eslint-plugin-next@14.2.5)(eslint@8.57.0)(prettier@3.3.3)(typescript@5.4.5)(vitest@2.0.5(@types/node@22.3.0)(jsdom@24.1.3)(terser@5.31.6))':
+ '@vercel/speed-insights@1.0.12(next@14.2.15(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(svelte@4.2.19)(vue@3.5.11(typescript@5.4.5))':
+ optionalDependencies:
+ next: 14.2.15(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ svelte: 4.2.19
+ vue: 3.5.11(typescript@5.4.5)
+
+ '@vercel/style-guide@6.0.0(@next/eslint-plugin-next@14.2.5)(eslint@8.57.0)(prettier@3.3.3)(typescript@5.4.5)(vitest@2.0.5)':
dependencies:
'@babel/core': 7.25.2
- '@babel/eslint-parser': 7.25.7(@babel/core@7.25.2)(eslint@8.57.0)
+ '@babel/eslint-parser': 7.25.8(@babel/core@7.25.2)(eslint@8.57.0)
'@rushstack/eslint-patch': 1.10.4
'@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.4.5)
@@ -20668,7 +21022,7 @@ snapshots:
eslint-plugin-testing-library: 6.3.0(eslint@8.57.0)(typescript@5.4.5)
eslint-plugin-tsdoc: 0.2.17
eslint-plugin-unicorn: 51.0.1(eslint@8.57.0)
- eslint-plugin-vitest: 0.3.26(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)(vitest@2.0.5(@types/node@22.3.0)(jsdom@24.1.3)(terser@5.31.6))
+ eslint-plugin-vitest: 0.3.26(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)(vitest@2.0.5)
prettier-plugin-packagejson: 2.5.3(prettier@3.3.3)
optionalDependencies:
'@next/eslint-plugin-next': 14.2.5
@@ -20752,7 +21106,7 @@ snapshots:
'@vue/compiler-core@3.5.11':
dependencies:
- '@babel/parser': 7.25.7
+ '@babel/parser': 7.25.8
'@vue/shared': 3.5.11
entities: 4.5.0
estree-walker: 2.0.2
@@ -20763,6 +21117,23 @@ snapshots:
'@vue/compiler-core': 3.5.11
'@vue/shared': 3.5.11
+ '@vue/compiler-sfc@3.5.11':
+ dependencies:
+ '@babel/parser': 7.25.8
+ '@vue/compiler-core': 3.5.11
+ '@vue/compiler-dom': 3.5.11
+ '@vue/compiler-ssr': 3.5.11
+ '@vue/shared': 3.5.11
+ estree-walker: 2.0.2
+ magic-string: 0.30.11
+ postcss: 8.4.47
+ source-map-js: 1.2.1
+
+ '@vue/compiler-ssr@3.5.11':
+ dependencies:
+ '@vue/compiler-dom': 3.5.11
+ '@vue/shared': 3.5.11
+
'@vue/language-core@1.8.27(typescript@5.4.5)':
dependencies:
'@volar/language-core': 1.11.1
@@ -20777,6 +21148,28 @@ snapshots:
optionalDependencies:
typescript: 5.4.5
+ '@vue/reactivity@3.5.11':
+ dependencies:
+ '@vue/shared': 3.5.11
+
+ '@vue/runtime-core@3.5.11':
+ dependencies:
+ '@vue/reactivity': 3.5.11
+ '@vue/shared': 3.5.11
+
+ '@vue/runtime-dom@3.5.11':
+ dependencies:
+ '@vue/reactivity': 3.5.11
+ '@vue/runtime-core': 3.5.11
+ '@vue/shared': 3.5.11
+ csstype: 3.1.3
+
+ '@vue/server-renderer@3.5.11(vue@3.5.11(typescript@5.4.5))':
+ dependencies:
+ '@vue/compiler-ssr': 3.5.11
+ '@vue/shared': 3.5.11
+ vue: 3.5.11(typescript@5.4.5)
+
'@vue/shared@3.5.11': {}
'@webassemblyjs/ast@1.12.1':
@@ -20921,6 +21314,31 @@ snapshots:
clean-stack: 2.2.0
indent-string: 4.0.0
+ ai@3.4.9(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.5.11(typescript@5.4.5))(zod@3.23.8):
+ dependencies:
+ '@ai-sdk/provider': 0.0.24
+ '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8)
+ '@ai-sdk/react': 0.0.62(react@18.3.1)(zod@3.23.8)
+ '@ai-sdk/solid': 0.0.49(zod@3.23.8)
+ '@ai-sdk/svelte': 0.0.51(svelte@4.2.19)(zod@3.23.8)
+ '@ai-sdk/ui-utils': 0.0.46(zod@3.23.8)
+ '@ai-sdk/vue': 0.0.54(vue@3.5.11(typescript@5.4.5))(zod@3.23.8)
+ '@opentelemetry/api': 1.9.0
+ eventsource-parser: 1.1.2
+ json-schema: 0.4.0
+ jsondiffpatch: 0.6.0
+ nanoid: 3.3.6
+ secure-json-parse: 2.7.0
+ zod-to-json-schema: 3.23.2(zod@3.23.8)
+ optionalDependencies:
+ react: 18.3.1
+ sswr: 2.1.0(svelte@4.2.19)
+ svelte: 4.2.19
+ zod: 3.23.8
+ transitivePeerDependencies:
+ - solid-js
+ - vue
+
ajv-keywords@3.5.2(ajv@6.12.6):
dependencies:
ajv: 6.12.6
@@ -21203,6 +21621,16 @@ snapshots:
postcss: 8.4.41
postcss-value-parser: 4.2.0
+ autoprefixer@10.4.20(postcss@8.4.47):
+ dependencies:
+ browserslist: 4.24.0
+ caniuse-lite: 1.0.30001667
+ fraction.js: 4.3.7
+ normalize-range: 0.1.2
+ picocolors: 1.1.0
+ postcss: 8.4.47
+ postcss-value-parser: 4.2.0
+
available-typed-arrays@1.0.7:
dependencies:
possible-typed-array-names: 1.0.0
@@ -21240,7 +21668,7 @@ snapshots:
babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.25.2):
dependencies:
- '@babel/compat-data': 7.25.7
+ '@babel/compat-data': 7.25.8
'@babel/core': 7.25.2
'@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.25.2)
semver: 6.3.1
@@ -21265,7 +21693,7 @@ snapshots:
babel-plugin-react-compiler@0.0.0-experimental-592953e-20240517:
dependencies:
'@babel/generator': 7.2.0
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
chalk: 4.1.2
invariant: 2.2.4
pretty-format: 24.9.0
@@ -21284,15 +21712,15 @@ snapshots:
dependencies:
'@babel/core': 7.25.2
- babel-preset-expo@11.0.15(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2)):
+ babel-preset-expo@11.0.15(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2)):
dependencies:
'@babel/plugin-proposal-decorators': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-export-namespace-from': 7.25.7(@babel/core@7.25.2)
- '@babel/plugin-transform-object-rest-spread': 7.25.7(@babel/core@7.25.2)
+ '@babel/plugin-transform-export-namespace-from': 7.25.8(@babel/core@7.25.2)
+ '@babel/plugin-transform-object-rest-spread': 7.25.8(@babel/core@7.25.2)
'@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.2)
'@babel/preset-react': 7.25.7(@babel/core@7.25.2)
'@babel/preset-typescript': 7.25.7(@babel/core@7.25.2)
- '@react-native/babel-preset': 0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))
+ '@react-native/babel-preset': 0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))
babel-plugin-react-compiler: 0.0.0-experimental-592953e-20240517
babel-plugin-react-native-web: 0.19.12
react-refresh: 0.14.2
@@ -21774,6 +22202,14 @@ snapshots:
code-block-writer@11.0.3: {}
+ code-red@1.0.4:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.0
+ '@types/estree': 1.0.6
+ acorn: 8.12.1
+ estree-walker: 3.0.3
+ periscopic: 3.1.0
+
codepage@1.15.0: {}
collapse-white-space@2.1.0: {}
@@ -22055,6 +22491,11 @@ snapshots:
mdn-data: 2.0.14
source-map: 0.6.1
+ css-tree@2.3.1:
+ dependencies:
+ mdn-data: 2.0.30
+ source-map-js: 1.2.1
+
css-what@6.1.0: {}
css.escape@1.5.1: {}
@@ -22230,6 +22671,8 @@ snapshots:
didyoumean@1.2.2: {}
+ diff-match-patch@1.0.5: {}
+
diff@4.0.2: {}
diffie-hellman@5.0.3:
@@ -22973,13 +23416,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-vitest@0.3.26(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)(vitest@2.0.5(@types/node@22.3.0)(jsdom@24.1.3)(terser@5.31.6)):
+ eslint-plugin-vitest@0.3.26(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)(vitest@2.0.5):
dependencies:
'@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.4.5)
eslint: 8.57.0
optionalDependencies:
'@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)
- vitest: 2.0.5(@types/node@22.3.0)(jsdom@24.1.3)(terser@5.31.6)
+ vitest: 2.0.5
transitivePeerDependencies:
- supports-color
- typescript
@@ -23106,6 +23549,8 @@ snapshots:
events@3.3.0: {}
+ eventsource-parser@1.1.2: {}
+
evp_bytestokey@1.0.3:
dependencies:
md5.js: 1.3.5
@@ -23147,35 +23592,35 @@ snapshots:
signal-exit: 4.1.0
strip-final-newline: 3.0.0
- expo-asset@10.0.10(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13)):
+ expo-asset@10.0.10(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13)):
dependencies:
- expo: 51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13)
- expo-constants: 16.0.2(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13))
+ expo: 51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13)
+ expo-constants: 16.0.2(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13))
invariant: 2.2.4
md5-file: 3.2.3
transitivePeerDependencies:
- supports-color
- expo-constants@16.0.2(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13)):
+ expo-constants@16.0.2(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13)):
dependencies:
'@expo/config': 9.0.3
'@expo/env': 0.3.0
- expo: 51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13)
+ expo: 51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13)
transitivePeerDependencies:
- supports-color
- expo-file-system@17.0.1(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13)):
+ expo-file-system@17.0.1(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13)):
dependencies:
- expo: 51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13)
+ expo: 51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13)
- expo-font@12.0.10(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13)):
+ expo-font@12.0.10(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13)):
dependencies:
- expo: 51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13)
+ expo: 51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13)
fontfaceobserver: 2.3.0
- expo-keep-awake@13.0.2(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13)):
+ expo-keep-awake@13.0.2(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13)):
dependencies:
- expo: 51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13)
+ expo: 51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13)
expo-modules-autolinking@1.11.1:
dependencies:
@@ -23191,7 +23636,7 @@ snapshots:
expo-status-bar@1.12.1: {}
- expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13):
+ expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13):
dependencies:
'@babel/runtime': 7.25.7
'@expo/cli': 0.18.28(encoding@0.1.13)(expo-modules-autolinking@1.11.1)
@@ -23199,11 +23644,11 @@ snapshots:
'@expo/config-plugins': 8.0.8
'@expo/metro-config': 0.18.11
'@expo/vector-icons': 14.0.4
- babel-preset-expo: 11.0.15(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))
- expo-asset: 10.0.10(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13))
- expo-file-system: 17.0.1(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13))
- expo-font: 12.0.10(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13))
- expo-keep-awake: 13.0.2(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13))
+ babel-preset-expo: 11.0.15(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))
+ expo-asset: 10.0.10(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13))
+ expo-file-system: 17.0.1(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13))
+ expo-font: 12.0.10(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13))
+ expo-keep-awake: 13.0.2(expo@51.0.26(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13))
expo-modules-autolinking: 1.11.1
expo-modules-core: 1.12.20
fbemitter: 3.0.0(encoding@0.1.13)
@@ -24508,15 +24953,15 @@ snapshots:
jsc-safe-url@0.2.4: {}
- jscodeshift@0.14.0(@babel/preset-env@7.25.7(@babel/core@7.25.2)):
+ jscodeshift@0.14.0(@babel/preset-env@7.25.8(@babel/core@7.25.2)):
dependencies:
'@babel/core': 7.25.2
- '@babel/parser': 7.25.7
+ '@babel/parser': 7.25.8
'@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.25.2)
'@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.25.2)
'@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.25.2)
'@babel/plugin-transform-modules-commonjs': 7.25.7(@babel/core@7.25.2)
- '@babel/preset-env': 7.25.7(@babel/core@7.25.2)
+ '@babel/preset-env': 7.25.8(@babel/core@7.25.2)
'@babel/preset-flow': 7.25.7(@babel/core@7.25.2)
'@babel/preset-typescript': 7.25.7(@babel/core@7.25.2)
'@babel/register': 7.25.7(@babel/core@7.25.2)
@@ -24594,6 +25039,8 @@ snapshots:
json-schema-traverse@1.0.0: {}
+ json-schema@0.4.0: {}
+
json-stable-stringify-without-jsonify@1.0.1: {}
json5@1.0.2:
@@ -24604,6 +25051,12 @@ snapshots:
jsonc-parser@3.3.1: {}
+ jsondiffpatch@0.6.0:
+ dependencies:
+ '@types/diff-match-patch': 1.0.36
+ chalk: 5.3.0
+ diff-match-patch: 1.0.5
+
jsonfile@4.0.0:
optionalDependencies:
graceful-fs: 4.2.11
@@ -24668,6 +25121,20 @@ snapshots:
kolorist@1.8.0: {}
+ langfuse-core@3.27.0:
+ dependencies:
+ mustache: 4.2.0
+
+ langfuse-vercel@3.27.0(ai@3.4.9(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.5.11(typescript@5.4.5))(zod@3.23.8)):
+ dependencies:
+ ai: 3.4.9(react@18.3.1)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.5.11(typescript@5.4.5))(zod@3.23.8)
+ langfuse: 3.27.0
+ langfuse-core: 3.27.0
+
+ langfuse@3.27.0:
+ dependencies:
+ langfuse-core: 3.27.0
+
language-subtag-registry@0.3.23: {}
language-tags@1.0.9:
@@ -24795,6 +25262,8 @@ snapshots:
emojis-list: 3.0.0
json5: 2.2.3
+ locate-character@3.0.0: {}
+
locate-path@3.0.0:
dependencies:
p-locate: 3.0.0
@@ -25159,6 +25628,8 @@ snapshots:
mdn-data@2.0.14: {}
+ mdn-data@2.0.30: {}
+
mdurl@2.0.0: {}
mdx-annotations@0.1.4:
@@ -25271,7 +25742,7 @@ snapshots:
metro-source-map@0.80.12:
dependencies:
'@babel/traverse': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
flow-enums-runtime: 0.0.6
invariant: 2.2.4
metro-symbolicate: 0.80.12
@@ -25309,8 +25780,8 @@ snapshots:
dependencies:
'@babel/core': 7.25.2
'@babel/generator': 7.25.7
- '@babel/parser': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/parser': 7.25.8
+ '@babel/types': 7.25.8
flow-enums-runtime: 0.0.6
metro: 0.80.12
metro-babel-transformer: 0.80.12
@@ -25330,10 +25801,10 @@ snapshots:
'@babel/code-frame': 7.25.7
'@babel/core': 7.25.2
'@babel/generator': 7.25.7
- '@babel/parser': 7.25.7
+ '@babel/parser': 7.25.8
'@babel/template': 7.25.7
'@babel/traverse': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
accepts: 1.3.8
chalk: 4.1.2
ci-info: 2.0.0
@@ -25799,6 +26270,8 @@ snapshots:
muggle-string@0.3.1: {}
+ mustache@4.2.0: {}
+
mz@2.7.0:
dependencies:
any-promise: 1.3.0
@@ -25818,6 +26291,8 @@ snapshots:
stacktrace-js: 2.0.2
stylis: 4.3.4
+ nanoid@3.3.6: {}
+
nanoid@3.3.7: {}
nanoid@5.0.7: {}
@@ -25855,9 +26330,9 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
- next-safe-action@7.9.3(next@14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8):
+ next-safe-action@7.9.3(next@14.2.15(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(zod@3.23.8):
dependencies:
- next: 14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ next: 14.2.15(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
@@ -25908,6 +26383,33 @@ snapshots:
- '@babel/core'
- babel-plugin-macros
+ next@14.2.15(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ '@next/env': 14.2.15
+ '@swc/helpers': 0.5.5
+ busboy: 1.6.0
+ caniuse-lite: 1.0.30001667
+ graceful-fs: 4.2.11
+ postcss: 8.4.31
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 14.2.15
+ '@next/swc-darwin-x64': 14.2.15
+ '@next/swc-linux-arm64-gnu': 14.2.15
+ '@next/swc-linux-arm64-musl': 14.2.15
+ '@next/swc-linux-x64-gnu': 14.2.15
+ '@next/swc-linux-x64-musl': 14.2.15
+ '@next/swc-win32-arm64-msvc': 14.2.15
+ '@next/swc-win32-ia32-msvc': 14.2.15
+ '@next/swc-win32-x64-msvc': 14.2.15
+ '@opentelemetry/api': 1.9.0
+ '@playwright/test': 1.45.3
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+
next@14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.45.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@next/env': 14.2.15
@@ -26489,13 +26991,13 @@ snapshots:
camelcase-css: 2.0.1
postcss: 8.4.41
- postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)):
+ postcss-load-config@4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@22.3.0)(typescript@5.4.5)):
dependencies:
lilconfig: 3.1.2
yaml: 2.5.1
optionalDependencies:
postcss: 8.4.41
- ts-node: 10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)
+ ts-node: 10.9.2(@swc/core@1.3.101)(@types/node@22.3.0)(typescript@5.4.5)
postcss-load-config@6.0.1(jiti@2.3.3)(postcss@8.4.47)(tsx@4.16.5)(yaml@2.5.1):
dependencies:
@@ -26593,11 +27095,11 @@ snapshots:
optionalDependencies:
prettier: 3.3.3
- prettier-plugin-tailwindcss@0.6.6(@trivago/prettier-plugin-sort-imports@4.3.0(prettier@3.3.3))(prettier@3.3.3):
+ prettier-plugin-tailwindcss@0.6.6(@trivago/prettier-plugin-sort-imports@4.3.0(@vue/compiler-sfc@3.5.11)(prettier@3.3.3))(prettier@3.3.3):
dependencies:
prettier: 3.3.3
optionalDependencies:
- '@trivago/prettier-plugin-sort-imports': 4.3.0(prettier@3.3.3)
+ '@trivago/prettier-plugin-sort-imports': 4.3.0(@vue/compiler-sfc@3.5.11)(prettier@3.3.3)
prettier@2.8.8: {}
@@ -26843,7 +27345,7 @@ snapshots:
dependencies:
'@babel/core': 7.25.2
'@babel/traverse': 7.25.7
- '@babel/types': 7.25.7
+ '@babel/types': 7.25.8
'@types/babel__core': 7.20.5
'@types/babel__traverse': 7.20.6
'@types/doctrine': 0.0.9
@@ -26868,7 +27370,7 @@ snapshots:
react-dom: 18.3.1(react@18.3.1)
react-is: 18.1.0
- react-email@2.1.6(@opentelemetry/api@1.9.0)(@swc/helpers@0.5.13)(eslint@8.57.0)(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)):
+ react-email@2.1.6(@opentelemetry/api@1.9.0)(@swc/helpers@0.5.13)(eslint@8.57.0)(ts-node@10.9.2):
dependencies:
'@babel/core': 7.24.5
'@babel/parser': 7.24.5
@@ -26908,7 +27410,7 @@ snapshots:
source-map-js: 1.0.2
stacktrace-parser: 0.1.10
tailwind-merge: 2.2.0
- tailwindcss: 3.4.0(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5))
+ tailwindcss: 3.4.0(ts-node@10.9.2)
typescript: 5.1.6
transitivePeerDependencies:
- '@opentelemetry/api'
@@ -26985,33 +27487,33 @@ snapshots:
transitivePeerDependencies:
- supports-color
- react-native-webview@13.8.6(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1):
+ react-native-webview@13.8.6(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1):
dependencies:
escape-string-regexp: 2.0.0
invariant: 2.2.4
react: 18.3.1
- react-native: 0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1)
+ react-native: 0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1)
- react-native-webview@13.8.6(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1):
+ react-native-webview@13.8.6(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1):
dependencies:
escape-string-regexp: 2.0.0
invariant: 2.2.4
react: 18.3.1
- react-native: 0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1)
+ react-native: 0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1)
- react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1):
+ react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1):
dependencies:
'@jest/create-cache-key-function': 29.7.0
'@react-native-community/cli': 13.6.9(encoding@0.1.13)
'@react-native-community/cli-platform-android': 13.6.9(encoding@0.1.13)
'@react-native-community/cli-platform-ios': 13.6.9(encoding@0.1.13)
'@react-native/assets-registry': 0.74.86
- '@react-native/codegen': 0.74.86(@babel/preset-env@7.25.7(@babel/core@7.25.2))
- '@react-native/community-cli-plugin': 0.74.86(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13)
+ '@react-native/codegen': 0.74.86(@babel/preset-env@7.25.8(@babel/core@7.25.2))
+ '@react-native/community-cli-plugin': 0.74.86(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13)
'@react-native/gradle-plugin': 0.74.86
'@react-native/js-polyfills': 0.74.86
'@react-native/normalize-colors': 0.74.86
- '@react-native/virtualized-lists': 0.74.86(@types/react@18.3.11)(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)
+ '@react-native/virtualized-lists': 0.74.86(@types/react@18.3.11)(react-native@0.74.4(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)
abort-controller: 3.0.0
anser: 1.4.10
ansi-regex: 5.0.1
@@ -27049,19 +27551,19 @@ snapshots:
- supports-color
- utf-8-validate
- react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1):
+ react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1):
dependencies:
'@jest/create-cache-key-function': 29.7.0
'@react-native-community/cli': 13.6.9(encoding@0.1.13)
'@react-native-community/cli-platform-android': 13.6.9(encoding@0.1.13)
'@react-native-community/cli-platform-ios': 13.6.9(encoding@0.1.13)
'@react-native/assets-registry': 0.74.87
- '@react-native/codegen': 0.74.87(@babel/preset-env@7.25.7(@babel/core@7.25.2))
- '@react-native/community-cli-plugin': 0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(encoding@0.1.13)
+ '@react-native/codegen': 0.74.87(@babel/preset-env@7.25.8(@babel/core@7.25.2))
+ '@react-native/community-cli-plugin': 0.74.87(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(encoding@0.1.13)
'@react-native/gradle-plugin': 0.74.87
'@react-native/js-polyfills': 0.74.87
'@react-native/normalize-colors': 0.74.87
- '@react-native/virtualized-lists': 0.74.87(@types/react@18.3.11)(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.7(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)
+ '@react-native/virtualized-lists': 0.74.87(@types/react@18.3.11)(react-native@0.74.5(@babel/core@7.25.2)(@babel/preset-env@7.25.8(@babel/core@7.25.2))(@types/react@18.3.11)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)
abort-controller: 3.0.0
anser: 1.4.10
ansi-regex: 5.0.1
@@ -27640,6 +28142,8 @@ snapshots:
search-insights@2.17.2: {}
+ secure-json-parse@2.7.0: {}
+
selderee@0.11.0:
dependencies:
parseley: 0.12.1
@@ -28001,6 +28505,11 @@ snapshots:
dependencies:
minipass: 7.1.2
+ sswr@2.1.0(svelte@4.2.19):
+ dependencies:
+ svelte: 4.2.19
+ swrev: 4.0.0
+
stack-generator@2.0.10:
dependencies:
stackframe: 1.3.4
@@ -28201,6 +28710,13 @@ snapshots:
optionalDependencies:
'@babel/core': 7.24.5
+ styled-jsx@5.1.1(@babel/core@7.25.2)(react@18.3.1):
+ dependencies:
+ client-only: 0.0.1
+ react: 18.3.1
+ optionalDependencies:
+ '@babel/core': 7.25.2
+
stylis@4.3.4: {}
sucrase@3.34.0:
@@ -28252,6 +28768,35 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
+ svelte@4.2.19:
+ dependencies:
+ '@ampproject/remapping': 2.3.0
+ '@jridgewell/sourcemap-codec': 1.5.0
+ '@jridgewell/trace-mapping': 0.3.25
+ '@types/estree': 1.0.6
+ acorn: 8.12.1
+ aria-query: 5.3.2
+ axobject-query: 4.1.0
+ code-red: 1.0.4
+ css-tree: 2.3.1
+ estree-walker: 3.0.3
+ is-reference: 3.0.2
+ locate-character: 3.0.0
+ magic-string: 0.30.11
+ periscopic: 3.1.0
+
+ swr@2.2.5(react@18.3.1):
+ dependencies:
+ client-only: 0.0.1
+ react: 18.3.1
+ use-sync-external-store: 1.2.2(react@18.3.1)
+
+ swrev@4.0.0: {}
+
+ swrv@1.0.4(vue@3.5.11(typescript@5.4.5)):
+ dependencies:
+ vue: 3.5.11(typescript@5.4.5)
+
symbol-tree@3.2.4: {}
synckit@0.9.2:
@@ -28267,7 +28812,7 @@ snapshots:
tailwind-merge@2.5.2: {}
- tailwindcss@3.4.0(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)):
+ tailwindcss@3.4.0(ts-node@10.9.2):
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
@@ -28286,7 +28831,7 @@ snapshots:
postcss: 8.4.41
postcss-import: 15.1.0(postcss@8.4.41)
postcss-js: 4.0.1(postcss@8.4.41)
- postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5))
+ postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@22.3.0)(typescript@5.4.5))
postcss-nested: 6.2.0(postcss@8.4.41)
postcss-selector-parser: 6.1.2
resolve: 1.22.8
@@ -28294,7 +28839,7 @@ snapshots:
transitivePeerDependencies:
- ts-node
- tailwindcss@3.4.10(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)):
+ tailwindcss@3.4.10(ts-node@10.9.2(@types/node@22.3.0)(typescript@5.4.5)):
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
@@ -28313,7 +28858,7 @@ snapshots:
postcss: 8.4.41
postcss-import: 15.1.0(postcss@8.4.41)
postcss-js: 4.0.1(postcss@8.4.41)
- postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5))
+ postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@22.3.0)(typescript@5.4.5))
postcss-nested: 6.2.0(postcss@8.4.41)
postcss-selector-parser: 6.1.2
resolve: 1.22.8
@@ -28321,7 +28866,7 @@ snapshots:
transitivePeerDependencies:
- ts-node
- tailwindcss@3.4.13(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5)):
+ tailwindcss@3.4.13(ts-node@10.9.2(typescript@5.4.5)):
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
@@ -28340,7 +28885,7 @@ snapshots:
postcss: 8.4.41
postcss-import: 15.1.0(postcss@8.4.41)
postcss-js: 4.0.1(postcss@8.4.41)
- postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5))
+ postcss-load-config: 4.0.2(postcss@8.4.41)(ts-node@10.9.2(@types/node@22.3.0)(typescript@5.4.5))
postcss-nested: 6.2.0(postcss@8.4.41)
postcss-selector-parser: 6.1.2
resolve: 1.22.8
@@ -28428,17 +28973,6 @@ snapshots:
'@swc/core': 1.3.101(@swc/helpers@0.5.13)
esbuild: 0.19.11
- terser-webpack-plugin@5.3.10(@swc/core@1.3.101(@swc/helpers@0.5.13))(webpack@5.95.0(@swc/core@1.3.101(@swc/helpers@0.5.13))):
- dependencies:
- '@jridgewell/trace-mapping': 0.3.25
- jest-worker: 27.5.1
- schema-utils: 3.3.0
- serialize-javascript: 6.0.2
- terser: 5.31.6
- webpack: 5.95.0(@swc/core@1.3.101(@swc/helpers@0.5.13))
- optionalDependencies:
- '@swc/core': 1.3.101(@swc/helpers@0.5.13)
-
terser-webpack-plugin@5.3.10(webpack@5.95.0):
dependencies:
'@jridgewell/trace-mapping': 0.3.25
@@ -28576,7 +29110,7 @@ snapshots:
'@ts-morph/common': 0.12.3
code-block-writer: 11.0.3
- ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.13))(@types/node@22.3.0)(typescript@5.4.5):
+ ts-node@10.9.2(@swc/core@1.3.101)(@types/node@22.3.0)(typescript@5.4.5):
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
@@ -28619,7 +29153,7 @@ snapshots:
tslib@2.7.0: {}
- tsup@8.3.0(@microsoft/api-extractor@7.43.0(@types/node@22.3.0))(@swc/core@1.3.101(@swc/helpers@0.5.13))(jiti@2.3.3)(postcss@8.4.47)(tsx@4.16.5)(typescript@5.4.5)(yaml@2.5.1):
+ tsup@8.3.0(@microsoft/api-extractor@7.43.0(@types/node@22.3.0))(@swc/core@1.3.101)(jiti@2.3.3)(postcss@8.4.47)(tsx@4.16.5)(typescript@5.4.5)(yaml@2.5.1):
dependencies:
bundle-require: 5.0.0(esbuild@0.23.1)
cac: 6.7.14
@@ -29025,6 +29559,25 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.2
+ vite-node@2.0.5:
+ dependencies:
+ cac: 6.7.14
+ debug: 4.3.7
+ pathe: 1.1.2
+ tinyrainbow: 1.2.0
+ vite: 5.4.8(@types/node@22.3.0)(terser@5.31.3)
+ transitivePeerDependencies:
+ - '@types/node'
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ optional: true
+
vite-node@2.0.5(@types/node@22.3.0)(terser@5.31.6):
dependencies:
cac: 6.7.14
@@ -29122,6 +29675,38 @@ snapshots:
typescript: 5.4.5
vitest: 2.0.5(@types/node@22.3.0)(jsdom@24.1.3)(terser@5.31.6)
+ vitest@2.0.5:
+ dependencies:
+ '@ampproject/remapping': 2.3.0
+ '@vitest/expect': 2.0.5
+ '@vitest/pretty-format': 2.1.2
+ '@vitest/runner': 2.0.5
+ '@vitest/snapshot': 2.0.5
+ '@vitest/spy': 2.0.5
+ '@vitest/utils': 2.0.5
+ chai: 5.1.1
+ debug: 4.3.7
+ execa: 8.0.1
+ magic-string: 0.30.11
+ pathe: 1.1.2
+ std-env: 3.7.0
+ tinybench: 2.9.0
+ tinypool: 1.0.1
+ tinyrainbow: 1.2.0
+ vite: 5.4.8(@types/node@22.3.0)(terser@5.31.3)
+ vite-node: 2.0.5
+ why-is-node-running: 2.3.0
+ transitivePeerDependencies:
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ optional: true
+
vitest@2.0.5(@types/node@22.3.0)(jsdom@24.1.3)(terser@5.31.6):
dependencies:
'@ampproject/remapping': 2.3.0
@@ -29176,6 +29761,16 @@ snapshots:
semver: 7.6.3
typescript: 5.4.5
+ vue@3.5.11(typescript@5.4.5):
+ dependencies:
+ '@vue/compiler-dom': 3.5.11
+ '@vue/compiler-sfc': 3.5.11
+ '@vue/runtime-dom': 3.5.11
+ '@vue/server-renderer': 3.5.11(vue@3.5.11(typescript@5.4.5))
+ '@vue/shared': 3.5.11
+ optionalDependencies:
+ typescript: 5.4.5
+
w3c-xmlserializer@5.0.0:
dependencies:
xml-name-validator: 5.0.0
@@ -29245,36 +29840,6 @@ snapshots:
- esbuild
- uglify-js
- webpack@5.95.0(@swc/core@1.3.101(@swc/helpers@0.5.13)):
- dependencies:
- '@types/estree': 1.0.6
- '@webassemblyjs/ast': 1.12.1
- '@webassemblyjs/wasm-edit': 1.12.1
- '@webassemblyjs/wasm-parser': 1.12.1
- acorn: 8.12.1
- acorn-import-attributes: 1.9.5(acorn@8.12.1)
- browserslist: 4.24.0
- chrome-trace-event: 1.0.4
- enhanced-resolve: 5.17.1
- es-module-lexer: 1.5.4
- eslint-scope: 5.1.1
- events: 3.3.0
- glob-to-regexp: 0.4.1
- graceful-fs: 4.2.11
- json-parse-even-better-errors: 2.3.1
- loader-runner: 4.3.0
- mime-types: 2.1.35
- neo-async: 2.6.2
- schema-utils: 3.3.0
- tapable: 2.2.1
- terser-webpack-plugin: 5.3.10(@swc/core@1.3.101(@swc/helpers@0.5.13))(webpack@5.95.0(@swc/core@1.3.101(@swc/helpers@0.5.13)))
- watchpack: 2.4.2
- webpack-sources: 3.2.3
- transitivePeerDependencies:
- - '@swc/core'
- - esbuild
- - uglify-js
-
webpack@5.95.0(@swc/core@1.3.101(@swc/helpers@0.5.13))(esbuild@0.19.11):
dependencies:
'@types/estree': 1.0.6
@@ -29560,6 +30125,10 @@ snapshots:
optionalDependencies:
decimal.js: 10.4.3
+ zod-to-json-schema@3.23.2(zod@3.23.8):
+ dependencies:
+ zod: 3.23.8
+
zod-validation-error@2.1.0(zod@3.23.8):
dependencies:
zod: 3.23.8
diff --git a/turbo.json b/turbo.json
index 23a02a2d45..0d029ac96a 100644
--- a/turbo.json
+++ b/turbo.json
@@ -66,6 +66,15 @@
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"],
"env": [
+ "AI_AZURE_EMBEDDINGS_API_KEY",
+ "AI_AZURE_LLM_API_KEY",
+ "AI_AZURE_EMBEDDINGS_DEPLOYMENT_ID",
+ "AI_AZURE_EMBEDDINGS_RESSOURCE_NAME",
+ "AI_AZURE_LLM_DEPLOYMENT_ID",
+ "AI_AZURE_LLM_RESSOURCE_NAME",
+ "LANGFUSE_SECRET_KEY",
+ "LANGFUSE_PUBLIC_KEY",
+ "LANGFUSE_BASEURL",
"AIRTABLE_CLIENT_ID",
"ASSET_PREFIX_URL",
"AZUREAD_CLIENT_ID",