From 9d24f98ee4d13d26205e248d5f471a43f7b24dac Mon Sep 17 00:00:00 2001 From: aXenDeveloper Date: Sun, 22 Sep 2024 19:24:15 +0200 Subject: [PATCH 1/6] feat(frontend): Add AI page in AdminCP --- apps/backend/schema.gql | 1 + .../admin/(auth)/(vitnode)/core/ai/layout.tsx | 21 + .../admin/(auth)/(vitnode)/core/ai/page.tsx | 5 + apps/frontend/src/graphql/types.ts | 1 + apps/frontend/src/plugins/admin/langs/en.json | 7 + apps/frontend/src/plugins/core/langs/en.json | 1 + packages/backend/package.json | 3 + .../src/core/admin/nav/show/show.service.ts | 12 + packages/backend/src/core/ai/ai.module.ts | 17 + .../src/core/ai/provider/ai.service.ts | 8 + .../backend/src/core/ai/test/test.resolver.ts | 13 + .../backend/src/core/ai/test/test.service.ts | 32 + packages/backend/src/core/core.module.ts | 3 + packages/backend/src/index.ts | 1 + .../eslint.shared.mjs | 1 + .../admin/views/core/ai/ai-admin-view.tsx | 13 + pnpm-lock.yaml | 564 ++++++++++++++++-- 17 files changed, 667 insertions(+), 36 deletions(-) create mode 100644 apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/layout.tsx create mode 100644 apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/page.tsx create mode 100644 packages/backend/src/core/ai/ai.module.ts create mode 100644 packages/backend/src/core/ai/provider/ai.service.ts create mode 100644 packages/backend/src/core/ai/test/test.resolver.ts create mode 100644 packages/backend/src/core/ai/test/test.service.ts create mode 100644 packages/frontend/src/views/admin/views/core/ai/ai-admin-view.tsx diff --git a/apps/backend/schema.gql b/apps/backend/schema.gql index 3056719a4..15e0853cb 100644 --- a/apps/backend/schema.gql +++ b/apps/backend/schema.gql @@ -250,6 +250,7 @@ type Mutation { admin__core_theme_editor__edit(colors: ColorsEditAdminThemeEditor, logos: LogosEditAdminThemeEditor!): String! admin__install__create_database: String! admin_sessions__sign_out: String! + core_ai__test: String! core_editor_files__delete(id: Int!, security_key: String): String! core_editor_files__upload(file: Upload!, folder: String!, plugin: String!): ShowCoreFiles! core_members__avatar__delete: String! diff --git a/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/layout.tsx b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/layout.tsx new file mode 100644 index 000000000..82c6ec3a6 --- /dev/null +++ b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/layout.tsx @@ -0,0 +1,21 @@ +import { useTranslations } from 'next-intl'; +import { Card } from 'vitnode-frontend/components/ui/card'; +import { HeaderContent } from 'vitnode-frontend/components/ui/header-content'; +import { Tabs, TabsTrigger } from 'vitnode-frontend/components/ui/tabs'; + +export default function Layout({ children }: { children: React.ReactNode }) { + const t = useTranslations('admin.core.ai'); + + return ( + <> + + + + {t('tabs.provider')} + + + + {children} + + ); +} diff --git a/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/page.tsx b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/page.tsx new file mode 100644 index 000000000..d41fdc062 --- /dev/null +++ b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/page.tsx @@ -0,0 +1,5 @@ +import { AiAdminView } from 'vitnode-frontend/views/admin/views/core/ai/ai-admin-view'; + +export default function Page() { + return ; +} diff --git a/apps/frontend/src/graphql/types.ts b/apps/frontend/src/graphql/types.ts index 5fc19b4be..e3d508638 100644 --- a/apps/frontend/src/graphql/types.ts +++ b/apps/frontend/src/graphql/types.ts @@ -282,6 +282,7 @@ export type Mutation = { admin__core_theme_editor__edit: Scalars['String']['output']; admin__install__create_database: Scalars['String']['output']; admin_sessions__sign_out: Scalars['String']['output']; + core_ai__test: Scalars['String']['output']; core_editor_files__delete: Scalars['String']['output']; core_editor_files__upload: ShowCoreFiles; core_members__avatar__delete: Scalars['String']['output']; diff --git a/apps/frontend/src/plugins/admin/langs/en.json b/apps/frontend/src/plugins/admin/langs/en.json index de1e6aa46..21296548d 100644 --- a/apps/frontend/src/plugins/admin/langs/en.json +++ b/apps/frontend/src/plugins/admin/langs/en.json @@ -450,6 +450,13 @@ "submit": "Yes, delete file" } } + }, + "ai": { + "title": "Artificial Intelligence (AI)", + "desc": "Integrate AI to boost user experience with personalized content, smarter search, and SEO optimization. Gain insights and enhance engagement with powerful AI tools.", + "tabs": { + "provider": "Provider" + } } }, "members": { diff --git a/apps/frontend/src/plugins/core/langs/en.json b/apps/frontend/src/plugins/core/langs/en.json index bc8f12cb3..72678ce69 100644 --- a/apps/frontend/src/plugins/core/langs/en.json +++ b/apps/frontend/src/plugins/core/langs/en.json @@ -353,6 +353,7 @@ "settings_email": "Email", "settings_authorization": "Authorization", "settings_legal": "Legal & Policies", + "ai": "Artificial Intelligence", "plugins": "Plugins", "styles": "Styles", "styles_theme-editor": "Theme Editor", diff --git a/packages/backend/package.json b/packages/backend/package.json index 2b9b52d15..7bdc5f763 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -56,6 +56,8 @@ "typescript": "^5.5.4" }, "dependencies": { + "@ai-sdk/google": "^0.0.48", + "@ai-sdk/openai": "^0.0.61", "@apollo/server": "^4.11.0", "@graphql-codegen/near-operation-file-preset": "^3.0.0", "@graphql-codegen/typescript": "^4.0.9", @@ -68,6 +70,7 @@ "@react-email/render": "^1.0.1", "@swc/cli": "^0.4.0", "@swc/core": "^1.7.26", + "ai": "^3.4.0", "busboy": "^1.6.0", "cookie-parser": "^1.4.6", "helmet": "^7.1.0", diff --git a/packages/backend/src/core/admin/nav/show/show.service.ts b/packages/backend/src/core/admin/nav/show/show.service.ts index 7863410dd..7f5685747 100644 --- a/packages/backend/src/core/admin/nav/show/show.service.ts +++ b/packages/backend/src/core/admin/nav/show/show.service.ts @@ -86,6 +86,18 @@ export class ShowAdminNavService { icon: 'languages', keywords: ['language'], }, + { + code: 'ai', + icon: 'brain', + keywords: [ + 'artificial', + 'intelligence', + 'gpt', + 'openai', + 'google', + 'gemini', + ], + }, { code: 'advanced', icon: 'cog', diff --git a/packages/backend/src/core/ai/ai.module.ts b/packages/backend/src/core/ai/ai.module.ts new file mode 100644 index 000000000..6d57b7eee --- /dev/null +++ b/packages/backend/src/core/ai/ai.module.ts @@ -0,0 +1,17 @@ +import { Global, Module } from '@nestjs/common'; + +import { AiService } from './provider/ai.service'; +import { TestCoreAiResolver } from './test/test.resolver'; +import { TestCoreAiService } from './test/test.service'; + +@Global() +@Module({ + providers: [AiService], + exports: [AiService], +}) +export class GlobalCoreAiModule {} + +@Module({ + providers: [TestCoreAiService, TestCoreAiResolver], +}) +export class CoreAiModule {} diff --git a/packages/backend/src/core/ai/provider/ai.service.ts b/packages/backend/src/core/ai/provider/ai.service.ts new file mode 100644 index 000000000..64fafbc9c --- /dev/null +++ b/packages/backend/src/core/ai/provider/ai.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AiService { + test() { + return 'test'; + } +} diff --git a/packages/backend/src/core/ai/test/test.resolver.ts b/packages/backend/src/core/ai/test/test.resolver.ts new file mode 100644 index 000000000..61d22a149 --- /dev/null +++ b/packages/backend/src/core/ai/test/test.resolver.ts @@ -0,0 +1,13 @@ +import { Mutation, Resolver } from '@nestjs/graphql'; + +import { TestCoreAiService } from './test.service'; + +@Resolver() +export class TestCoreAiResolver { + constructor(private readonly service: TestCoreAiService) {} + + @Mutation(() => String) + async core_ai__test(): Promise { + return this.service.test(); + } +} diff --git a/packages/backend/src/core/ai/test/test.service.ts b/packages/backend/src/core/ai/test/test.service.ts new file mode 100644 index 000000000..ad05f131b --- /dev/null +++ b/packages/backend/src/core/ai/test/test.service.ts @@ -0,0 +1,32 @@ +import { CustomError } from '@/errors'; +import { createGoogleGenerativeAI } from '@ai-sdk/google'; +import { Injectable } from '@nestjs/common'; +import { generateText } from 'ai'; + +import { AiService } from '../provider/ai.service'; + +@Injectable() +export class TestCoreAiService { + constructor(private readonly aiService: AiService) {} + + async test(): Promise { + const google = createGoogleGenerativeAI({ + apiKey: '', + }); + + try { + const result = await generateText({ + model: google('gemini-1.0-pro'), + prompt: 'Tell me a joke', + }); + } catch (error) { + const err = error as Error; + throw new CustomError({ + code: 'AI_ERROR', + message: err.message, + }); + } + + return 'test'; + } +} diff --git a/packages/backend/src/core/core.module.ts b/packages/backend/src/core/core.module.ts index 994f7b48f..2f1f84da1 100644 --- a/packages/backend/src/core/core.module.ts +++ b/packages/backend/src/core/core.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { AdminModule } from './admin/admin.module'; +import { CoreAiModule, GlobalCoreAiModule } from './ai/ai.module'; import { CoreEditorModule } from './editor/editor.module'; import { CoreFilesModule, GlobalCoreFilesModule } from './files/files.module'; import { GlobalCoreHelpersModule } from './helpers/helpers.module'; @@ -34,6 +35,8 @@ import { CoreThemeEditorModule } from './theme_editor/theme_editor.module'; CoreSettingsModule, CoreThemeEditorModule, TermsCoreModule, + CoreAiModule, + GlobalCoreAiModule, ], }) export class CoreModule {} diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index f65d437df..2eef0526e 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -2,6 +2,7 @@ export * from './app.module'; export * from './core/admin/security/captcha/captcha.service'; // Services export * from './core/admin/sessions/authorization/authorization.service'; +export * from './core/ai/provider/ai.service'; export * from './core/helpers/string_language/helpers.service'; export * from './core/sessions/authorization/internal/internal_authorization.service'; export * from './decorators'; diff --git a/packages/eslint-config-typescript-vitnode/eslint.shared.mjs b/packages/eslint-config-typescript-vitnode/eslint.shared.mjs index cad940c7c..64b77859a 100644 --- a/packages/eslint-config-typescript-vitnode/eslint.shared.mjs +++ b/packages/eslint-config-typescript-vitnode/eslint.shared.mjs @@ -23,6 +23,7 @@ export default [ }, { rules: { + 'perfectionist/sort-exports': 'warn', '@typescript-eslint/no-dynamic-delete': 'off', 'perfectionist/sort-named-imports': 'warn', 'perfectionist/sort-intersection-types': 'warn', diff --git a/packages/frontend/src/views/admin/views/core/ai/ai-admin-view.tsx b/packages/frontend/src/views/admin/views/core/ai/ai-admin-view.tsx new file mode 100644 index 000000000..afb593e9e --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/ai/ai-admin-view.tsx @@ -0,0 +1,13 @@ +'use client'; +import { AutoForm } from '@/components/form/auto-form'; +import * as z from 'zod'; + +export const AiAdminView = () => { + const formSchema = z.object({}); + + return ( + <> + + + ); +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a274b7afa..d6712bcc1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,7 +83,7 @@ importers: version: 4.2.3(graphql@16.9.0) '@nestjs/cli': specifier: ^10.4.5 - version: 10.4.5(@swc/cli@0.4.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(chokidar@3.6.0))(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.19.12) + version: 10.4.5(@swc/cli@0.4.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(chokidar@3.6.0))(@swc/core@1.7.26(@swc/helpers@0.5.13)) '@nestjs/schematics': specifier: ^10.1.4 version: 10.1.4(chokidar@3.6.0)(typescript@5.6.2) @@ -122,7 +122,7 @@ importers: version: 0.24.2 drizzle-orm: specifier: ^0.33.0 - version: 0.33.0(@types/pg@8.11.10)(@types/react@18.3.7)(pg@8.13.0)(react@19.0.0-rc-fb9a90fa48-20240614) + version: 0.33.0(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.7)(pg@8.13.0)(react@19.0.0-rc-fb9a90fa48-20240614) eslint-config-typescript-vitnode: specifier: workspace:* version: link:../../packages/eslint-config-typescript-vitnode @@ -134,7 +134,7 @@ importers: version: 0.5.21 ts-loader: specifier: ^9.5.1 - version: 9.5.1(typescript@5.6.2)(webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.19.12)) + version: 9.5.1(typescript@5.6.2)(webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))) ts-node: specifier: ^10.9.2 version: 10.9.2(@swc/core@1.7.26(@swc/helpers@0.5.13))(@types/node@22.5.5)(typescript@5.6.2) @@ -152,16 +152,16 @@ importers: dependencies: geist: specifier: ^1.3.1 - version: 1.3.1(next@15.0.0-canary.158(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614)) + version: 1.3.1(next@15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614)) lucide-react: specifier: ^0.441.0 version: 0.441.0(react@19.0.0-rc-fb9a90fa48-20240614) next: specifier: 15.0.0-canary.158 - version: 15.0.0-canary.158(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614) + version: 15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614) next-intl: specifier: ^3.19.3 - version: 3.19.3(next@15.0.0-canary.158(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614) + version: 3.19.3(next@15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614) react: specifier: 19.0.0-rc-fb9a90fa48-20240614 version: 19.0.0-rc-fb9a90fa48-20240614 @@ -211,6 +211,12 @@ importers: packages/backend: dependencies: + '@ai-sdk/google': + specifier: ^0.0.48 + version: 0.0.48(zod@3.23.8) + '@ai-sdk/openai': + specifier: ^0.0.61 + version: 0.0.61(zod@3.23.8) '@apollo/server': specifier: ^4.11.0 version: 4.11.0(graphql@16.9.0) @@ -247,6 +253,9 @@ importers: '@swc/core': specifier: ^1.7.26 version: 1.7.26(@swc/helpers@0.5.13) + ai: + specifier: ^3.4.0 + version: 3.4.0(react@19.0.0-rc-e56f4ae3-20240830)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.5.8(typescript@5.6.2))(zod@3.23.8) busboy: specifier: ^1.6.0 version: 1.6.0 @@ -343,7 +352,7 @@ importers: version: 0.24.2 drizzle-orm: specifier: ^0.33.0 - version: 0.33.0(@types/pg@8.11.10)(@types/react@18.3.7)(pg@8.13.0)(react@19.0.0-rc-e56f4ae3-20240830) + version: 0.33.0(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.7)(pg@8.13.0)(react@19.0.0-rc-e56f4ae3-20240830) eslint-config-typescript-vitnode: specifier: workspace:* version: link:../eslint-config-typescript-vitnode @@ -434,7 +443,7 @@ importers: version: 6.10.0(eslint@9.10.0(jiti@1.21.6)) eslint-plugin-perfectionist: specifier: ^3.6.0 - version: 3.6.0(eslint@9.10.0(jiti@1.21.6))(typescript@5.6.2) + version: 3.6.0(eslint@9.10.0(jiti@1.21.6))(svelte@4.2.19)(typescript@5.6.2) eslint-plugin-prettier: specifier: ^5.2.1 version: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.10.0(jiti@1.21.6)))(eslint@9.10.0(jiti@1.21.6))(prettier@3.3.3) @@ -612,7 +621,7 @@ importers: version: 0.3.0(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830) nextjs-toploader: specifier: ^3.6.15 - version: 3.6.15(next@15.0.0-canary.158(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830))(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830) + version: 3.6.15(next@15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830))(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830) nprogress: specifier: ^0.2.0 version: 0.2.0 @@ -703,10 +712,10 @@ importers: version: 0.441.0(react@19.0.0-rc-e56f4ae3-20240830) next: specifier: 15.0.0-canary.158 - version: 15.0.0-canary.158(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830) + version: 15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830) next-intl: specifier: ^3.19.3 - version: 3.19.3(next@15.0.0-canary.158(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830) + version: 3.19.3(next@15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830) react: specifier: 19.0.0-rc-e56f4ae3-20240830 version: 19.0.0-rc-e56f4ae3-20240830 @@ -734,6 +743,79 @@ importers: packages: + '@ai-sdk/google@0.0.48': + resolution: {integrity: sha512-8L7a5WVuTZjeU7Q0rPn+mL5VvqkN1zDe5WQgzmu6ppXAJ4KULO4ca5sueJSJj01etJyFhcqnY3g7UNLNn5iP6A==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + + '@ai-sdk/openai@0.0.61': + resolution: {integrity: sha512-yIJ70xU9sbDjVAaNoq+W+0jnAgIUsx4e9VTnoNPXNTIQRpgpLvQ7iG8GYNgujO4oX4sLiHsWpOEMzrSwD0mNmw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + + '@ai-sdk/provider-utils@1.0.19': + resolution: {integrity: sha512-p02Fq5Mnc8T6nwRBN1Iaou8YXvN1sDS6hbmJaD5UaRbXjizbh+8rpFS/o7jqAHTwf3uHCDitP3pnODyHdc/CDQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@ai-sdk/provider@0.0.23': + resolution: {integrity: sha512-oAc49O5+xypVrKM7EUU5P/Y4DUL4JZUWVxhejoAVOTOl3WZUEWsMbP3QZR+TrimQIsS0WR/n9UuF6U0jPdp0tQ==} + engines: {node: '>=18'} + + '@ai-sdk/react@0.0.59': + resolution: {integrity: sha512-1WbgO3J2/OoheMuNMxy5itJ3NVqOpqpAQxFNp7AoXgnDv4wDF4kTif61rTlKh7dCPvBHj2HXLmob+TrVFaWhYw==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 + zod: ^3.0.0 + peerDependenciesMeta: + react: + optional: true + zod: + optional: true + + '@ai-sdk/solid@0.0.47': + resolution: {integrity: sha512-lVMxIxtuNqoo/TObSFGflEP2dUeJv7bfPQbS4jHTZGBNlyhgBRY2Xc19yNjA3QKRfvQNDVoQusqxn+18MiHJJQ==} + engines: {node: '>=18'} + peerDependencies: + solid-js: ^1.7.7 + peerDependenciesMeta: + solid-js: + optional: true + + '@ai-sdk/svelte@0.0.49': + resolution: {integrity: sha512-gV0MhaWxkatjf7uJrCAHO3bWrihokNUwGhuMCgyG+y53lwJKAYhR0zCoDRM2HnTJ89fdnx/PVe3R9fOWEVY5qA==} + engines: {node: '>=18'} + peerDependencies: + svelte: ^3.0.0 || ^4.0.0 + peerDependenciesMeta: + svelte: + optional: true + + '@ai-sdk/ui-utils@0.0.44': + resolution: {integrity: sha512-0qiyun/n5zqJzQs/WfQT86dZE5DiDhSHJc7b7ZGLYvNMztHkRQmak2zUCZP4IyGVZEicyEPQK6NEEpBgkmd3Dg==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@ai-sdk/vue@0.0.51': + resolution: {integrity: sha512-6RjuuRGf749EjnsfbETJpF0fmq6a1lF6qUUUnd/Q1Ojf0tX8fI4qwvNykbECZHWuIj42EqZ3HDuNNR9c8oG4rA==} + engines: {node: '>=18'} + peerDependencies: + vue: ^3.3.4 + peerDependenciesMeta: + vue: + optional: true + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -2488,6 +2570,10 @@ packages: '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + '@peculiar/asn1-schema@2.3.13': resolution: {integrity: sha512-3Xq3a01WkHRZL8X04Zsfg//mGaA21xlL4tlVn4v2xGT0JStiztATRkMwa5b+f/HXmY2smsiLXYK46Gwgzvfg3g==} @@ -3763,6 +3849,9 @@ packages: '@types/cross-spawn@6.0.6': resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==} + '@types/diff-match-patch@1.0.36': + resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==} + '@types/eslint@9.6.1': resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} @@ -3937,6 +4026,35 @@ packages: resolution: {integrity: sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vue/compiler-core@3.5.8': + resolution: {integrity: sha512-Uzlxp91EPjfbpeO5KtC0KnXPkuTfGsNDeaKQJxQN718uz+RqDYarEf7UhQJGK+ZYloD2taUbHTI2J4WrUaZQNA==} + + '@vue/compiler-dom@3.5.8': + resolution: {integrity: sha512-GUNHWvoDSbSa5ZSHT9SnV5WkStWfzJwwTd6NMGzilOE/HM5j+9EB9zGXdtu/fCNEmctBqMs6C9SvVPpVPuk1Eg==} + + '@vue/compiler-sfc@3.5.8': + resolution: {integrity: sha512-taYpngQtSysrvO9GULaOSwcG5q821zCoIQBtQQSx7Uf7DxpR6CIHR90toPr9QfDD2mqHQPCSgoWBvJu0yV9zjg==} + + '@vue/compiler-ssr@3.5.8': + resolution: {integrity: sha512-W96PtryNsNG9u0ZnN5Q5j27Z/feGrFV6zy9q5tzJVyJaLiwYxvC0ek4IXClZygyhjm+XKM7WD9pdKi/wIRVC/Q==} + + '@vue/reactivity@3.5.8': + resolution: {integrity: sha512-mlgUyFHLCUZcAYkqvzYnlBRCh0t5ZQfLYit7nukn1GR96gc48Bp4B7OIcSfVSvlG1k3BPfD+p22gi1t2n9tsXg==} + + '@vue/runtime-core@3.5.8': + resolution: {integrity: sha512-fJuPelh64agZ8vKkZgp5iCkPaEqFJsYzxLk9vSC0X3G8ppknclNDr61gDc45yBGTaN5Xqc1qZWU3/NoaBMHcjQ==} + + '@vue/runtime-dom@3.5.8': + resolution: {integrity: sha512-DpAUz+PKjTZPUOB6zJgkxVI3GuYc2iWZiNeeHQUw53kdrparSTG6HeXUrYDjaam8dVsCdvQxDz6ZWxnyjccUjQ==} + + '@vue/server-renderer@3.5.8': + resolution: {integrity: sha512-7AmC9/mEeV9mmXNVyUIm1a1AjUhyeeGNbkLh39J00E7iPeGks8OGRB5blJiMmvqSh8SkaS7jkLWSpXtxUCeagA==} + peerDependencies: + vue: 3.5.8 + + '@vue/shared@3.5.8': + resolution: {integrity: sha512-mJleSWbAGySd2RJdX1RBtcrUBX6snyOc0qHpgk3lGi4l9/P/3ny3ELqFWqYdkXIwwNN/kdm8nD9ky8o6l/Lx2A==} + '@webassemblyjs/ast@1.12.1': resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} @@ -4040,6 +4158,27 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} + ai@3.4.0: + resolution: {integrity: sha512-79MZD3zoljLBf7Jtmd39bhDA3W7WPsozIi99sLrZlgxzh3JBX1XtCxHwrUYHQtAxl21BGzHmXpgNfLiQjdTAfA==} + 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-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -4116,6 +4255,10 @@ packages: aria-query@5.1.3: resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + array-buffer-byte-length@1.0.1: resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} engines: {node: '>= 0.4'} @@ -4427,6 +4570,9 @@ packages: react: ^18.0.0 react-dom: ^18.0.0 + code-red@1.0.4: + resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -4599,6 +4745,10 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + 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'} @@ -4727,6 +4877,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'} @@ -5109,6 +5262,12 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -5124,6 +5283,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'} + execa@0.7.0: resolution: {integrity: sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==} engines: {node: '>=4'} @@ -5724,6 +5887,9 @@ packages: resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} engines: {node: '>=0.10.0'} + is-reference@3.0.2: + resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -5869,6 +6035,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==} @@ -5887,6 +6056,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@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} @@ -5967,6 +6141,9 @@ packages: resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} engines: {node: '>=6.11.5'} + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -6066,6 +6243,9 @@ packages: resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==} engines: {node: '>=12'} + magic-string@0.30.11: + resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + magic-string@0.30.8: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} engines: {node: '>=12'} @@ -6091,6 +6271,9 @@ packages: peerDependencies: react: 18.x + 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==} @@ -6213,6 +6396,11 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + 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} @@ -6529,6 +6717,9 @@ packages: resolution: {integrity: sha512-U94a+eXHzct7vAd19GH3UQ2dH4Satbng0MyYTMaQatL0pvYYL5CTPR25HBhKtecl+4bfu1/i3vC6k0hydO5Vcw==} engines: {node: '>=14.16'} + periscopic@3.1.0: + resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + pg-cloudflare@1.1.1: resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} @@ -7158,6 +7349,9 @@ packages: scuid@1.1.0: resolution: {integrity: sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg==} + secure-json-parse@2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + selderee@0.11.0: resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} @@ -7315,6 +7509,11 @@ packages: sponge-case@1.0.1: resolution: {integrity: sha512-dblb9Et4DAtiZ5YSUZHLl4XhH4uK80GhAZrVXdN4O2P4gQ40Wa5UIOPUHlA/nFd2PLblBZWUioLMMAVrgpoYcA==} + sswr@2.1.0: + resolution: {integrity: sha512-Cqc355SYlTAaUt8iDPaC/4DPPXK925PePLMxyBKuWd5kKc5mwsG3nT9+Mq2tyguL5s7b4Jg+IRMpTRsNTAfpSQ==} + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -7454,9 +7653,26 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svelte@4.2.19: + resolution: {integrity: sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==} + engines: {node: '>=16'} + swap-case@2.0.2: resolution: {integrity: sha512-kc6S2YS/2yXbtkSMunBtKdah4VFETZ8Oh6ONSmSd9bRxhqTrtARUCBUiWXH3xVPpvR7tz2CSnkuXVE42EcGnMw==} + 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-observable@1.2.0: resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==} engines: {node: '>=0.10.0'} @@ -7865,6 +8081,14 @@ packages: react: ^16.8 || ^17.0 || ^18.0 react-dom: ^16.8 || ^17.0 || ^18.0 + vue@3.5.8: + resolution: {integrity: sha512-hvuvuCy51nP/1fSRvrrIqTLSvrSyz2Pq+KQ8S8SXCxTWVE0nMaOnSDnSOxV1eYmGfvK7mqiwvd1C59CEEz7dAQ==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} @@ -8064,11 +8288,88 @@ packages: resolution: {integrity: sha512-7ukbu6aQKx34OQ7PfUIxOuAhk2MvyZY/t4/IJsVzy76zuMzfhE74+Dbyp8SHiUJPTPkF0FflP1KVrGJ7gk9IHw==} engines: {node: '>=14.13.1'} + zod-to-json-schema@3.23.2: + resolution: {integrity: sha512-uSt90Gzc/tUfyNqxnjlfBs8W6WSGpNBv0rVsNxP/BVSMHMKGdthPYff4xtCHYloJGM0CFxFsb3NbC0eqPhfImw==} + peerDependencies: + zod: ^3.23.3 + zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} snapshots: + '@ai-sdk/google@0.0.48(zod@3.23.8)': + dependencies: + '@ai-sdk/provider': 0.0.23 + '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) + json-schema: 0.4.0 + zod: 3.23.8 + + '@ai-sdk/openai@0.0.61(zod@3.23.8)': + dependencies: + '@ai-sdk/provider': 0.0.23 + '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) + zod: 3.23.8 + + '@ai-sdk/provider-utils@1.0.19(zod@3.23.8)': + dependencies: + '@ai-sdk/provider': 0.0.23 + 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.23': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@0.0.59(react@19.0.0-rc-e56f4ae3-20240830)(zod@3.23.8)': + dependencies: + '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) + '@ai-sdk/ui-utils': 0.0.44(zod@3.23.8) + swr: 2.2.5(react@19.0.0-rc-e56f4ae3-20240830) + optionalDependencies: + react: 19.0.0-rc-e56f4ae3-20240830 + zod: 3.23.8 + + '@ai-sdk/solid@0.0.47(zod@3.23.8)': + dependencies: + '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) + '@ai-sdk/ui-utils': 0.0.44(zod@3.23.8) + transitivePeerDependencies: + - zod + + '@ai-sdk/svelte@0.0.49(svelte@4.2.19)(zod@3.23.8)': + dependencies: + '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) + '@ai-sdk/ui-utils': 0.0.44(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.44(zod@3.23.8)': + dependencies: + '@ai-sdk/provider': 0.0.23 + '@ai-sdk/provider-utils': 1.0.19(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.51(vue@3.5.8(typescript@5.6.2))(zod@3.23.8)': + dependencies: + '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) + '@ai-sdk/ui-utils': 0.0.44(zod@3.23.8) + swrv: 1.0.4(vue@3.5.8(typescript@5.6.2)) + optionalDependencies: + vue: 3.5.8(typescript@5.6.2) + transitivePeerDependencies: + - zod + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -9807,7 +10108,7 @@ snapshots: lodash.omit: 4.5.0 tslib: 2.6.3 - '@nestjs/cli@10.4.5(@swc/cli@0.4.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(chokidar@3.6.0))(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.19.12)': + '@nestjs/cli@10.4.5(@swc/cli@0.4.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(chokidar@3.6.0))(@swc/core@1.7.26(@swc/helpers@0.5.13))': dependencies: '@angular-devkit/core': 17.3.8(chokidar@3.6.0) '@angular-devkit/schematics': 17.3.8(chokidar@3.6.0) @@ -9817,7 +10118,7 @@ snapshots: chokidar: 3.6.0 cli-table3: 0.6.5 commander: 4.1.1 - fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.3.3)(webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.19.12)) + fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.3.3)(webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))) glob: 10.4.2 inquirer: 8.2.6 node-emoji: 1.11.0 @@ -9826,7 +10127,7 @@ snapshots: tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.1.0 typescript: 5.3.3 - webpack: 5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.19.12) + webpack: 5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13)) webpack-node-externals: 3.0.0 optionalDependencies: '@swc/cli': 0.4.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(chokidar@3.6.0) @@ -10018,6 +10319,8 @@ snapshots: '@one-ini/wasm@0.1.1': {} + '@opentelemetry/api@1.9.0': {} + '@peculiar/asn1-schema@2.3.13': dependencies: asn1js: 3.0.5 @@ -11376,6 +11679,8 @@ snapshots: dependencies: '@types/node': 22.5.5 + '@types/diff-match-patch@1.0.36': {} + '@types/eslint@9.6.1': dependencies: '@types/estree': 1.0.6 @@ -11586,6 +11891,60 @@ snapshots: '@typescript-eslint/types': 8.6.0 eslint-visitor-keys: 3.4.3 + '@vue/compiler-core@3.5.8': + dependencies: + '@babel/parser': 7.25.6 + '@vue/shared': 3.5.8 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.8': + dependencies: + '@vue/compiler-core': 3.5.8 + '@vue/shared': 3.5.8 + + '@vue/compiler-sfc@3.5.8': + dependencies: + '@babel/parser': 7.25.6 + '@vue/compiler-core': 3.5.8 + '@vue/compiler-dom': 3.5.8 + '@vue/compiler-ssr': 3.5.8 + '@vue/shared': 3.5.8 + 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.8': + dependencies: + '@vue/compiler-dom': 3.5.8 + '@vue/shared': 3.5.8 + + '@vue/reactivity@3.5.8': + dependencies: + '@vue/shared': 3.5.8 + + '@vue/runtime-core@3.5.8': + dependencies: + '@vue/reactivity': 3.5.8 + '@vue/shared': 3.5.8 + + '@vue/runtime-dom@3.5.8': + dependencies: + '@vue/reactivity': 3.5.8 + '@vue/runtime-core': 3.5.8 + '@vue/shared': 3.5.8 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.8(vue@3.5.8(typescript@5.6.2))': + dependencies: + '@vue/compiler-ssr': 3.5.8 + '@vue/shared': 3.5.8 + vue: 3.5.8(typescript@5.6.2) + + '@vue/shared@3.5.8': {} + '@webassemblyjs/ast@1.12.1': dependencies: '@webassemblyjs/helper-numbers': 1.11.6 @@ -11728,6 +12087,31 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 + ai@3.4.0(react@19.0.0-rc-e56f4ae3-20240830)(sswr@2.1.0(svelte@4.2.19))(svelte@4.2.19)(vue@3.5.8(typescript@5.6.2))(zod@3.23.8): + dependencies: + '@ai-sdk/provider': 0.0.23 + '@ai-sdk/provider-utils': 1.0.19(zod@3.23.8) + '@ai-sdk/react': 0.0.59(react@19.0.0-rc-e56f4ae3-20240830)(zod@3.23.8) + '@ai-sdk/solid': 0.0.47(zod@3.23.8) + '@ai-sdk/svelte': 0.0.49(svelte@4.2.19)(zod@3.23.8) + '@ai-sdk/ui-utils': 0.0.44(zod@3.23.8) + '@ai-sdk/vue': 0.0.51(vue@3.5.8(typescript@5.6.2))(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: 19.0.0-rc-e56f4ae3-20240830 + sswr: 2.1.0(svelte@4.2.19) + svelte: 4.2.19 + zod: 3.23.8 + transitivePeerDependencies: + - solid-js + - vue + ajv-formats@2.1.1(ajv@8.12.0): optionalDependencies: ajv: 8.12.0 @@ -11795,6 +12179,8 @@ snapshots: dependencies: deep-equal: 2.2.3 + aria-query@5.3.2: {} + array-buffer-byte-length@1.0.1: dependencies: call-bind: 1.0.7 @@ -12213,6 +12599,14 @@ snapshots: - '@types/react' - '@types/react-dom' + 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 + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -12399,6 +12793,11 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-tree@2.3.1: + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.2.1 + css-what@6.1.0: {} cssesc@3.0.0: {} @@ -12514,6 +12913,8 @@ snapshots: didyoumean@1.2.2: {} + diff-match-patch@1.0.5: {} + diff@4.0.2: {} dir-glob@3.0.1: @@ -12562,15 +12963,17 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.33.0(@types/pg@8.11.10)(@types/react@18.3.7)(pg@8.13.0)(react@19.0.0-rc-e56f4ae3-20240830): + drizzle-orm@0.33.0(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.7)(pg@8.13.0)(react@19.0.0-rc-e56f4ae3-20240830): optionalDependencies: + '@opentelemetry/api': 1.9.0 '@types/pg': 8.11.10 '@types/react': 18.3.7 pg: 8.13.0 react: 19.0.0-rc-e56f4ae3-20240830 - drizzle-orm@0.33.0(@types/pg@8.11.10)(@types/react@18.3.7)(pg@8.13.0)(react@19.0.0-rc-fb9a90fa48-20240614): + drizzle-orm@0.33.0(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.7)(pg@8.13.0)(react@19.0.0-rc-fb9a90fa48-20240614): optionalDependencies: + '@opentelemetry/api': 1.9.0 '@types/pg': 8.11.10 '@types/react': 18.3.7 pg: 8.13.0 @@ -12861,13 +13264,15 @@ snapshots: safe-regex-test: 1.0.3 string.prototype.includes: 2.0.0 - eslint-plugin-perfectionist@3.6.0(eslint@9.10.0(jiti@1.21.6))(typescript@5.6.2): + eslint-plugin-perfectionist@3.6.0(eslint@9.10.0(jiti@1.21.6))(svelte@4.2.19)(typescript@5.6.2): dependencies: '@typescript-eslint/types': 8.6.0 '@typescript-eslint/utils': 8.6.0(eslint@9.10.0(jiti@1.21.6))(typescript@5.6.2) eslint: 9.10.0(jiti@1.21.6) minimatch: 9.0.5 natural-compare-lite: 1.4.0 + optionalDependencies: + svelte: 4.2.19 transitivePeerDependencies: - supports-color - typescript @@ -12979,6 +13384,12 @@ snapshots: estraverse@5.3.0: {} + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.6 + esutils@2.0.3: {} etag@1.8.1: {} @@ -12987,6 +13398,8 @@ snapshots: events@3.3.0: {} + eventsource-parser@1.1.2: {} + execa@0.7.0: dependencies: cross-spawn: 5.1.0 @@ -13194,7 +13607,7 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@9.0.2(typescript@5.3.3)(webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.19.12)): + fork-ts-checker-webpack-plugin@9.0.2(typescript@5.3.3)(webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))): dependencies: '@babel/code-frame': 7.24.7 chalk: 4.1.2 @@ -13209,7 +13622,7 @@ snapshots: semver: 7.6.3 tapable: 2.2.1 typescript: 5.3.3 - webpack: 5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.19.12) + webpack: 5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13)) form-data@4.0.0: dependencies: @@ -13255,9 +13668,9 @@ snapshots: functions-have-names@1.2.3: {} - geist@1.3.1(next@15.0.0-canary.158(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614)): + geist@1.3.1(next@15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614)): dependencies: - next: 15.0.0-canary.158(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614) + next: 15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614) gensync@1.0.0-beta.2: {} @@ -13692,6 +14105,10 @@ snapshots: is-plain-obj@1.1.0: {} + is-reference@3.0.2: + dependencies: + '@types/estree': 1.0.6 + is-regex@1.1.4: dependencies: call-bind: 1.0.7 @@ -13816,6 +14233,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json-to-pretty-yaml@1.2.2: @@ -13829,6 +14248,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@6.1.0: dependencies: universalify: 2.0.1 @@ -13918,6 +14343,8 @@ snapshots: loader-runner@4.3.0: {} + locate-character@3.0.0: {} + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -14012,6 +14439,10 @@ snapshots: luxon@3.4.4: {} + magic-string@0.30.11: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + magic-string@0.30.8: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -14041,6 +14472,8 @@ snapshots: marked: 7.0.4 react: 19.0.0-rc-fb9a90fa48-20240614 + mdn-data@2.0.30: {} + mdurl@2.0.0: {} media-typer@0.3.0: {} @@ -14135,6 +14568,8 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 + nanoid@3.3.6: {} + nanoid@3.3.7: {} natural-compare-lite@1.4.0: {} @@ -14145,19 +14580,19 @@ snapshots: neo-async@2.6.2: {} - next-intl@3.19.3(next@15.0.0-canary.158(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830): + next-intl@3.19.3(next@15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 0.6.3 - next: 15.0.0-canary.158(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830) + next: 15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830) react: 19.0.0-rc-e56f4ae3-20240830 use-intl: 3.19.3(react@19.0.0-rc-e56f4ae3-20240830) - next-intl@3.19.3(next@15.0.0-canary.158(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614): + next-intl@3.19.3(next@15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614): dependencies: '@formatjs/intl-localematcher': 0.5.4 negotiator: 0.6.3 - next: 15.0.0-canary.158(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614) + next: 15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614) react: 19.0.0-rc-fb9a90fa48-20240614 use-intl: 3.19.3(react@19.0.0-rc-fb9a90fa48-20240614) @@ -14166,7 +14601,7 @@ snapshots: react: 19.0.0-rc-e56f4ae3-20240830 react-dom: 19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830) - next@15.0.0-canary.158(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830): + next@15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830): dependencies: '@next/env': 15.0.0-canary.158 '@swc/counter': 0.1.3 @@ -14188,12 +14623,13 @@ snapshots: '@next/swc-win32-arm64-msvc': 15.0.0-canary.158 '@next/swc-win32-ia32-msvc': 15.0.0-canary.158 '@next/swc-win32-x64-msvc': 15.0.0-canary.158 + '@opentelemetry/api': 1.9.0 sharp: 0.33.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - next@15.0.0-canary.158(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614): + next@15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614): dependencies: '@next/env': 15.0.0-canary.158 '@swc/counter': 0.1.3 @@ -14215,14 +14651,15 @@ snapshots: '@next/swc-win32-arm64-msvc': 15.0.0-canary.158 '@next/swc-win32-ia32-msvc': 15.0.0-canary.158 '@next/swc-win32-x64-msvc': 15.0.0-canary.158 + '@opentelemetry/api': 1.9.0 sharp: 0.33.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - nextjs-toploader@3.6.15(next@15.0.0-canary.158(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830))(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830): + nextjs-toploader@3.6.15(next@15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830))(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830): dependencies: - next: 15.0.0-canary.158(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830) + next: 15.0.0-canary.158(@opentelemetry/api@1.9.0)(react-dom@19.0.0-rc-e56f4ae3-20240830(react@19.0.0-rc-e56f4ae3-20240830))(react@19.0.0-rc-e56f4ae3-20240830) nprogress: 0.2.0 prop-types: 15.8.1 react: 19.0.0-rc-e56f4ae3-20240830 @@ -14475,6 +14912,12 @@ snapshots: peek-readable@5.2.0: {} + periscopic@3.1.0: + dependencies: + '@types/estree': 1.0.6 + estree-walker: 3.0.3 + is-reference: 3.0.2 + pg-cloudflare@1.1.1: optional: true @@ -15073,6 +15516,8 @@ snapshots: scuid@1.1.0: {} + secure-json-parse@2.7.0: {} + selderee@0.11.0: dependencies: parseley: 0.12.1 @@ -15270,6 +15715,11 @@ snapshots: dependencies: tslib: 2.7.0 + sswr@2.1.0(svelte@4.2.19): + dependencies: + svelte: 4.2.19 + swrev: 4.0.0 + statuses@2.0.1: {} stdin-discarder@0.2.2: {} @@ -15431,10 +15881,39 @@ 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 + swap-case@2.0.2: dependencies: tslib: 2.7.0 + swr@2.2.5(react@19.0.0-rc-e56f4ae3-20240830): + dependencies: + client-only: 0.0.1 + react: 19.0.0-rc-e56f4ae3-20240830 + use-sync-external-store: 1.2.2(react@19.0.0-rc-e56f4ae3-20240830) + + swrev@4.0.0: {} + + swrv@1.0.4(vue@3.5.8(typescript@5.6.2)): + dependencies: + vue: 3.5.8(typescript@5.6.2) + symbol-observable@1.2.0: {} symbol-observable@4.0.0: {} @@ -15488,17 +15967,16 @@ snapshots: mkdirp: 3.0.1 yallist: 5.0.0 - terser-webpack-plugin@5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.19.12)(webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.19.12)): + terser-webpack-plugin@5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(webpack@5.94.0(@swc/core@1.7.26(@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.33.0 - webpack: 5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.19.12) + webpack: 5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13)) optionalDependencies: '@swc/core': 1.7.26(@swc/helpers@0.5.13) - esbuild: 0.19.12 terser@5.33.0: dependencies: @@ -15567,7 +16045,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-loader@9.5.1(typescript@5.6.2)(webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.19.12)): + ts-loader@9.5.1(typescript@5.6.2)(webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))): dependencies: chalk: 4.1.2 enhanced-resolve: 5.17.1 @@ -15575,7 +16053,7 @@ snapshots: semver: 7.6.3 source-map: 0.7.4 typescript: 5.6.2 - webpack: 5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.19.12) + webpack: 5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13)) ts-log@2.2.5: {} @@ -15853,6 +16331,16 @@ snapshots: - '@types/react' - '@types/react-dom' + vue@3.5.8(typescript@5.6.2): + dependencies: + '@vue/compiler-dom': 3.5.8 + '@vue/compiler-sfc': 3.5.8 + '@vue/runtime-dom': 3.5.8 + '@vue/server-renderer': 3.5.8(vue@3.5.8(typescript@5.6.2)) + '@vue/shared': 3.5.8 + optionalDependencies: + typescript: 5.6.2 + w3c-keyname@2.2.8: {} watchpack@2.4.2: @@ -15882,7 +16370,7 @@ snapshots: webpack-sources@3.2.3: {} - webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.19.12): + webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13)): dependencies: '@types/estree': 1.0.6 '@webassemblyjs/ast': 1.12.1 @@ -15904,7 +16392,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.19.12)(webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))(esbuild@0.19.12)) + terser-webpack-plugin: 5.3.10(@swc/core@1.7.26(@swc/helpers@0.5.13))(webpack@5.94.0(@swc/core@1.7.26(@swc/helpers@0.5.13))) watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -16063,4 +16551,8 @@ snapshots: dependencies: css-what: 6.1.0 + zod-to-json-schema@3.23.2(zod@3.23.8): + dependencies: + zod: 3.23.8 + zod@3.23.8: {} From 6a043b0f90f8ca0fef075dc11e8412c3e400a9e0 Mon Sep 17 00:00:00 2001 From: aXenDeveloper Date: Wed, 25 Sep 2024 14:05:31 +0200 Subject: [PATCH 2/6] fix: Display logo when upload to the server in email settings AdminCP --- packages/backend/src/core/admin/email/helpers.service.ts | 2 +- .../src/core/admin/email/settings/edit/edit.service.ts | 2 +- packages/frontend/src/helpers/zod.ts | 9 +++++++++ .../email/hooks/use-email-settings-form-admin.ts | 4 ++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/core/admin/email/helpers.service.ts b/packages/backend/src/core/admin/email/helpers.service.ts index 104bd6d9c..aef8ea2bd 100644 --- a/packages/backend/src/core/admin/email/helpers.service.ts +++ b/packages/backend/src/core/admin/email/helpers.service.ts @@ -22,7 +22,7 @@ export class HelpersAdminEmailSettingsService { protected readonly path: string = join( ABSOLUTE_PATHS_BACKEND.plugin({ code: 'core' }).root, 'utils', - 'email.config.json', + '.email.config.json', ); protected getEmailCredentials(): EmailCredentialsFile { diff --git a/packages/backend/src/core/admin/email/settings/edit/edit.service.ts b/packages/backend/src/core/admin/email/settings/edit/edit.service.ts index 11090468e..2f1e5eaa8 100644 --- a/packages/backend/src/core/admin/email/settings/edit/edit.service.ts +++ b/packages/backend/src/core/admin/email/settings/edit/edit.service.ts @@ -71,7 +71,7 @@ export class EditAdminEmailSettingsService extends HelpersAdminEmailSettingsServ fs.writeFileSync(configPath, JSON.stringify(newData, null, 2), 'utf8'); - // Remove email.config.json if provider is none + // Remove .email.config.json if provider is none if (provider === EmailProvider.none && fs.existsSync(this.path)) { fs.unlinkSync(this.path); diff --git a/packages/frontend/src/helpers/zod.ts b/packages/frontend/src/helpers/zod.ts index fe589def6..cb5d44aa2 100644 --- a/packages/frontend/src/helpers/zod.ts +++ b/packages/frontend/src/helpers/zod.ts @@ -21,6 +21,15 @@ export const zodFile = z.union([ }), ]); +export const zodFileDefaultObject = { + mimetype: '', + file_name: '', + file_name_original: '', + dir_folder: '', + extension: '', + file_size: 0, +}; + export const zodTag = z.array( z.object({ id: z.number(), diff --git a/packages/frontend/src/views/admin/views/core/settings/email/hooks/use-email-settings-form-admin.ts b/packages/frontend/src/views/admin/views/core/settings/email/hooks/use-email-settings-form-admin.ts index 4acf96478..d9c79ea04 100644 --- a/packages/frontend/src/views/admin/views/core/settings/email/hooks/use-email-settings-form-admin.ts +++ b/packages/frontend/src/views/admin/views/core/settings/email/hooks/use-email-settings-form-admin.ts @@ -1,7 +1,7 @@ import { Admin__Core_Email_Settings__ShowQuery } from '@/graphql/queries/admin/settings/admin__core_email_settings__show.generated'; import { EmailProvider } from '@/graphql/types'; import { getHSLFromString, isColorBrightness } from '@/helpers/colors'; -import { zodFile } from '@/helpers/zod'; +import { zodFile, zodFileDefaultObject } from '@/helpers/zod'; import { useTranslations } from 'next-intl'; import { UseFormReturn } from 'react-hook-form'; import { toast } from 'sonner'; @@ -16,7 +16,7 @@ export const useEmailSettingsFormAdmin = ({ const formSchema = z.object({ color_primary: z.string().default(data.color_primary), - logo: zodFile.optional(), + logo: zodFile.default(data.logo ?? zodFileDefaultObject).optional(), provider: z.nativeEnum(EmailProvider).default(data.provider), smtp: z.object({ host: z From 860abdbc6588d0d28368ea40562e71915b0c7830 Mon Sep 17 00:00:00 2001 From: aXenDeveloper Date: Thu, 26 Sep 2024 12:36:57 +0200 Subject: [PATCH 3/6] feat: Add provider settings AI in AdminCP --- README.md | 16 +++--- apps/backend/schema.gql | 12 +++++ .../admin/(auth)/(vitnode)/core/ai/layout.tsx | 24 ++------- .../admin/(auth)/(vitnode)/core/ai/page.tsx | 4 +- apps/frontend/src/graphql/types.ts | 14 ++++++ apps/frontend/src/plugins/admin/langs/en.json | 12 ++++- apps/frontend/src/plugins/core/langs/en.json | 2 +- .../welcome/templates/default-page.tsx | 2 +- .../backend/src/core/admin/admin.module.ts | 2 + .../backend/src/core/admin/ai/ai.helpers.ts | 29 +++++++++++ .../backend/src/core/admin/ai/ai.module.ts | 9 ++++ .../src/core/admin/ai/show/show.dto.ts | 15 ++++++ .../src/core/admin/ai/show/show.resolver.ts | 17 +++++++ .../src/core/admin/ai/show/show.service.ts | 11 ++++ packages/backend/src/providers/config.ts | 12 +++++ .../eslint.shared.mjs | 1 + .../components/form/fields/radio-group.tsx | 7 +++ .../ai/admin__core_ai__show.generated.ts | 17 +++++++ .../queries/admin/ai/admin__core_ai__show.gql | 6 +++ packages/frontend/src/graphql/types.ts | 15 ++++++ .../views/core/ai/actions/testing/testing.tsx | 16 ++++++ .../admin/views/core/ai/ai-admin-view.tsx | 13 ----- .../src/views/admin/views/core/ai/layout.tsx | 37 ++++++++++++++ .../admin/views/core/ai/providers/content.tsx | 50 +++++++++++++++++++ .../ai/providers/providers-ai-admin-view.tsx | 8 +++ .../src/views/admin/views/core/ai/query.ts | 21 ++++++++ 26 files changed, 326 insertions(+), 46 deletions(-) create mode 100644 packages/backend/src/core/admin/ai/ai.helpers.ts create mode 100644 packages/backend/src/core/admin/ai/ai.module.ts create mode 100644 packages/backend/src/core/admin/ai/show/show.dto.ts create mode 100644 packages/backend/src/core/admin/ai/show/show.resolver.ts create mode 100644 packages/backend/src/core/admin/ai/show/show.service.ts create mode 100644 packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.generated.ts create mode 100644 packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.gql create mode 100644 packages/frontend/src/views/admin/views/core/ai/actions/testing/testing.tsx delete mode 100644 packages/frontend/src/views/admin/views/core/ai/ai-admin-view.tsx create mode 100644 packages/frontend/src/views/admin/views/core/ai/layout.tsx create mode 100644 packages/frontend/src/views/admin/views/core/ai/providers/content.tsx create mode 100644 packages/frontend/src/views/admin/views/core/ai/providers/providers-ai-admin-view.tsx create mode 100644 packages/frontend/src/views/admin/views/core/ai/query.ts diff --git a/README.md b/README.md index cbf342c43..79a6bf9c0 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,15 @@ # VitNode -> [!WARNING] -> 🛠️ VitNode is still in development! You can try it out, but it is not recommended to use it now in production. +VitNode is a **Modular Application Platform** build on top of [NextJS (React)](https://nextjs.org/), [NestJS (Express)](https://nestjs.com/) and [PostgreSQL](https://www.postgresql.org/) offering a customizable, scalable environment for building apps. Its easy-to-use plugin system, modern tools, and flexible integrations let you develop complex apps or launch quickly with simplicity, security, and efficiency. -VitNode is a Community Management Software built on top of [NextJS (React)](https://nextjs.org/), [NestJS (Express)](https://nestjs.com/) and [PostgreSQL](https://www.postgresql.org/). +## Why VitNode? -**Community Management Software** is more than a CMS — it's a full-stack solution designed to empower developers with flexibility and ease. With built-in community features like user management, notifications, activity feeds, search content, device tracker, and more. **VitNode** is the perfect solution for building websites and applications that engage users and grow communities. +- **Purpose & Flexibility**: If you’re looking to build a full-stack web application (all-in-one) that needs modularity and includes complex backend logic, integrations, and the ability to manage both content and features like a CMS, +- **Freandly for No-dev users** - who can use drag-and-drop interfaces and customizable themes, +- **Freandly for Developers** - who can dive into code for API integrations, CRON jobs, etc. +- **Development Tools and Features** - VitNode provides an entire ecosystem for building and managing applications. It integrates features like email management, CRON jobs, group and user management, security settings, and more. +- **Security and Compliance** - Comes with built-in security features such as CSRF protection, CORS, rate limiting, CAPTCHA integrations, and more. ## 🚀 Installation @@ -34,11 +37,6 @@ pnpm create vitnode-app@latest ## Requirements -| 📦 Package Manager | Minimum | Recommended | -| :----------------- | :------ | :---------- | -| npm | 7 | 9 | -| pnpm | 8 | 9 | - | 🛠️ Software | Minimum | Recommended | | :---------- | :------ | :---------- | | Node.js | 18 | 20 | diff --git a/apps/backend/schema.gql b/apps/backend/schema.gql index 41e692892..0cd632f27 100644 --- a/apps/backend/schema.gql +++ b/apps/backend/schema.gql @@ -2,6 +2,12 @@ # THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) # ------------------------------------------------------ +enum AiProvider { + google + none + openai +} + enum AllowTypeFilesEnum { all images @@ -295,6 +301,7 @@ type PageInfo { } type Query { + admin__core_ai__show: ShowAdminCoreAiObj! admin__core_authorization_settings__show: ShowAdminAuthorizationSettingsObj! admin__core_email__logs(cursor: Int, first: Int, last: Int): LogsAdminEmailObj! admin__core_email_settings__show: ShowAdminEmailSettingsServiceObj! @@ -352,6 +359,11 @@ type ShowAdminCaptchaSecurityObj { type: CaptchaTypeEnum! } +type ShowAdminCoreAiObj { + key: String! + provider: AiProvider! +} + type ShowAdminEmailSettingsServiceObj { color_primary: String! from: String! diff --git a/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/layout.tsx b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/layout.tsx index 82c6ec3a6..0116e9ab3 100644 --- a/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/layout.tsx +++ b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/layout.tsx @@ -1,21 +1,7 @@ -import { useTranslations } from 'next-intl'; -import { Card } from 'vitnode-frontend/components/ui/card'; -import { HeaderContent } from 'vitnode-frontend/components/ui/header-content'; -import { Tabs, TabsTrigger } from 'vitnode-frontend/components/ui/tabs'; +import { AiAdminLayout } from 'vitnode-frontend/views/admin/views/core/ai/layout'; -export default function Layout({ children }: { children: React.ReactNode }) { - const t = useTranslations('admin.core.ai'); - - return ( - <> - - - - {t('tabs.provider')} - - - - {children} - - ); +export default function Layout( + props: React.ComponentProps, +) { + return ; } diff --git a/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/page.tsx b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/page.tsx index d41fdc062..f2c8cea9f 100644 --- a/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/page.tsx +++ b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/page.tsx @@ -1,5 +1,5 @@ -import { AiAdminView } from 'vitnode-frontend/views/admin/views/core/ai/ai-admin-view'; +import { ProvidersAiAdminView } from 'vitnode-frontend/views/admin/views/core/ai/providers/providers-ai-admin-view'; export default function Page() { - return ; + return ; } diff --git a/apps/frontend/src/graphql/types.ts b/apps/frontend/src/graphql/types.ts index 0ee55488d..809781b8e 100644 --- a/apps/frontend/src/graphql/types.ts +++ b/apps/frontend/src/graphql/types.ts @@ -16,6 +16,13 @@ export type Scalars = { Upload: { input: File; output: File; } }; +export const AiProvider = { + google: 'google', + none: 'none', + openai: 'openai' +} as const; + +export type AiProvider = typeof AiProvider[keyof typeof AiProvider]; export const AllowTypeFilesEnum = { all: 'all', images: 'images', @@ -659,6 +666,7 @@ export type PageInfo = { export type Query = { __typename?: 'Query'; + admin__core_ai__show: ShowAdminCoreAiObj; admin__core_authorization_settings__show: ShowAdminAuthorizationSettingsObj; admin__core_email__logs: LogsAdminEmailObj; admin__core_email_settings__show: ShowAdminEmailSettingsServiceObj; @@ -835,6 +843,12 @@ export type ShowAdminCaptchaSecurityObj = { type: CaptchaTypeEnum | `${CaptchaTypeEnum}`; }; +export type ShowAdminCoreAiObj = { + __typename?: 'ShowAdminCoreAiObj'; + key: Scalars['String']['output']; + provider: AiProvider | `${AiProvider}`; +}; + export type ShowAdminEmailSettingsServiceObj = { __typename?: 'ShowAdminEmailSettingsServiceObj'; color_primary: Scalars['String']['output']; diff --git a/apps/frontend/src/plugins/admin/langs/en.json b/apps/frontend/src/plugins/admin/langs/en.json index 2855058ef..da27d37c0 100644 --- a/apps/frontend/src/plugins/admin/langs/en.json +++ b/apps/frontend/src/plugins/admin/langs/en.json @@ -480,9 +480,19 @@ }, "ai": { "title": "Artificial Intelligence (AI)", - "desc": "Integrate AI to boost user experience with personalized content, smarter search, and SEO optimization. Gain insights and enhance engagement with powerful AI tools.", + "desc": "Integrate powerful AI to boost user experience with personalized content, smarter search, SEO optimization and more.", "tabs": { "provider": "Provider" + }, + "testing": { + "title": "Test Your AI" + }, + "provider": { + "title": "Provider", + "desc": "Choose the AI provider to use in your website.", + "none": "None", + "google": "Google AI", + "openai": "OpenAI" } } }, diff --git a/apps/frontend/src/plugins/core/langs/en.json b/apps/frontend/src/plugins/core/langs/en.json index 89e4bc9e8..92284468e 100644 --- a/apps/frontend/src/plugins/core/langs/en.json +++ b/apps/frontend/src/plugins/core/langs/en.json @@ -353,7 +353,7 @@ "settings_email": "Email", "settings_authorization": "Authorization", "settings_legal": "Legal & Policies", - "ai": "Artificial Intelligence", + "ai": "AI", "plugins": "Plugins", "styles": "Styles", "styles_theme-editor": "Theme Editor", diff --git a/apps/frontend/src/plugins/welcome/templates/default-page.tsx b/apps/frontend/src/plugins/welcome/templates/default-page.tsx index 02aad9d18..cad005f57 100644 --- a/apps/frontend/src/plugins/welcome/templates/default-page.tsx +++ b/apps/frontend/src/plugins/welcome/templates/default-page.tsx @@ -11,7 +11,7 @@ export default function DefaultPage() {

