Compare commits

..

4 Commits

Author SHA1 Message Date
Johannes
5854bd635f add translations 2026-03-02 15:46:37 -03:00
Johannes
25ebb6411c feat: enhance AI settings management in organization
Updated organization AI settings to include separate toggles for smart tools and data analysis features. Refactored related components and tests to accommodate the new settings structure, ensuring a more granular control over AI functionalities. Adjusted localization strings for clarity on AI features.
2026-03-02 15:43:26 -03:00
Dhruwang
421a732875 feat: refactor organization update actions for name and AI settings
Updated the organization update actions to separate the handling of organization name and AI settings. Introduced `updateOrganizationNameAction` and `updateOrganizationAISettingsAction` for more specific updates. Adjusted related components to utilize the new actions accordingly.
2026-03-02 14:13:02 +05:30
Dhruwang
03ec8603bb feat: add Formbricks AI toggle to organization settings
Made-with: Cursor
2026-03-02 14:07:35 +05:30
38 changed files with 342 additions and 46 deletions

View File

@@ -27,7 +27,7 @@ export const updateOrganizationNameAction = authenticatedActionClient
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: Record<string, any>;
parsedInput: z.infer<typeof ZUpdateOrganizationNameAction>;
}) => {
await checkAuthorizationUpdated({
userId: ctx.user.id,
@@ -51,6 +51,49 @@ export const updateOrganizationNameAction = authenticatedActionClient
)
);
const ZUpdateOrganizationAISettingsAction = z.object({
organizationId: ZId,
data: ZOrganizationUpdateInput.pick({ isAISmartToolsEnabled: true, isAIDataAnalysisEnabled: true }),
});
export const updateOrganizationAISettingsAction = authenticatedActionClient
.schema(ZUpdateOrganizationAISettingsAction)
.action(
withAuditLogging(
"updated",
"organization",
async ({
ctx,
parsedInput,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer<typeof ZUpdateOrganizationAISettingsAction>;
}) => {
await checkAuthorizationUpdated({
userId: ctx.user.id,
organizationId: parsedInput.organizationId,
access: [
{
type: "organization",
schema: ZOrganizationUpdateInput.pick({
isAISmartToolsEnabled: true,
isAIDataAnalysisEnabled: true,
}),
data: parsedInput.data,
roles: ["owner", "manager"],
},
],
});
ctx.auditLoggingCtx.organizationId = parsedInput.organizationId;
const oldObject = await getOrganization(parsedInput.organizationId);
const result = await updateOrganization(parsedInput.organizationId, parsedInput.data);
ctx.auditLoggingCtx.oldObject = oldObject;
ctx.auditLoggingCtx.newObject = result;
return result;
}
)
);
const ZDeleteOrganizationAction = z.object({
organizationId: ZId,
});

View File

@@ -0,0 +1,101 @@
"use client";
import { useRouter } from "next/navigation";
import { useState } from "react";
import toast from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { TOrganizationRole } from "@formbricks/types/memberships";
import { TOrganization } from "@formbricks/types/organizations";
import { updateOrganizationAISettingsAction } from "@/app/(app)/environments/[environmentId]/settings/(organization)/general/actions";
import { getAccessFlags } from "@/lib/membership/utils";
import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { Alert, AlertDescription } from "@/modules/ui/components/alert";
import { Label } from "@/modules/ui/components/label";
import { Switch } from "@/modules/ui/components/switch";
interface AISettingsToggleProps {
organization: TOrganization;
membershipRole?: TOrganizationRole;
}
export const AISettingsToggle = ({ organization, membershipRole }: Readonly<AISettingsToggleProps>) => {
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const router = useRouter();
const { isOwner, isManager } = getAccessFlags(membershipRole);
const canEdit = isOwner || isManager;
const handleToggle = async (
field: "isAISmartToolsEnabled" | "isAIDataAnalysisEnabled",
checked: boolean
) => {
setIsLoading(true);
try {
const response = await updateOrganizationAISettingsAction({
organizationId: organization.id,
data: { [field]: checked },
});
if (response?.data) {
toast.success(t("environments.settings.general.ai_settings_updated_successfully"));
router.refresh();
} else {
const errorMessage = getFormattedErrorMessage(response);
toast.error(errorMessage);
}
} catch {
toast.error(t("common.something_went_wrong"));
} finally {
setIsLoading(false);
}
};
return (
<div className="space-y-4">
<div className="flex items-start space-x-2">
<Switch
id="ai-smart-tools-toggle"
className="mt-0.5"
checked={organization.isAISmartToolsEnabled}
disabled={isLoading || !canEdit}
onCheckedChange={(checked) => handleToggle("isAISmartToolsEnabled", checked)}
/>
<div>
<Label htmlFor="ai-smart-tools-toggle">
{t("environments.settings.general.ai_smart_tools_enabled")}
</Label>
<p className="text-xs text-slate-500">
{t("environments.settings.general.ai_smart_tools_enabled_description")}
</p>
</div>
</div>
<div className="flex items-start space-x-2">
<Switch
id="ai-data-analysis-toggle"
className="mt-0.5"
checked={organization.isAIDataAnalysisEnabled}
disabled={isLoading || !canEdit}
onCheckedChange={(checked) => handleToggle("isAIDataAnalysisEnabled", checked)}
/>
<div>
<Label htmlFor="ai-data-analysis-toggle">
{t("environments.settings.general.ai_data_analysis_enabled")}
</Label>
<p className="text-xs text-slate-500">
{t("environments.settings.general.ai_data_analysis_enabled_description")}
</p>
</div>
</div>
{!canEdit && (
<Alert variant="warning">
<AlertDescription>
{t("common.only_owners_managers_and_manage_access_members_can_perform_this_action")}
</AlertDescription>
</Alert>
)}
</div>
);
};

View File

@@ -11,6 +11,7 @@ import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper
import { PageHeader } from "@/modules/ui/components/page-header";
import packageJson from "@/package.json";
import { SettingsCard } from "../../components/SettingsCard";
import { AISettingsToggle } from "./components/AISettingsToggle";
import { DeleteOrganization } from "./components/DeleteOrganization";
import { EditOrganizationNameForm } from "./components/EditOrganizationNameForm";
import { SecurityListTip } from "./components/SecurityListTip";
@@ -60,6 +61,11 @@ const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
membershipRole={currentUserMembership?.role}
/>
</SettingsCard>
<SettingsCard
title={t("environments.settings.general.ai_enabled")}
description={t("environments.settings.general.ai_enabled_description")}>
<AISettingsToggle organization={organization} membershipRole={currentUserMembership?.role} />
</SettingsCard>
<EmailCustomizationSettings
organization={organization}
hasWhiteLabelPermission={hasWhiteLabelPermission}

View File

@@ -82,7 +82,8 @@ const mockOrganization: TOrganization = {
},
periodStart: new Date(),
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
};
const mockSurveys: TSurvey[] = [

View File

@@ -43,7 +43,8 @@ const mockOrganization: TOrganization = {
},
periodStart: new Date(),
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
};
const mockFollowUp: TSurveyCreateInputWithEnvironmentId["followUps"][number] = {

View File

@@ -1176,6 +1176,13 @@ checksums:
environments/settings/enterprise/sso: 95e98e279bb89233d63549b202bd9112
environments/settings/enterprise/teams: 21ab78abcba0f16c3029741563f789ea
environments/settings/enterprise/unlock_the_full_power_of_formbricks_free_for_30_days: 104d07b63a42911c9673ceb08a4dbd43
environments/settings/general/ai_data_analysis_enabled: 45fabb594da6851f73fef50ca40fe525
environments/settings/general/ai_data_analysis_enabled_description: 46d4f0bdf4ebf89e78f79cc961a2de83
environments/settings/general/ai_enabled: 3cb1fce89c525e754448d5bd143eb6b5
environments/settings/general/ai_enabled_description: e8c3e9f362588898a6cea85e18c013a1
environments/settings/general/ai_settings_updated_successfully: 2a6f534dc3a246ced46becd8a4a9543d
environments/settings/general/ai_smart_tools_enabled: 1dda984f5262c5f9120ee9a409236758
environments/settings/general/ai_smart_tools_enabled_description: 1ceca6707746d3ab4a530712a06d91da
environments/settings/general/bulk_invite_warning_description: e8737a2fbd5ff353db5580d17b4b5a37
environments/settings/general/cannot_delete_only_organization: 833cc6848b28f2694a4552b4de91a6ba
environments/settings/general/cannot_leave_only_organization: dd8463262e4299fef7ad73512225c55b

View File

@@ -40,7 +40,8 @@ describe("auth", () => {
},
periodStart: new Date(),
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
},
];
vi.mocked(getOrganizationsByUserId).mockResolvedValue(mockOrganizations);

