Skip to content
Merged

Test #65

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
118 changes: 118 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,119 @@
# ChatAPI
ChatAPI is a robust AI chatbot application designed to deliver seamless and customizable interactions with AI models. Developed using Node.js, Express, and Handlebars, it supports multiple AI providers and offers an intuitive interface for managing chat histories, settings, and more.

## Key Features

- **Multi-Model Compatibility**: Supports AI models like Gemini, NVIDIA, and Mallow.
- **Customizable User Experience**: Adjust themes, font sizes, temperature, and model preferences.
- **Chat History Management**: Save, retrieve, and delete chat histories effortlessly.
- **Modern Interface**: Clean, responsive design with features like skeleton loading, copy-to-clipboard for code blocks, and styled tables.
- **Admin Dashboard**: Manage AI model configurations and quotas with ease.
- **Robust Error Handling**: Comprehensive error management for API requests and database operations.

## Technology Stack

- **Backend**: Node.js, Express.js
- **Database**: PostgreSQL
- **AI Integration**: Google Gemini API
- **Authentication**: Custom-built authentication system
- **Frontend**: Handlebars templating engine
- **Styling**: Custom CSS
- **Performance Enhancements**: Compression and minification enabled

## Prerequisites

- Node.js (v14 or higher)
- PostgreSQL database
- Google Cloud Platform account (for Gemini API)
- Properly configured environment variables

## Environment Variables

Create a `.env` file in the root directory with the following variables:

```env
NEON_POSTGRES=your_postgres_connection_string
Main_SECRET_TOKEN=your_secret_token
session_seceret_key=your_session_secret
IsDeployed=true_or_false
UserCredentialTable=your_credentials_table_name
RECAPTCHA_SECRET_KEY=your_recaptcha_secret
GEMINI_API_KEY=your_gemini_api_key
GOOGLE_APPLICATION_CREDENTIALS=path_to_google_credentials
```

## Get Free API

### Gemini
https://aistudio.google.com/app/apikey
### Nvidia(llama)


## Installation Guide

1. Clone the repository:
```bash
git clone https://github.com/MIbnEKhalid/ChatAPI
cd ChatAPI
```

2. Install dependencies:
```bash
npm install
```

3. Set up the database:
- Update the connection strings in your `.env` file.
- [`routes/pool.js`](routes/pool.js) will automatically create tables in db from [`model/db.sql`](model/db.sql)

4. Start the development server:
```bash
npm run dev
```

The application will be accessible at `http://localhost:3030`.

## API Endpoints

### Chat Endpoints
- `POST /api/chat` - Interact with the AI.
- `GET /api/chat/history` - Retrieve chat history.
- `GET /api/chat/history/:id` - Fetch specific chat history.
- `POST /api/chat/history` - Save chat history.
- `DELETE /api/chat/history/:id` - Remove chat history.

### User Settings
- `GET /api/settings` - Retrieve user settings.
- `POST /api/settings` - Update user settings.

### Authentication
- `POST /api/auth/login` - Log in a user.
- `POST /api/auth/logout` - Log out a user.
- `GET /api/auth/check` - Verify authentication status.

## Project Structure

```
├── routes/ # API routes and controllers
├── views/ # Handlebars templates
├── public/ # Static assets
├── documentation/ # Project documentation
├── index.js # Main application file
└── package.json # Project dependencies
```

## Contribution Guidelines