- Dive in and create install or your first plugin! + Jump right in and build or install your first plugin!

diff --git a/packages/backend/src/core/admin/admin.module.ts b/packages/backend/src/core/admin/admin.module.ts index f48c6c950..6d482d522 100644 --- a/packages/backend/src/core/admin/admin.module.ts +++ b/packages/backend/src/core/admin/admin.module.ts @@ -1,5 +1,6 @@ import { Module } from '@nestjs/common'; +import { AdminAiModule } from './ai/ai.module'; import { AdminEmailModule, GlobalAdminEmailModule } from './email/email.module'; import { AdminFilesModule } from './files/files.module'; import { AdminGroupsModule } from './groups/groups.module'; @@ -42,6 +43,7 @@ import { AdminThemeEditorModule } from './theme_editor/theme_editor.module'; AdminEmailModule, AdminSecurityModule, AdminNavModule, + AdminAiModule, ], }) export class AdminModule {} diff --git a/packages/backend/src/core/admin/ai/ai.helpers.ts b/packages/backend/src/core/admin/ai/ai.helpers.ts new file mode 100644 index 000000000..aceea1cb2 --- /dev/null +++ b/packages/backend/src/core/admin/ai/ai.helpers.ts @@ -0,0 +1,29 @@ +import { ABSOLUTE_PATHS_BACKEND, getConfigFile } from '@/index'; +import { existsSync, readFileSync } from 'fs'; +import { join } from 'path'; + +import { ShowAdminCoreAiObj } from './show/show.dto'; + +export class HelpersCoreAi { + protected readonly path: string = join( + ABSOLUTE_PATHS_BACKEND.plugin({ code: 'core' }).root, + 'utils', + '.ai.config.json', + ); + + protected getAiCredentials() { + const config = getConfigFile(); + const defaultAiCredentials: Pick = { + key: '', + }; + + const aiCredentials: ShowAdminCoreAiObj = existsSync(this.path) + ? JSON.parse(readFileSync(this.path, 'utf-8')) + : defaultAiCredentials; + + return { + ...config.ai, + key: aiCredentials.key || defaultAiCredentials.key, + }; + } +} diff --git a/packages/backend/src/core/admin/ai/ai.module.ts b/packages/backend/src/core/admin/ai/ai.module.ts new file mode 100644 index 000000000..4c2f8f100 --- /dev/null +++ b/packages/backend/src/core/admin/ai/ai.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; + +import { ShowAdminCoreAiResolver } from './show/show.resolver'; +import { ShowAdminCoreAiService } from './show/show.service'; + +@Module({ + providers: [ShowAdminCoreAiResolver, ShowAdminCoreAiService], +}) +export class AdminAiModule {} diff --git a/packages/backend/src/core/admin/ai/show/show.dto.ts b/packages/backend/src/core/admin/ai/show/show.dto.ts new file mode 100644 index 000000000..d06377b3b --- /dev/null +++ b/packages/backend/src/core/admin/ai/show/show.dto.ts @@ -0,0 +1,15 @@ +import { AiProvider } from '@/providers'; +import { Field, ObjectType, registerEnumType } from '@nestjs/graphql'; + +registerEnumType(AiProvider, { + name: 'AiProvider', +}); + +@ObjectType() +export class ShowAdminCoreAiObj { + @Field(() => String) + key: string; + + @Field(() => AiProvider) + provider: AiProvider; +} diff --git a/packages/backend/src/core/admin/ai/show/show.resolver.ts b/packages/backend/src/core/admin/ai/show/show.resolver.ts new file mode 100644 index 000000000..516a47a51 --- /dev/null +++ b/packages/backend/src/core/admin/ai/show/show.resolver.ts @@ -0,0 +1,17 @@ +import { AdminAuthGuards } from '@/utils'; +import { UseGuards } from '@nestjs/common'; +import { Query, Resolver } from '@nestjs/graphql'; + +import { ShowAdminCoreAiObj } from './show.dto'; +import { ShowAdminCoreAiService } from './show.service'; + +@Resolver() +export class ShowAdminCoreAiResolver { + constructor(private readonly service: ShowAdminCoreAiService) {} + + @Query(() => ShowAdminCoreAiObj) + @UseGuards(AdminAuthGuards) + admin__core_ai__show(): ShowAdminCoreAiObj { + return this.service.show(); + } +} diff --git a/packages/backend/src/core/admin/ai/show/show.service.ts b/packages/backend/src/core/admin/ai/show/show.service.ts new file mode 100644 index 000000000..5f6de4af5 --- /dev/null +++ b/packages/backend/src/core/admin/ai/show/show.service.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; + +import { HelpersCoreAi } from '../ai.helpers'; +import { ShowAdminCoreAiObj } from './show.dto'; + +@Injectable() +export class ShowAdminCoreAiService extends HelpersCoreAi { + show(): ShowAdminCoreAiObj { + return this.getAiCredentials(); + } +} diff --git a/packages/backend/src/providers/config.ts b/packages/backend/src/providers/config.ts index 294922d97..65e443edf 100644 --- a/packages/backend/src/providers/config.ts +++ b/packages/backend/src/providers/config.ts @@ -23,7 +23,16 @@ export enum EmailProvider { smtp = 'smtp', } +export enum AiProvider { + google = 'google', + none = 'none', + openai = 'openai', +} + export interface ConfigType { + ai: { + provider: AiProvider; + }; editor: { files: { allow_type: AllowTypeFilesEnum; @@ -90,6 +99,9 @@ export const DEFAULT_CONFIG_DATA: ConfigType = { allow_type: AllowTypeFilesEnum.all, }, }, + ai: { + provider: AiProvider.none, + }, settings: { main: { site_name: 'VitNode', diff --git a/packages/eslint-config-typescript-vitnode/eslint.shared.mjs b/packages/eslint-config-typescript-vitnode/eslint.shared.mjs index 64b77859a..167421d2c 100644 --- a/packages/eslint-config-typescript-vitnode/eslint.shared.mjs +++ b/packages/eslint-config-typescript-vitnode/eslint.shared.mjs @@ -23,6 +23,7 @@ export default [ }, { rules: { + 'perfectionist/sort-enums': 'warn', 'perfectionist/sort-exports': 'warn', '@typescript-eslint/no-dynamic-delete': 'off', 'perfectionist/sort-named-imports': 'warn', diff --git a/packages/frontend/src/components/form/fields/radio-group.tsx b/packages/frontend/src/components/form/fields/radio-group.tsx index e4cfb5967..054a850e7 100644 --- a/packages/frontend/src/components/form/fields/radio-group.tsx +++ b/packages/frontend/src/components/form/fields/radio-group.tsx @@ -53,6 +53,13 @@ export function AutoFormRadioGroup({ values = baseValues.map(value => [value, value]); } + // Move 'none' to the top + const noneValue = values.find(value => value[0] === 'none'); + if (noneValue) { + values = values.filter(value => value[0] !== 'none'); + values.unshift(noneValue); + } + return ( {label && ( diff --git a/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.generated.ts b/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.generated.ts new file mode 100644 index 000000000..7f01182f2 --- /dev/null +++ b/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.generated.ts @@ -0,0 +1,17 @@ +import * as Types from '../../../types'; + +import gql from 'graphql-tag'; +export type Admin__Core_Ai__ShowQueryVariables = Types.Exact<{ [key: string]: never; }>; + + +export type Admin__Core_Ai__ShowQuery = { __typename?: 'Query', admin__core_ai__show: { __typename?: 'ShowAdminCoreAiObj', key: string, provider: Types.AiProvider } }; + + +export const Admin__Core_Ai__Show = gql` + query Admin__core_ai__show { + admin__core_ai__show { + key + provider + } +} + `; \ No newline at end of file diff --git a/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.gql b/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.gql new file mode 100644 index 000000000..a39455fab --- /dev/null +++ b/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.gql @@ -0,0 +1,6 @@ +query Admin__core_ai__show { + admin__core_ai__show { + key + provider + } +} diff --git a/packages/frontend/src/graphql/types.ts b/packages/frontend/src/graphql/types.ts index 7fb8e68f2..809781b8e 100644 --- a/packages/frontend/src/graphql/types.ts +++ b/packages/frontend/src/graphql/types.ts @@ -16,6 +16,13 @@ export type Scalars = { Upload: { input: File; output: File; } }; +export const AiProvider = { + google: 'google', + none: 'none', + openai: 'openai' +} as const; + +export type AiProvider = typeof AiProvider[keyof typeof AiProvider]; export const AllowTypeFilesEnum = { all: 'all', images: 'images', @@ -299,6 +306,7 @@ export type Mutation = { admin__core_theme_editor__edit: Scalars['String']['output']; admin__install__create_database: Scalars['String']['output']; admin_sessions__sign_out: Scalars['String']['output']; + core_ai__test: Scalars['String']['output']; core_editor_files__delete: Scalars['String']['output']; core_editor_files__upload: ShowCoreFiles; core_members__avatar__delete: Scalars['String']['output']; @@ -658,6 +666,7 @@ export type PageInfo = { export type Query = { __typename?: 'Query'; + admin__core_ai__show: ShowAdminCoreAiObj; admin__core_authorization_settings__show: ShowAdminAuthorizationSettingsObj; admin__core_email__logs: LogsAdminEmailObj; admin__core_email_settings__show: ShowAdminEmailSettingsServiceObj; @@ -834,6 +843,12 @@ export type ShowAdminCaptchaSecurityObj = { type: CaptchaTypeEnum | `${CaptchaTypeEnum}`; }; +export type ShowAdminCoreAiObj = { + __typename?: 'ShowAdminCoreAiObj'; + key: Scalars['String']['output']; + provider: AiProvider | `${AiProvider}`; +}; + export type ShowAdminEmailSettingsServiceObj = { __typename?: 'ShowAdminEmailSettingsServiceObj'; color_primary: Scalars['String']['output']; diff --git a/packages/frontend/src/views/admin/views/core/ai/actions/testing/testing.tsx b/packages/frontend/src/views/admin/views/core/ai/actions/testing/testing.tsx new file mode 100644 index 000000000..c7cd17f11 --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/ai/actions/testing/testing.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { Button } from '@/components/ui/button'; +import { FlaskConical } from 'lucide-react'; +import { useTranslations } from 'next-intl'; + +export const TestingActionAiAdmin = ({ disabled }: { disabled: boolean }) => { + const t = useTranslations('admin.core.ai.testing'); + + return ( + + ); +}; diff --git a/packages/frontend/src/views/admin/views/core/ai/ai-admin-view.tsx b/packages/frontend/src/views/admin/views/core/ai/ai-admin-view.tsx deleted file mode 100644 index afb593e9e..000000000 --- a/packages/frontend/src/views/admin/views/core/ai/ai-admin-view.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client'; -import { AutoForm } from '@/components/form/auto-form'; -import * as z from 'zod'; - -export const AiAdminView = () => { - const formSchema = z.object({}); - - return ( - <> - - - ); -}; diff --git a/packages/frontend/src/views/admin/views/core/ai/layout.tsx b/packages/frontend/src/views/admin/views/core/ai/layout.tsx new file mode 100644 index 000000000..c8ec23c6d --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/ai/layout.tsx @@ -0,0 +1,37 @@ +import { Card } from '@/components/ui/card'; +import { HeaderContent } from '@/components/ui/header-content'; +import { Tabs, TabsTrigger } from '@/components/ui/tabs'; +import { AiProvider } from '@/graphql/types'; +import { getTranslations } from 'next-intl/server'; + +import { TestingActionAiAdmin } from './actions/testing/testing'; +import { getAdminAiSettings } from './query'; + +export const AiAdminLayout = async ({ + children, +}: { + children: React.ReactNode; +}) => { + const [t, data] = await Promise.all([ + getTranslations('admin.core.ai'), + getAdminAiSettings(), + ]); + + return ( + <> + + + + + + + {t('tabs.provider')} + + + + {children} + + ); +}; diff --git a/packages/frontend/src/views/admin/views/core/ai/providers/content.tsx b/packages/frontend/src/views/admin/views/core/ai/providers/content.tsx new file mode 100644 index 000000000..19a2c1449 --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/ai/providers/content.tsx @@ -0,0 +1,50 @@ +'use client'; + +import { AutoForm } from '@/components/form/auto-form'; +import { + AutoFormRadioGroup, + AutoFormRadioGroupProps, +} from '@/components/form/fields/radio-group'; +import { Admin__Core_Ai__ShowQuery } from '@/graphql/queries/admin/ai/admin__core_ai__show.generated'; +import { AiProvider } from '@/graphql/types'; +import { useTranslations } from 'next-intl'; +import * as z from 'zod'; + +export const ContentProvidersAiAdmin = ({ + admin__core_ai__show: data, +}: Admin__Core_Ai__ShowQuery) => { + const t = useTranslations('admin.core.ai'); + const formSchema = z.object({ + provider: z.nativeEnum(AiProvider).default(data.provider), + }); + + return ( + <> + + + ); +}; diff --git a/packages/frontend/src/views/admin/views/core/ai/providers/providers-ai-admin-view.tsx b/packages/frontend/src/views/admin/views/core/ai/providers/providers-ai-admin-view.tsx new file mode 100644 index 000000000..d40409c0e --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/ai/providers/providers-ai-admin-view.tsx @@ -0,0 +1,8 @@ +import { getAdminAiSettings } from '../query'; +import { ContentProvidersAiAdmin } from './content'; + +export const ProvidersAiAdminView = async () => { + const data = await getAdminAiSettings(); + + return ; +}; diff --git a/packages/frontend/src/views/admin/views/core/ai/query.ts b/packages/frontend/src/views/admin/views/core/ai/query.ts new file mode 100644 index 000000000..02da0fa0b --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/ai/query.ts @@ -0,0 +1,21 @@ +import { fetcher } from '@/graphql/fetcher'; +import { + Admin__Core_Ai__Show, + Admin__Core_Ai__ShowQuery, + Admin__Core_Ai__ShowQueryVariables, +} from '@/graphql/queries/admin/ai/admin__core_ai__show.generated'; + +export const getAdminAiSettings = async () => { + const data = await fetcher< + Admin__Core_Ai__ShowQuery, + Admin__Core_Ai__ShowQueryVariables + >({ + query: Admin__Core_Ai__Show, + cache: 'force-cache', + next: { + tags: ['admin__core_ai__show'], + }, + }); + + return data; +}; From bfc79f692a63fb696c39990007beadf7c8629c22 Mon Sep 17 00:00:00 2001 From: aXenDeveloper Date: Thu, 26 Sep 2024 16:25:22 +0200 Subject: [PATCH 4/6] feat: Create mutation to edit ai settings in AdminCP --- apps/backend/schema.gql | 2 +- apps/frontend/src/graphql/types.ts | 8 +++- apps/frontend/src/plugins/admin/langs/en.json | 1 + .../backend/src/core/admin/ai/ai.module.ts | 9 +++- .../src/core/admin/ai/edit/edit.dto.ts | 11 +++++ .../src/core/admin/ai/edit/edit.resolver.ts | 20 +++++++++ .../src/core/admin/ai/edit/edit.service.ts | 45 +++++++++++++++++++ .../src/core/admin/ai/show/show.dto.ts | 3 -- .../src/core/admin/ai/show/show.service.ts | 2 +- .../src/core/{admin => }/ai/ai.helpers.ts | 8 ++-- .../src/core/ai/provider/ai.service.ts | 34 ++++++++++++-- .../backend/src/core/ai/test/test.service.ts | 20 +-------- .../ai/admin__core_ai__edit.generated.ts | 19 ++++++++ .../admin/ai/admin__core_ai__edit.gql | 5 +++ .../ai/admin__core_ai__show.generated.ts | 3 +- .../queries/admin/ai/admin__core_ai__show.gql | 1 - packages/frontend/src/graphql/types.ts | 8 +++- .../admin/views/core/ai/providers/content.tsx | 32 ++++++++++--- .../core/ai/providers/hooks/mutation-api.ts | 29 ++++++++++++ .../hooks/use-providers-ai-form-admin.ts | 32 +++++++++++++ .../hooks/use-email-settings-form-admin.ts | 1 + 21 files changed, 252 insertions(+), 41 deletions(-) create mode 100644 packages/backend/src/core/admin/ai/edit/edit.dto.ts create mode 100644 packages/backend/src/core/admin/ai/edit/edit.resolver.ts create mode 100644 packages/backend/src/core/admin/ai/edit/edit.service.ts rename packages/backend/src/core/{admin => }/ai/ai.helpers.ts (75%) create mode 100644 packages/frontend/src/graphql/mutations/admin/ai/admin__core_ai__edit.generated.ts create mode 100644 packages/frontend/src/graphql/mutations/admin/ai/admin__core_ai__edit.gql create mode 100644 packages/frontend/src/views/admin/views/core/ai/providers/hooks/mutation-api.ts create mode 100644 packages/frontend/src/views/admin/views/core/ai/providers/hooks/use-providers-ai-form-admin.ts diff --git a/apps/backend/schema.gql b/apps/backend/schema.gql index 0cd632f27..50404b1cc 100644 --- a/apps/backend/schema.gql +++ b/apps/backend/schema.gql @@ -231,6 +231,7 @@ type LogsAdminEmailObj { } type Mutation { + admin__core_ai__edit(key: String, provider: AiProvider!): ShowAdminCoreAiObj! admin__core_authorization_settings__edit(force_login: Boolean!, lock_register: Boolean!): ShowAdminAuthorizationSettingsObj! admin__core_email_settings__edit(color_primary: String!, color_primary_foreground: String!, from: String!, logo: UploadWithKeepCoreFilesArgs, provider: EmailProvider!, resend_key: String, smtp: SMTPEditAdminEmailSettingsService): ShowAdminEmailSettingsServiceObj! admin__core_email_settings__test(message: String!, preview_text: String, subject: String!, to: String!): String! @@ -360,7 +361,6 @@ type ShowAdminCaptchaSecurityObj { } type ShowAdminCoreAiObj { - key: String! provider: AiProvider! } diff --git a/apps/frontend/src/graphql/types.ts b/apps/frontend/src/graphql/types.ts index 809781b8e..fb0b14b04 100644 --- a/apps/frontend/src/graphql/types.ts +++ b/apps/frontend/src/graphql/types.ts @@ -266,6 +266,7 @@ export type LogsAdminEmailObj = { export type Mutation = { __typename?: 'Mutation'; + admin__core_ai__edit: ShowAdminCoreAiObj; admin__core_authorization_settings__edit: ShowAdminAuthorizationSettingsObj; admin__core_email_settings__edit: ShowAdminEmailSettingsServiceObj; admin__core_email_settings__test: Scalars['String']['output']; @@ -319,6 +320,12 @@ export type Mutation = { }; +export type MutationAdmin__Core_Ai__EditArgs = { + key?: InputMaybe; + provider: AiProvider; +}; + + export type MutationAdmin__Core_Authorization_Settings__EditArgs = { force_login: Scalars['Boolean']['input']; lock_register: Scalars['Boolean']['input']; @@ -845,7 +852,6 @@ export type ShowAdminCaptchaSecurityObj = { export type ShowAdminCoreAiObj = { __typename?: 'ShowAdminCoreAiObj'; - key: Scalars['String']['output']; provider: AiProvider | `${AiProvider}`; }; diff --git a/apps/frontend/src/plugins/admin/langs/en.json b/apps/frontend/src/plugins/admin/langs/en.json index da27d37c0..f45949bf9 100644 --- a/apps/frontend/src/plugins/admin/langs/en.json +++ b/apps/frontend/src/plugins/admin/langs/en.json @@ -487,6 +487,7 @@ "testing": { "title": "Test Your AI" }, + "key": "API Key", "provider": { "title": "Provider", "desc": "Choose the AI provider to use in your website.", diff --git a/packages/backend/src/core/admin/ai/ai.module.ts b/packages/backend/src/core/admin/ai/ai.module.ts index 4c2f8f100..185177c8e 100644 --- a/packages/backend/src/core/admin/ai/ai.module.ts +++ b/packages/backend/src/core/admin/ai/ai.module.ts @@ -1,9 +1,16 @@ import { Module } from '@nestjs/common'; +import { EditAdminCoreAiResolver } from './edit/edit.resolver'; +import { EditAdminCoreAiService } from './edit/edit.service'; import { ShowAdminCoreAiResolver } from './show/show.resolver'; import { ShowAdminCoreAiService } from './show/show.service'; @Module({ - providers: [ShowAdminCoreAiResolver, ShowAdminCoreAiService], + providers: [ + ShowAdminCoreAiResolver, + ShowAdminCoreAiService, + EditAdminCoreAiResolver, + EditAdminCoreAiService, + ], }) export class AdminAiModule {} diff --git a/packages/backend/src/core/admin/ai/edit/edit.dto.ts b/packages/backend/src/core/admin/ai/edit/edit.dto.ts new file mode 100644 index 000000000..c902994ce --- /dev/null +++ b/packages/backend/src/core/admin/ai/edit/edit.dto.ts @@ -0,0 +1,11 @@ +import { AiProvider } from '@/providers'; +import { ArgsType, Field } from '@nestjs/graphql'; + +@ArgsType() +export class EditAdminCoreAiArgs { + @Field(() => String, { nullable: true }) + key: null | string; + + @Field(() => AiProvider) + provider: AiProvider; +} diff --git a/packages/backend/src/core/admin/ai/edit/edit.resolver.ts b/packages/backend/src/core/admin/ai/edit/edit.resolver.ts new file mode 100644 index 000000000..23f642778 --- /dev/null +++ b/packages/backend/src/core/admin/ai/edit/edit.resolver.ts @@ -0,0 +1,20 @@ +import { AdminAuthGuards } from '@/utils'; +import { UseGuards } from '@nestjs/common'; +import { Args, Mutation, Resolver } from '@nestjs/graphql'; + +import { ShowAdminCoreAiObj } from '../show/show.dto'; +import { EditAdminCoreAiArgs } from './edit.dto'; +import { EditAdminCoreAiService } from './edit.service'; + +@Resolver() +export class EditAdminCoreAiResolver { + constructor(private readonly service: EditAdminCoreAiService) {} + + @Mutation(() => ShowAdminCoreAiObj) + @UseGuards(AdminAuthGuards) + async admin__core_ai__edit( + @Args() args: EditAdminCoreAiArgs, + ): Promise { + return this.service.edit(args); + } +} diff --git a/packages/backend/src/core/admin/ai/edit/edit.service.ts b/packages/backend/src/core/admin/ai/edit/edit.service.ts new file mode 100644 index 000000000..6355dd972 --- /dev/null +++ b/packages/backend/src/core/admin/ai/edit/edit.service.ts @@ -0,0 +1,45 @@ +import { AiProvider, configPath, ConfigType, getConfigFile } from '@/providers'; +import { Injectable } from '@nestjs/common'; +import { unlink, writeFile } from 'fs/promises'; + +import { AiCredentialsFile, HelpersCoreAi } from '../../../ai/ai.helpers'; +import { ShowAdminCoreAiObj } from '../show/show.dto'; +import { EditAdminCoreAiArgs } from './edit.dto'; + +@Injectable() +export class EditAdminCoreAiService extends HelpersCoreAi { + async edit({ + provider, + key, + }: EditAdminCoreAiArgs): Promise { + const configSettings = getConfigFile(); + + // Update settings + const newData: ConfigType = { + ...configSettings, + ai: { + ...configSettings.ai, + provider, + }, + }; + await writeFile(configPath, JSON.stringify(newData, null, 2)); + + if (provider === AiProvider.none) { + // Remove .ai.config.json file if provider is none + await unlink(this.path); + + return this.getAiCredentials(); + } + + // Update credentials + if (key) { + const newCredentials: AiCredentialsFile = { + key, + }; + + await writeFile(this.path, JSON.stringify(newCredentials, null, 2)); + } + + return this.getAiCredentials(); + } +} diff --git a/packages/backend/src/core/admin/ai/show/show.dto.ts b/packages/backend/src/core/admin/ai/show/show.dto.ts index d06377b3b..780fff9ee 100644 --- a/packages/backend/src/core/admin/ai/show/show.dto.ts +++ b/packages/backend/src/core/admin/ai/show/show.dto.ts @@ -7,9 +7,6 @@ registerEnumType(AiProvider, { @ObjectType() export class ShowAdminCoreAiObj { - @Field(() => String) - key: string; - @Field(() => AiProvider) provider: AiProvider; } diff --git a/packages/backend/src/core/admin/ai/show/show.service.ts b/packages/backend/src/core/admin/ai/show/show.service.ts index 5f6de4af5..b1dbe45d5 100644 --- a/packages/backend/src/core/admin/ai/show/show.service.ts +++ b/packages/backend/src/core/admin/ai/show/show.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { HelpersCoreAi } from '../ai.helpers'; +import { HelpersCoreAi } from '../../../ai/ai.helpers'; import { ShowAdminCoreAiObj } from './show.dto'; @Injectable() diff --git a/packages/backend/src/core/admin/ai/ai.helpers.ts b/packages/backend/src/core/ai/ai.helpers.ts similarity index 75% rename from packages/backend/src/core/admin/ai/ai.helpers.ts rename to packages/backend/src/core/ai/ai.helpers.ts index aceea1cb2..70212513a 100644 --- a/packages/backend/src/core/admin/ai/ai.helpers.ts +++ b/packages/backend/src/core/ai/ai.helpers.ts @@ -2,7 +2,9 @@ import { ABSOLUTE_PATHS_BACKEND, getConfigFile } from '@/index'; import { existsSync, readFileSync } from 'fs'; import { join } from 'path'; -import { ShowAdminCoreAiObj } from './show/show.dto'; +export interface AiCredentialsFile { + key: string; +} export class HelpersCoreAi { protected readonly path: string = join( @@ -13,11 +15,11 @@ export class HelpersCoreAi { protected getAiCredentials() { const config = getConfigFile(); - const defaultAiCredentials: Pick = { + const defaultAiCredentials: AiCredentialsFile = { key: '', }; - const aiCredentials: ShowAdminCoreAiObj = existsSync(this.path) + const aiCredentials: AiCredentialsFile = existsSync(this.path) ? JSON.parse(readFileSync(this.path, 'utf-8')) : defaultAiCredentials; diff --git a/packages/backend/src/core/ai/provider/ai.service.ts b/packages/backend/src/core/ai/provider/ai.service.ts index 64fafbc9c..46f5ff3e4 100644 --- a/packages/backend/src/core/ai/provider/ai.service.ts +++ b/packages/backend/src/core/ai/provider/ai.service.ts @@ -1,8 +1,36 @@ +import { CustomError } from '@/errors'; +import { createGoogleGenerativeAI } from '@ai-sdk/google'; import { Injectable } from '@nestjs/common'; +import { generateText, LanguageModel } from 'ai'; + +import { HelpersCoreAi } from '../ai.helpers'; @Injectable() -export class AiService { - test() { - return 'test'; +export class AiService extends HelpersCoreAi { + private getModel(): LanguageModel { + const aiCredentials = this.getAiCredentials(); + + const google = createGoogleGenerativeAI({ + apiKey: aiCredentials.key, + }); + + return google('gemini-1.0-pro'); + } + + async generateText(args: Omit[0], 'model'>) { + try { + const result = await generateText({ + model: this.getModel(), + ...args, + }); + + return result; + } catch (error) { + const err = error as Error; + throw new CustomError({ + code: 'AI_ERROR', + message: err.message, + }); + } } } diff --git a/packages/backend/src/core/ai/test/test.service.ts b/packages/backend/src/core/ai/test/test.service.ts index ad05f131b..7301eeaca 100644 --- a/packages/backend/src/core/ai/test/test.service.ts +++ b/packages/backend/src/core/ai/test/test.service.ts @@ -1,7 +1,4 @@ -import { CustomError } from '@/errors'; -import { createGoogleGenerativeAI } from '@ai-sdk/google'; import { Injectable } from '@nestjs/common'; -import { generateText } from 'ai'; import { AiService } from '../provider/ai.service'; @@ -10,23 +7,10 @@ export class TestCoreAiService { constructor(private readonly aiService: AiService) {} async test(): Promise { - const google = createGoogleGenerativeAI({ - apiKey: '', + const results = await this.aiService.generateText({ + prompt: 'Tell me a joke', }); - try { - const result = await generateText({ - model: google('gemini-1.0-pro'), - prompt: 'Tell me a joke', - }); - } catch (error) { - const err = error as Error; - throw new CustomError({ - code: 'AI_ERROR', - message: err.message, - }); - } - return 'test'; } } diff --git a/packages/frontend/src/graphql/mutations/admin/ai/admin__core_ai__edit.generated.ts b/packages/frontend/src/graphql/mutations/admin/ai/admin__core_ai__edit.generated.ts new file mode 100644 index 000000000..c7683e6d4 --- /dev/null +++ b/packages/frontend/src/graphql/mutations/admin/ai/admin__core_ai__edit.generated.ts @@ -0,0 +1,19 @@ +import * as Types from '../../../types'; + +import gql from 'graphql-tag'; +export type Admin__Core_Ai__EditMutationVariables = Types.Exact<{ + provider: Types.AiProvider; + key?: Types.InputMaybe; +}>; + + +export type Admin__Core_Ai__EditMutation = { __typename?: 'Mutation', admin__core_ai__edit: { __typename?: 'ShowAdminCoreAiObj', provider: Types.AiProvider } }; + + +export const Admin__Core_Ai__Edit = gql` + mutation Admin__core_ai__edit($provider: AiProvider!, $key: String) { + admin__core_ai__edit(provider: $provider, key: $key) { + provider + } +} + `; \ No newline at end of file diff --git a/packages/frontend/src/graphql/mutations/admin/ai/admin__core_ai__edit.gql b/packages/frontend/src/graphql/mutations/admin/ai/admin__core_ai__edit.gql new file mode 100644 index 000000000..ac4420eb1 --- /dev/null +++ b/packages/frontend/src/graphql/mutations/admin/ai/admin__core_ai__edit.gql @@ -0,0 +1,5 @@ +mutation Admin__core_ai__edit($provider: AiProvider!, $key: String) { + admin__core_ai__edit(provider: $provider, key: $key) { + provider + } +} diff --git a/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.generated.ts b/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.generated.ts index 7f01182f2..855f6c730 100644 --- a/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.generated.ts +++ b/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.generated.ts @@ -4,13 +4,12 @@ import gql from 'graphql-tag'; export type Admin__Core_Ai__ShowQueryVariables = Types.Exact<{ [key: string]: never; }>; -export type Admin__Core_Ai__ShowQuery = { __typename?: 'Query', admin__core_ai__show: { __typename?: 'ShowAdminCoreAiObj', key: string, provider: Types.AiProvider } }; +export type Admin__Core_Ai__ShowQuery = { __typename?: 'Query', admin__core_ai__show: { __typename?: 'ShowAdminCoreAiObj', provider: Types.AiProvider } }; export const Admin__Core_Ai__Show = gql` query Admin__core_ai__show { admin__core_ai__show { - key provider } } diff --git a/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.gql b/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.gql index a39455fab..887d7221f 100644 --- a/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.gql +++ b/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__show.gql @@ -1,6 +1,5 @@ query Admin__core_ai__show { admin__core_ai__show { - key provider } } diff --git a/packages/frontend/src/graphql/types.ts b/packages/frontend/src/graphql/types.ts index 809781b8e..fb0b14b04 100644 --- a/packages/frontend/src/graphql/types.ts +++ b/packages/frontend/src/graphql/types.ts @@ -266,6 +266,7 @@ export type LogsAdminEmailObj = { export type Mutation = { __typename?: 'Mutation'; + admin__core_ai__edit: ShowAdminCoreAiObj; admin__core_authorization_settings__edit: ShowAdminAuthorizationSettingsObj; admin__core_email_settings__edit: ShowAdminEmailSettingsServiceObj; admin__core_email_settings__test: Scalars['String']['output']; @@ -319,6 +320,12 @@ export type Mutation = { }; +export type MutationAdmin__Core_Ai__EditArgs = { + key?: InputMaybe; + provider: AiProvider; +}; + + export type MutationAdmin__Core_Authorization_Settings__EditArgs = { force_login: Scalars['Boolean']['input']; lock_register: Scalars['Boolean']['input']; @@ -845,7 +852,6 @@ export type ShowAdminCaptchaSecurityObj = { export type ShowAdminCoreAiObj = { __typename?: 'ShowAdminCoreAiObj'; - key: Scalars['String']['output']; provider: AiProvider | `${AiProvider}`; }; diff --git a/packages/frontend/src/views/admin/views/core/ai/providers/content.tsx b/packages/frontend/src/views/admin/views/core/ai/providers/content.tsx index 19a2c1449..a6f682767 100644 --- a/packages/frontend/src/views/admin/views/core/ai/providers/content.tsx +++ b/packages/frontend/src/views/admin/views/core/ai/providers/content.tsx @@ -1,26 +1,36 @@ 'use client'; -import { AutoForm } from '@/components/form/auto-form'; +import { AutoForm, DependencyType } from '@/components/form/auto-form'; +import { + AutoFormInput, + AutoFormInputProps, +} from '@/components/form/fields/input'; import { AutoFormRadioGroup, AutoFormRadioGroupProps, } from '@/components/form/fields/radio-group'; import { Admin__Core_Ai__ShowQuery } from '@/graphql/queries/admin/ai/admin__core_ai__show.generated'; -import { AiProvider } from '@/graphql/types'; import { useTranslations } from 'next-intl'; -import * as z from 'zod'; + +import { useProvidersAiFormAdmin } from './hooks/use-providers-ai-form-admin'; export const ContentProvidersAiAdmin = ({ admin__core_ai__show: data, }: Admin__Core_Ai__ShowQuery) => { const t = useTranslations('admin.core.ai'); - const formSchema = z.object({ - provider: z.nativeEnum(AiProvider).default(data.provider), - }); + const { formSchema, onSubmit } = useProvidersAiFormAdmin(data); return ( <> provider === 'none', + }, + ]} fields={[ { id: 'provider', @@ -41,8 +51,18 @@ export const ContentProvidersAiAdmin = ({ }, } as AutoFormRadioGroupProps, }, + { + id: 'key', + component: AutoFormInput, + label: t('key'), + componentProps: { + placeholder: '**********', + type: 'password', + } as AutoFormInputProps, + }, ]} formSchema={formSchema} + onSubmit={onSubmit} theme="horizontal" /> diff --git a/packages/frontend/src/views/admin/views/core/ai/providers/hooks/mutation-api.ts b/packages/frontend/src/views/admin/views/core/ai/providers/hooks/mutation-api.ts new file mode 100644 index 000000000..93bbee9a4 --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/ai/providers/hooks/mutation-api.ts @@ -0,0 +1,29 @@ +'use server'; + +import { fetcher } from '@/graphql/fetcher'; +import { + Admin__Core_Ai__Edit, + Admin__Core_Ai__EditMutation, + Admin__Core_Ai__EditMutationVariables, +} from '@/graphql/mutations/admin/ai/admin__core_ai__edit.generated'; +import { revalidatePath } from 'next/cache'; + +export const mutationApi = async ( + variables: Admin__Core_Ai__EditMutationVariables, +) => { + try { + await fetcher< + Admin__Core_Ai__EditMutation, + Admin__Core_Ai__EditMutationVariables + >({ + query: Admin__Core_Ai__Edit, + variables, + }); + + revalidatePath('/', 'layout'); + } catch (error) { + const e = error as Error; + + return { error: e.message }; + } +}; diff --git a/packages/frontend/src/views/admin/views/core/ai/providers/hooks/use-providers-ai-form-admin.ts b/packages/frontend/src/views/admin/views/core/ai/providers/hooks/use-providers-ai-form-admin.ts new file mode 100644 index 000000000..2e0e29380 --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/ai/providers/hooks/use-providers-ai-form-admin.ts @@ -0,0 +1,32 @@ +import { Admin__Core_Ai__ShowQuery } from '@/graphql/queries/admin/ai/admin__core_ai__show.generated'; +import { AiProvider } from '@/graphql/types'; +import { useTranslations } from 'next-intl'; +import { toast } from 'sonner'; +import * as z from 'zod'; + +import { mutationApi } from './mutation-api'; + +export const useProvidersAiFormAdmin = ( + data: Admin__Core_Ai__ShowQuery['admin__core_ai__show'], +) => { + const t = useTranslations('core'); + const formSchema = z.object({ + provider: z.nativeEnum(AiProvider).default(data.provider), + key: z.string().optional(), + }); + + const onSubmit = async (values: z.infer) => { + const mutation = await mutationApi(values); + if (mutation?.error) { + toast.error(t('errors.title'), { + description: t('errors.internal_server_error'), + }); + + return; + } + + toast.success(t('saved_success')); + }; + + return { formSchema, onSubmit }; +}; diff --git a/packages/frontend/src/views/admin/views/core/settings/email/hooks/use-email-settings-form-admin.ts b/packages/frontend/src/views/admin/views/core/settings/email/hooks/use-email-settings-form-admin.ts index d9c79ea04..1af6cce17 100644 --- a/packages/frontend/src/views/admin/views/core/settings/email/hooks/use-email-settings-form-admin.ts +++ b/packages/frontend/src/views/admin/views/core/settings/email/hooks/use-email-settings-form-admin.ts @@ -42,6 +42,7 @@ export const useEmailSettingsFormAdmin = ({ resend_key: z.string().default('').optional(), from: z.string().default(data.from), }); + const onSubmit = async ( values: z.infer, form: UseFormReturn>, From 14db5a5f2a4489e8b980e63bdfa5951a78d965b0 Mon Sep 17 00:00:00 2001 From: aXenDeveloper Date: Mon, 30 Sep 2024 19:22:23 +0200 Subject: [PATCH 5/6] feat: Add dialog for testing AI in AdminCP --- apps/backend/schema.gql | 2 +- .../admin/(auth)/(vitnode)/core/ai/layout.tsx | 7 -- .../admin/(auth)/(vitnode)/core/ai/page.tsx | 5 - .../(vitnode)/core/ai/settings/page.tsx | 10 ++ apps/frontend/src/graphql/types.ts | 7 +- apps/frontend/src/plugins/admin/langs/en.json | 36 +++++--- apps/frontend/src/plugins/core/langs/en.json | 1 + .../backend/src/core/admin/ai/ai.module.ts | 4 + .../src/core/admin/ai/test/test.resolver.ts | 15 +++ .../core/{ => admin}/ai/test/test.service.ts | 11 +-- .../src/core/admin/nav/show/show.service.ts | 20 ++-- packages/backend/src/core/ai/ai.module.ts | 7 -- .../src/core/ai/provider/ai.service.ts | 8 ++ .../backend/src/core/ai/test/test.resolver.ts | 13 --- packages/backend/src/core/core.module.ts | 3 +- .../ai/admin__core_ai__test.generated.ts | 16 ++++ .../queries/admin/ai/admin__core_ai__test.gql | 3 + packages/frontend/src/graphql/types.ts | 7 +- .../core/ai/actions/testing/mutation-api.ts | 28 ++++++ .../views/core/ai/actions/testing/testing.tsx | 92 ++++++++++++++++++- .../src/views/admin/views/core/ai/layout.tsx | 37 -------- .../ai/providers/providers-ai-admin-view.tsx | 8 -- .../src/views/admin/views/core/ai/query.ts | 21 ----- .../ai/{providers => settings}/content.tsx | 38 ++++++-- .../hooks/mutation-api.ts | 0 .../hooks/use-settings-ai-form-admin.ts} | 6 +- .../ai/settings/settings-ai-admin-view.tsx | 59 ++++++++++++ 27 files changed, 317 insertions(+), 147 deletions(-) delete mode 100644 apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/layout.tsx delete mode 100644 apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/page.tsx create mode 100644 apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/settings/page.tsx create mode 100644 packages/backend/src/core/admin/ai/test/test.resolver.ts rename packages/backend/src/core/{ => admin}/ai/test/test.service.ts (50%) delete mode 100644 packages/backend/src/core/ai/test/test.resolver.ts create mode 100644 packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__test.generated.ts create mode 100644 packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__test.gql create mode 100644 packages/frontend/src/views/admin/views/core/ai/actions/testing/mutation-api.ts delete mode 100644 packages/frontend/src/views/admin/views/core/ai/layout.tsx delete mode 100644 packages/frontend/src/views/admin/views/core/ai/providers/providers-ai-admin-view.tsx delete mode 100644 packages/frontend/src/views/admin/views/core/ai/query.ts rename packages/frontend/src/views/admin/views/core/ai/{providers => settings}/content.tsx (56%) rename packages/frontend/src/views/admin/views/core/ai/{providers => settings}/hooks/mutation-api.ts (100%) rename packages/frontend/src/views/admin/views/core/ai/{providers/hooks/use-providers-ai-form-admin.ts => settings/hooks/use-settings-ai-form-admin.ts} (86%) create mode 100644 packages/frontend/src/views/admin/views/core/ai/settings/settings-ai-admin-view.tsx diff --git a/apps/backend/schema.gql b/apps/backend/schema.gql index 09682106a..6bebacdd7 100644 --- a/apps/backend/schema.gql +++ b/apps/backend/schema.gql @@ -232,6 +232,7 @@ type LogsAdminEmailObj { type Mutation { admin__core_ai__edit(key: String, provider: AiProvider!): ShowAdminCoreAiObj! + admin__core_ai__test(prompt: String!): String! admin__core_authorization_settings__edit(force_login: Boolean!, lock_register: Boolean!, require_confirm_email: Boolean!): ShowAdminAuthorizationSettingsObj! admin__core_email_settings__edit(color_primary: String!, color_primary_foreground: String!, from: String!, logo: UploadWithKeepCoreFilesArgs, provider: EmailProvider!, resend_key: String, smtp: SMTPEditAdminEmailSettingsService): ShowAdminEmailSettingsServiceObj! admin__core_email_settings__test(message: String!, preview_text: String, subject: String!, to: String!): String! @@ -273,7 +274,6 @@ type Mutation { admin__core_theme_editor__edit(colors: ColorsEditAdminThemeEditor, logos: LogosEditAdminThemeEditor!): String! admin__install__create_database: String! admin_sessions__sign_out: String! - core_ai__test: String! core_editor_files__delete(id: Int!, security_key: String): String! core_editor_files__upload(file: Upload!, folder: String!, plugin: String!): ShowCoreFiles! core_members__avatar__delete: String! diff --git a/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/layout.tsx b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/layout.tsx deleted file mode 100644 index 0116e9ab3..000000000 --- a/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/layout.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { AiAdminLayout } from 'vitnode-frontend/views/admin/views/core/ai/layout'; - -export default function Layout( - props: React.ComponentProps, -) { - return ; -} diff --git a/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/page.tsx b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/page.tsx deleted file mode 100644 index f2c8cea9f..000000000 --- a/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { ProvidersAiAdminView } from 'vitnode-frontend/views/admin/views/core/ai/providers/providers-ai-admin-view'; - -export default function Page() { - return ; -} diff --git a/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/settings/page.tsx b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/settings/page.tsx new file mode 100644 index 000000000..c52f88b9f --- /dev/null +++ b/apps/frontend/src/app/[locale]/admin/(auth)/(vitnode)/core/ai/settings/page.tsx @@ -0,0 +1,10 @@ +import { + generateMetadataSettingsAiAdmin, + SettingsAiAdminView, +} from 'vitnode-frontend/views/admin/views/core/ai/settings/settings-ai-admin-view'; + +export const generateMetadata = generateMetadataSettingsAiAdmin; + +export default function Page() { + return ; +} diff --git a/apps/frontend/src/graphql/types.ts b/apps/frontend/src/graphql/types.ts index c65c9a687..feff07d4b 100644 --- a/apps/frontend/src/graphql/types.ts +++ b/apps/frontend/src/graphql/types.ts @@ -267,6 +267,7 @@ export type LogsAdminEmailObj = { export type Mutation = { __typename?: 'Mutation'; admin__core_ai__edit: ShowAdminCoreAiObj; + admin__core_ai__test: Scalars['String']['output']; admin__core_authorization_settings__edit: ShowAdminAuthorizationSettingsObj; admin__core_email_settings__edit: ShowAdminEmailSettingsServiceObj; admin__core_email_settings__test: Scalars['String']['output']; @@ -308,7 +309,6 @@ export type Mutation = { admin__core_theme_editor__edit: Scalars['String']['output']; admin__install__create_database: Scalars['String']['output']; admin_sessions__sign_out: Scalars['String']['output']; - core_ai__test: Scalars['String']['output']; core_editor_files__delete: Scalars['String']['output']; core_editor_files__upload: ShowCoreFiles; core_members__avatar__delete: Scalars['String']['output']; @@ -327,6 +327,11 @@ export type MutationAdmin__Core_Ai__EditArgs = { }; +export type MutationAdmin__Core_Ai__TestArgs = { + prompt: Scalars['String']['input']; +}; + + export type MutationAdmin__Core_Authorization_Settings__EditArgs = { force_login: Scalars['Boolean']['input']; lock_register: Scalars['Boolean']['input']; diff --git a/apps/frontend/src/plugins/admin/langs/en.json b/apps/frontend/src/plugins/admin/langs/en.json index 8fc7a81e8..ae6ae4fa3 100644 --- a/apps/frontend/src/plugins/admin/langs/en.json +++ b/apps/frontend/src/plugins/admin/langs/en.json @@ -498,21 +498,29 @@ } }, "ai": { - "title": "Artificial Intelligence (AI)", - "desc": "Integrate powerful AI to boost user experience with personalized content, smarter search, SEO optimization and more.", - "tabs": { - "provider": "Provider" - }, - "testing": { - "title": "Test Your AI" - }, - "key": "API Key", - "provider": { - "title": "Provider", - "desc": "Choose the AI provider to use in your website.", + "settings": { + "key": "API Key", + "title": "Settings (AI)", + "desc": "Integrate powerful Artificial Intelligence to boost user experience with personalized content, smarter search, SEO optimization and more.", "none": "None", - "google": "Google AI", - "openai": "OpenAI" + "google": { + "title": "Google AI", + "desc": "Read more on Google AI - VitNode Docs." + }, + "openai": { + "title": "OpenAI", + "desc": "Read more on OpenAI - VitNode Docs." + }, + "provider": "Provider", + "testing": { + "title": "Test Your AI", + "desc": "Here you can test your AI to see how it works.", + "response": "Response", + "response_success": "If you see the response, your AI is working correctly.", + "prompt": "Prompt", + "prompt_placeholder": "Ask a question...", + "submit": "Ask" + } } } }, diff --git a/apps/frontend/src/plugins/core/langs/en.json b/apps/frontend/src/plugins/core/langs/en.json index e4e6e5a20..30008a4a0 100644 --- a/apps/frontend/src/plugins/core/langs/en.json +++ b/apps/frontend/src/plugins/core/langs/en.json @@ -320,6 +320,7 @@ "settings_authorization": "Authorization", "settings_legal": "Legal & Policies", "ai": "AI", + "ai_settings": "Settings", "plugins": "Plugins", "styles": "Styles", "styles_theme-editor": "Theme Editor", diff --git a/packages/backend/src/core/admin/ai/ai.module.ts b/packages/backend/src/core/admin/ai/ai.module.ts index 185177c8e..099535686 100644 --- a/packages/backend/src/core/admin/ai/ai.module.ts +++ b/packages/backend/src/core/admin/ai/ai.module.ts @@ -4,6 +4,8 @@ import { EditAdminCoreAiResolver } from './edit/edit.resolver'; import { EditAdminCoreAiService } from './edit/edit.service'; import { ShowAdminCoreAiResolver } from './show/show.resolver'; import { ShowAdminCoreAiService } from './show/show.service'; +import { TestAdminCoreAiResolver } from './test/test.resolver'; +import { TestAdminCoreAiService } from './test/test.service'; @Module({ providers: [ @@ -11,6 +13,8 @@ import { ShowAdminCoreAiService } from './show/show.service'; ShowAdminCoreAiService, EditAdminCoreAiResolver, EditAdminCoreAiService, + TestAdminCoreAiResolver, + TestAdminCoreAiService, ], }) export class AdminAiModule {} diff --git a/packages/backend/src/core/admin/ai/test/test.resolver.ts b/packages/backend/src/core/admin/ai/test/test.resolver.ts new file mode 100644 index 000000000..952b2dddf --- /dev/null +++ b/packages/backend/src/core/admin/ai/test/test.resolver.ts @@ -0,0 +1,15 @@ +import { Args, Mutation, Resolver } from '@nestjs/graphql'; + +import { TestAdminCoreAiService } from './test.service'; + +@Resolver() +export class TestAdminCoreAiResolver { + constructor(private readonly service: TestAdminCoreAiService) {} + + @Mutation(() => String) + async admin__core_ai__test( + @Args('prompt', { type: () => String }) prompt: string, + ): Promise { + return this.service.test(prompt); + } +} diff --git a/packages/backend/src/core/ai/test/test.service.ts b/packages/backend/src/core/admin/ai/test/test.service.ts similarity index 50% rename from packages/backend/src/core/ai/test/test.service.ts rename to packages/backend/src/core/admin/ai/test/test.service.ts index 7301eeaca..a60cd4380 100644 --- a/packages/backend/src/core/ai/test/test.service.ts +++ b/packages/backend/src/core/admin/ai/test/test.service.ts @@ -1,16 +1,15 @@ +import { AiService } from '@/core/ai/provider/ai.service'; import { Injectable } from '@nestjs/common'; -import { AiService } from '../provider/ai.service'; - @Injectable() -export class TestCoreAiService { +export class TestAdminCoreAiService { constructor(private readonly aiService: AiService) {} - async test(): Promise { + async test(prompt: string): Promise { const results = await this.aiService.generateText({ - prompt: 'Tell me a joke', + prompt, }); - return 'test'; + return results.text; } } diff --git a/packages/backend/src/core/admin/nav/show/show.service.ts b/packages/backend/src/core/admin/nav/show/show.service.ts index e28b42dbe..399879310 100644 --- a/packages/backend/src/core/admin/nav/show/show.service.ts +++ b/packages/backend/src/core/admin/nav/show/show.service.ts @@ -89,13 +89,19 @@ export class ShowAdminNavService { { code: 'ai', icon: 'brain', - keywords: [ - 'artificial', - 'intelligence', - 'gpt', - 'openai', - 'google', - 'gemini', + keywords: [], + children: [ + { + code: 'settings', + keywords: [ + 'artificial', + 'intelligence', + 'gpt', + 'openai', + 'google', + 'gemini', + ], + }, ], }, { diff --git a/packages/backend/src/core/ai/ai.module.ts b/packages/backend/src/core/ai/ai.module.ts index 6d57b7eee..bda976619 100644 --- a/packages/backend/src/core/ai/ai.module.ts +++ b/packages/backend/src/core/ai/ai.module.ts @@ -1,8 +1,6 @@ import { Global, Module } from '@nestjs/common'; import { AiService } from './provider/ai.service'; -import { TestCoreAiResolver } from './test/test.resolver'; -import { TestCoreAiService } from './test/test.service'; @Global() @Module({ @@ -10,8 +8,3 @@ import { TestCoreAiService } from './test/test.service'; exports: [AiService], }) export class GlobalCoreAiModule {} - -@Module({ - providers: [TestCoreAiService, TestCoreAiResolver], -}) -export class CoreAiModule {} diff --git a/packages/backend/src/core/ai/provider/ai.service.ts b/packages/backend/src/core/ai/provider/ai.service.ts index 46f5ff3e4..4b9f4fd4a 100644 --- a/packages/backend/src/core/ai/provider/ai.service.ts +++ b/packages/backend/src/core/ai/provider/ai.service.ts @@ -1,4 +1,5 @@ import { CustomError } from '@/errors'; +import { AiProvider } from '@/providers'; import { createGoogleGenerativeAI } from '@ai-sdk/google'; import { Injectable } from '@nestjs/common'; import { generateText, LanguageModel } from 'ai'; @@ -10,6 +11,13 @@ export class AiService extends HelpersCoreAi { private getModel(): LanguageModel { const aiCredentials = this.getAiCredentials(); + if (aiCredentials.provider === AiProvider.none) { + throw new CustomError({ + code: 'AI_PROVIDER_NOT_SET', + message: 'AI provider is not set.', + }); + } + const google = createGoogleGenerativeAI({ apiKey: aiCredentials.key, }); diff --git a/packages/backend/src/core/ai/test/test.resolver.ts b/packages/backend/src/core/ai/test/test.resolver.ts deleted file mode 100644 index 61d22a149..000000000 --- a/packages/backend/src/core/ai/test/test.resolver.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Mutation, Resolver } from '@nestjs/graphql'; - -import { TestCoreAiService } from './test.service'; - -@Resolver() -export class TestCoreAiResolver { - constructor(private readonly service: TestCoreAiService) {} - - @Mutation(() => String) - async core_ai__test(): Promise { - return this.service.test(); - } -} diff --git a/packages/backend/src/core/core.module.ts b/packages/backend/src/core/core.module.ts index 2f1f84da1..5a1155fc9 100644 --- a/packages/backend/src/core/core.module.ts +++ b/packages/backend/src/core/core.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { AdminModule } from './admin/admin.module'; -import { CoreAiModule, GlobalCoreAiModule } from './ai/ai.module'; +import { GlobalCoreAiModule } from './ai/ai.module'; import { CoreEditorModule } from './editor/editor.module'; import { CoreFilesModule, GlobalCoreFilesModule } from './files/files.module'; import { GlobalCoreHelpersModule } from './helpers/helpers.module'; @@ -35,7 +35,6 @@ import { CoreThemeEditorModule } from './theme_editor/theme_editor.module'; CoreSettingsModule, CoreThemeEditorModule, TermsCoreModule, - CoreAiModule, GlobalCoreAiModule, ], }) diff --git a/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__test.generated.ts b/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__test.generated.ts new file mode 100644 index 000000000..326409bed --- /dev/null +++ b/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__test.generated.ts @@ -0,0 +1,16 @@ +import * as Types from '../../../types'; + +import gql from 'graphql-tag'; +export type Admin__Core_Ai__TestMutationVariables = Types.Exact<{ + prompt: Types.Scalars['String']['input']; +}>; + + +export type Admin__Core_Ai__TestMutation = { __typename?: 'Mutation', admin__core_ai__test: string }; + + +export const Admin__Core_Ai__Test = gql` + mutation Admin__core_ai__test($prompt: String!) { + admin__core_ai__test(prompt: $prompt) +} + `; \ No newline at end of file diff --git a/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__test.gql b/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__test.gql new file mode 100644 index 000000000..df074b3da --- /dev/null +++ b/packages/frontend/src/graphql/queries/admin/ai/admin__core_ai__test.gql @@ -0,0 +1,3 @@ +mutation Admin__core_ai__test($prompt: String!) { + admin__core_ai__test(prompt: $prompt) +} diff --git a/packages/frontend/src/graphql/types.ts b/packages/frontend/src/graphql/types.ts index c65c9a687..feff07d4b 100644 --- a/packages/frontend/src/graphql/types.ts +++ b/packages/frontend/src/graphql/types.ts @@ -267,6 +267,7 @@ export type LogsAdminEmailObj = { export type Mutation = { __typename?: 'Mutation'; admin__core_ai__edit: ShowAdminCoreAiObj; + admin__core_ai__test: Scalars['String']['output']; admin__core_authorization_settings__edit: ShowAdminAuthorizationSettingsObj; admin__core_email_settings__edit: ShowAdminEmailSettingsServiceObj; admin__core_email_settings__test: Scalars['String']['output']; @@ -308,7 +309,6 @@ export type Mutation = { admin__core_theme_editor__edit: Scalars['String']['output']; admin__install__create_database: Scalars['String']['output']; admin_sessions__sign_out: Scalars['String']['output']; - core_ai__test: Scalars['String']['output']; core_editor_files__delete: Scalars['String']['output']; core_editor_files__upload: ShowCoreFiles; core_members__avatar__delete: Scalars['String']['output']; @@ -327,6 +327,11 @@ export type MutationAdmin__Core_Ai__EditArgs = { }; +export type MutationAdmin__Core_Ai__TestArgs = { + prompt: Scalars['String']['input']; +}; + + export type MutationAdmin__Core_Authorization_Settings__EditArgs = { force_login: Scalars['Boolean']['input']; lock_register: Scalars['Boolean']['input']; diff --git a/packages/frontend/src/views/admin/views/core/ai/actions/testing/mutation-api.ts b/packages/frontend/src/views/admin/views/core/ai/actions/testing/mutation-api.ts new file mode 100644 index 000000000..2ab0b0ba7 --- /dev/null +++ b/packages/frontend/src/views/admin/views/core/ai/actions/testing/mutation-api.ts @@ -0,0 +1,28 @@ +'use server'; + +import { fetcher } from '@/graphql/fetcher'; +import { + Admin__Core_Ai__Test, + Admin__Core_Ai__TestMutation, + Admin__Core_Ai__TestMutationVariables, +} from '@/graphql/queries/admin/ai/admin__core_ai__test.generated'; + +export const mutationApi = async ( + variables: Admin__Core_Ai__TestMutationVariables, +) => { + try { + const data = await fetcher< + Admin__Core_Ai__TestMutation, + Admin__Core_Ai__TestMutationVariables + >({ + query: Admin__Core_Ai__Test, + variables, + }); + + return { data: data.admin__core_ai__test }; + } catch (error) { + const e = error as Error; + + return { error: e.message }; + } +}; diff --git a/packages/frontend/src/views/admin/views/core/ai/actions/testing/testing.tsx b/packages/frontend/src/views/admin/views/core/ai/actions/testing/testing.tsx index c7cd17f11..84211d9d9 100644 --- a/packages/frontend/src/views/admin/views/core/ai/actions/testing/testing.tsx +++ b/packages/frontend/src/views/admin/views/core/ai/actions/testing/testing.tsx @@ -1,16 +1,98 @@ 'use client'; +import { AutoForm } from '@/components/form/auto-form'; +import { + AutoFormInput, + AutoFormInputProps, +} from '@/components/form/fields/input'; import { Button } from '@/components/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; import { FlaskConical } from 'lucide-react'; import { useTranslations } from 'next-intl'; +import React from 'react'; +import { toast } from 'sonner'; +import { z } from 'zod'; + +import { mutationApi } from './mutation-api'; export const TestingActionAiAdmin = ({ disabled }: { disabled: boolean }) => { - const t = useTranslations('admin.core.ai.testing'); + const t = useTranslations('admin.core.ai.settings.testing'); + const tCore = useTranslations('core.global.errors'); + const [response, setResponse] = React.useState(''); + + const formSchema = z.object({ + prompt: z.string().default(''), + }); + + const onSubmit = async (values: z.infer) => { + const mutation = await mutationApi(values); + + if (mutation.error || !mutation.data) { + toast.error(tCore('title'), { + description: tCore('internal_server_error'), + }); + + return; + } + + setResponse(mutation.data); + }; return ( - + + + + + + + + {t('title')} + {t('desc')} + + + } + > + {response && ( +
+ +