View File

@@ -55,7 +55,8 @@ describe("Organization Service", () => {
periodStart: new Date(),
period: "monthly" as const,
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
whitelabel: false,
};
@@ -110,7 +111,8 @@ describe("Organization Service", () => {
periodStart: new Date(),
period: "monthly" as const,
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
whitelabel: false,
},
];
@@ -163,7 +165,8 @@ describe("Organization Service", () => {
periodStart: new Date(),
period: "monthly" as const,
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
whitelabel: false,
};
@@ -224,7 +227,8 @@ describe("Organization Service", () => {
periodStart: new Date(),
period: "monthly" as const,
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
whitelabel: false,
memberships: [{ userId: "user1" }, { userId: "user2" }],
projects: [
@@ -256,7 +260,8 @@ describe("Organization Service", () => {
periodStart: expect.any(Date),
period: "monthly",
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
whitelabel: false,
});
expect(prisma.organization.update).toHaveBeenCalledWith({

View File

@@ -25,7 +25,8 @@ export const select: Prisma.OrganizationSelect = {
updatedAt: true,
name: true,
billing: true,
isAIEnabled: true,
isAISmartToolsEnabled: true,
isAIDataAnalysisEnabled: true,
whitelabel: true,
};

View File

@@ -232,7 +232,8 @@ export const mockOrganizationOutput: TOrganization = {
name: "mock Organization",
createdAt: currentDate,
updatedAt: currentDate,
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
billing: {
stripeCustomerId: null,
plan: "free",

View File

@@ -73,7 +73,8 @@ describe("User Service", () => {
},
periodStart: new Date(),
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
},
{
id: "org2",
@@ -93,7 +94,8 @@ describe("User Service", () => {
},
periodStart: new Date(),
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
},
];

View File

@@ -1243,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Schalte die volle Power von Formbricks frei. 30 Tage kostenlos."
},
"general": {
"ai_data_analysis_enabled": "Datenanreicherung & Analyse (KI)",
"ai_data_analysis_enabled_description": "KI, um mehr aus deinen Daten herauszuholen, Dashboards, Diagramme, Berichte und mehr einzurichten. Greift auf deine Erfahrungsdaten zu.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "Verwalte KI-gestützte Funktionen für diese Organisation.",
"ai_settings_updated_successfully": "KI-Einstellungen erfolgreich aktualisiert",
"ai_smart_tools_enabled": "Intelligente Funktionen (KI)",
"ai_smart_tools_enabled_description": "KI, um dir zu helfen, in kürzerer Zeit mehr zu erreichen. Greift niemals auf mit Formbricks gesammelte Daten zu. Wird nur verwendet, um z. B. Umfragen in andere Sprachen zu übersetzen.",
"bulk_invite_warning_description": "Bitte beachte, dass im Free-Plan alle Organisationsmitglieder automatisch die Rolle \"Owner\" zugewiesen bekommen, unabhängig von der im CSV-File angegebenen Rolle.",
"cannot_delete_only_organization": "Das ist deine einzige Organisation, sie kann nicht gelöscht werden. Erstelle zuerst eine neue Organisation.",
"cannot_leave_only_organization": "Du kannst diese Organisation nicht verlassen, da es deine einzige Organisation ist. Erstelle zuerst eine neue Organisation.",

View File

@@ -1243,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Unlock the full power of Formbricks. Free for 30 days."
},
"general": {
"ai_data_analysis_enabled": "Data enrichment & analysis (AI)",
"ai_data_analysis_enabled_description": "AI to get more out of your data, setup dashboards, charts, reports and more. Touches your experience data.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "Manage AI-powered features for this organization.",
"ai_settings_updated_successfully": "AI settings updated successfully",
"ai_smart_tools_enabled": "Smart functionality (AI)",
"ai_smart_tools_enabled_description": "AI to help you achieve more in less time. Never touches data collected with Formbricks. Only used to e.g. translate surveys to other languages.",
"bulk_invite_warning_description": "On the free plan, all organization members are always assigned the “Owner” role.",
"cannot_delete_only_organization": "This is your only organization, it cannot be deleted. Create a new organization first.",
"cannot_leave_only_organization": "You cannot leave this organization as it is your only organization. Create a new organization first.",

View File

@@ -1243,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloquea todo el potencial de Formbricks. Gratis durante 30 días."
},
"general": {
"ai_data_analysis_enabled": "Enriquecimiento y análisis de datos (IA)",
"ai_data_analysis_enabled_description": "IA para sacar más partido a tus datos, configurar paneles, gráficos, informes y más. Accede a los datos de experiencia.",
"ai_enabled": "IA de Formbricks",
"ai_enabled_description": "Gestiona las funciones impulsadas por IA para esta organización.",
"ai_settings_updated_successfully": "Configuración de IA actualizada correctamente",
"ai_smart_tools_enabled": "Funcionalidad inteligente (IA)",
"ai_smart_tools_enabled_description": "IA para ayudarte a conseguir más en menos tiempo. Nunca accede a los datos recopilados con Formbricks. Solo se usa para, por ejemplo, traducir encuestas a otros idiomas.",
"bulk_invite_warning_description": "En el plan gratuito, a todos los miembros de la organización se les asigna siempre el rol de \"Propietario\".",
"cannot_delete_only_organization": "Esta es tu única organización, no se puede eliminar. Crea una nueva organización primero.",
"cannot_leave_only_organization": "No puedes abandonar esta organización ya que es tu única organización. Crea una nueva organización primero.",

View File

@@ -1243,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Débloquez tout le potentiel de Formbricks. Gratuit pendant 30 jours."
},
"general": {
"ai_data_analysis_enabled": "Enrichissement et analyse des données (IA)",
"ai_data_analysis_enabled_description": "L'IA pour tirer le meilleur parti de vos données, configurer des tableaux de bord, des graphiques, des rapports et plus encore. Accède à vos données d'expérience.",
"ai_enabled": "IA Formbricks",
"ai_enabled_description": "Gérer les fonctionnalités alimentées par l'IA pour cette organisation.",
"ai_settings_updated_successfully": "Paramètres IA mis à jour avec succès",
"ai_smart_tools_enabled": "Fonctionnalités intelligentes (IA)",
"ai_smart_tools_enabled_description": "L'IA pour vous aider à accomplir plus en moins de temps. N'accède jamais aux données collectées avec Formbricks. Utilisée uniquement pour, par exemple, traduire les sondages dans d'autres langues.",
"bulk_invite_warning_description": "Dans le plan gratuit, tous les membres de l'organisation se voient toujours attribuer le rôle \"Owner\".",
"cannot_delete_only_organization": "C'est votre seule organisation, elle ne peut pas être supprimée. Créez d'abord une nouvelle organisation.",
"cannot_leave_only_organization": "Vous ne pouvez pas quitter cette organisation car c'est votre seule organisation. Créez d'abord une nouvelle organisation.",

View File

@@ -1243,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "A Formbricks teljes erejének feloldása. 30 napig ingyen."
},
"general": {
"ai_data_analysis_enabled": "Adatgazdagítás és elemzés (AI)",
"ai_data_analysis_enabled_description": "AI segítségével többet hozhat ki az adataiból, irányítópultokat, diagramokat, jelentéseket és egyebeket állíthat be. Hozzáfér az élményekhez kapcsolódó adatokhoz.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "AI-alapú funkciók kezelése ehhez a szervezethez.",
"ai_settings_updated_successfully": "AI beállítások sikeresen frissítve",
"ai_smart_tools_enabled": "Intelligens funkciók (AI)",
"ai_smart_tools_enabled_description": "AI segítségével kevesebb idő alatt többet érhet el. Soha nem fér hozzá a Formbricks által gyűjtött adatokhoz. Csak például felmérések más nyelvekre történő fordításához használatos.",
"bulk_invite_warning_description": "Az ingyenes csomagban az összes szervezeti tag mindig a „Tulajdonos” szerephez van hozzárendelve.",
"cannot_delete_only_organization": "Ez az egyetlen szervezete, nem lehet törölni. Először hozzon létre egy új szervezetet.",
"cannot_leave_only_organization": "Nem hagyhatja el ezt a szervezetet, mivel ez az egyetlen szervezete. Először hozzon létre egy új szervezetet.",

View File

@@ -1243,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Formbricksの全機能をアンロック。30日間無料。"
},
"general": {
"ai_data_analysis_enabled": "データエンリッチメントと分析AI",
"ai_data_analysis_enabled_description": "AIを活用してデータから最大限の価値を引き出し、ダッシュボード、チャート、レポートなどを設定できます。エクスペリエンスデータに触れます。",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "この組織のAI機能を管理します。",
"ai_settings_updated_successfully": "AI設定が正常に更新されました",
"ai_smart_tools_enabled": "スマート機能AI",
"ai_smart_tools_enabled_description": "AIを活用して、より短時間でより多くのことを達成できます。Formbricksで収集されたデータには一切触れません。アンケートを他の言語に翻訳するなどの用途にのみ使用されます。",
"bulk_invite_warning_description": "無料プランでは、すべての組織メンバーに常に「オーナー」ロールが割り当てられます。",
"cannot_delete_only_organization": "これはあなたの唯一の組織です。削除できません。まず新しい組織を作成してください。",
"cannot_leave_only_organization": "これはあなたの唯一の組織であるため、離れることはできません。まず新しい組織を作成してください。",

View File

@@ -1243,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Ontgrendel de volledige kracht van Formbricks. 30 dagen gratis."
},
"general": {
"ai_data_analysis_enabled": "Dataverrijking & analyse (AI)",
"ai_data_analysis_enabled_description": "AI om meer uit je data te halen, dashboards op te zetten, grafieken, rapporten en meer. Raakt je ervaringsdata aan.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "Beheer AI-functies voor deze organisatie.",
"ai_settings_updated_successfully": "AI-instellingen succesvol bijgewerkt",
"ai_smart_tools_enabled": "Slimme functionaliteit (AI)",
"ai_smart_tools_enabled_description": "AI om je te helpen meer te bereiken in minder tijd. Raakt nooit data aan die met Formbricks is verzameld. Wordt alleen gebruikt om bijvoorbeeld enquêtes naar andere talen te vertalen.",
"bulk_invite_warning_description": "Bij het gratis abonnement krijgen alle organisatieleden altijd de rol 'Eigenaar' toegewezen.",
"cannot_delete_only_organization": "Dit is uw enige organisatie. Deze kan niet worden verwijderd. Maak eerst een nieuwe organisatie aan.",
"cannot_leave_only_organization": "U kunt deze organisatie niet verlaten, aangezien dit uw enige organisatie is. Maak eerst een nieuwe organisatie aan.",

View File

@@ -1243,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloqueie todo o poder do Formbricks. Grátis por 30 dias."
},
"general": {
"ai_data_analysis_enabled": "Enriquecimento e análise de dados (IA)",
"ai_data_analysis_enabled_description": "IA para extrair mais dos seus dados, configurar dashboards, gráficos, relatórios e muito mais. Acessa os dados da sua experiência.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "Gerencie recursos com IA para esta organização.",
"ai_settings_updated_successfully": "Configurações de IA atualizadas com sucesso",
"ai_smart_tools_enabled": "Funcionalidades inteligentes (IA)",
"ai_smart_tools_enabled_description": "IA para ajudar você a conquistar mais em menos tempo. Nunca acessa dados coletados com o Formbricks. Usado apenas para, por exemplo, traduzir pesquisas para outros idiomas.",
"bulk_invite_warning_description": "Por favor, note que no Plano Gratuito, todos os membros da organização são automaticamente atribuídos ao papel de 'Owner', independentemente do papel especificado no arquivo CSV.",
"cannot_delete_only_organization": "Essa é sua única organização, não pode ser deletada. Crie uma nova organização primeiro.",
"cannot_leave_only_organization": "Você não pode sair dessa organização porque é a sua única. Crie uma nova organização primeiro.",

View File

@@ -1243,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Desbloqueie todo o poder do Formbricks. Grátis por 30 dias."
},
"general": {
"ai_data_analysis_enabled": "Enriquecimento e análise de dados (IA)",
"ai_data_analysis_enabled_description": "IA para tirar mais partido dos teus dados, configurar dashboards, gráficos, relatórios e muito mais. Acede aos dados da tua experiência.",
"ai_enabled": "IA da Formbricks",
"ai_enabled_description": "Gerir funcionalidades com IA para esta organização.",
"ai_settings_updated_successfully": "Definições de IA atualizadas com sucesso",
"ai_smart_tools_enabled": "Funcionalidade inteligente (IA)",
"ai_smart_tools_enabled_description": "IA para te ajudar a alcançar mais em menos tempo. Nunca acede aos dados recolhidos com o Formbricks. Apenas usado para, por exemplo, traduzir inquéritos para outros idiomas.",
"bulk_invite_warning_description": "No plano gratuito, todos os membros da organização são sempre atribuídos ao papel de \"Proprietário\".",
"cannot_delete_only_organization": "Esta é a sua única organização, não pode ser eliminada. Crie uma nova organização primeiro.",
"cannot_leave_only_organization": "Não pode sair desta organização, pois é a sua única organização. Crie uma nova organização primeiro.",

View File

@@ -1243,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Deblocați puterea completă a Formbricks. Gratuit timp de 30 de zile."
},
"general": {
"ai_data_analysis_enabled": "Îmbogățire și analiză de date (AI)",
"ai_data_analysis_enabled_description": "AI pentru a obține mai mult din datele tale, configurare dashboard-uri, grafice, rapoarte și multe altele. Accesează datele tale de experiență.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "Gestionează funcționalitățile bazate pe AI pentru această organizație.",
"ai_settings_updated_successfully": "Setările AI au fost actualizate cu succes",
"ai_smart_tools_enabled": "Funcționalitate inteligentă (AI)",
"ai_smart_tools_enabled_description": "AI care te ajută să faci mai mult în mai puțin timp. Nu accesează niciodată datele colectate cu Formbricks. Folosit doar, de exemplu, pentru a traduce chestionare în alte limbi.",
"bulk_invite_warning_description": "În planul gratuit, toți membrii organizației sunt întotdeauna alocați rolului „Proprietar”.",
"cannot_delete_only_organization": "Aceasta este singura ta organizație, nu poate fi ștearsă. Creează mai întâi o nouă organizație.",
"cannot_leave_only_organization": "Nu poți părăsi această organizație deoarece este singura ta organizație. Creează mai întâi o nouă organizație.",

View File

@@ -1243,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Откройте все возможности Formbricks. Бесплатно на 30 дней."
},
"general": {
"ai_data_analysis_enabled": "Обогащение и анализ данных (ИИ)",
"ai_data_analysis_enabled_description": "ИИ для получения большего от твоих данных: настройка дашбордов, графиков, отчетов и не только. Работает с твоими данными об опыте.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "Управляй функциями на базе ИИ для этой организации.",
"ai_settings_updated_successfully": "Настройки AI успешно обновлены",
"ai_smart_tools_enabled": "Умные функции (ИИ)",
"ai_smart_tools_enabled_description": "ИИ помогает тебе делать больше за меньшее время. Никогда не использует данные, собранные с помощью Formbricks. Применяется, например, для перевода опросов на другие языки.",
"bulk_invite_warning_description": "В бесплатном тарифе всем участникам организации всегда назначается роль \"Владелец\".",
"cannot_delete_only_organization": "Это ваша единственная организация, её нельзя удалить. Сначала создайте новую организацию.",
"cannot_leave_only_organization": "Вы не можете покинуть эту организацию, так как она у вас единственная. Сначала создайте новую организацию.",

View File

@@ -1243,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "Lås upp Formbricks fulla kraft. Gratis i 30 dagar."
},
"general": {
"ai_data_analysis_enabled": "Dataförbättring & analys (AI)",
"ai_data_analysis_enabled_description": "AI för att få ut mer av din data, skapa dashboards, diagram, rapporter och mer. Använder din upplevelsedata.",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "Hantera AI-drivna funktioner för den här organisationen.",
"ai_settings_updated_successfully": "AI-inställningarna har uppdaterats",
"ai_smart_tools_enabled": "Smarta funktioner (AI)",
"ai_smart_tools_enabled_description": "AI som hjälper dig att göra mer på kortare tid. Rör aldrig data som samlats in med Formbricks. Används bara till t.ex. att översätta enkäter till andra språk.",
"bulk_invite_warning_description": "På gratisplanen tilldelas alla organisationsmedlemmar alltid rollen \"Ägare\".",
"cannot_delete_only_organization": "Detta är din enda organisation, den kan inte tas bort. Skapa en ny organisation först.",
"cannot_leave_only_organization": "Du kan inte lämna denna organisation eftersom det är din enda organisation. Skapa en ny organisation först.",

View File

@@ -1243,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "解锁 Formbricks 的全部功能。免费使用 30 天。"
},
"general": {
"ai_data_analysis_enabled": "数据增强与分析AI",
"ai_data_analysis_enabled_description": "使用 AI 深度挖掘你的数据,设置仪表盘、图表、报告等。会处理你的体验数据。",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "管理该组织的 AI 驱动功能。",
"ai_settings_updated_successfully": "AI 设置已成功更新",
"ai_smart_tools_enabled": "智能功能AI",
"ai_smart_tools_enabled_description": "AI 帮你更高效地完成更多任务。绝不会接触 Formbricks 收集的数据,仅用于如问卷翻译等功能。",
"bulk_invite_warning_description": "在免费计划中,所有组织成员都会被分配为 \"Owner \"角色。",
"cannot_delete_only_organization": "这是 您 唯一的 组织,不可 删除。请 先 创建一个新的 组织。",
"cannot_leave_only_organization": "您 不能 离开 此 组织,因为 这是 您 唯一的 组织。请 先 创建一个新的 组织。",

View File

@@ -1243,6 +1243,13 @@
"unlock_the_full_power_of_formbricks_free_for_30_days": "免費解鎖 Formbricks 的全部功能,為期 30 天。"
},
"general": {
"ai_data_analysis_enabled": "資料增強與分析AI",
"ai_data_analysis_enabled_description": "利用 AI 深入分析你的資料,建立儀表板、圖表、報告等。會處理你的體驗資料。",
"ai_enabled": "Formbricks AI",
"ai_enabled_description": "管理此組織的 AI 功能。",
"ai_settings_updated_successfully": "AI 設定已成功更新",
"ai_smart_tools_enabled": "智慧功能AI",
"ai_smart_tools_enabled_description": "AI 幫你更快完成更多事。絕不會接觸 Formbricks 收集的資料,只用於像是將問卷翻譯成其他語言等用途。",
"bulk_invite_warning_description": "在免費方案中,所有組織成員始終會被指派「擁有者」角色。",
"cannot_delete_only_organization": "這是您唯一的組織,無法刪除。請先建立新組織。",
"cannot_leave_only_organization": "您無法離開此組織,因為它是您唯一的組織。請先建立新組織。",

View File

@@ -40,7 +40,8 @@ describe("getFirstOrganization", () => {
projects: 3,
},
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
};
vi.mocked(prisma.organization.findFirst).mockResolvedValue(org);
const result = await getFirstOrganization();

View File

@@ -45,7 +45,8 @@ export const mockSamlAccount: Account = {
export const mockOrganization: Organization = {
id: "org-123",
name: "Test Organization",
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
whitelabel: {
enabled: false,
},

View File

@@ -268,7 +268,8 @@ describe("utils.ts", () => {
updatedAt: new Date("2024-01-02"),
name: "Test Organization",
billing: { plan: "free" },
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
whitelabel: false,
memberships: [
{
@@ -408,7 +409,8 @@ describe("utils.ts", () => {
updatedAt: new Date("2024-01-02"),
name: "Test Organization",
billing: { plan: "free", limits: {} },
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
whitelabel: false,
memberships: [
{
@@ -512,7 +514,8 @@ describe("utils.ts", () => {
createdAt: new Date(),
updatedAt: new Date(),
billing: { plan: "free", limits: {} },
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
whitelabel: false,
memberships: [], // No membership
},
@@ -596,7 +599,8 @@ describe("utils.ts", () => {
createdAt: new Date(),
updatedAt: new Date(),
billing: { plan: "free", limits: {} },
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
whitelabel: false,
memberships: [{ userId: "user123", organizationId: "org123", role: "owner", accepted: true }],
},
@@ -635,7 +639,8 @@ describe("utils.ts", () => {
createdAt: new Date(),
updatedAt: new Date(),
billing: { plan: "pro", limits: {} },
isAIEnabled: true,
isAISmartToolsEnabled: true,
isAIDataAnalysisEnabled: true,
whitelabel: true,
memberships: [{ userId: "user123", organizationId: "org456", role: "member", accepted: true }],
},

View File

@@ -173,7 +173,8 @@ export const getEnvironmentWithRelations = reactCache(async (environmentId: stri
updatedAt: true,
name: true,
billing: true,
isAIEnabled: true,
isAISmartToolsEnabled: true,
isAIDataAnalysisEnabled: true,
whitelabel: true,
// Current user's membership only (filtered at DB level)
memberships: {
@@ -232,7 +233,8 @@ export const getEnvironmentWithRelations = reactCache(async (environmentId: stri
updatedAt: data.project.organization.updatedAt,
name: data.project.organization.name,
billing: data.project.organization.billing,
isAIEnabled: data.project.organization.isAIEnabled,
isAISmartToolsEnabled: data.project.organization.isAISmartToolsEnabled,
isAIDataAnalysisEnabled: data.project.organization.isAIDataAnalysisEnabled,
whitelabel: data.project.organization.whitelabel,
},
environments: data.project.environments,

View File

@@ -151,7 +151,8 @@ describe("Survey Editor Library Tests", () => {
period: "monthly" as const,
periodStart: new Date(),
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
};
beforeEach(() => {

View File

@@ -82,8 +82,12 @@ describe("getOrganizationAIKeys", () => {
});
const mockOrgId = "org_test789";
const mockOrganizationData: Pick<Organization, "isAIEnabled" | "billing"> = {
isAIEnabled: true,
const mockOrganizationData: Pick<
Organization,
"isAISmartToolsEnabled" | "isAIDataAnalysisEnabled" | "billing"
> = {
isAISmartToolsEnabled: true,
isAIDataAnalysisEnabled: true,
billing: {
plan: "free",
stripeCustomerId: null,
@@ -109,7 +113,8 @@ describe("getOrganizationAIKeys", () => {
id: mockOrgId,
},
select: {
isAIEnabled: true,
isAISmartToolsEnabled: true,
isAIDataAnalysisEnabled: true,
billing: true,
},
});

View File

@@ -29,14 +29,17 @@ export const getOrganizationIdFromEnvironmentId = reactCache(
);
export const getOrganizationAIKeys = reactCache(
async (organizationId: string): Promise<Pick<Organization, "isAIEnabled" | "billing"> | null> => {
async (
organizationId: string
): Promise<Pick<Organization, "isAISmartToolsEnabled" | "isAIDataAnalysisEnabled" | "billing"> | null> => {
try {
const organization = await prisma.organization.findUnique({
where: {
id: organizationId,
},
select: {
isAIEnabled: true,
isAISmartToolsEnabled: true,
isAIDataAnalysisEnabled: true,
billing: true,
},
});

View File

@@ -454,7 +454,8 @@ describe("data", () => {
name: "Test Organization",
billing: mockBilling,
whitelabel: null,
isAIEnabled: true,
isAISmartToolsEnabled: true,
isAIDataAnalysisEnabled: true,
};
test("should fetch organization billing successfully", async () => {

View File

@@ -0,0 +1,4 @@
-- AlterTable
ALTER TABLE "Organization" ADD COLUMN "isAISmartToolsEnabled" BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE "Organization" ADD COLUMN "isAIDataAnalysisEnabled" BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE "Organization" DROP COLUMN "isAIEnabled";

View File

@@ -663,22 +663,24 @@ model Project {
/// @property projects - Collection of projects owned by the organization
/// @property billing - JSON field containing billing information
/// @property whitelabel - Whitelabel configuration for the organization
/// @property isAIEnabled - Controls access to AI-powered features
/// @property isAISmartToolsEnabled - Controls access to AI smart tools (e.g. translations) that never touch collected data
/// @property isAIDataAnalysisEnabled - Controls access to AI data analysis features that touch experience data
model Organization {
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
memberships Membership[]
projects Project[]
id String @id @default(cuid())
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @updatedAt @map(name: "updated_at")
name String
memberships Membership[]
projects Project[]
/// [OrganizationBilling]
billing Json
billing Json
/// [OrganizationWhitelabel]
whitelabel Json @default("{}")
invites Invite[]
isAIEnabled Boolean @default(false)
teams Team[]
apiKeys ApiKey[]
whitelabel Json @default("{}")
invites Invite[]
isAISmartToolsEnabled Boolean @default(false)
isAIDataAnalysisEnabled Boolean @default(false)
teams Team[]
apiKeys ApiKey[]
}
enum OrganizationRole {

View File

@@ -37,5 +37,6 @@ export const ZOrganization = z.object({
name: z.string(),
whitelabel: ZOrganizationWhiteLabel,
billing: ZOrganizationBilling as z.ZodType<Organization["billing"]>,
isAIEnabled: z.boolean().default(false) as z.ZodType<Organization["isAIEnabled"]>,
isAISmartToolsEnabled: z.boolean().default(false) as z.ZodType<Organization["isAISmartToolsEnabled"]>,
isAIDataAnalysisEnabled: z.boolean().default(false) as z.ZodType<Organization["isAIDataAnalysisEnabled"]>,
}) satisfies z.ZodType<Organization>;

View File

@@ -134,7 +134,8 @@ export const exampleData = {
},
},
},
isAIEnabled: false,
isAISmartToolsEnabled: false,
isAIDataAnalysisEnabled: false,
} as unknown as TOrganization,
},

View File

@@ -50,7 +50,8 @@ export const ZOrganization = z.object({
}),
whitelabel: ZOrganizationWhitelabel.optional(),
billing: ZOrganizationBilling,
isAIEnabled: z.boolean().default(false),
isAISmartToolsEnabled: z.boolean().default(false),
isAIDataAnalysisEnabled: z.boolean().default(false),
});
export const ZOrganizationCreateInput = z.object({
@@ -64,7 +65,8 @@ export const ZOrganizationUpdateInput = z.object({
name: z.string(),
whitelabel: ZOrganizationWhitelabel.optional(),
billing: ZOrganizationBilling.optional(),
isAIEnabled: z.boolean().optional(),
isAISmartToolsEnabled: z.boolean().optional(),
isAIDataAnalysisEnabled: z.boolean().optional(),
});
export type TOrganizationUpdateInput = z.infer<typeof ZOrganizationUpdateInput>;