1. Fork the repository.
2. Create a feature branch (`git checkout -b feature/AmazingFeature`).
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`).
4. Push to the branch (`git push origin feature/AmazingFeature`).
5. Open a Pull Request.

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

## Authors

Developed by Muhammad Bin Khalid (MIbnEKhalid) and Maaz Waheed (42Wor) at mbktechstudio.
50 changes: 0 additions & 50 deletions documentation/database.md

This file was deleted.

110 changes: 90 additions & 20 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from "path";
import { fileURLToPath } from "url";
import dotenv from "dotenv";
import mainRoutes from "./routes/main.js";
import dashboardRoutes from "./routes/dashboard.js";
import { engine } from "express-handlebars";
import Handlebars from "handlebars";
import minifyHTML from "express-minify-html";
Expand Down Expand Up @@ -36,28 +37,92 @@ router.use(
);
// Configure Handlebars
router.engine("handlebars", engine({
defaultLayout: false,
partialsDir: [
path.join(__dirname, "views/templates"),
path.join(__dirname, "views/notice"),
path.join(__dirname, "views")
],
cache: false,
helpers: { // <-- ADD THIS helpers OBJECT
eq: function (a, b) { // <-- Move your helpers inside here
return a === b;
},
encodeURIComponent: function (str) {
return encodeURIComponent(str);
},
formatTimestamp: function (timestamp) {
return new Date(timestamp).toLocaleString();
},
jsonStringify: function (context) {
return JSON.stringify(context);
}
defaultLayout: false,
partialsDir: [
path.join(__dirname, "views/templates"),
path.join(__dirname, "views/notice"),
path.join(__dirname, "views")
],
cache: false,
helpers: { // <-- ADD THIS helpers OBJECT
eq: function (a, b) { // <-- Move your helpers inside here
return a === b;
},
encodeURIComponent: function (str) {
return encodeURIComponent(str);
},
formatTimestamp: function (timestamp) {
return new Date(timestamp).toLocaleString();
},
jsonStringify: function (context) {
return JSON.stringify(context);
},
percentage: function (used, total) {
if (total === 0) return 0;
return Math.round((used / total) * 100);
}, formatDate: function (dateString) {
if (!dateString) return '';
const date = new Date(dateString);
return date.toLocaleString();
},
gt: function (a, b) {
return a > b;
},
lt: function (a, b) {
return a < b;
},
add: function (a, b) {
return a + b;
},
subtract: function (a, b) {
return a - b;
},
range: function (start, end) {
const result = [];
for (let i = start; i <= end; i++) {
result.push(i);
}
return result;
},
formatTime: function (index) {
// Simple implementation - in a real app you might want actual timestamps
return `Message ${index + 1}`;
},
and: function (...args) {
// Remove the options object that is provided by Handlebars
const options = args.pop();
return args.every(Boolean);
},
neq: function (a, b, options) {
if (options && typeof options.fn === 'function') {
return a !== b ? options.fn(this) : options.inverse(this);
}
// Fallback for inline usage
return a !== b;
},
truncate: function (str, len) {
if (typeof str !== "string") return "";
// Default length = 50 if not provided
const limit = len || 50;
return str.length > limit ? str.substring(0, limit) + "..." : str;
},
formatUptime: function () { // <-- New helper
const uptime = process.uptime();
const h = Math.floor(uptime / 3600);
const m = Math.floor((uptime % 3600) / 60);
const s = Math.floor(uptime % 60);
return `${h}h ${m}m ${s}s`;
}
}
}));

Handlebars.registerHelper('divide', function (value, divisor, multiplier) {
if (divisor == 0) {
return 0;
}
return (value / divisor) * multiplier;
});

router.set("view engine", "handlebars");
router.set("views", path.join(__dirname, "views"));

Expand Down Expand Up @@ -101,6 +166,11 @@ router.get("/info/Credits", async (req, res) => {
router.use(mbkAuthRouter);

router.use("/", mainRoutes);
router.use("/", dashboardRoutes);

router.get("/admin/*", async (req, res) => {
res.redirect("/admin/dashboard");
});

router.get('/simulate-error', (req, res, next) => {
next(new Error('Simulated router error'));
Expand Down
65 changes: 65 additions & 0 deletions model/db.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE IF NOT EXISTS ai_history_chatapi (
id SERIAL PRIMARY KEY,
conversation_id UUID NOT NULL DEFAULT uuid_generate_v4(),
conversation_history JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ,
temperature FLOAT DEFAULT 1.0,
username TEXT
);
CREATE INDEX IF NOT EXISTS idx_ai_history_chatapi_username ON ai_history_chatapi(username);
CREATE INDEX IF NOT EXISTS idx_ai_history_chatapi_id ON ai_history_chatapi(id);
CREATE INDEX IF NOT EXISTS idx_ai_history_chatapi_created_at ON ai_history_chatapi(created_at);
CREATE INDEX IF NOT EXISTS idx_ai_history_chatapi_conversation_id ON ai_history_chatapi(conversation_id);
CREATE INDEX IF NOT EXISTS idx_ai_history_chatapi_conversation_history ON ai_history_chatapi USING gin (conversation_history);

UPDATE ai_history_chatapi
SET updated_at = created_at
WHERE updated_at IS NULL;

CREATE TABLE IF NOT EXISTS user_settings_chatapi (
id SERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
theme VARCHAR(20) DEFAULT 'dark',
font_size INTEGER DEFAULT 16,
ai_model VARCHAR(50) DEFAULT 'default',
temperature FLOAT DEFAULT 1.0,
daily_message_limit INTEGER DEFAULT 100,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_user_settings_chatapi_username ON user_settings_chatapi(username);

CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_trigger
WHERE tgname = 'update_user_settings_chatapi_updated_at'
) THEN
CREATE TRIGGER update_user_settings_chatapi_updated_at
BEFORE UPDATE ON user_settings_chatapi
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
END IF;
END;
$$;

CREATE TABLE IF NOT EXISTS user_message_logs_chatapi (
id SERIAL PRIMARY KEY,
username VARCHAR(255) NOT NULL,
message_count INTEGER DEFAULT 0,
date DATE DEFAULT CURRENT_DATE,
CONSTRAINT unique_user_date UNIQUE (username, date)
);
CREATE INDEX IF NOT EXISTS idx_user_message_logs_chatapi_username_date ON user_message_logs_chatapi(username, date);
Loading