diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index cb8f29b0..c0e2c9e3 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -8,33 +8,71 @@ concurrency: cancel-in-progress: true jobs: - build: - name: 🏗️ Build - runs-on: windows-latest - strategy: - fail-fast: false + version-bump: + name: 🔢 Version Bump + runs-on: ubuntu-latest steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 with: - fetch-depth: 2 + fetch-depth: 0 + + - name: Automated Version Bump + uses: phips28/gh-action-bump-version@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + commit-message: 'CI: bumps version to {{version}} [skip ci]' + skip-tag: 'true' - - name: Set up conda - uses: conda-incubator/setup-miniconda@v2 + build-linux: + name: 🏗️ Build Linux + runs-on: ubuntu-latest + needs: version-bump + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 with: - activate-environment: default - environment-file: api/environment.yml + fetch-depth: 0 + - name: Install dependencies + run: npm install + + - name: 📦 Electron Builder Linux + run: npm run electron:package:linux + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Linux artifact + uses: actions/upload-artifact@v2 + with: + name: LinguifAI-Linux + path: dist/*.AppImage + + + build-windows: + name: 🏗️ Build Windows + runs-on: windows-latest + needs: version-bump + strategy: + fail-fast: false + steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 with: - fetch-depth: 2 + fetch-depth: 0 - name: ⎔ Setup Node uses: actions/setup-node@v3 with: cache: "npm" + - name: Set up conda + uses: conda-incubator/setup-miniconda@v2 + with: + activate-environment: default + environment-file: api/environment.yml + - name: 🟨 Setup Python uses: actions/setup-python@v3 with: @@ -49,42 +87,72 @@ jobs: - name: Install dependencies run: npm install - - name: 📦 Electron Builder + - name: 📦 Electron Builder Windows run: npm run electron:package:win env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Print directory tree - run: tree + - name: Upload Windows artifact + uses: actions/upload-artifact@v2 + with: + name: LinguifAI-Windows + path: dist/*.exe + + publish-release: + name: 🚀 Publish Release + runs-on: ubuntu-latest + needs: [build-linux, build-windows] + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get version of the project + id: project-version + uses: euberdeveloper/ga-project-version@main - - name: Get latest release number - id: get_latest_release - uses: actions/github-script@v4 + - name: Download Linux artifact + uses: actions/download-artifact@v2 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const response = await github.repos.listReleases({ - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 10 - }); - - const latestPreRelease = response.data[0]; - const preReleaseTag = latestPreRelease.name; - const versionParts = preReleaseTag.replace(/^v/, '').split('.'); - const newVersion = `${parseInt(versionParts[0])}.${parseInt(versionParts[1])}.${parseInt(versionParts[2]) + 1}`; - console.log(`::set-output name=new_version::${newVersion}`); - - - name: Rename file - run: ren "dist\LinguifAI Setup 0.1.0.exe" "LinguifAI Setup ${{ steps.get_latest_release.outputs.new_version }}.exe" + name: LinguifAI-Linux + path: dist/ + + - name: Download Windows artifact + uses: actions/download-artifact@v2 + with: + name: LinguifAI-Windows + path: dist/ - name: Create Release id: create_release - uses: softprops/action-gh-release@v1 + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - files: | - ./dist/LinguifAI\ Setup\ ${{ steps.get_latest_release.outputs.new_version }}.exe - tag_name: v${{ steps.get_latest_release.outputs.new_version }} - name: v${{ steps.get_latest_release.outputs.new_version }} + tag_name: v${{ steps.project-version.outputs.version }} + release_name: v${{ steps.project-version.outputs.version }} + draft: false prerelease: true - body: ${{ github.event.head_commit.message }} + + - name: Upload Linux artifact + id: upload-linux + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: dist/LinguifAI-${{ steps.project-version.outputs.version }}.AppImage + asset_name: LinguifAI-${{ steps.project-version.outputs.version }}.AppImage + asset_content_type: application/octet-stream + + - name: Upload Windows artifact + id: upload-windows + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: dist/LinguifAI Setup ${{ steps.project-version.outputs.version }}.exe + asset_name: LinguifAI-Setup-${{ steps.project-version.outputs.version }}.exe + asset_content_type: application/octet-stream diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 26cfe58b..afc4a910 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,105 +1,71 @@ name: CI -# Controls when the workflow will run on: push: branches: ["main"] -# Prevent duplicate workflows from running concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: - # Static tests don't involve any logic or context. - # They are just a set of tests that can detect if we are not introducing any faulty code. - static-test: - name: 🔬 Static tests + version-bump: + name: 🔢 Version Bump runs-on: ubuntu-latest steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 with: - fetch-depth: 2 - - - name: ⎔ Setup Node - uses: actions/setup-node@v3 + fetch-depth: 0 + + - name: Automated Version Bump + uses: phips28/gh-action-bump-version@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - cache: "npm" - - - name: 🟨 Setup Python - uses: actions/setup-python@v3 + commit-message: 'feat: bumps minor version to {{version}} [skip ci]' + skip-tag: 'true' - - name: 📦 Install Node dependencies - run: npm install - - # # Unit tests are tests that are not dependent on any external service. - # # Usually, they are tests that are testing the logic of a specific function or component. - unit-test: - needs: [static-test] - name: 🚦 Unit tests + build-linux: + name: 🏗️ Build Linux runs-on: ubuntu-latest + needs: version-bump steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 with: - fetch-depth: 2 + fetch-depth: 0 - - name: ⎔ Setup Node - uses: actions/setup-node@v3 - with: - cache: "npm" - - - name: 🟨 Setup Python - uses: actions/setup-python@v3 - - - name: 📦 Install dependencies + - name: Install dependencies run: npm install - - name: 🚦 Run unit tests - run: npm test - - # # Integration tests are tests that are dependent on external services. - integration-test: - needs: [static-test] - name: 🚥 Integration tests - runs-on: ubuntu-latest - steps: - - name: ⬇️ Checkout repo - uses: actions/checkout@v4 - with: - fetch-depth: 2 + - name: 📦 Electron Builder Linux + run: npm run electron:package:linux + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: ⎔ Setup Node - uses: actions/setup-node@v3 + - name: Upload Linux artifact + uses: actions/upload-artifact@v2 with: - cache: "npm" - - - name: 🟨 Setup Python - uses: actions/setup-python@v3 - - - name: 📦 Install dependencies - run: npm install + name: LinguifAI-Linux + path: dist/*.AppImage - # - name: 🐳 Docker compose - # run: - # docker-compose up -d && sleep 3 && pnpm prisma migrate reset --force - # --skip-seed - - name: 🚦 Run integration tests - run: npm test - - # Create Build - build: - needs: [static-test, unit-test, integration-test] - name: 🏗️ Build + build-windows: + name: 🏗️ Build Windows runs-on: windows-latest + needs: version-bump strategy: fail-fast: false steps: - name: ⬇️ Checkout repo uses: actions/checkout@v4 with: - fetch-depth: 2 + fetch-depth: 0 + + - name: ⎔ Setup Node + uses: actions/setup-node@v3 + with: + cache: "npm" - name: Set up conda uses: conda-incubator/setup-miniconda@v2 @@ -107,16 +73,6 @@ jobs: activate-environment: default environment-file: api/environment.yml - - name: ⬇️ Checkout repo - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - - name: ⎔ Setup Node - uses: actions/setup-node@v3 - with: - cache: "npm" - - name: 🟨 Setup Python uses: actions/setup-python@v3 with: @@ -131,55 +87,74 @@ jobs: - name: Install dependencies run: npm install - - name: 📦 Electron Builder + - name: 📦 Electron Builder Windows run: npm run electron:package:win env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Print directory tree - run: tree + - name: Upload Windows artifact + uses: actions/upload-artifact@v2 + with: + name: LinguifAI-Windows + path: dist/*.exe + + publish-release: + name: 🚀 Publish Release + runs-on: ubuntu-latest + needs: [build-linux, build-windows] + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get version of the project + id: project-version + uses: euberdeveloper/ga-project-version@main - - name: Get latest release number - id: get_latest_release - uses: actions/github-script@v4 + - name: Download Linux artifact + uses: actions/download-artifact@v2 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const response = await github.repos.listReleases({ - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 10 - }); - - const latestPreRelease = response.data[0]; - const preReleaseTag = latestPreRelease.name; - const versionParts = preReleaseTag.replace(/^v/, '').split('.'); - const newVersion = `${parseInt(versionParts[0])}.${parseInt(versionParts[1])+1}.0`; - console.log(`::set-output name=new_version::${newVersion}`); - - - name: Rename file - run: ren "dist\LinguifAI Setup 0.1.0.exe" "LinguifAI Setup ${{ steps.get_latest_release.outputs.new_version }}.exe" - - - name: Get latest commit message - id: get_latest_commit_message - uses: actions/github-script@v4 + name: LinguifAI-Linux + path: dist/ + + - name: Download Windows artifact + uses: actions/download-artifact@v2 with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const response = await github.repos.listCommits({ - owner: context.repo.owner, - repo: context.repo.repo, - per_page: 1, - }); - console.log(`::set-output name=commit_message::${response.data[0].commit.message}`); - + name: LinguifAI-Windows + path: dist/ + + - run: tree + - name: Create Release id: create_release - uses: softprops/action-gh-release@v1 + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - files: | - ./dist/LinguifAI\ Setup\ ${{ steps.get_latest_release.outputs.new_version }}.exe - tag_name: v${{ steps.get_latest_release.outputs.new_version }} - name: v${{ steps.get_latest_release.outputs.new_version }} + tag_name: v${{ steps.project-version.outputs.version }} + release_name: v${{ steps.project-version.outputs.version }} + draft: false prerelease: true - body: ${{ steps.get_latest_commit_message.outputs.commit_message }} + + - name: Upload Linux artifact + id: upload-linux + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: dist/LinguifAI-${{ steps.project-version.outputs.version }}.AppImage + asset_name: LinguifAI-${{ steps.project-version.outputs.version }}.AppImage + asset_content_type: application/octet-stream + + - name: Upload Windows artifact + id: upload-windows + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: dist/LinguifAI Setup ${{ steps.project-version.outputs.version }}.exe + asset_name: LinguifAI-Setup-${{ steps.project-version.outputs.version }}.exe + asset_content_type: application/octet-stream diff --git a/.gitignore b/.gitignore index 97584132..24c002ad 100644 --- a/.gitignore +++ b/.gitignore @@ -15,15 +15,21 @@ api/__pycache__/ # misc .DS_Store + +# env +api/.env +.env .env.local .env.development.local .env.test.local .env.production.local +# log files npm-debug.log* yarn-debug.log* yarn-error.log* + training_progress.json api/encoders api/models/*.keras diff --git a/api/DataProcesser.py b/api/DataProcesser.py index 6eb67f29..639ae068 100644 --- a/api/DataProcesser.py +++ b/api/DataProcesser.py @@ -19,6 +19,7 @@ import torch from collections import Counter from functools import partial +import pickle import nltk from nltk.tokenize import wordpunct_tokenize @@ -35,8 +36,11 @@ def handle_classify(self, df, classifier): classifier_switcher = get_available_classifiers() # id: nome_arquivo model_name = classifier_switcher[classifier] if model_name.endswith('.pkl'): - pipeline = self.get_pipeline(model_name) - return self.pretrained_predict(df, pipeline) + pipeline, custom = self.get_pipeline(model_name) + if custom: + return self.pretrained_predict(df, pipeline, model_name) + else: + return self.pretrained_predict(df, pipeline) else: return self.trained_predict(df, model_name) #classifier_switcher = { @@ -48,18 +52,23 @@ def handle_classify(self, df, classifier): #return classifier_switcher.get(classifier, lambda: "Invalid Classifier")(df) def get_pipeline(self, model_name): + if os.path.exists('assets/tweet_emotions.csv'): + prefix = '' + else: + prefix = 'public/' if model_name=="emotion_pipeline.pkl": - df = pd.read_csv('api/training_df/tweet_emotions.csv') + df = pd.read_csv(prefix + 'assets/tweet_emotions.csv') train_data, test_data, train_target, test_target = train_test_split(df['content'], df['sentiment'], test_size=0.2, shuffle=True) elif model_name=="hate_speech.pkl": - df = pd.read_csv('api/training_df/nb_hatespeech.csv', sep=';') + df = pd.read_csv(prefix + 'assets/nb_hatespeech.csv', sep=';') train_data, test_data, train_target, test_target = train_test_split(df['comment'], df['isHate'], test_size=0.2, shuffle=True) elif model_name=="text_classification_pipeline.pkl": - df = pd.read_csv('api/training_df/nb_news.csv') + df = pd.read_csv(prefix + 'assets/nb_news.csv') train_data, test_data, train_target, test_target = train_test_split(df['short_description'], df['category'], test_size=0.2, shuffle=True) else: - return None - return make_pipeline(TfidfVectorizer(), MultinomialNB()).fit(train_data, train_target) + with open(f'api/models/{model_name}', 'rb') as file: + return pickle.load(file), True + return make_pipeline(TfidfVectorizer(), MultinomialNB()).fit(train_data, train_target), False def generate_statistics(self, df): unique_labels = df['output_column'].unique() @@ -95,11 +104,22 @@ def nb_news_application(self, df): df['output_column'] = df['input_column'].apply(news_prediction) return df - def pretrained_predict(self, df, pipeline): + def pretrained_predict(self, df, pipeline, model_name = None): + if model_name: + label_map_filename = f"api/encoders/LabelMapping-{model_name.split('_')[0]}.joblib" + label_encoder = joblib.load(label_map_filename) + texts_to_predict = df['input_column'] texts_to_predict = [str(text) for text in texts_to_predict] + predictions = pipeline.predict(texts_to_predict) - df['output_column'] = predictions + + if model_name: + label_predictions = label_encoder.inverse_transform(predictions) + df['output_column'] = label_predictions + else: + df['output_column'] = predictions + return df def load_weights_and_model(self, name): @@ -114,6 +134,9 @@ def trained_predict(self, df, model_name): label_map_filename = f"api/encoders/LabelMapping-{model_name}.joblib" label_encoder = joblib.load(label_map_filename) + vocab_file = f"api/encoders/Vocab-{model_name}.joblib" + token2id = joblib.load(vocab_file) + model = self.load_weights_and_model(model_name) model.eval() @@ -138,17 +161,12 @@ def trained_predict(self, df, model_name): lambda tokens: any(token != '' for token in tokens), )] - vocab = sorted({ - sublst for lst in df.tokens.tolist() for sublst in lst - }) - self.token2idx = {token: idx for idx, token in enumerate(vocab)} - - self.token2idx[''] = max(self.token2idx.values()) + 1 - - self.idx2token = {idx: token for token, idx in self.token2idx.items()} + #token2id[''] = max(token2id.values()) + 1 + if '' not in token2id: + token2id[''] = max(token2id.values()) + 1 df['indexed_tokens'] = df.tokens.apply( - lambda tokens: [self.token2idx[token] for token in tokens], + lambda tokens: [token2id.get(token, 0) for token in tokens], ) predictions = [] diff --git a/api/Neural_Network.py b/api/Neural_Network.py new file mode 100644 index 00000000..8b82df08 --- /dev/null +++ b/api/Neural_Network.py @@ -0,0 +1,103 @@ +import re +import os +import joblib +import json +import pickle +from functools import partial +from collections import Counter + +import pandas as pd +from nltk.corpus import stopwords +from nltk import wordpunct_tokenize +from tqdm import tqdm +from sklearn.preprocessing import LabelEncoder +from sklearn.feature_extraction.text import TfidfVectorizer +from sklearn.naive_bayes import MultinomialNB +from sklearn.pipeline import make_pipeline +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score, classification_report + +tqdm.pandas() + +import nltk +nltk.download('stopwords') + +def tokenize(text, stop_words): + text = str(text) + text = re.sub(r'[^\w\s]', '', text) + text = text.lower() + tokens = wordpunct_tokenize(text) + tokens = [token for token in tokens if token not in stop_words] + return tokens + +class CustomDataset: + def __init__(self, df, max_vocab, max_len, name): + stop_words = set(stopwords.words('english')) + + df['tokens'] = df.input_text.progress_apply( + partial(tokenize, stop_words=stop_words), + ) + + all_tokens = [token for tokens in df.tokens.tolist() for token in tokens] + common_tokens = set([token for token, _ in Counter(all_tokens).most_common(max_vocab)]) + df['tokens'] = df.tokens.progress_apply( + partial(remove_rare_words, common_tokens=common_tokens, max_len=max_len), + ) + + df = df[df.tokens.progress_apply(lambda tokens: any(token != '' for token in tokens))] + + df['clean_text'] = df.tokens.apply(lambda tokens: ' '.join(tokens)) + + self.text = df.clean_text.tolist() + self.labels = df.labels.tolist() + + label_encoder = LabelEncoder() + self.encoded_labels = label_encoder.fit_transform(df['labels']) + encoder_name = f"LabelMapping-{name}.joblib" + encoder_filename = os.path.join("api", "encoders", encoder_name) + os.makedirs(os.path.dirname(encoder_filename), exist_ok=True) + joblib.dump(label_encoder, encoder_filename) + +def remove_rare_words(tokens, common_tokens, max_len): + return [token if token in common_tokens else '' for token in tokens][-max_len:] + +def create_and_train_nb_model(df, name, epochs = 10, batch_size = 16, learning_rate = 0.001, valid_ratio=0.05, test_ratio=0.05): + max_vocab = 20000 + max_len = 200 + + dataset = CustomDataset(df, max_vocab, max_len, name) + + X_train, X_temp, y_train, y_temp = train_test_split( + dataset.text, dataset.encoded_labels, test_size=valid_ratio + test_ratio, random_state=42) + X_valid, X_test, y_valid, y_test = train_test_split(X_temp, y_temp, test_size=test_ratio / (valid_ratio + test_ratio), random_state=42) + + # Creating a pipeline with TF-IDF vectorizer and Multinomial Naive Bayes + pipeline = make_pipeline(TfidfVectorizer(max_features=max_vocab), MultinomialNB()) + + # Fitting the model to the training data + pipeline.fit(X_train, y_train) + + # Evaluating on validation set + valid_preds = pipeline.predict(X_valid) + valid_acc = accuracy_score(y_valid, valid_preds) + print(f'Validation Accuracy: {valid_acc:.2f}') + print(f'Validation Report:\n{classification_report(y_valid, valid_preds)}') + + # Evaluating on test set + test_preds = pipeline.predict(X_test) + test_acc = accuracy_score(y_test, test_preds) + print(f'Test Accuracy: {test_acc:.2f}') + print(f'Test Report:\n{classification_report(y_test, test_preds)}') + + # Saving the pipeline to a file + model_path = os.path.join('api', 'models', f"{name}_pipeline.pkl") + os.makedirs(os.path.dirname(model_path), exist_ok=True) + with open(model_path, "wb") as model_file: + pickle.dump(pipeline, model_file) + + training_progress = { + 'training_progress': 0, + 'training_in_progress': False + } + with open('training_progress.json', 'w') as file: + json.dump(training_progress, file) \ No newline at end of file diff --git a/api/Neural_Network2.py b/api/Neural_Network2.py index 011b9f90..51b58dd2 100644 --- a/api/Neural_Network2.py +++ b/api/Neural_Network2.py @@ -17,7 +17,8 @@ from torch.optim.lr_scheduler import CosineAnnealingLR from torch.utils.data import Dataset, DataLoader from torch.utils.data.dataset import random_split -from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence +from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence, pad_sequence +from torchtext.vocab import build_vocab_from_iterator tqdm.pandas() @@ -73,6 +74,11 @@ def __init__(self, df, max_vocab, max_len, name): # Add a padding idx self.token2idx[''] = max(self.token2idx.values()) + 1 + vocab_filename = f"Vocab-{name}" + vocab_file = os.path.join("api", "encoders", f"{vocab_filename}.joblib") + os.makedirs(os.path.dirname(vocab_file), exist_ok=True) + joblib.dump(self.token2idx, vocab_file) + self.idx2token = {idx: token for token, idx in self.token2idx.items()} df['indexed_tokens'] = df.tokens.apply( @@ -91,7 +97,7 @@ def __init__(self, df, max_vocab, max_len, name): self.targets = df.encoded_labels.tolist() def __getitem__(self, i): - return self.sequences[i], self.targets[i], self.text[i] + return torch.tensor(self.sequences[i]), torch.tensor(self.targets[i]), self.text[i] def __len__(self): return len(self.sequences) @@ -108,10 +114,11 @@ def split_train_valid_test(corpus, valid_ratio=0.1, test_ratio=0.1): def collate(batch): - inputs = [item[0] for item in batch] - target = torch.LongTensor([item[1] for item in batch]) - text = [item[2] for item in batch] - return inputs, target, text + inputs, target, text = zip(*batch) + + padded_inputs = pad_sequence(inputs, batch_first=True, padding_value=0) + + return padded_inputs, torch.tensor(target), text def pad_sequences(sequences, padding_val=0, pad_left=False): """Pad a list of sequences to the same length with a padding_val.""" @@ -139,6 +146,7 @@ def __init__(self, output_size, hidden_size, vocab_size, padding_idx, self.dropout_probability = dropout_probability self.device = device self.padding_idx = padding_idx + self.vocab_size = vocab_size # We need to multiply some layers by two if the model is bidirectional self.input_size_factor = 2 if bidirectional else 1 @@ -209,8 +217,8 @@ def forward(self, inputs, return_activations=False): lengths, permutation_indices = lengths.sort(0, descending=True) # Pad sequences so that they are all the same length - padded_inputs = pad_sequences(inputs, padding_val=self.padding_idx) - inputs = torch.LongTensor(padded_inputs) + #padded_inputs = pad_sequences(inputs, padding_val=self.padding_idx) + inputs = torch.LongTensor(inputs) # Sort inputs inputs = inputs[permutation_indices].to(self.device) @@ -247,10 +255,17 @@ def train_epoch(model, optimizer, scheduler, train_loader, criterion, curr_epoch progress_bar = tqdm(train_loader, desc='Training', leave=False) num_iters = 0 for inputs, target, text in progress_bar: + # print(inputs) + # print(target) + # print(text) target = target.to(device) - # Clean old gradients - optimizer.zero_grad() + # Verifica se o cancelamento foi solicitado a cada batch + with open('training_progress.json', 'r') as f: + data = json.load(f) + if data.get('cancel_requested', False): + print("Training canceled during epoch:", curr_epoch) + return total_loss / max(total, 1), True # Retorna a perda média e o status de cancelamento # Forwards pass output = model(inputs) @@ -258,6 +273,9 @@ def train_epoch(model, optimizer, scheduler, train_loader, criterion, curr_epoch # Calculate how wrong the model is loss = criterion(output, target) + # Clean old gradients + optimizer.zero_grad() + # Perform gradient descent, backwards pass loss.backward() @@ -268,17 +286,22 @@ def train_epoch(model, optimizer, scheduler, train_loader, criterion, curr_epoch # Record metrics total_loss += loss.item() total += len(target) + + returnLoss = total_loss / max(total, 1) + num_iters += 1 if num_iters % 20 == 0: - with open('training_progress.json', 'w') as f: - progress = 100 * (curr_epoch + num_iters/len(train_loader)) / num_total_epochs - training_progress = { + with open('training_progress.json', 'r+') as f: + progress = 100 * (curr_epoch + num_iters / len(train_loader)) / num_total_epochs + data.update({ 'training_progress': progress, - 'training_in_progress': True - } - json.dump(training_progress, f) + 'training_in_progress': True, + }) + f.seek(0) + json.dump(data, f) + f.truncate() - return total_loss / total + return returnLoss, False def validate_epoch(model, valid_loader, criterion): model.eval() @@ -298,10 +321,10 @@ def validate_epoch(model, valid_loader, criterion): total_loss += loss.item() total += len(target) - return total_loss / total - -def create_and_train_model(df, name, epochs = 10, batch_size = 32, learning_rate = 0.001): + return total_loss / total if total != 0 else 0 +def create_and_train_rnn_model(df, name, epochs = 10, batch_size = 32, learning_rate = 0.001): + # Configurações iniciais e preparações do modelo dropout_probability = 0.2 n_rnn_layers = 1 embedding_dimension = 128 @@ -312,68 +335,85 @@ def create_and_train_model(df, name, epochs = 10, batch_size = 32, learning_rate valid_ratio = 0.05 test_ratio = 0.05 + # Preparação do dataset dataset = CustomDataset(df, max_vocab, max_len, name) - train_dataset, valid_dataset, test_dataset = split_train_valid_test( dataset, valid_ratio=valid_ratio, test_ratio=test_ratio) - len(train_dataset), len(valid_dataset), len(test_dataset) - + # Preparação dos DataLoader train_loader = DataLoader(train_dataset, batch_size=batch_size, collate_fn=collate) valid_loader = DataLoader(valid_dataset, batch_size=batch_size, collate_fn=collate) - test_loader = DataLoader(test_dataset, batch_size=batch_size, collate_fn=collate) - + #test_loader = DataLoader(test_dataset, batch_size=batch_size, collate_fn=collate) + # Inicialização do modelo model = RNNClassifier( - output_size=len(df.labels), + output_size=len(df['labels'].unique()), hidden_size=hidden_size, embedding_dimension=embedding_dimension, - vocab_size=len(dataset.token2idx), + vocab_size=len(dataset.token2idx)+1, padding_idx=dataset.token2idx[''], dropout_probability=dropout_probability, bidirectional=is_bidirectional, n_layers=n_rnn_layers, device=device, - batch_size=batch_size, + batch_size=batch_size ) - model = model.to(device) + model.to(device) - criterion = nn.CrossEntropyLoss() - optimizer = optim.Adam( - filter(lambda p: p.requires_grad, model.parameters()), - lr=learning_rate, - ) - scheduler = CosineAnnealingLR(optimizer, 1) + # Definição da função de perda e otimizador + criterion = torch.nn.CrossEntropyLoss() + optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate) + scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, 1) - n_epochs = 0 train_losses, valid_losses = [], [] + canceled = False for curr_epoch in range(epochs): - train_loss = train_epoch(model, optimizer, scheduler, train_loader, criterion, curr_epoch, epochs) - valid_loss = validate_epoch(model, valid_loader, criterion) + train_loss, canceled = train_epoch(model, optimizer, scheduler, train_loader, criterion, curr_epoch, epochs) + if canceled: + print(f"Training canceled during epoch {curr_epoch + 1}") + break + valid_loss = validate_epoch(model, valid_loader, criterion) tqdm.write( - f'epoch #{n_epochs + 1:3d}\ttrain_loss: {train_loss:.2e}' - f'\tvalid_loss: {valid_loss:.2e}\n', + f'Epoch #{curr_epoch + 1:3d}\ttrain_loss: {train_loss:.2e}' + f'\tvalid_loss: {valid_loss:.2e}' ) - # Early stopping if the current valid_loss is greater than the last three valid losses - if len(valid_losses) > 2 and all(valid_loss >= loss - for loss in valid_losses[-3:]): - print('Stopping early') + if len(valid_losses) > 2 and all(valid_loss >= loss for loss in valid_losses[-3:]): + print('Stopping early due to lack of improvement in validation loss.') break train_losses.append(train_loss) valid_losses.append(valid_loss) - n_epochs += 1 - - model_path = os.path.join('api', 'models', name) - os.makedirs(os.path.dirname(model_path), exist_ok=True) - torch.save(model, model_path) - - training_progress = { - 'training_progress': 0, - 'training_in_progress': True - } - with open('training_progress.json', 'w') as file: - json.dump(training_progress, file) + # Atualizar a train_losses e valid_losses no arquivo de progresso + with open('training_progress.json', 'r+') as f: + data = json.load(f) + data.update({ + 'train_losses': train_losses, + 'valid_losses': valid_losses, + + }) + f.seek(0) + json.dump(data, f) + f.truncate() + + # Finalizar e salvar o modelo se não foi cancelado + if not canceled: + model_path = os.path.join('api', 'models', name) + os.makedirs(os.path.dirname(model_path), exist_ok=True) + #torch.save(model.state_dict(), model_path) + torch.save(model, model_path) + + # Atualizar e salvar o estado de treinamento final + training_progress = { + 'training_progress': 100, + 'training_in_progress': False, + 'cancel_requested': False, + 'train_losses': train_losses, + 'valid_losses': valid_losses, + } + with open('training_progress.json', 'w') as file: + json.dump(training_progress, file) + + print("Training complete.") \ No newline at end of file diff --git a/api/app.py b/api/app.py index e2cbcb1d..7bd778bf 100644 --- a/api/app.py +++ b/api/app.py @@ -1,35 +1,103 @@ from flask import Flask, jsonify, request from flask_cors import CORS from DataProcesser import DataProcesser -from Neural_Network2 import create_and_train_model +from Neural_Network2 import create_and_train_rnn_model +from Neural_Network import create_and_train_nb_model from available_classifiers import get_available_classifiers import time import os +from io import StringIO +import sys import pandas as pd import nltk +import os +from dotenv import load_dotenv import json +import openai import asyncio import logging -nltk.download('wordnet') +from langchain.schema.document import Document +from langchain.chains.question_answering.chain import load_qa_chain +from langchain_community.vectorstores import FAISS +from langchain_openai import OpenAIEmbeddings +from langchain_community.llms import OpenAI +nltk.download('wordnet') +load_dotenv() app = Flask(__name__) +CORS(app) # CORS(app, resources={r"/*": {"origins": "http://localhost:3000"}}) server_thread = None -CORS(app) # Permite todas as origens por padrão (não recomendado para produção) - +# openai.api_key = os.getenv('OPEN_AI_KEY') log = logging.getLogger('werkzeug') log.disabled = True +selectedFile = None app.config['JSONIFY_PRETTYPRINT_REGULAR'] = True data_processer = DataProcesser() + loop = asyncio.get_event_loop() -def run_flask_app(): - app.run(host='0.0.0.0', port=5000, debug=True, use_reloader=False) +df = None + +def split_dataframe_into_documents(df): + documents = [] + for index, row in df.iterrows(): + content = row.to_json() + documents.append(Document(page_content=content)) + return documents + + +@app.route('/chat', methods=['POST']) +def chat(): + global df + data = request.get_json() + user_message = data.get('message') + chat_history = data.get('history', []) + api_key = data.get('apikey') + + if df is not None: + documents = split_dataframe_into_documents(df) + + embeddings = OpenAIEmbeddings(api_key=api_key) + vectorstore = FAISS.from_documents(documents, embeddings) + + relevant_docs = vectorstore.similarity_search(user_message, k=5) + + llm = OpenAI(api_key=api_key, model="gpt-3.5-turbo-instruct") + qa_chain = load_qa_chain(llm, chain_type="stuff") + + response = qa_chain.run(input_documents=relevant_docs, question=user_message) + bot_reply = response + + return jsonify(reply=bot_reply) + else: + print("No df") + return jsonify(reply="No data available."), 400 + + # messages = [{"role": "system", "content": "You are a helpful assistant."}] + # for msg in chat_history: + # messages.append({"role": "user" if msg['origin'] == 'user' else "assistant", "content": msg['text']}) + # messages.append({"role": "user", "content": user_message}) + + # try: + # client = openai.OpenAI(api_key = api_key) + # response = client.chat.completions.create( + # model="gpt-3.5-turbo", + # messages=messages, + # max_tokens=200 + # ) + # bot_reply = response.choices[0].message.content.strip() + + # return jsonify(reply=bot_reply) + # except Exception as e: + # print(f"Error: {e}") + # return jsonify(reply="Desculpe, ocorreu um erro ao processar sua mensagem."), 500 + def shutdown_server(): print("Server shutting down...") @@ -39,6 +107,22 @@ def shutdown_server(): def hello_world(): return jsonify("Hello, world!") + +@app.route('/receive', methods=['POST']) +def receive_file(): + global df + + if 'file' not in request.files: + return jsonify({'error': 'No file part'}), 400 + file = request.files['file'] + if file.filename == '': + return jsonify({'error': 'No selected file'}), 400 + if file: + df = pd.read_csv(file) + return jsonify({'message': 'File uploaded successfully'}), 200 + + + @app.route('/classify', methods=["POST"]) def upload_file(): received_data = request.get_json() @@ -58,13 +142,53 @@ def get_classifiers(): classifiers = get_available_classifiers() return jsonify(classifiers) + @app.get('/shutdown') def shutdown(): shutdown_server() return 'Server shutting down...' -@app.route('/neural-network', methods=["POST"]) -def train_model(): +@app.route('/neural-network-rnn', methods=["POST"]) +def train_rnn_model(): + received_data = request.json + + if received_data: + selected_data = received_data.get('data') + selected_label = received_data.get('label') + epochs = received_data.get('epochs') + batch_size = received_data.get('batch_size') + learning_rate = received_data.get('learning_rate') + name = received_data.get('name') + + # + print("\n") + print("Received data: " + str(len(selected_data))) + print("Received label: " + str(len(selected_label))) + print("Name: " + str(name)) + print("Epochs: " + str(epochs)) + print("Batch Size: " + str(batch_size)) + print("Learning Rate: " + str(learning_rate)) + print("\n") + else: + return jsonify({"message": "No data received."}), 400 + + # reseta status + training_progress = { + 'training_progress': 0, + 'training_in_progress': True, + 'cancel_requested': False + } + with open('training_progress.json', 'w') as file: + json.dump(training_progress, file) + + df = pd.DataFrame({'input_text': selected_data, 'labels': selected_label}) + + create_and_train_rnn_model(df, name, epochs, batch_size, learning_rate) + + return jsonify({"message": "Model train started successfully."}), 200 + +@app.route('/neural-network-nb', methods=["POST"]) +def train_nb_model(): received_data = request.json if received_data: @@ -97,7 +221,7 @@ def train_model(): df = pd.DataFrame({'input_text': selected_data, 'labels': selected_label}) - create_and_train_model(df, name, epochs, batch_size, learning_rate) + create_and_train_nb_model(df, name, epochs, batch_size, learning_rate) return jsonify({"message": "Model train started successfully."}), 200 @@ -120,9 +244,78 @@ def get_training_status(): return jsonify({'training_in_progress': True, 'training_progress': 0}) training_status = data.get('training_in_progress', False) progress = data.get('training_progress', 0) - return jsonify({'training_in_progress': training_status, 'training_progress': progress}) + cancel_request = data.get('cancel_requested', False) + train_losses = data.get('train_losses', []) + valid_losses = data.get('valid_losses', []) + return jsonify({'training_in_progress': training_status, 'training_progress': progress, 'cancel_requested': cancel_request, 'train_losses': train_losses,'valid_losses': valid_losses,}) except FileNotFoundError: - return jsonify({'training_in_progress': False, 'training_progress': 0}) + return jsonify({'training_in_progress': False, 'training_progress': 0, 'cancel_requested': False, 'train_losses': [],'valid_losses': []}) + +@app.route('/cancel-training', methods=['POST']) +def cancel_training(): + try: + with open('training_progress.json', 'r+') as file: + data = json.load(file) + data['cancel_requested'] = True + file.seek(0) + json.dump(data, file) + file.truncate() + return jsonify({'message': 'Cancellation requested.'}), 200 + except Exception as e: + return jsonify({'Error': str(e)}), 500 + + +@app.route('/apikey', methods=['POST']) +def apikey(): + try: + data = request.get_json() + api_key = data.get('apikey') + client = openai.OpenAI(api_key = api_key) + _ = client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[{'content':'a','role':'user'}], + max_tokens=1 + ) + return jsonify(reply="Chave de API válida! ;)"), 202 + except (openai.APIConnectionError, openai.AuthenticationError): + return jsonify(reply="chave de API inválida! ;( Por favor, tente de novo."), 200 + except Exception as e: + print(f"Error: {e}") + return jsonify(reply="Desculpe, ocorreu um erro ao processar sua mensagem."), 500 + + +# @app.route('/chat', methods=['POST']) +# def chat(): +# global df +# if df is not None: +# # run rag +# print(df.head(1)) +# else: +# print("No df") +# data = request.get_json() +# user_message = data.get('message') +# chat_history = data.get('history', []) +# api_key = data.get('apikey') + +# messages = [{"role": "system", "content": "You are a helpful assistant."}] +# for msg in chat_history: +# messages.append({"role": "user" if msg['origin'] == 'user' else "assistant", "content": msg['text']}) +# messages.append({"role": "user", "content": user_message}) + +# try: +# client = openai.OpenAI(api_key = api_key) +# response = client.chat.completions.create( +# model="gpt-3.5-turbo", # ou a gente poderia ver com gpt 4 mas por enquanto coloquei 3.5 +# messages=messages, +# max_tokens=200 +# ) +# bot_reply = response.choices[0].message.content.strip() + +# return jsonify(reply=bot_reply) +# except Exception as e: +# print(f"Error: {e}") +# return jsonify(reply="Desculpe, ocorreu um erro ao processar sua mensagem."), 500 + if __name__ == '__main__': diff --git a/api/app.spec b/api/app.spec index 26cf43b8..d5abf94e 100644 --- a/api/app.spec +++ b/api/app.spec @@ -1,6 +1,5 @@ # -*- mode: python ; coding: utf-8 -*- -# Specify the entry point Python script entry_point = 'app.py' # Collect necessary data files and binaries diff --git a/api/available_classifiers.py b/api/available_classifiers.py index 8a3c47bd..da0ad0cc 100644 --- a/api/available_classifiers.py +++ b/api/available_classifiers.py @@ -1,13 +1,11 @@ import os def get_available_classifiers(): - model_folder = 'api/models' - + model_folder = next((folder for folder in ['models', 'api/models', '_internal/models'] if os.path.exists(folder)), None) + # Verifica se o diretório 'models' existe - - ## retorna o path atual - if not os.path.exists(model_folder): - return [] + if not model_folder: + return {0 : "emotion_pipeline.pkl", 1 : "hate_speech.pkl", 2 : "text_classification_pipeline.pkl"} # Obtém a lista de arquivos no diretório 'models' model_files = os.listdir(model_folder) @@ -15,5 +13,8 @@ def get_available_classifiers(): for file in model_files: classifiers[len(classifiers)] = file + classifiers[len(classifiers)] = "emotion_pipeline.pkl" + classifiers[len(classifiers)] = "hate_speech.pkl" + classifiers[len(classifiers)] = "text_classification_pipeline.pkl" return classifiers \ No newline at end of file diff --git a/api/models/emotion_pipeline.pkl b/api/models/emotion_pipeline.pkl deleted file mode 100644 index 37bd3550..00000000 Binary files a/api/models/emotion_pipeline.pkl and /dev/null differ diff --git a/api/models/hate_speech.pkl b/api/models/hate_speech.pkl deleted file mode 100644 index edde8a41..00000000 Binary files a/api/models/hate_speech.pkl and /dev/null differ diff --git a/api/models/sacasd b/api/models/sacasd deleted file mode 100644 index d10fb95a..00000000 Binary files a/api/models/sacasd and /dev/null differ diff --git a/api/models/text_classification_pipeline.pkl b/api/models/text_classification_pipeline.pkl deleted file mode 100644 index 893cf9ec..00000000 Binary files a/api/models/text_classification_pipeline.pkl and /dev/null differ diff --git a/api/requirements.txt b/api/requirements.txt index 06510281..d615355d 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -4,4 +4,12 @@ Flask-Cors==4.0.0 pandas==2.0.3 nltk==3.8.1 scikit-learn==1.3.0 -numpy==1.26.4 \ No newline at end of file +numpy==1.26.4 +python-dotenv==1.0.1 +openai==1.33.0 +flask_sse==1.0.0 +langchain==0.2.5 +langchain-community==0.2.5 +tiktoken==0.7.0 +langchain-openai==0.1.8 +faiss-cpu==1.8.0 \ No newline at end of file diff --git a/helper.js b/helper.js index c2c5d7e9..a53548e7 100644 --- a/helper.js +++ b/helper.js @@ -44,5 +44,6 @@ function printAsarDirectoryTree(asarPath, currentPath = '') { } } + // Print out the directory tree of the app.asar file printAsarDirectoryTree(asarFilePath); diff --git a/package-lock.json b/package-lock.json index 3d079d19..aa8cd56d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "LinguifAI", - "version": "0.1.0", + "version": "0.2.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "LinguifAI", - "version": "0.1.0", + "version": "0.2.3", "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", @@ -19,10 +19,12 @@ "@types/node": "^16.18.48", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", + "apexcharts": "^3.49.1", "asar": "^3.2.0", "axios": "^1.6.0", "electron-process": "^0.2.0", "react": "^18.2.0", + "react-apexcharts": "^1.4.1", "react-dom": "^18.2.0", "react-icons": "^4.12.0", "react-papaparse": "^4.1.0", @@ -33,6 +35,8 @@ "typescript": "^4.9.5" }, "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", "concurrently": "^8.2.2", "electron": "^26.6.10", "electron-builder": "^24.13.3", @@ -77,11 +81,11 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -151,11 +155,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", - "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "dependencies": { - "@babel/types": "^7.24.0", + "@babel/types": "^7.24.7", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -165,11 +169,11 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -202,18 +206,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz", - "integrity": "sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz", + "integrity": "sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", "semver": "^6.3.1" }, "engines": { @@ -255,42 +259,46 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", - "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz", + "integrity": "sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==", "dependencies": { - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -326,20 +334,20 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz", - "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", "engines": { "node": ">=6.9.0" } @@ -361,13 +369,13 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", - "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", + "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -388,39 +396,40 @@ } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", - "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "engines": { "node": ">=6.9.0" } @@ -460,11 +469,11 @@ } }, "node_modules/@babel/highlight": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", - "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -538,9 +547,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", - "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -706,9 +715,17 @@ } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, "engines": { "node": ">=6.9.0" }, @@ -1549,13 +1566,13 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz", - "integrity": "sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { @@ -1943,6 +1960,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", @@ -2010,31 +2038,31 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", - "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", - "dependencies": { - "@babel/code-frame": "^7.24.1", - "@babel/generator": "^7.24.1", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.24.1", - "@babel/types": "^7.24.0", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -2043,12 +2071,12 @@ } }, "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -5201,6 +5229,11 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, + "node_modules/@yr/monotone-cubic-spline": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", + "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==" + }, "node_modules/@zeit/schemas": { "version": "2.29.0", "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.29.0.tgz", @@ -5465,6 +5498,20 @@ "node": ">= 8" } }, + "node_modules/apexcharts": { + "version": "3.49.1", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.49.1.tgz", + "integrity": "sha512-MqGtlq/KQuO8j0BBsUJYlRG8VBctKwYdwuBtajHgHTmSgUU3Oai+8oYN/rKCXwXzrUlYA+GiMgotAIbXY2BCGw==", + "dependencies": { + "@yr/monotone-cubic-spline": "^1.0.3", + "svg.draggable.js": "^2.2.2", + "svg.easing.js": "^2.0.0", + "svg.filter.js": "^2.0.2", + "svg.pathmorphing.js": "^0.1.3", + "svg.resize.js": "^1.4.3", + "svg.select.js": "^3.0.1" + } + }, "node_modules/app-builder-bin": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.0.0.tgz", @@ -17514,6 +17561,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-apexcharts": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.1.tgz", + "integrity": "sha512-G14nVaD64Bnbgy8tYxkjuXEUp/7h30Q0U33xc3AwtGFijJB9nHqOt1a6eG0WBn055RgRg+NwqbKGtqPxy15d0Q==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "apexcharts": "^3.41.0", + "react": ">=0.13" + } + }, "node_modules/react-app-polyfill": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", @@ -19900,6 +19959,89 @@ "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" }, + "node_modules/svg.draggable.js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", + "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", + "dependencies": { + "svg.js": "^2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.easing.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", + "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", + "dependencies": { + "svg.js": ">=2.3.x" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.filter.js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", + "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" + }, + "node_modules/svg.pathmorphing.js": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", + "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", + "dependencies": { + "svg.js": "^2.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", + "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", + "dependencies": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js/node_modules/svg.select.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", + "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.select.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", + "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "dependencies": { + "svg.js": "^2.6.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", diff --git a/package.json b/package.json index afca0ced..8519555e 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,10 @@ "name": "LinguifAI", "description": "", "author": { - "name": "TailLinguifAI", - "email": "" + "name": "Cameron", + "email": "cameron.maloney@warriorlife.net" }, - "version": "0.1.0", + "version": "0.2.3", "main": "./public/electron.js", "homepage": "./", "private": true, @@ -21,10 +21,12 @@ "@types/node": "^16.18.48", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", + "apexcharts": "^3.49.1", "asar": "^3.2.0", "axios": "^1.6.0", "electron-process": "^0.2.0", "react": "^18.2.0", + "react-apexcharts": "^1.4.1", "react-dom": "^18.2.0", "react-icons": "^4.12.0", "react-papaparse": "^4.1.0", @@ -37,8 +39,10 @@ "scripts": { "start2": "set BROWSER=NONE && react-scripts start", "start": "concurrently \"npm run start:react\" \"npm run start:flask\"", + "start-with-exe": "concurrently \"npm run start:react\" \"npm run start:flask-exe\"", "start:react": "react-scripts start", "start:flask": "python api/app.py", + "start:flask-exe": "cd dist/app && app.exe", "build": "react-scripts build", "ciBuild": "CI=true react-scripts build", "test": "react-scripts test", @@ -47,7 +51,8 @@ "start-electron": "set ELECTRON_START_URL=http://localhost:3000/ && electron .", "pack": "electron-builder --dir", "dist": "electron-builder", - "electron:package:win": "npm run build && electron-builder -w -c.extraMetadata.main=build/electron.js" + "electron:package:win": "npm run build && electron-builder -w -c.extraMetadata.main=build/electron.js", + "electron:package:linux": "npm run build && electron-builder -l -c.extraMetadata.main=build/electron.js" }, "eslintConfig": { "extends": [ @@ -68,6 +73,8 @@ ] }, "devDependencies": { + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", "concurrently": "^8.2.2", "electron": "^26.6.10", "electron-builder": "^24.13.3", @@ -81,6 +88,12 @@ "nsis" ] }, + "linux": { + "target": [ + "AppImage", + "deb" + ] + }, "files": [ "build/**/*", "node_modules/**/*", diff --git a/api/training_df/nb_hatespeech.csv b/public/assets/nb_hatespeech.csv similarity index 100% rename from api/training_df/nb_hatespeech.csv rename to public/assets/nb_hatespeech.csv diff --git a/api/training_df/nb_news.csv b/public/assets/nb_news.csv similarity index 100% rename from api/training_df/nb_news.csv rename to public/assets/nb_news.csv diff --git a/api/training_df/tweet_emotions.csv b/public/assets/tweet_emotions.csv similarity index 100% rename from api/training_df/tweet_emotions.csv rename to public/assets/tweet_emotions.csv diff --git a/public/electron.js b/public/electron.js index cb7d85aa..7c5883dc 100644 --- a/public/electron.js +++ b/public/electron.js @@ -52,6 +52,7 @@ function startFlaskProcess() { flaskProcess.stderr.on('data', (data) => { console.error(`Flask process stderr: ${data}`); }); + } catch (error) { console.error(`Flask process error: ${error}`); } @@ -77,7 +78,7 @@ function createWindow() { mainWindow.on('closed', function () { mainWindow = null; // Kill Flask server when the window is closed - flaskProcess.kill(); + flaskProcess.kill('SIGKILL'); }); } diff --git a/src/App.css b/src/App.css index 4e6b4487..e69de29b 100644 --- a/src/App.css +++ b/src/App.css @@ -1,4 +0,0 @@ -@import url("https://fonts.googleapis.com/css2?family=Roboto+Mono&display=swap"); -.font-roboto { - font-family: "Roboto Mono", monospace; -} diff --git a/src/Shared/apexChartsOptions.ts b/src/Shared/apexChartsOptions.ts new file mode 100644 index 00000000..37eba799 --- /dev/null +++ b/src/Shared/apexChartsOptions.ts @@ -0,0 +1,56 @@ +export const ReactApexChartsDefaultOptions = { + chart: { + toolbar: { + show: false, + }, + zoom: { + enabled: false, + }, + }, + xaxis: { + labels: { + style: { + colors: "#000", + }, + }, + title: { + text: "Epoch", + style: { + color: "#000", + }, + }, + }, + yaxis: { + labels: { + formatter: (value: number) => value.toFixed(4), + style: { + colors: "#000", + }, + }, + title: { + text: "Loss", + style: { + color: "#000", + }, + }, + }, + tooltip: { + theme: "dark", + }, + grid: { + borderColor: "#777", + strokeDashArray: 1, + }, + title: { + text: "Loss vs Epochs", + align: 'center' as 'center', + style: { + color: "#000", + }, + }, + legend: { + labels: { + colors: "#000", // Muda a cor das legendas + }, + }, +}; diff --git a/src/assets/images/tailLogo.svg b/src/assets/images/tailLogo.svg new file mode 100644 index 00000000..7a4fafd4 --- /dev/null +++ b/src/assets/images/tailLogo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/chatbot/chatbot.tsx b/src/components/chatbot/chatbot.tsx new file mode 100644 index 00000000..b7113369 --- /dev/null +++ b/src/components/chatbot/chatbot.tsx @@ -0,0 +1,169 @@ +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; + +interface Message { + text: string; + origin: 'user' | 'bot'; +} + +const ChatBot: React.FC = () => { + const [apikey, setApiKey] = useState(""); + const [firstTime, setFirstTime] = useState(true); + const [apiKeyInput, setApiKeyInput] = useState(""); + const [isOpen, setIsOpen] = useState(false); + const [message, setMessage] = useState(""); + const [chatHistory, setChatHistory] = useState([]); + + useEffect(() => { + if (isOpen && !apikey && firstTime) { + sendAPIKeyMessage(); + } + }, [isOpen, apikey]); + + useEffect(() => { + if (isOpen && chatHistory.length === 0 && apikey) { + sendInitialMessage(); + } + }, [isOpen, chatHistory, apikey]); + + const toggleChat = () => { + setIsOpen(!isOpen); + }; + + const sendMessage = async () => { + if (message.trim() === "" || !apikey) return; + + const newMessage: Message = { text: message, origin: 'user' }; + setChatHistory(prevHistory => [...prevHistory, newMessage]); + + try { + const response = await axios.post('http://localhost:5000/chat', { + message, + history: [...chatHistory, newMessage], + apikey + }); + + const botResponse: Message = { text: response.data.reply, origin: 'bot' }; + setChatHistory(prevHistory => [...prevHistory, botResponse]); + } catch (error) { + console.error("Error sending message:", error); + const errorMessage: Message = { text: "Desculpe, ocorreu um erro. Tente novamente.", origin: 'bot' }; + setChatHistory(prevHistory => [...prevHistory, errorMessage]); + } + + setMessage(""); + }; + + const sendAPIKeyMessage = () => { + setChatHistory(prevHistory => [ + ...prevHistory, + { text: "Olá! Eu sou o (LinguiTalk ou LinguaBot). Coloca a sua chave:", origin: 'bot' } + ]); + }; + + const sendInitialMessage = () => { + setChatHistory(prevHistory => [ + ...prevHistory, + { text: "Olá! Eu sou o (LinguiTalk ou LinguaBot). Como posso ajudar?", origin: 'bot' } + ]); + }; + + const handleApiKeySubmit = async () => { + setApiKey(apiKeyInput); + const response = await axios.post('http://localhost:5000/apikey', { + apikey: apiKeyInput + }); + const botResponse: Message = { text: response.data.reply, origin: 'bot' }; + setChatHistory(prevHistory => [...prevHistory, botResponse]); + setApiKeyInput(""); + if (response.status == 202) { + sendInitialMessage() + } + else { + setApiKey("") + setFirstTime(false) + } + }; + + return ( +
+ + + {/* Chat Dialog */} + {isOpen && ( +
+
+

LinguiTalk ou LinguaBot

+ +
+
+
+ {chatHistory.map((msg, index) => ( +
+

+ {msg.origin === 'user' ? 'Você' : 'LinguiBot'} +

+

{msg.text}

+
+ ))} +
+
+ {apikey ? ( +
+ setMessage(e.target.value)} + onKeyPress={(e) => { + if (e.key === 'Enter') sendMessage(); + }} + /> + +
+ ) : ( +
+ setApiKeyInput(e.target.value)} + onKeyPress={(e) => { + if (e.key === 'Enter') handleApiKeySubmit(); + }} + /> + +
+ )} +
+ )} +
+ ); +}; + +export default ChatBot; diff --git a/src/components/csvTable/csvTable.tsx b/src/components/csvTable/csvTable.tsx index 6c3bd5f8..6c6a855c 100644 --- a/src/components/csvTable/csvTable.tsx +++ b/src/components/csvTable/csvTable.tsx @@ -1,50 +1,37 @@ import React from "react"; -import './csvTable.css'; interface Props { - head: any[]; + head: string[]; data: any[][]; } export default function CsvTable({ data, head }: Props) { return ( -
- - - - {head.map((headTitle, index) => { - return ( - - ); - })} +
+
- {headTitle} -
+ + + {head.map((headTitle, index) => ( + + ))} - {data.map((row, index) => { - return ( - - {row.map((cell, cellIndex) => { - return ( - + { + row.map((cell, cellIndex) => ( + - ); - })} - - ); - })} + + + )) + } + + ))}
+ {headTitle} +
+ {data.map((row, rowIndex) => ( +
+
{cell} -
diff --git a/src/components/menu/menu.tsx b/src/components/menu/menu.tsx index e1007ee2..604a7421 100644 --- a/src/components/menu/menu.tsx +++ b/src/components/menu/menu.tsx @@ -1,20 +1,45 @@ import React, { useState } from "react"; -import { HiOutlineMenu, HiOutlineX } from "react-icons/hi"; // Importando ícones do React Icons -import { Link } from "react-router-dom"; +import { HiOutlineMenu, HiOutlineX } from "react-icons/hi"; +import { Link, useLocation } from "react-router-dom"; import { CSSTransition } from "react-transition-group"; import "./menu.css"; export function Menu() { const [isExpanded, setIsExpanded] = useState(false); + const location = useLocation(); const toggleMenu = () => { setIsExpanded(!isExpanded); }; + const menuItems = [ + { + path: "/", + title: "Classificar", + description: "Utilize modelos pré-treinados ou use os seus próprios modelos para classificar texto", + }, + { + path: "/train", + title: "Treinar Classificador", + description: "Faça upload dos seus dados e treine o seu próprio classificador textual", + }, + { + path: "/about", + title: "Sobre", + description: "Detalhes sobre o projeto", + }, + { + path: "https://forms.gle/Snud46RwuwT16Mrb9", + title: "Feedback", + description: "Envie sua opinião", + external: true, + }, + ]; + return ( - <> +
- + ); } diff --git a/src/components/resultTable/resultTable.tsx b/src/components/resultTable/resultTable.tsx index e9f4a4fe..b54326a4 100644 --- a/src/components/resultTable/resultTable.tsx +++ b/src/components/resultTable/resultTable.tsx @@ -1,4 +1,10 @@ import React from "react"; +import CsvTable from "../csvTable/csvTable"; + +interface TableData { + Coluna: string; + Valor: any; +} interface Props { data: { [key: string]: any }; @@ -6,38 +12,12 @@ interface Props { } export default function ResultTable({ data, classifierName }: Props) { - return ( -
- - - - - + const tableData: TableData[] = Object.entries(data).map(([key, value]) => ({ + Coluna: key, + Valor: value, + })); + + const convertedTableData: any[][] = tableData.slice(0, 4).map((item) => [item.Coluna, item.Valor]); - - - - - {Object.keys(data) - .slice(0, 10) - .map((row: any, index: number) => { - return ( - - - - - - ); - })} - -
{"Index"}{"Input"}{"Output"}
{index}{row} - {data[row]} -
-
- ); + return ; } diff --git a/src/components/selectFileCard/selectFileCard.tsx b/src/components/selectFileCard/selectFileCard.tsx index e1b94c3c..30d01fe1 100644 --- a/src/components/selectFileCard/selectFileCard.tsx +++ b/src/components/selectFileCard/selectFileCard.tsx @@ -2,8 +2,9 @@ import { Icon } from "@iconify/react"; import { ChangeEvent, useState } from "react"; import Papa from "papaparse"; import CsvTable from "../csvTable/csvTable"; +import { Link } from "@mui/material"; -interface props { +interface Props { selectedFile: File | null; setSelectedFile: (file: File | null) => void; setData: (data: any[][]) => void; @@ -19,40 +20,38 @@ export default function SelectFileCard({ data, header, setHeader, -}: props) { +}: Props) { const [isDragging, setIsDragging] = useState(false); - // Selecionar arquivo + // Handle file selection from file input const handleFileChange = async (event: ChangeEvent) => { const file = event.target.files?.[0]; if (file && file.name.endsWith(".csv")) { setSelectedFile(file); - - Papa.parse(file, { - header: true, - dynamicTyping: true, - skipEmptyLines: true, - complete(results, file) { - let chaves = Object.keys(results.data[0] || []); - - let data: any[][] = results.data.map((row: any) => { - let newRow: any[] = []; - chaves.forEach((chave) => { - newRow.push(row[chave]); - }); - return newRow; - }); - - setData(data); - setHeader(chaves); - }, - }); + parseCSV(file); } else { setSelectedFile(null); } }; - // Arrastar e soltar + // Parse CSV file + const parseCSV = (file: File) => { + Papa.parse(file, { + header: true, + dynamicTyping: true, + skipEmptyLines: true, + complete(results) { + const keys = Object.keys(results.data[0] || []); + const data: any[][] = results.data.map((row: any) => { + return keys.map((key) => row[key]); + }); + setData(data); + setHeader(keys); + }, + }); + }; + + // Handle file drop const handleDrop = (event: React.DragEvent) => { setIsDragging(false); event.preventDefault(); @@ -60,75 +59,68 @@ export default function SelectFileCard({ const file = event.dataTransfer.files[0]; if (file && file.name.endsWith(".csv")) { setSelectedFile(file); + parseCSV(file); } else { setSelectedFile(null); } }; const handleDragOver = (event: React.DragEvent) => { - setIsDragging(true); // Quando o usuário arrasta algo para cima + setIsDragging(true); event.preventDefault(); }; const handleDragLeave = () => { - setIsDragging(false); // Quando o usuário cancela o arrasto + setIsDragging(false); }; - return ( + return !selectedFile ? (
0 ? `w-4/5` : `w-2/5` - } relative mx-auto mt-24 bg-main-dark border-2 border-main-lighter text-white pt-4 px-4 placeholder-gray-300 rounded-3xl min-h-min flex flex-col items-center justify-between ${ - isDragging ? "blur-sm" : "" - }`} + className={`${data.length > 0 ? `w-4/5` : `w-2/5` + } min-h-[170px] relative mx-auto mt-24 bg-gray-100 border-dashed border-2 border-gray-700 text-gray-600 px-4 placeholder-gray-300 rounded-md flex flex-col items-center justify-center gap-4 ${isDragging ? "blur-sm" : "" + }`} onDrop={handleDrop} onDragOver={handleDragOver} onDragLeave={handleDragLeave} > - {isDragging ? ( -

Arrastar e soltar

- ) : ( - <> - - - {selectedFile ? ( // se tiver arquivo selecionado, seu nome é exibido -

{selectedFile.name}

- ) : ( - <> -

Arrastar e soltar

-

Ou

- - )} - - {data.length > 0 && ( - - )} - - - - - )} + +

+ Arraste e solte ou{" "} + { + const fileInput = document.getElementById("fileInput"); + if (fileInput) { + fileInput.click(); + } + }} + > + selecione do Computador + + +

+
+ ) : ( +
0 ? `w-4/5` : `w-2/5` + } min-h-[170px] relative mx-auto flex flex-col items-center justify-center ${isDragging ? "blur-sm" : "" + }`} + onDrop={handleDrop} + onDragOver={handleDragOver} + onDragLeave={handleDragLeave} + > + {data.length > 0 && }
); } diff --git a/src/index.css b/src/index.css index ae228ce9..bf8caa94 100644 --- a/src/index.css +++ b/src/index.css @@ -2,12 +2,18 @@ @tailwind components; @tailwind utilities; +@layer base { + body { + font-family: Inter, sans-serif; + } + + code, + pre { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + } +} + * { margin: 0; padding: 0; } - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", - monospace; -} diff --git a/src/pages/about.tsx b/src/pages/about.tsx index 6f52b7fb..13bb5f42 100644 --- a/src/pages/about.tsx +++ b/src/pages/about.tsx @@ -1,144 +1,8 @@ -import { Menu } from "../components/menu/menu"; -import { FaLinkedin, FaGithub } from "react-icons/fa"; +import Layout from "./layout/layout"; +import AboutView from "./views/aboutView"; export default function About() { - const teamMembers = [ - { - name: "Jonas Gabriel", - linkedin: "https://www.linkedin.com/in/jonas-gabriel-araujo/", - github: "https://github.com/jonasgabriel18", - photo: "../../../assets/jonas.jpg", - }, - { - name: "Luiz Gusttavo", - linkedin: - "https://www.linkedin.com/in/luiz-gusttavo-oliveira-de-souza-7538091b1/", - github: "https://github.com/GusttavoOliveira", - photo: "../../../assets/luiz.jpg", - }, + return ; - { - name: "Bertrand Lira", - linkedin: "https://www.linkedin.com/in/bertrand-lira-veloso-52aa4926a/", - github: "https://github.com/BertrandLira", - photo: "../../../assets/bertrand.jpg", - }, - - { - name: "Cameron Maloney", - linkedin: "https://www.linkedin.com/in/cameronmal/", - github: "https://github.com/cmaloney111", - photo: "../../../assets/cameron.jpg", - }, - - { - name: "Gisele Silva", - linkedin: "https://www.linkedin.com/in/gisele-silva-6692941a4/", - github: "https://github.com/GiseleBr678", - photo: "../../../assets/LINDADEMAIS/gisele.jpg", - }, - - { - name: "Thauã Magalhães", - linkedin: "https://www.linkedin.com/in/thaua-lucas/", - github: "https://github.com/tahaluh", - photo: "../../../assets/thaua.jpg", - }, - - { - name: "Thiago Rodrigues", - linkedin: "https://www.linkedin.com/in/thiago-rodrigues-b8a328249/", - github: "https://github.com/tahaluh", - photo: "../../../assets/thiago.jpg", - }, - ]; - - return ( -
- - -
-

