Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add AI providers #524

Merged
merged 8 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.17 | 20 |
Expand Down
14 changes: 14 additions & 0 deletions apps/backend/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
# ------------------------------------------------------

enum AiProvider {
google
none
openai
}

enum AllowTypeFilesEnum {
all
images
Expand Down Expand Up @@ -225,6 +231,8 @@ type LogsAdminEmailObj {
}

type Mutation {
admin__core_ai__edit(key: String, model: 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!
Expand Down Expand Up @@ -295,6 +303,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!
Expand Down Expand Up @@ -355,6 +364,11 @@ type ShowAdminCaptchaSecurityObj {
type: CaptchaTypeEnum!
}

type ShowAdminCoreAiObj {
model: String
provider: AiProvider!
}

type ShowAdminEmailSettingsServiceObj {
color_primary: String!
from: String!
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <SettingsAiAdminView />;
}
28 changes: 28 additions & 0 deletions apps/frontend/src/graphql/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -259,6 +266,8 @@ 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'];
Expand Down Expand Up @@ -312,6 +321,18 @@ export type Mutation = {
};


export type MutationAdmin__Core_Ai__EditArgs = {
key?: InputMaybe<Scalars['String']['input']>;
model: Scalars['String']['input'];
provider: AiProvider;
};


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'];
Expand Down Expand Up @@ -665,6 +686,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;
Expand Down Expand Up @@ -850,6 +872,12 @@ export type ShowAdminCaptchaSecurityObj = {
type: CaptchaTypeEnum | `${CaptchaTypeEnum}`;
};

export type ShowAdminCoreAiObj = {
__typename?: 'ShowAdminCoreAiObj';
model?: Maybe<Scalars['String']['output']>;
provider: AiProvider | `${AiProvider}`;
};

export type ShowAdminEmailSettingsServiceObj = {
__typename?: 'ShowAdminEmailSettingsServiceObj';
color_primary: Scalars['String']['output'];
Expand Down
27 changes: 27 additions & 0 deletions apps/frontend/src/plugins/admin/langs/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,33 @@
"submit": "Yes, delete file"
}
}
},
"ai": {
"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": {
"title": "Google AI",
"desc": "Read more on <link>Google AI - VitNode Docs</link>."
},
"openai": {
"title": "OpenAI",
"desc": "Read more on <link>OpenAI - VitNode Docs</link>."
},
"model": "Model",
"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"
}
}
}
},
"members": {
Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/src/plugins/core/langs/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,8 @@
"settings_email": "Email",
"settings_authorization": "Authorization",
"settings_legal": "Legal & Policies",
"ai": "AI",
"ai_settings": "Settings",
"plugins": "Plugins",
"styles": "Styles",
"styles_theme-editor": "Theme Editor",
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -68,6 +70,7 @@
"@react-email/render": "^1.0.1",
"@swc/cli": "^0.4.0",
"@swc/core": "^1.7.28",
"ai": "^3.4.0",
"busboy": "^1.6.0",
"cookie-parser": "^1.4.6",
"helmet": "^8.0.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/core/admin/admin.module.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -42,6 +43,7 @@ import { AdminThemeEditorModule } from './theme_editor/theme_editor.module';
AdminEmailModule,
AdminSecurityModule,
AdminNavModule,
AdminAiModule,
],
})
export class AdminModule {}
20 changes: 20 additions & 0 deletions packages/backend/src/core/admin/ai/ai.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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';
import { TestAdminCoreAiResolver } from './test/test.resolver';
import { TestAdminCoreAiService } from './test/test.service';

@Module({
providers: [
ShowAdminCoreAiResolver,
ShowAdminCoreAiService,
EditAdminCoreAiResolver,
EditAdminCoreAiService,
TestAdminCoreAiResolver,
TestAdminCoreAiService,
],
})
export class AdminAiModule {}
14 changes: 14 additions & 0 deletions packages/backend/src/core/admin/ai/edit/edit.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AiProvider } from '@/providers';
import { ArgsType, Field } from '@nestjs/graphql';

@ArgsType()
export class EditAdminCoreAiArgs {
@Field(() => String, { nullable: true })
key: null | string;

@Field(() => String)
model: string;

@Field(() => AiProvider)
provider: AiProvider;
}
20 changes: 20 additions & 0 deletions packages/backend/src/core/admin/ai/edit/edit.resolver.ts
Original file line number Diff line number Diff line change
@@ -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<ShowAdminCoreAiObj> {
return this.service.edit(args);
}
}
47 changes: 47 additions & 0 deletions packages/backend/src/core/admin/ai/edit/edit.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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,
model,
}: EditAdminCoreAiArgs): Promise<ShowAdminCoreAiObj> {
const configSettings = getConfigFile();

// Update settings
const newData: ConfigType = {
...configSettings,
ai: {
...configSettings.ai,
provider,
model,
},
};
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();
}
}
15 changes: 15 additions & 0 deletions packages/backend/src/core/admin/ai/show/show.dto.ts
Original file line number Diff line number Diff line change
@@ -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, { nullable: true })
model?: string;

@Field(() => AiProvider)
provider: AiProvider;
}
17 changes: 17 additions & 0 deletions packages/backend/src/core/admin/ai/show/show.resolver.ts
Original file line number Diff line number Diff line change
@@ -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();
}
}
13 changes: 13 additions & 0 deletions packages/backend/src/core/admin/ai/show/show.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Injectable } from '@nestjs/common';

import { HelpersCoreAi } from '../../../ai/ai.helpers';
import { ShowAdminCoreAiObj } from './show.dto';

@Injectable()
export class ShowAdminCoreAiService extends HelpersCoreAi {
show(): ShowAdminCoreAiObj {
const data = this.getAiCredentials();

return data;
}
}
15 changes: 15 additions & 0 deletions packages/backend/src/core/admin/ai/test/test.resolver.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
return this.service.test(prompt);
}
}
Loading
Loading