- LinguifAI -

- -

- O LinguifAI é uma aplicação inovadora desenvolvida pela diretoria de - NLP da TAIL, projetada para simplificar e agilizar o treinamento e a - classificação de modelos de Machine Learning em texto. Com essa - ferramenta, é possível experimentar diversos algoritmos e ajustar - parâmetros de forma intuitiva, tornando o processo de desenvolvimento - de modelos NLP mais acessível e eficiente. -

- -

Tecnologias Utilizadas:

- -
- React - - Electron - - Python - -
- Flask -
- - tensorFlow -
- -

Conheça a Equipe:

- -
- {teamMembers.map((member, index) => ( -
- {member.name} -

{member.name}

- -
- ))} -
-
-
- ); } diff --git a/src/pages/home.tsx b/src/pages/home.tsx index 0dcd7ff8..edcda349 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -1,207 +1,7 @@ -import { useState, useEffect } from "react"; -import SelectFileCard from "../components/selectFileCard/selectFileCard"; -import axios from "axios"; -import ResultTable from "../components/resultTable/resultTable"; -import { Menu } from "../components/menu/menu"; +import Layout from "./layout/layout"; +import HomeView from "./views/homeView"; -export default function Home() { - const [selectedFile, setSelectedFile] = useState(null); - - const [data, setData] = useState([]); - const [header, setHeader] = useState([]); - - const [selectedColumn, setSelectedColumn] = useState(0); - const [selectedClassifier, setSelectedClassifier] = useState(""); - - const [classifiers, setClassifiers] = useState<{ [key: string]: string }>({}); - - const [result, setResult] = useState<{ [key: string]: any }>({}); - - const [isLoading, setIsLoading] = useState(false); - - useEffect(() => { - const fetchClassifiers = async () => { - setTimeout(async () => { - try { - const response = await axios.get( - "http://localhost:5000/get-classifiers" - ); - setClassifiers(response.data); - } catch (error) { - try { - const response = await axios.get( - "http://localhost:5000/get-classifiers" - ); - setClassifiers(response.data); - } catch (error) { - console.error("Error fetching classifiers:", error); - } - } - }, 25000); - }; - - fetchClassifiers(); - }, []); - - - const handleChangeSelectedColumn = (event: any) => { - setSelectedColumn(event.target.value); - }; - - const handleChangeSelectedClassifier = (event: any) => { - setSelectedClassifier(event.target.value); - }; - - const handleSubmit = async () => { - setIsLoading(true); - let selectedData = data.map((row) => row[selectedColumn]); - const response = await axios - .post("http://localhost:5000/classify", { - data: selectedData, - classifier: selectedClassifier, - }) - .catch((error) => { - console.error(error.response.data); - }); - - if (response && response.data) { - const parsedData = JSON.parse(response.data.result); - - const input: any[] = Object.values(parsedData.input_column); - const output: any[] = Object.values(parsedData.output_column).flat(1); - - if (input.length === output.length) { - // cria um dicionário com os valores de input e output - const result = input.reduce((acc, key, index) => { - acc[key] = output[index]; - return acc; - }, {} as { [key: string]: any }); - console.log(result); - setResult(result); - } else { - console.error("Os arrays 'input' e 'output' tem tamanhos diferentes."); - } - } - - setIsLoading(false); - }; - - const handleDownloadOutputCSV = async () => { - // adicionar uma coluna no data - - const finalHeader = header.concat(`${selectedClassifier}_output`); - const finalData = data.map((row) => - row.concat(result[row[selectedColumn]]) - ); - - // gera um csv com os dados - - const csv = finalHeader - .join(",") - .concat("\n") - .concat(finalData.map((row) => row.join(",")).join("\n")); - - // cria um link para download do csv - - const csvFile = new Blob([csv], { type: "text/csv" }); - const csvURL = window.URL.createObjectURL(csvFile); - const tempLink = document.createElement("a"); - tempLink.href = csvURL; - tempLink.setAttribute("download", "output.csv"); - tempLink.click(); - }; - - return ( -
- - -
-

- LinguifAI -

- - { - - } - - {selectedFile && ( - <> -
- -
-
- -
-
- -
- {Object.keys(result).length > 0 && ( -
- -
- -
-
- )} - - )} -
-
- ); +export default function Home() { + return ; } diff --git a/src/pages/layout/header.tsx b/src/pages/layout/header.tsx new file mode 100644 index 00000000..d1a90c7a --- /dev/null +++ b/src/pages/layout/header.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { Menu } from "../../components/menu/menu"; +import logo from "../../assets/images/tailLogo.svg"; + +interface HeaderProps { + title: string; +} + +const Header: React.FC = ({ title }) => { + return ( +
+ +
+ Logo da LinguifAI +

+ LinguifAI +

+
+
+ ); +}; + +export default Header; diff --git a/src/pages/layout/layout.tsx b/src/pages/layout/layout.tsx new file mode 100644 index 00000000..40e58b1f --- /dev/null +++ b/src/pages/layout/layout.tsx @@ -0,0 +1,19 @@ +import React, { ReactNode } from "react"; +import { Menu } from "../../components/menu/menu"; +import logo from "../../assets/images/tailLogo.svg"; +import Header from "./header"; +import ChatBot from "../../components/chatbot/chatbot"; + +interface LayoutProps { + children: ReactNode; +} + +export default function Layout({ children }: LayoutProps) { + return ( +
+
+
{children}
+ +
+ ); +} diff --git a/src/pages/train.tsx b/src/pages/train.tsx index 91be7f58..c20d55bf 100644 --- a/src/pages/train.tsx +++ b/src/pages/train.tsx @@ -3,339 +3,13 @@ import SelectFileCard from "../components/selectFileCard/selectFileCard"; import axios from "axios"; import ResultTable from "../components/resultTable/resultTable"; import { Menu } from "../components/menu/menu"; +import { ReactApexChartsDefaultOptions } from "../Shared/apexChartsOptions"; +import Layout from "./layout/layout"; +import TrainView from "./views/trainView"; export default function Train() { - const [selectedFile, setSelectedFile] = useState(null); - - const [data, setData] = useState([]); - const [header, setHeader] = useState([]); - - const [selectedColumn, setSelectedColumn] = useState(0); - const [selectedLabel, setSelectedLabel] = useState(0); - - const [isLoading, setIsLoading] = useState(false); - - const handleChangeSelectedColumn = (event: any) => { - setSelectedColumn(event.target.value); - }; - - const handleChangeSelectedLabel = (event: any) => { - setSelectedLabel(event.target.value); - }; - - const handleSubmit = async () => { - setIsLoading(true); - setLoadingProgress(0); - - let selectedData = data.map((row) => ({ - value: row[selectedColumn], - label: row[selectedLabel], - })); - - let selectedLabels = data.map((row) => row[selectedLabel]); - let selectedValues = data.map((row) => row[selectedColumn]); - - const sendData = { - data: selectedValues, - label: selectedLabels, - batch_size: batchSize || 16, - epochs: epochs || 50, - learning_rate: learningRate || 0.001, - name: modelName || "trained-model", - }; - - console.log(sendData); - - - const url = "http://localhost:5000/neural-network"; - - await axios - .post(url, sendData) - .catch(async (error) => { - await axios - .post(url, sendData) - .catch(async (error) => { - await axios - .post(url, sendData) - .catch(async (error) => { - await axios - .post(url, sendData) - .catch((error) => { - throw new Error(error); - }) - }) - }) - }); - - setIsLoading(false); - }; - - const [batchSize, setBatchSize] = useState(16); - const [epochs, setEpochs] = useState(50); - const [learningRate, setLearningRate] = useState(0.001); - const [modelName, setModelName] = useState(""); - - const handleBatchSizeChange = ( - event: React.ChangeEvent - ) => { - const value = parseInt(event.target.value); - setBatchSize(value); - }; - - const handleEpochsChange = (event: React.ChangeEvent) => { - const value = parseInt(event.target.value); - setEpochs(value); - }; - - const handleLearningRateChange = ( - event: React.ChangeEvent - ) => { - const value = parseFloat(event.target.value); - setLearningRate(value); - }; - - const handleModelNameChange = ( - event: React.ChangeEvent - ) => { - const value = event.target.value; - setModelName(value); - }; - - const [advancedOptionsVisible, setAdvancedOptionsVisible] = useState(false); - - const toggleAdvancedOptions = () => { - setAdvancedOptionsVisible(!advancedOptionsVisible); - }; - - // carregamento - - const [loadingProgress, setLoadingProgress] = useState(0); - const prevLoadingProgressRef = useRef(0); // Explicitly type prevLoadingProgressRef - - useEffect(() => { - const fetchData = async () => { - try { - const response = await axios.get("http://localhost:5000/training-status"); - const { training_progress, training_in_progress } = response.data; - const newProgress: number = training_in_progress || training_progress === 100 ? training_progress : 0; // Explicitly type newProgress - updateLoadingProgress(newProgress); - } catch (error) { - console.error("Error fetching progress:", error); - } - }; - - const updateLoadingProgress = (newProgress: number) => { // Explicitly type newProgress parameter - const duration = 1000; // Duration in milliseconds for the transition - const startTime = Date.now(); - const startProgress = prevLoadingProgressRef.current; - - const updateProgress = () => { - const elapsedTime = Date.now() - startTime; - const progress = Math.min(1, elapsedTime / duration); // Ensure progress doesn't exceed 1 - const interpolatedProgress = startProgress + (newProgress - startProgress) * progress; - setLoadingProgress(interpolatedProgress); - - if (progress < 1) { - requestAnimationFrame(updateProgress); - } else { - prevLoadingProgressRef.current = newProgress; - } - }; - - updateProgress(); - }; - - const interval = setInterval(fetchData, 1050); - - return () => clearInterval(interval); - }, []); return ( -
- - -
-

- LinguifAI -

- - { - - } - - {selectedFile && ( - <> -
- - -
- -
- - -
- -
- - -
- - - - {advancedOptionsVisible && ( -
-
- - -
- -
- - -
- -
- - -
-
- )} - -
-
- {isLoading && ( -
-
-
-
-
- {`Treinamento em ${loadingProgress.toFixed(2)}%`} -
-
- )} - - -
-
- - )} -
-
+ ); -} \ No newline at end of file +} diff --git a/src/pages/views/aboutView.tsx b/src/pages/views/aboutView.tsx new file mode 100644 index 00000000..14134db9 --- /dev/null +++ b/src/pages/views/aboutView.tsx @@ -0,0 +1,128 @@ +import React from "react"; +import { Menu } from "../../components/menu/menu"; +import { FaGithub, FaLinkedin } from "react-icons/fa"; + +export default function AboutView() { + const teamMembers = [ + { + name: "Jonas Gabriel", + linkedin: "https://www.linkedin.com/in/jonas-gabriel-araujo/", + github: "https://github.com/jonasgabriel18", + photo: "../../../assets/jonas.jpg", + }, + { + name: "Luiz Gusttavo", + linkedin: "https://www.linkedin.com/in/luiz-gusttavo-oliveira-de-souza-7538091b1/", + github: "https://github.com/GusttavoOliveira", + photo: "../../../assets/luiz.jpg", + }, + { + name: "Bertrand Lira", + linkedin: "https://www.linkedin.com/in/bertrand-lira-veloso-52aa4926a/", + github: "https://github.com/BertrandLira", + photo: "../../../assets/bertrand.jpg", + }, + { + name: "Cameron Maloney", + linkedin: "https://www.linkedin.com/in/cameronmal/", + github: "https://github.com/cmaloney111", + photo: "../../../assets/cameron.jpg", + }, + { + name: "Gisele Silva", + linkedin: "https://www.linkedin.com/in/gisele-silva-6692941a4/", + github: "https://github.com/GiseleBr678", + photo: "../../../assets/LINDADEMAIS/gisele.jpg", + }, + { + name: "Thauã Magalhães", + linkedin: "https://www.linkedin.com/in/thaua-lucas/", + github: "https://github.com/tahaluh", + photo: "../../../assets/thaua.jpg", + }, + { + name: "Thiago Rodrigues", + linkedin: "https://www.linkedin.com/in/thiago-rodrigues-b8a328249/", + github: "https://github.com/tahaluh", + photo: "../../../assets/thiago.jpg", + }, + ]; + + return ( +
+ + +
+

+ O LinguifAI é uma aplicação inovadora desenvolvida pela diretoria de NLP da TAIL, projetada para simplificar e agilizar o treinamento e a classificação de modelos de Machine Learning em texto. Com essa ferramenta, é possível experimentar diversos algoritmos e ajustar parâmetros de forma intuitiva, tornando o processo de desenvolvimento de modelos NLP mais acessível e eficiente. +

+ +

Tecnologias Utilizadas:

+ +
+ React + Electron + Python +
+ Flask +
+ TensorFlow +
+ +

Conheça nossa equipe:

+ +
+ {teamMembers.map((member, index) => ( +
+ {member.name} +

{member.name}

+ +
+ ))} +
+
+
+ ); +} diff --git a/src/pages/views/homeView.tsx b/src/pages/views/homeView.tsx new file mode 100644 index 00000000..ad60840d --- /dev/null +++ b/src/pages/views/homeView.tsx @@ -0,0 +1,194 @@ +import React, { useState, useEffect } from "react"; +import axios from "axios"; +import SelectFileCard from "../../components/selectFileCard/selectFileCard"; +import ResultTable from "../../components/resultTable/resultTable"; + +export default function HomeView() { + const [selectedFile, setSelectedFile] = useState(null); + const [data, setData] = useState([]); + const [header, setHeader] = useState([]); + const [selectedColumn, setSelectedColumn] = useState(0); + const [selectedClassifier, setSelectedClassifier] = useState(""); + const [classifiers, setClassifiers] = useState<{ [key: string]: string }>({}); + const [result, setResult] = useState<{ [key: string]: any }>({}); + const [isLoading, setIsLoading] = useState(false); + const [isBackendAvailable, setIsBackendAvailable] = useState(false); + + useEffect(() => { + const fetchClassifiers = async () => { + while (true) { + try { + const response = await axios.get("http://localhost:5000/get-classifiers"); + console.log(response); + setClassifiers(response.data); + setIsBackendAvailable(true); + break; + } catch (error) { + console.error("Error fetching classifiers, retrying...", error); + await new Promise((resolve) => setTimeout(resolve, 3000)); + } + } + }; + + fetchClassifiers(); + }, []); + + useEffect(() => { + const sendSelectedFileToBackend = async () => { + if (selectedFile) { + const formData = new FormData(); + formData.append("file", selectedFile); + + try { + await axios.post("http://localhost:5000/receive", formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }); + } catch (error) { + console.error("Error uploading file", error); + } + } + }; + + sendSelectedFileToBackend(); + }, [selectedFile]); + + const handleChangeSelectedColumn = (event: React.ChangeEvent) => { + setSelectedColumn(Number(event.target.value)); + }; + + const handleChangeSelectedClassifier = (event: React.ChangeEvent) => { + setSelectedClassifier(event.target.value); + }; + + const handleSubmit = async () => { + setIsLoading(true); + let selectedData = data.map((row) => row[selectedColumn]); + const response = await axios.post("http://localhost:5000/classify", { + data: selectedData, + classifier: selectedClassifier, + }).catch((error) => { + console.error(error.response.data); + }); + + if (response && response.data) { + const parsedData = JSON.parse(response.data.result); + const input: any[] = Object.values(parsedData.input_column); + const output: any[] = Object.values(parsedData.output_column).flat(1); + + if (input.length === output.length) { + const result = input.reduce((acc, key, index) => { + acc[key] = output[index]; + return acc; + }, {} as { [key: string]: any }); + + console.log(result); + setResult(result); + } else { + console.error("Os arrays 'input' e 'output' têm tamanhos diferentes."); + } + } + + setIsLoading(false); + }; + + const handleDownloadOutputCSV = async () => { + const finalHeader = header.concat(`${selectedClassifier}_output`); + const finalData = data.map((row) => row.concat(result[row[selectedColumn]])); + + const csv = finalHeader.join(",").concat("\n").concat(finalData.map((row) => row.join(",")).join("\n")); + + const csvFile = new Blob([csv], { type: "text/csv" }); + const csvURL = window.URL.createObjectURL(csvFile); + const tempLink = document.createElement("a"); + tempLink.href = csvURL; + tempLink.setAttribute("download", "output.csv"); + tempLink.click(); + }; + + if (!isBackendAvailable) { + return
Carregando backend...
; + } + + return ( +
+ + + {selectedFile && ( + <> +
+ + +
+
+ + +
+
+ +
+ {Object.keys(result).length > 0 && ( +
+ +
+ +
+
+ )} + + )} +
+ ); +} diff --git a/src/pages/views/trainView.tsx b/src/pages/views/trainView.tsx new file mode 100644 index 00000000..19be82ae --- /dev/null +++ b/src/pages/views/trainView.tsx @@ -0,0 +1,381 @@ +import React, { useState, useEffect, useRef } from "react"; +import axios from "axios"; +import ReactApexChart from "react-apexcharts"; +import SelectFileCard from "../../components/selectFileCard/selectFileCard"; +import { ReactApexChartsDefaultOptions } from "../../Shared/apexChartsOptions"; + +export default function TrainView() { + const [selectedFile, setSelectedFile] = useState(null); + + const [data, setData] = useState([]); + const [header, setHeader] = useState([]); + + const [selectedColumn, setSelectedColumn] = useState(0); + const [selectedLabel, setSelectedLabel] = useState(0); + + const [isLoading, setIsLoading] = useState(false); + const [hasTrained, setHasTrained] = useState(false); + const [isCancelling, setIsCancelling] = useState(false); + + const handleChangeSelectedColumn = (event: React.ChangeEvent) => { + setSelectedColumn(Number(event.target.value)); + }; + + const handleChangeSelectedLabel = (event: React.ChangeEvent) => { + setSelectedLabel(Number(event.target.value)); + }; + + const handleCancelTraining = async () => { + setIsCancelling(true); + try { + await axios.post('http://localhost:5000/cancel-training'); + alert('Treinamento cancelado com sucesso!'); + } catch (error) { + console.error('Erro ao cancelar o treinamento:', error); + alert('Falha ao cancelar o treinamento.'); + } + setIsCancelling(false); + }; + + const handleRnnSubmit = async () => { + setIsLoading(true); + setHasTrained(true); + setLoadingProgress(0); + setTrainLosses([]); + setValidLosses([]); + + let selectedData = data.map((row) => ({ + value: row[selectedColumn], + label: row[selectedLabel], + })); + + let selectedLabels = data.map((row) => row[selectedLabel]); + let selectedValues = data.map((row) => row[selectedColumn]); + + const sendData = { + data: selectedValues, + label: selectedLabels, + batch_size: batchSize || 16, + epochs: epochs || 50, + learning_rate: learningRate || 0.001, + name: modelName || "trained-model", + }; + + console.log(sendData); + + const url = "http://localhost:5000/neural-network-rnn"; + + await axios.post(url, sendData).catch(async (error) => { + await axios.post(url, sendData).catch(async (error) => { + await axios.post(url, sendData).catch(async (error) => { + await axios.post(url, sendData).catch((error) => { + throw new Error(error); + }); + }); + }); + }); + + setIsLoading(false); + }; + + const handleNbSubmit = async () => { + setIsLoading(true); + setLoadingProgress(0); + + let selectedData = data.map((row) => ({ + value: row[selectedColumn], + label: row[selectedLabel], + })); + + let selectedLabels = data.map((row) => row[selectedLabel]); + let selectedValues = data.map((row) => row[selectedColumn]); + + const sendData = { + data: selectedValues, + label: selectedLabels, + batch_size: batchSize || 16, + epochs: epochs || 50, + learning_rate: learningRate || 0.001, + name: modelName || "trained-model", + }; + + console.log(sendData); + + const url = "http://localhost:5000/neural-network-nb"; + + await axios.post(url, sendData).catch(async (error) => { + await axios.post(url, sendData).catch(async (error) => { + await axios.post(url, sendData).catch(async (error) => { + await axios.post(url, sendData).catch((error) => { + throw new Error(error); + }); + }); + }); + }); + + setIsLoading(false); + }; + + const [batchSize, setBatchSize] = useState(16); + const [epochs, setEpochs] = useState(50); + const [learningRate, setLearningRate] = useState(0.001); + const [modelName, setModelName] = useState(""); + + const handleBatchSizeChange = ( + event: React.ChangeEvent + ) => { + const value = parseInt(event.target.value); + setBatchSize(value); + }; + + const handleEpochsChange = (event: React.ChangeEvent) => { + const value = parseInt(event.target.value); + setEpochs(value); + }; + + const handleLearningRateChange = ( + event: React.ChangeEvent + ) => { + const value = parseFloat(event.target.value); + setLearningRate(value); + }; + + const handleModelNameChange = ( + event: React.ChangeEvent + ) => { + const value = event.target.value; + setModelName(value); + }; + + const [advancedOptionsVisible, setAdvancedOptionsVisible] = useState(false); + + const toggleAdvancedOptions = () => { + setAdvancedOptionsVisible(!advancedOptionsVisible); + }; + + const [loadingProgress, setLoadingProgress] = useState(0); + const [train_losses, setTrainLosses] = useState([]); + const [valid_losses, setValidLosses] = useState([]); + const prevLoadingProgressRef = useRef(0); + + useEffect(() => { + const fetchData = async () => { + try { + const response = await axios.get( + "http://localhost:5000/training-status" + ); + const { training_progress, training_in_progress, train_losses, valid_losses } = response.data; + const newProgress: number = + training_in_progress || training_progress === 100 + ? training_progress + : 0; + updateLoadingProgress(newProgress); + + setTrainLosses(train_losses); + setValidLosses(valid_losses); + } catch (error) { + console.error("Error fetching progress:", error); + } + }; + + const updateLoadingProgress = (newProgress: number) => { + const duration = 1000; + const startTime = Date.now(); + const startProgress = prevLoadingProgressRef.current; + + const updateProgress = () => { + const elapsedTime = Date.now() - startTime; + const progress = Math.min(1, elapsedTime / duration); + const interpolatedProgress = + startProgress + (newProgress - startProgress) * progress; + setLoadingProgress(interpolatedProgress); + + if (progress < 1) { + requestAnimationFrame(updateProgress); + } else { + prevLoadingProgressRef.current = newProgress; + } + }; + + updateProgress(); + }; + + const interval = setInterval(fetchData, 1050); + + return () => clearInterval(interval); + }, []); + + return ( +
+ + + {selectedFile && ( + <> +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
+
+ {isLoading && ( +
+
+
+
+ {`Treinamento em ${loadingProgress.toFixed(2)}%`} +
+
+ )} + + {!isLoading && } + + {!isLoading && } + + {hasTrained && train_losses.length > 0 && ( +
+ +
+ )} + + {isLoading && ( + + )} +
+
+ + )} +
+ ); +} + diff --git a/yarn.lock b/yarn.lock index 15eb72e9..faf41fa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,12 +34,12 @@ jsonpointer "^5.0.0" leven "^3.1.0" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.1", "@babel/code-frame@^7.8.3": - version "7.24.2" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz" - integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.24.7", "@babel/code-frame@^7.8.3": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== dependencies: - "@babel/highlight" "^7.24.2" + "@babel/highlight" "^7.24.7" picocolors "^1.0.0" "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5", "@babel/compat-data@^7.24.4": @@ -77,22 +77,22 @@ eslint-visitor-keys "^2.1.0" semver "^6.3.1" -"@babel/generator@^7.22.15", "@babel/generator@^7.24.1", "@babel/generator@^7.7.2": - version "7.24.4" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz" - integrity sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw== +"@babel/generator@^7.22.15", "@babel/generator@^7.24.7", "@babel/generator@^7.7.2": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz" + integrity sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA== dependencies: - "@babel/types" "^7.24.0" + "@babel/types" "^7.24.7" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" -"@babel/helper-annotate-as-pure@^7.22.5": - version "7.22.5" - resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz" - integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== +"@babel/helper-annotate-as-pure@^7.18.6", "@babel/helper-annotate-as-pure@^7.22.5", "@babel/helper-annotate-as-pure@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz" + integrity sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg== dependencies: - "@babel/types" "^7.22.5" + "@babel/types" "^7.24.7" "@babel/helper-builder-binary-assignment-operator-visitor@^7.22.15": version "7.22.15" @@ -112,19 +112,19 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.24.1", "@babel/helper-create-class-features-plugin@^7.24.4": - version "7.24.4" - resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz" - integrity sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-member-expression-to-functions" "^7.23.0" - "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/helper-replace-supers" "^7.24.1" - "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.24.1", "@babel/helper-create-class-features-plugin@^7.24.4", "@babel/helper-create-class-features-plugin@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz" + integrity sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.7" + "@babel/helper-optimise-call-expression" "^7.24.7" + "@babel/helper-replace-supers" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" semver "^6.3.1" "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5": @@ -147,32 +147,35 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== +"@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz" + integrity sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ== + dependencies: + "@babel/types" "^7.24.7" -"@babel/helper-function-name@^7.22.5", "@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== +"@babel/helper-function-name@^7.22.5", "@babel/helper-function-name@^7.23.0", "@babel/helper-function-name@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz" + integrity sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA== dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" + "@babel/template" "^7.24.7" + "@babel/types" "^7.24.7" -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== +"@babel/helper-hoist-variables@^7.22.5", "@babel/helper-hoist-variables@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz" + integrity sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ== dependencies: - "@babel/types" "^7.22.5" + "@babel/types" "^7.24.7" -"@babel/helper-member-expression-to-functions@^7.23.0": - version "7.23.0" - resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz" - integrity sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA== +"@babel/helper-member-expression-to-functions@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz" + integrity sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w== dependencies: - "@babel/types" "^7.23.0" + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.22.15", "@babel/helper-module-imports@^7.24.1", "@babel/helper-module-imports@^7.24.3": version "7.24.3" @@ -192,17 +195,17 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.20" -"@babel/helper-optimise-call-expression@^7.22.5": - version "7.22.5" - resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz" - integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== +"@babel/helper-optimise-call-expression@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz" + integrity sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A== dependencies: - "@babel/types" "^7.22.5" + "@babel/types" "^7.24.7" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.24.0" - resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz" - integrity sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz" + integrity sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg== "@babel/helper-remap-async-to-generator@^7.22.20": version "7.22.20" @@ -213,14 +216,14 @@ "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-wrap-function" "^7.22.20" -"@babel/helper-replace-supers@^7.24.1": - version "7.24.1" - resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz" - integrity sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ== +"@babel/helper-replace-supers@^7.24.1", "@babel/helper-replace-supers@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz" + integrity sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg== dependencies: - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-member-expression-to-functions" "^7.23.0" - "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.7" + "@babel/helper-optimise-call-expression" "^7.24.7" "@babel/helper-simple-access@^7.22.5": version "7.22.5" @@ -229,29 +232,30 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-skip-transparent-expression-wrappers@^7.20.0", "@babel/helper-skip-transparent-expression-wrappers@^7.22.5": - version "7.22.5" - resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz" - integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== +"@babel/helper-skip-transparent-expression-wrappers@^7.20.0", "@babel/helper-skip-transparent-expression-wrappers@^7.22.5", "@babel/helper-skip-transparent-expression-wrappers@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz" + integrity sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ== dependencies: - "@babel/types" "^7.22.5" + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== +"@babel/helper-split-export-declaration@^7.22.6", "@babel/helper-split-export-declaration@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz" + integrity sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA== dependencies: - "@babel/types" "^7.22.5" + "@babel/types" "^7.24.7" -"@babel/helper-string-parser@^7.23.4": - version "7.24.1" - resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz" - integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== +"@babel/helper-string-parser@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz" + integrity sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg== -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== +"@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== "@babel/helper-validator-option@^7.23.5": version "7.23.5" @@ -276,20 +280,20 @@ "@babel/traverse" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/highlight@^7.24.2": - version "7.24.2" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz" - integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== +"@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== dependencies: - "@babel/helper-validator-identifier" "^7.22.20" + "@babel/helper-validator-identifier" "^7.24.7" chalk "^2.4.2" js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.16", "@babel/parser@^7.24.0", "@babel/parser@^7.24.1": - version "7.24.4" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz" - integrity sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.16", "@babel/parser@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz" + integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw== "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.4": version "7.24.4" @@ -373,6 +377,16 @@ "@babel/helper-create-class-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-proposal-private-property-in-object@^7.21.11": + version "7.21.11" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz" + integrity sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.21.0" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz" @@ -836,14 +850,14 @@ "@babel/helper-create-class-features-plugin" "^7.24.1" "@babel/helper-plugin-utils" "^7.24.0" -"@babel/plugin-transform-private-property-in-object@^7.24.1": - version "7.24.1" - resolved "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz" - integrity sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg== +"@babel/plugin-transform-private-property-in-object@^7.24.1", "@babel/plugin-transform-private-property-in-object@^7.24.7": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz" + integrity sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA== dependencies: - "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.24.1" - "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-transform-property-literals@^7.24.1": @@ -1128,38 +1142,38 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.22.15", "@babel/template@^7.24.0", "@babel/template@^7.3.3": - version "7.24.0" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz" - integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== - dependencies: - "@babel/code-frame" "^7.23.5" - "@babel/parser" "^7.24.0" - "@babel/types" "^7.24.0" - -"@babel/traverse@^7.22.15", "@babel/traverse@^7.22.20", "@babel/traverse@^7.7.2": - version "7.24.1" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz" - integrity sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ== - dependencies: - "@babel/code-frame" "^7.24.1" - "@babel/generator" "^7.24.1" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.24.1" - "@babel/types" "^7.24.0" +"@babel/template@^7.22.15", "@babel/template@^7.24.0", "@babel/template@^7.24.7", "@babel/template@^7.3.3": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz" + integrity sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/traverse@^7.22.15", "@babel/traverse@^7.22.20", "@babel/traverse@^7.24.7", "@babel/traverse@^7.7.2": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz" + integrity sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.24.7" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-hoist-variables" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/types" "^7.24.7" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.12.6", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.24.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.24.0" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz" - integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== +"@babel/types@^7.0.0", "@babel/types@^7.12.6", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.4", "@babel/types@^7.24.0", "@babel/types@^7.24.7", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.24.7" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz" + integrity sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q== dependencies: - "@babel/helper-string-parser" "^7.23.4" - "@babel/helper-validator-identifier" "^7.22.20" + "@babel/helper-string-parser" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": @@ -2904,6 +2918,11 @@ resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +"@yr/monotone-cubic-spline@^1.0.3": + version "1.0.3" + resolved "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz" + integrity sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA== + "@zeit/schemas@2.29.0": version "2.29.0" resolved "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.29.0.tgz" @@ -3115,6 +3134,19 @@ anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +apexcharts@^3.41.0, apexcharts@^3.49.1: + version "3.49.1" + resolved "https://registry.npmjs.org/apexcharts/-/apexcharts-3.49.1.tgz" + integrity sha512-MqGtlq/KQuO8j0BBsUJYlRG8VBctKwYdwuBtajHgHTmSgUU3Oai+8oYN/rKCXwXzrUlYA+GiMgotAIbXY2BCGw== + dependencies: + "@yr/monotone-cubic-spline" "^1.0.3" + svg.draggable.js "^2.2.2" + svg.easing.js "^2.0.0" + svg.filter.js "^2.0.2" + svg.pathmorphing.js "^0.1.3" + svg.resize.js "^1.4.3" + svg.select.js "^3.0.1" + app-builder-bin@4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.0.0.tgz" @@ -5037,9 +5069,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== ejs@^3.1.6, ejs@^3.1.8: - version "3.1.9" - resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz" - integrity sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ== + version "3.1.10" + resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== dependencies: jake "^10.8.5" @@ -9821,6 +9853,13 @@ rcedit@^3.0.1: dependencies: cross-spawn-windows-exe "^1.1.0" +react-apexcharts@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.1.tgz" + integrity sha512-G14nVaD64Bnbgy8tYxkjuXEUp/7h30Q0U33xc3AwtGFijJB9nHqOt1a6eG0WBn055RgRg+NwqbKGtqPxy15d0Q== + dependencies: + prop-types "^15.8.1" + react-app-polyfill@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz" @@ -9994,7 +10033,7 @@ react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" -react@*, "react@^17.0.0 || ^18.0.0", react@^18.0.0, react@^18.2.0, "react@>= 16", react@>=16, react@>=16.6.0, react@>=16.8, react@>=16.8.0: +react@*, "react@^17.0.0 || ^18.0.0", react@^18.0.0, react@^18.2.0, "react@>= 16", react@>=0.13, react@>=16, react@>=16.6.0, react@>=16.8, react@>=16.8.0: version "18.2.0" resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== @@ -11370,6 +11409,61 @@ svg-parser@^2.0.2: resolved "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz" integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== +svg.draggable.js@^2.2.2: + version "2.2.2" + resolved "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz" + integrity sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw== + dependencies: + svg.js "^2.0.1" + +svg.easing.js@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz" + integrity sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA== + dependencies: + svg.js ">=2.3.x" + +svg.filter.js@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz" + integrity sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw== + dependencies: + svg.js "^2.2.5" + +svg.js@^2.0.1, svg.js@^2.2.5, svg.js@^2.4.0, svg.js@^2.6.5, svg.js@>=2.3.x: + version "2.7.1" + resolved "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz" + integrity sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA== + +svg.pathmorphing.js@^0.1.3: + version "0.1.3" + resolved "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz" + integrity sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww== + dependencies: + svg.js "^2.4.0" + +svg.resize.js@^1.4.3: + version "1.4.3" + resolved "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz" + integrity sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw== + dependencies: + svg.js "^2.6.5" + svg.select.js "^2.1.2" + +svg.select.js@^2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz" + integrity sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ== + dependencies: + svg.js "^2.2.5" + +svg.select.js@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz" + integrity sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw== + dependencies: + svg.js "^2.6.5" + svgo@^1.2.2: version "1.3.2" resolved "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz"