├── .github └── workflows │ └── codeql.yml ├── LICENSE.txt ├── README.md ├── THIRD_PARTY_NOTICES.md ├── back-end ├── .gitignore ├── Dockerfile-backend ├── requirements.txt └── src │ ├── api │ ├── __init__.py │ ├── app_api_v1.py │ ├── embedding_api_v1.py │ ├── file_api_v1.py │ ├── quota_api_v1.py │ ├── shared_link_api_v1.py │ ├── task_api_v1.py │ └── user_api_v1.py │ ├── app.py │ ├── celery_worker.py │ ├── config.py │ ├── connection.py │ ├── core │ ├── __init__.py │ ├── auth │ │ ├── __init__.py │ │ ├── authenticator.py │ │ ├── extractor.py │ │ └── token.py │ ├── component │ │ ├── __init__.py │ │ ├── chain.py │ │ ├── doc_search.py │ │ ├── google_search.py │ │ ├── parser.py │ │ ├── prompt.py │ │ ├── table.py │ │ ├── text.py │ │ ├── utils.py │ │ └── youtube_transcript.py │ ├── doc_search │ │ ├── __init__.py │ │ ├── doc_transformer.py │ │ └── vector_store.py │ ├── interface │ │ ├── __init__.py │ │ └── ops_interface.py │ ├── llm_processor │ │ ├── __init__.py │ │ ├── anthropic_processor.py │ │ └── openai.py │ ├── task.py │ └── task_progress.py │ ├── model │ ├── __init__.py │ ├── application.py │ ├── base.py │ ├── file.py │ ├── shared_link.py │ ├── types.py │ └── user.py │ ├── services │ ├── __init__.py │ ├── quota_service.py │ ├── user_api_key_service.py │ └── user_service.py │ ├── test │ ├── ut_app_api_v1.py │ ├── ut_embedding_api_v1.py │ ├── ut_file_api_v1.py │ ├── ut_task_api_v1.py │ └── ut_user_api_v1.py │ └── util │ ├── __init__.py │ ├── celery_init.py │ ├── logger.py │ ├── resp.py │ ├── timestamp_util.py │ └── uid_gen.py ├── docker-compose.yml ├── front-end ├── .gitignore ├── Dockerfile-frontend ├── craco.config.js ├── package-lock.json ├── package.json ├── public │ ├── index.html │ ├── logo.svg │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── api │ │ ├── account.ts │ │ ├── apiKeys.ts │ │ ├── applications.ts │ │ ├── embedding.ts │ │ ├── file.ts │ │ ├── job.ts │ │ ├── quota.ts │ │ └── sharedLink.ts │ ├── assets │ │ ├── discord-mark-black.svg │ │ ├── platform_cover.svg │ │ ├── text_logo.png │ │ ├── text_logo_dark.png │ │ └── undraw_loading.svg │ ├── components │ │ ├── AccountManagement │ │ │ ├── LoginForm.js │ │ │ ├── RegisterForm.js │ │ │ └── Styles.less │ │ ├── MainHeader │ │ │ ├── MainHeader.js │ │ │ └── MainHeader.less │ │ └── Playground │ │ │ ├── Anthropic │ │ │ ├── AnthropicApi.js │ │ │ ├── AnthropicInput.js │ │ │ ├── AnthropicInput.less │ │ │ ├── AnthropicModelSettings.js │ │ │ └── AnthropicModelSettings.less │ │ │ ├── BatchInput │ │ │ ├── BatchInput.js │ │ │ ├── BatchInput.less │ │ │ ├── EditableCell.js │ │ │ ├── FileHandler.js │ │ │ └── JobModal.js │ │ │ ├── Common │ │ │ ├── ResponseCard.js │ │ │ └── ResponseCard.less │ │ │ ├── ComponentCard │ │ │ ├── ComponentCard.js │ │ │ ├── ComponentCard.less │ │ │ └── ModelInput.js │ │ │ ├── DocSearch │ │ │ ├── DocSearch.js │ │ │ └── DocSearch.less │ │ │ ├── GoogleSearch │ │ │ ├── GoogleSearch.js │ │ │ ├── GoogleSearch.less │ │ │ ├── GoogleSearchApi.js │ │ │ ├── GoogleSearchSettings.js │ │ │ └── GoogleSearchSettings.less │ │ │ ├── OpenAI │ │ │ ├── OpenAIApi.js │ │ │ ├── OpenAIInput.js │ │ │ ├── OpenAIInput.less │ │ │ ├── OpenAIModelSettings.js │ │ │ └── OpenAIModelSettings.less │ │ │ ├── Output │ │ │ ├── Output.js │ │ │ └── Output.less │ │ │ ├── Sider │ │ │ ├── Sider.js │ │ │ └── Sider.less │ │ │ ├── TagParser │ │ │ ├── TagParser.js │ │ │ └── TagParser.less │ │ │ ├── TextInput │ │ │ ├── TextInput.js │ │ │ └── TextInput.less │ │ │ ├── TitleCard │ │ │ ├── SaveModule.js │ │ │ ├── ShareModule.js │ │ │ ├── TitleCard.js │ │ │ └── TitleCard.less │ │ │ └── YouTubeTranscript │ │ │ ├── YouTubeTranscript.js │ │ │ ├── YouTubeTranscript.less │ │ │ └── YouTubeTranscriptApi.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── pages │ │ ├── AccountManagement │ │ │ ├── LoginPage.js │ │ │ ├── RegisterPage.js │ │ │ └── Styles.less │ │ ├── Application │ │ │ ├── ApplicationLoader.js │ │ │ ├── ApplicationPage.js │ │ │ └── ApplicationPage.less │ │ ├── Data │ │ │ ├── DataPage.js │ │ │ ├── DataPage.less │ │ │ ├── EmbeddingsPage.js │ │ │ └── EmbeddingsPage.less │ │ ├── Home │ │ │ ├── HomePage.js │ │ │ └── HomePage.less │ │ ├── Jobs │ │ │ ├── JobDetailPage.js │ │ │ ├── JobDetailPage.less │ │ │ ├── JobsPage.js │ │ │ └── JobsPage.less │ │ ├── Playground │ │ │ ├── PlaygroundGenerationPage.js │ │ │ ├── PlaygroundGenerationPage.less │ │ │ ├── PlaygroundHomePage.js │ │ │ ├── PlaygroundHomePage.less │ │ │ ├── PlaygroundLoader.js │ │ │ ├── PlaygroundPage.js │ │ │ └── PlaygroundPage.less │ │ └── UserSettings │ │ │ ├── UserSettingsPage.js │ │ │ └── UserSettingsPage.less │ ├── redux │ │ ├── actions │ │ │ ├── applicationActions.js │ │ │ ├── componentParamActions.js │ │ │ ├── jobActions.js │ │ │ ├── resetActions.js │ │ │ └── userActions.js │ │ ├── reducers │ │ │ ├── applicationDataReducer.js │ │ │ ├── applicationReducer.js │ │ │ ├── componentParamReducer.js │ │ │ ├── index.js │ │ │ ├── jobReducer.js │ │ │ └── userReducer.js │ │ └── store.js │ ├── reportWebVitals.js │ ├── routes │ │ └── PrivateRoute.js │ ├── setupTests.js │ ├── styles.less │ └── utils │ │ ├── dataUtils.js │ │ └── formatUtils.js └── yarn.lock ├── scripts └── init_db.sql ├── start_project_mac_or_linux.sh └── start_project_win.bat /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '31 18 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | # Runner size impacts CodeQL analysis time. To learn more, please see: 27 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 28 | # - https://gh.io/supported-runners-and-hardware-resources 29 | # - https://gh.io/using-larger-runners 30 | # Consider using larger runners for possible analysis time improvements. 31 | runs-on: ubuntu-latest 32 | timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} 33 | permissions: 34 | actions: read 35 | contents: read 36 | security-events: write 37 | 38 | strategy: 39 | fail-fast: false 40 | matrix: 41 | language: [ 'javascript-typescript', 'python' ] 42 | # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] 43 | # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both 44 | # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 45 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 46 | 47 | steps: 48 | - name: Checkout repository 49 | uses: actions/checkout@v3 50 | 51 | # Initializes the CodeQL tools for scanning. 52 | - name: Initialize CodeQL 53 | uses: github/codeql-action/init@v2 54 | with: 55 | languages: ${{ matrix.language }} 56 | # If you wish to specify custom queries, you can do so here or in a config file. 57 | # By default, queries listed here will override any specified in a config file. 58 | # Prefix the list here with "+" to use these queries and those in the config file. 59 | 60 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 61 | # queries: security-extended,security-and-quality 62 | 63 | 64 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). 65 | # If this step fails, then you should remove it and run the build manually (see below) 66 | - name: Autobuild 67 | uses: github/codeql-action/autobuild@v2 68 | 69 | # ℹ️ Command-line programs to run using the OS shell. 70 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 71 | 72 | # If the Autobuild fails above, remove it and uncomment the following three lines. 73 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 74 | 75 | # - run: | 76 | # echo "Run, Build Application using script" 77 | # ./location_of_script_within_repo/buildscript.sh 78 | 79 | - name: Perform CodeQL Analysis 80 | uses: github/codeql-action/analyze@v2 81 | with: 82 | category: "/language:${{matrix.language}}" 83 | -------------------------------------------------------------------------------- /back-end/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .idea 3 | celery_worker_log.txt 4 | src/vector_store -------------------------------------------------------------------------------- /back-end/Dockerfile-backend: -------------------------------------------------------------------------------- 1 | # Use Python 3.8.10 base image 2 | FROM python:3.8.10 3 | 4 | # Set the working directory in the container 5 | WORKDIR /app 6 | 7 | # Copy the dependencies file to the working directory 8 | COPY requirements.txt . 9 | 10 | # Install any needed packages specified in requirements.txt 11 | RUN pip install --no-cache-dir -r requirements.txt 12 | 13 | # Copy the current directory contents into the container at /app 14 | COPY ./src /app 15 | 16 | # Make port 5001 available to the world outside this container 17 | EXPOSE 5001 18 | 19 | # Run the application 20 | CMD ["python", "app.py"] 21 | -------------------------------------------------------------------------------- /back-end/requirements.txt: -------------------------------------------------------------------------------- 1 | cryptography==2.8 # use Fernet for authorization 2 | flask==2.3.2 3 | flask-cors==4.0.0 4 | flask-sqlalchemy==3.1.1 5 | flask-testing==0.8.1 6 | flask_jwt_extended==4.5.2 7 | inflection==0.5.1 8 | langchain==0.0.330 9 | openai==0.28.1 10 | pandas==1.3.5 11 | PyJWT==2.8.0 # token 12 | pymysql==1.0.2 13 | pytest==7.4.0 14 | pyyaml==6.0 15 | pydantic==1.10.12 16 | redis==4.4.2 17 | requests==2.31.0 18 | shortuuid==1.0.11 19 | sqlalchemy==2.0.22 20 | # uwsgi==2.0.21 21 | virtualenv==20.17.1 22 | werkzeug==2.3.6 23 | celery==5.3.1 24 | tiktoken==0.4.0 25 | chromadb==0.4.8 26 | lancedb==0.2.2 27 | chardet==5.2.0 28 | anthropic==0.3.11 29 | google-api-python-client==2.104.0 30 | pytube==15.0.0 31 | youtube-transcript-api==0.6.1 -------------------------------------------------------------------------------- /back-end/src/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnchoringAI/anchoring-ai/7fb3973590fe2c316fdf0159174dfc9dd57928ec/back-end/src/api/__init__.py -------------------------------------------------------------------------------- /back-end/src/api/quota_api_v1.py: -------------------------------------------------------------------------------- 1 | """Quota API.""" 2 | from flask import Blueprint, jsonify, g 3 | from werkzeug.exceptions import BadRequest 4 | from services.quota_service import QuotaService 5 | from core.auth.authenticator import login_required, get_current_user 6 | 7 | quota_api_v1 = Blueprint('quota_api_v1', __name__, url_prefix='/v1/quota') 8 | 9 | 10 | @quota_api_v1.before_request 11 | @login_required 12 | def load_user_id(): 13 | """Load user ID.""" 14 | g.current_user_id = get_current_user().get_id() 15 | 16 | 17 | @quota_api_v1.route('/check', methods=['GET']) 18 | @login_required 19 | def get_quota(): 20 | """Get quota.""" 21 | try: 22 | user_id = g.current_user_id 23 | result = QuotaService.check_user_quota(user_id) 24 | if "error" in result: 25 | raise BadRequest(result["error"]) 26 | return jsonify(result) 27 | except BadRequest as e: 28 | return jsonify(error=str(e)), 400 29 | 30 | # @quota_api_v1.route('/update', methods=['POST']) 31 | # @login_required 32 | # def update_quota(): 33 | # try: 34 | # user_id = g.current_user_id 35 | # amount = request.json.get('amount') 36 | # if not amount: 37 | # raise BadRequest("Amount not provided") 38 | 39 | # result = QuotaService.update_user_quota(user_id, amount) 40 | # if "error" in result: 41 | # raise BadRequest(result["error"]) 42 | # return response(result) 43 | # except BadRequest as e: 44 | # return response(str(e), 400) 45 | -------------------------------------------------------------------------------- /back-end/src/api/shared_link_api_v1.py: -------------------------------------------------------------------------------- 1 | """Shared link API.""" 2 | import json 3 | 4 | from flask import Blueprint, Response, request, jsonify, g 5 | 6 | from connection import db 7 | from core.auth.authenticator import login_required, get_current_user 8 | from model.shared_link import DbSharedLink 9 | from model.application import DbAppBuild, DbAppTask 10 | from util.uid_gen import gen_uuid 11 | 12 | shared_link_api_v1 = Blueprint( 13 | 'shared_link_api_v1', __name__, url_prefix='/v1/shared') 14 | 15 | 16 | @shared_link_api_v1.before_request 17 | @login_required 18 | def load_user_id(): 19 | """Load user ID.""" 20 | g.current_user_id = get_current_user().get_id() 21 | 22 | 23 | @shared_link_api_v1.route('/app/', methods=['GET']) 24 | @login_required 25 | def load_share_link_app(link_id): 26 | """Load share link app.""" 27 | return _load_share_link_generic(link_id, 'APP') 28 | 29 | 30 | @shared_link_api_v1.route('/task/', methods=['GET']) 31 | @login_required 32 | def load_share_link_task(link_id): 33 | """Load share link task.""" 34 | return _load_share_link_generic(link_id, 'TASK') 35 | 36 | 37 | def _load_share_link_generic(link_id, expected_type): 38 | share_link_record = DbSharedLink.query.filter_by(id=link_id).first() 39 | 40 | if not share_link_record or share_link_record.resource_type != expected_type: 41 | return {"message": "Shared link not found or incorrect type."}, 404 42 | 43 | resource_type = share_link_record.resource_type 44 | resource_id = share_link_record.resource_id 45 | 46 | if resource_type == 'APP': 47 | resource = DbAppBuild.query.filter_by(id=resource_id).filter( 48 | DbAppBuild.deleted_at.is_(None)).first() 49 | elif resource_type == 'TASK': 50 | resource = DbAppTask.query.filter_by(id=resource_id).filter( 51 | DbAppTask.deleted_at.is_(None)).first() 52 | else: 53 | return {"message": "Invalid resource type."}, 400 54 | 55 | if resource: 56 | return Response(json.dumps(resource.as_dict())) 57 | 58 | return {"message": "Resource not found or deleted."}, 404 59 | 60 | 61 | @shared_link_api_v1.route('/generate', methods=['POST']) 62 | @login_required 63 | def generate_share_link(): 64 | """Generate share link.""" 65 | request_data = request.get_json() 66 | 67 | # Extract necessary data from the request 68 | resource_type = request_data.get('resource_type') 69 | resource_id = request_data.get('resource_id') 70 | expires_at = request_data.get('expires_at') 71 | 72 | # Get the current user's ID 73 | created_by = g.current_user_id 74 | 75 | # Verify if the resource can be shared (created by the user or is published) 76 | if resource_type == 'APP': 77 | resource = DbAppBuild.query.filter( 78 | DbAppBuild.id == resource_id, 79 | DbAppBuild.deleted_at.is_(None), 80 | (DbAppBuild.created_by == created_by) | 81 | (DbAppBuild.published.is_(True)) 82 | ).first() 83 | elif resource_type == 'TASK': 84 | resource = DbAppTask.query.filter( 85 | DbAppTask.id == resource_id, 86 | DbAppTask.deleted_at.is_(None), 87 | (DbAppTask.created_by == created_by) | 88 | (DbAppTask.published.is_(True)) 89 | ).first() 90 | else: 91 | return {"message": "Invalid resource type."}, 400 92 | 93 | if not resource: 94 | return {"message": "Resource not found, deleted, or access denied."}, 404 95 | 96 | # Generate a unique ID for the share link 97 | share_link_id = gen_uuid() 98 | 99 | # Create a new DbSharedLink object 100 | new_share_link = DbSharedLink( 101 | id=share_link_id, 102 | created_by=created_by, 103 | resource_id=resource_id, 104 | resource_type=resource_type, 105 | expires_at=expires_at 106 | ) 107 | 108 | # Add the new object to the database 109 | db.session.add(new_share_link) 110 | db.session.commit() 111 | 112 | # Return the shareable URL as the response 113 | return jsonify({"message": "Share link generated successfully", 114 | "share_link_id": share_link_id, 115 | "resource_type": resource_type}), 201 116 | -------------------------------------------------------------------------------- /back-end/src/app.py: -------------------------------------------------------------------------------- 1 | """App.""" 2 | import os 3 | 4 | import langchain 5 | from flask_cors import CORS 6 | from flask_jwt_extended import JWTManager 7 | from langchain.cache import InMemoryCache 8 | 9 | from api.app_api_v1 import app_api_v1 10 | from api.file_api_v1 import file_api_v1 11 | from api.task_api_v1 import task_api_v1 12 | from api.user_api_v1 import user_api_v1 13 | from api.quota_api_v1 import quota_api_v1 14 | from api.embedding_api_v1 import embedding_api_v1 15 | from api.shared_link_api_v1 import shared_link_api_v1 16 | from config import DevelopmentConfig, app 17 | from connection import db 18 | from util.celery_init import celery_init_app 19 | 20 | 21 | CORS(app) 22 | # TO-DO: Distinguish between environment variables to obtain different configurations 23 | app.config.from_object(DevelopmentConfig) 24 | 25 | app.config['JWT_SECRET_KEY'] = os.getenv('JWT_SECRET_KEY') 26 | jwt = JWTManager(app) 27 | 28 | celery_app = celery_init_app(app) 29 | 30 | db.init_app(app) 31 | langchain.llm_cache = InMemoryCache() 32 | 33 | # print(os.getenv("OPENAI_API_KEY")) 34 | # openai.api_key = os.getenv("OPENAI_API_KEY") 35 | # openai.Model.list() 36 | 37 | app.register_blueprint(app_api_v1) 38 | app.register_blueprint(task_api_v1) 39 | app.register_blueprint(user_api_v1) 40 | app.register_blueprint(quota_api_v1) 41 | app.register_blueprint(file_api_v1) 42 | app.register_blueprint(embedding_api_v1) 43 | app.register_blueprint(shared_link_api_v1) 44 | 45 | 46 | if __name__ == '__main__': 47 | app.run(port=5001, debug=True) 48 | -------------------------------------------------------------------------------- /back-end/src/celery_worker.py: -------------------------------------------------------------------------------- 1 | """Celery network.""" 2 | from app import celery_app 3 | 4 | if __name__ == '__main__': 5 | celery_app.worker_main( 6 | # argv=['-A', 'app.celery_app', 'worker', '--loglevel=info', '--pool=solo']) for Windows 7 | argv=['-A', 'app.celery_app', 'worker', '--loglevel=info']) 8 | -------------------------------------------------------------------------------- /back-end/src/config.py: -------------------------------------------------------------------------------- 1 | """Config.""" 2 | import secrets 3 | import os 4 | 5 | from flask import Flask 6 | 7 | from util.logger import Logger 8 | 9 | 10 | app = Flask(__name__) 11 | 12 | 13 | # pylint: disable=too-few-public-methods 14 | class BaseConfig: 15 | """Base config.""" 16 | DIALECT = 'mysql' 17 | DRIVER = 'pymysql' 18 | SQLALCHEMY_TRACK_MODIFICATIONS = False 19 | SQLALCHEMY_ECHO = True 20 | 21 | 22 | # pylint: disable=too-few-public-methods 23 | class DevelopmentConfig(BaseConfig): 24 | """Development config.""" 25 | USERNAME = 'llm_ops' 26 | PASSWORD = '123' 27 | HOST = 'localhost' 28 | PORT = '3306' 29 | DATABASE = 'llm' 30 | DB_URI = f'mysql+pymysql://{USERNAME}:{PASSWORD}@{HOST}:{PORT}/{DATABASE}?charset=utf8' 31 | SQLALCHEMY_DATABASE_URI = DB_URI 32 | SECRET_KEY = secrets.token_urlsafe(32) 33 | REDIS_HOST = "localhost" 34 | REDIS_PORT = 3306 35 | CELERY = { 36 | 'broker_url': 'redis://localhost:6379/0', 37 | 'result_backend': 'redis://localhost:6379/1' 38 | } 39 | OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") 40 | VECTOR_STORE = { 41 | 'db_path': os.path.join(os.path.dirname(os.path.abspath(__file__)), "vector_store/lancedb") 42 | } 43 | 44 | 45 | logger = Logger("LLM_Ops_Logger", level="INFO") 46 | -------------------------------------------------------------------------------- /back-end/src/connection.py: -------------------------------------------------------------------------------- 1 | """Connection.""" 2 | import redis 3 | from flask_sqlalchemy import SQLAlchemy 4 | 5 | from config import app 6 | 7 | db = SQLAlchemy() 8 | 9 | redis_host = app.config.get("REDIS_HOST") 10 | redis_port = app.config.get("REDIS_PORT") 11 | r = redis.Redis(host=redis_host, port=redis_port) 12 | -------------------------------------------------------------------------------- /back-end/src/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnchoringAI/anchoring-ai/7fb3973590fe2c316fdf0159174dfc9dd57928ec/back-end/src/core/__init__.py -------------------------------------------------------------------------------- /back-end/src/core/auth/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnchoringAI/anchoring-ai/7fb3973590fe2c316fdf0159174dfc9dd57928ec/back-end/src/core/auth/__init__.py -------------------------------------------------------------------------------- /back-end/src/core/auth/authenticator.py: -------------------------------------------------------------------------------- 1 | """Authenticator.""" 2 | from functools import wraps 3 | 4 | from flask import request, g 5 | 6 | from core.auth.extractor import JwtTokenExtractor 7 | from core.auth.token import JwtToken 8 | from services.user_service import get_user_by_id 9 | 10 | 11 | class Authenticator: 12 | """Authenticator.""" 13 | 14 | def __init__(self, token_extractor: JwtTokenExtractor): 15 | self._token_extractor = token_extractor 16 | 17 | def authenticate(self, req) -> JwtToken: 18 | """Authenticate.""" 19 | token = self.extract_token_from_request(req) 20 | self.assert_login_uniqueness(token) 21 | self.assert_token_version_up_to_date(token) 22 | 23 | return token 24 | 25 | @staticmethod 26 | def assert_token_version_up_to_date(token: JwtToken): 27 | return True 28 | 29 | @staticmethod 30 | def assert_user_account_validity(token: JwtToken): 31 | return True 32 | 33 | @staticmethod 34 | def assert_login_uniqueness(token: JwtToken): 35 | # TO-DO: session id 36 | return True 37 | 38 | def extract_token_from_request(self, req): 39 | """Extract token from request.""" 40 | return self._token_extractor.extract_from(req) 41 | 42 | 43 | authenticator = Authenticator(JwtTokenExtractor()) 44 | 45 | 46 | def get_current_user(): 47 | """Get current user.""" 48 | return g.current_user 49 | 50 | 51 | def set_current_user(user): 52 | """Set current user.""" 53 | g.current_user = user 54 | 55 | 56 | def login_required(f): 57 | """Login required.""" 58 | @wraps(f) 59 | def wrapper(*args, **kw): 60 | # NOTES: 61 | # Obsoleted Chengdu third part login validation. 62 | # But keeping the code for the other third part login.\ 63 | token = authenticator.authenticate(request) 64 | set_current_user( 65 | get_user_by_id(token.user_id) 66 | ) 67 | return f(*args, **kw) 68 | 69 | return wrapper 70 | -------------------------------------------------------------------------------- /back-end/src/core/auth/extractor.py: -------------------------------------------------------------------------------- 1 | """Extractor.""" 2 | import re 3 | 4 | import jwt 5 | from flask import request 6 | 7 | from core.auth.token import JwtToken 8 | 9 | HTTP_HEADER_AUTHORIZATION_FIELD = "XAuthorization" 10 | 11 | 12 | class JwtTokenExtractor: 13 | """JWT token extractor.""" 14 | 15 | def __init__(self, request_header_field: str = HTTP_HEADER_AUTHORIZATION_FIELD): 16 | self._header_field = request_header_field 17 | 18 | def extract_from(self, flask_request): 19 | """Extract from.""" 20 | if type(flask_request) is not type(request): 21 | raise TypeError("input item is not a flask request(LocalProxy)") 22 | 23 | raw_token_str = flask_request.headers.get(self._header_field, "") 24 | 25 | if raw_token_str == "": 26 | raw_token_str = flask_request.headers.environ.get( 27 | "HTTP_XAUTHORIZATION", "") 28 | if raw_token_str == "": 29 | raw_token_str = flask_request.headers.environ.get( 30 | "XAuthorization", "") 31 | 32 | if not raw_token_str: 33 | raise jwt.InvalidTokenError( 34 | f"missing {HTTP_HEADER_AUTHORIZATION_FIELD} field in request header") 35 | 36 | parsed_token = re.findall(r" *Bearer +(\S*)$", raw_token_str) 37 | if not parsed_token: 38 | raise jwt.InvalidTokenError( 39 | f"{HTTP_HEADER_AUTHORIZATION_FIELD} header is not in format of Bearer ," 40 | " got {raw_token_str}") 41 | 42 | token_str = parsed_token[0] 43 | if token_str.startswith("Bearer "): 44 | token_str = token_str[len("Bearer "):] 45 | return JwtToken.digest(token_str) 46 | -------------------------------------------------------------------------------- /back-end/src/core/auth/token.py: -------------------------------------------------------------------------------- 1 | """Token.""" 2 | import json 3 | 4 | import jwt 5 | from cryptography.fernet import Fernet 6 | 7 | from util.timestamp_util import get_future_timestamp, has_passed_timestamp 8 | 9 | 10 | JWT_SIGNATURE_SALT = "ZofqhbIRrBQoyRmyFcOghyAeJV2vNetne7Dukvuvtxk=" 11 | PAYLOAD_ENC_SALT = b"BA7i2vIF567g_9rA6joA8PTF3P_wtHe4MqUP0Fkgq3Y=" 12 | 13 | EXPIRE_TIMESPAN_SEC = 7 * 24 * 3600 # 7 days 14 | JWT_AUDIENCE = "LLMOPS" 15 | 16 | 17 | class JwtToken: 18 | """JTW token.""" 19 | # -------- Static Parameters -------- 20 | encryptor = Fernet(PAYLOAD_ENC_SALT) 21 | jwt_sign_salt = JWT_SIGNATURE_SALT 22 | 23 | def __init__( 24 | self, 25 | user_id: int, 26 | expire_timestamp: float, 27 | version: int, 28 | ): 29 | self.user_id = user_id 30 | self.expire_timestamp = expire_timestamp 31 | self.version = version 32 | 33 | def to_json(self): 34 | """To JSON.""" 35 | return self.__dict__ 36 | 37 | @classmethod 38 | def generate( 39 | cls, 40 | user_id: int, 41 | expire_timespan_sec: float = EXPIRE_TIMESPAN_SEC, 42 | version: int = 0, 43 | ): 44 | token = cls(user_id=user_id, 45 | expire_timestamp=get_future_timestamp( 46 | secs=expire_timespan_sec), 47 | version=version) 48 | 49 | return token 50 | 51 | def to_str(self) -> str: 52 | # use abbreviation for shorter payload 53 | # u -> user_id 54 | # exp -> expire_time 55 | # vs -> version 56 | payload = { 57 | "u": self.user_id, 58 | "exp": self.expire_timestamp, 59 | "vs": self.version, 60 | } 61 | 62 | # since jwt transport payload in plain text, encrypt the payload 63 | encrypt_payload = self.encryptor.encrypt( 64 | json.dumps(payload).encode("utf-8")) 65 | return "Bearer " + jwt.encode( 66 | { 67 | "enc": encrypt_payload.hex(), 68 | "exp": self.expire_timestamp, 69 | "aud": JWT_AUDIENCE, 70 | }, self.jwt_sign_salt, algorithm="HS256" 71 | ) 72 | 73 | @classmethod 74 | def validate(cls, payload_dict) -> None: 75 | """Validate.""" 76 | for necessary_field in ["u", "exp", "vs"]: 77 | if necessary_field not in payload_dict: 78 | raise jwt.InvalidTokenError( 79 | "payload dict has no required field {}".format( 80 | necessary_field) 81 | ) 82 | if has_passed_timestamp(float(payload_dict["exp"])): 83 | raise jwt.ExpiredSignatureError("token has expired") 84 | 85 | @classmethod 86 | def digest(cls, token_str: str): 87 | """ 88 | digest pure token_str 89 | 90 | Args: 91 | token_str: pure token string parsed from request header without the "Bearer" type field. 92 | """ 93 | if token_str.startswith('Bearer '): 94 | token_str = token_str[len("Bearer "):] 95 | 96 | encrypt_payload = bytes.fromhex( 97 | jwt.decode(token_str, 98 | cls.jwt_sign_salt, 99 | algorithms=["HS256"], 100 | audience=JWT_AUDIENCE).get("enc", None) 101 | ) 102 | if encrypt_payload is None: 103 | raise jwt.InvalidTokenError("token has no payload") 104 | try: 105 | # If payload parsing fails, prompt the user to log in again 106 | payload = json.loads(cls.encryptor.decrypt( 107 | encrypt_payload).decode("utf-8")) 108 | except Exception as e: 109 | raise jwt.InvalidTokenError( 110 | f"token cannot digest, {e}") 111 | cls.validate(payload) 112 | return cls( 113 | user_id=payload["u"], 114 | expire_timestamp=payload["exp"], 115 | version=payload["vs"], 116 | ) 117 | -------------------------------------------------------------------------------- /back-end/src/core/component/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnchoringAI/anchoring-ai/7fb3973590fe2c316fdf0159174dfc9dd57928ec/back-end/src/core/component/__init__.py -------------------------------------------------------------------------------- /back-end/src/core/component/doc_search.py: -------------------------------------------------------------------------------- 1 | """Doc search.""" 2 | from config import logger 3 | from core.component.utils import generate_valid_prompt 4 | 5 | 6 | # pylint: disable=too-few-public-methods 7 | class DocSearch: 8 | """Doc search.""" 9 | 10 | def __init__(self, vector_store, text_template, top_n): 11 | self.vector_store = vector_store 12 | self.text_template = text_template 13 | self.top_n = top_n 14 | 15 | def search(self, input_variables=None): 16 | """Search.""" 17 | valid_prompt, valid_input_variables = generate_valid_prompt( 18 | self.text_template, input_variables) 19 | 20 | query = valid_prompt.format(**valid_input_variables) 21 | 22 | res = self.vector_store.similarity_search(query, k=self.top_n) 23 | 24 | logger.debug(f"Query: {query}\n") 25 | logger.debug(f"Similar Docs: {res}\n") 26 | 27 | return "\t".join(res) 28 | -------------------------------------------------------------------------------- /back-end/src/core/component/google_search.py: -------------------------------------------------------------------------------- 1 | from langchain.tools import Tool 2 | from langchain.utilities import GoogleSearchAPIWrapper 3 | import os 4 | 5 | from config import logger 6 | 7 | 8 | class GoogleSearch: 9 | """Google Search class.""" 10 | 11 | def __init__(self, llm_api_key_dict, num_results): 12 | self.cse_id = "741eb9c5cc36f4d43" 13 | self.num_results = num_results 14 | 15 | if "google_search_api_key" not in llm_api_key_dict: 16 | logger.warning("No google_search_api_key provided") 17 | return None 18 | self.api_key = llm_api_key_dict["google_search_api_key"] 19 | 20 | def _construct_google_search_tool(self): 21 | """Construct a Tool object for Google Search.""" 22 | search = GoogleSearchAPIWrapper(google_api_key=self.api_key, google_cse_id=self.cse_id, k=self.num_results) 23 | 24 | tool = Tool( 25 | name="Google Search", 26 | description="Search Google for recent results.", 27 | func=search.run, 28 | ) 29 | 30 | return tool 31 | 32 | def search(self, text_obj, input_variables=None): 33 | """Execute a Google Search.""" 34 | query = text_obj.text_convert(input_variables=input_variables) 35 | 36 | tool = self._construct_google_search_tool() 37 | try: 38 | res = tool.run(query) 39 | except Exception as e: 40 | res = "" 41 | logger.error(e) 42 | 43 | logger.debug(f"Search Query: {query}") 44 | logger.debug(f"Search Result: {res}") 45 | 46 | return res 47 | 48 | 49 | # Usage example: 50 | if __name__ == "__main__": 51 | google_search = GoogleSearch() 52 | # Replace with your actual Google API Key 53 | result = google_search.search("Obama's first name?", "your_google_api_key") 54 | print(result) 55 | -------------------------------------------------------------------------------- /back-end/src/core/component/parser.py: -------------------------------------------------------------------------------- 1 | """Parser.""" 2 | from langchain.output_parsers import RegexParser 3 | 4 | from config import logger 5 | 6 | 7 | # Helper function to make the tag case-insensitive 8 | def make_case_insensitive(tag): 9 | return ''.join(['[{}{}]'.format(c.lower(), c.upper()) for c in tag]) 10 | 11 | 12 | # pylint: disable=too-few-public-methods 13 | class TagParser: 14 | """Tag parser.""" 15 | 16 | def __init__(self, tag): 17 | self.tag = tag 18 | 19 | def _construct_tag_parser(self, output_key): 20 | # Make the tag case-insensitive 21 | tag_insensitive = make_case_insensitive(self.tag) 22 | 23 | # Create a RegexParser with the case-insensitive and multiline regex 24 | parser = RegexParser( 25 | regex=fr"<\s*{tag_insensitive}\s*>([\s\S]*?)<\s*/{tag_insensitive}\s*>", 26 | output_keys=[output_key]) 27 | 28 | return parser 29 | 30 | def parse(self, text_obj, input_variables=None): 31 | """Parse.""" 32 | text = text_obj.text_convert(input_variables=input_variables) 33 | 34 | parser = self._construct_tag_parser("parser_output") 35 | try: 36 | res = parser.parse(text).get("parser_output", "") 37 | except ValueError as e: 38 | res = "" 39 | logger.error(e) 40 | 41 | logger.debug(f"Raw Text: {text}") 42 | logger.debug(f"Target Tag: {self.tag}") 43 | logger.debug(f"Parse Output: {res}") 44 | 45 | return res 46 | -------------------------------------------------------------------------------- /back-end/src/core/component/prompt.py: -------------------------------------------------------------------------------- 1 | """Prompt.""" 2 | from config import logger 3 | from core.component.utils import generate_valid_prompt 4 | 5 | 6 | # pylint: disable=too-few-public-methods 7 | class Prompt: 8 | """Prompt.""" 9 | 10 | def __init__(self, llm_processor): 11 | self.llm_processor = llm_processor 12 | 13 | def complete(self, text_obj, input_variables=None): 14 | """Complete.""" 15 | text = text_obj.text_convert(input_variables=input_variables) 16 | 17 | res = self.llm_processor.complete(text) 18 | 19 | logger.debug(f"Text: {text}\n") 20 | 21 | return res["result"] 22 | -------------------------------------------------------------------------------- /back-end/src/core/component/table.py: -------------------------------------------------------------------------------- 1 | """Table.""" 2 | 3 | # pylint: disable=too-few-public-methods 4 | 5 | 6 | class Table: 7 | """Table.""" 8 | 9 | def __init__(self, scheme): 10 | self.scheme = scheme 11 | 12 | def load_variables(self, input_variables=None): 13 | """Load variables.""" 14 | result = {} 15 | 16 | if input_variables is None: 17 | return None 18 | 19 | for variable in input_variables: 20 | if variable in self.scheme: 21 | result[variable] = input_variables[variable] 22 | 23 | return result 24 | -------------------------------------------------------------------------------- /back-end/src/core/component/text.py: -------------------------------------------------------------------------------- 1 | """Text.""" 2 | from config import logger 3 | from core.component.utils import generate_valid_prompt 4 | 5 | 6 | # pylint: disable=too-few-public-methods 7 | class Text: 8 | """Text.""" 9 | 10 | def __init__(self, text_template): 11 | self.text_template = text_template 12 | 13 | def text_convert(self, input_variables=None): 14 | """Text convert.""" 15 | valid_prompt, valid_input_variables = generate_valid_prompt( 16 | self.text_template, input_variables) 17 | res = valid_prompt.format(**valid_input_variables) 18 | 19 | logger.debug(f"Text: {valid_prompt.template}\n") 20 | logger.debug(f"Input Variables: {valid_input_variables}\n") 21 | logger.debug(f"Final Text: {res}\n") 22 | 23 | return res 24 | -------------------------------------------------------------------------------- /back-end/src/core/component/utils.py: -------------------------------------------------------------------------------- 1 | """Utils.""" 2 | from langchain.prompts import PromptTemplate 3 | 4 | 5 | def generate_valid_prompt(text, input_variables=None): 6 | """Generate valid prompt.""" 7 | if input_variables is None: 8 | input_variables = {} 9 | 10 | def replace_placeholders(template, variables): 11 | for key, value in variables.items(): 12 | placeholder = f"{{{key}}}" 13 | template = template.replace(placeholder, str(value)) 14 | return template 15 | 16 | def escape_f_string(text): 17 | return text.replace('{', '{{').replace('}', '}}') 18 | 19 | replaced_text = replace_placeholders(text, input_variables) 20 | escaped_text = escape_f_string(replaced_text) 21 | 22 | prompt = PromptTemplate.from_template("") 23 | prompt.template = escaped_text 24 | prompt.validate_template = False 25 | valid_input_variables = {} 26 | 27 | return prompt, valid_input_variables 28 | -------------------------------------------------------------------------------- /back-end/src/core/component/youtube_transcript.py: -------------------------------------------------------------------------------- 1 | from langchain.document_loaders import YoutubeLoader 2 | from youtube_transcript_api import NoTranscriptFound, TranscriptsDisabled 3 | from urllib.error import HTTPError, URLError 4 | from config import logger 5 | import os 6 | 7 | class YouTubeTranscript: 8 | """YouTube Transcript class.""" 9 | 10 | def get_transcript(self, text_obj, input_variables=None): 11 | """Execute YouTube Transcript Extraction.""" 12 | video_url = text_obj.text_convert(input_variables=input_variables) 13 | 14 | # Instantiate the YoutubeLoader with the given URL 15 | loader = YoutubeLoader.from_youtube_url( 16 | video_url, 17 | add_video_info=True, 18 | language=["en"], 19 | translation="en" 20 | ) 21 | 22 | try: 23 | loader = YoutubeLoader.from_youtube_url( 24 | video_url, 25 | add_video_info=True, 26 | language=["en"], 27 | translation="en" 28 | ) 29 | documents = loader.load() 30 | transcript = [doc.dict() for doc in documents] 31 | except ValueError as ve: 32 | logger.error(f"Value error: {ve}") 33 | transcript = [] 34 | except (HTTPError, URLError) as net_err: 35 | logger.error(f"Network error: {net_err}") 36 | transcript = [] 37 | except ImportError as imp_err: 38 | logger.error(f"Import error: {imp_err}") 39 | transcript = [] 40 | except (TranscriptsDisabled, NoTranscriptFound) as api_err: 41 | logger.error(f"API error: {api_err}") 42 | transcript = [] 43 | except Exception as e: 44 | logger.error(f"An unexpected error occurred: {e}") 45 | transcript = [] 46 | 47 | logger.debug(f"Transcript: {transcript}") 48 | return transcript 49 | -------------------------------------------------------------------------------- /back-end/src/core/doc_search/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnchoringAI/anchoring-ai/7fb3973590fe2c316fdf0159174dfc9dd57928ec/back-end/src/core/doc_search/__init__.py -------------------------------------------------------------------------------- /back-end/src/core/doc_search/doc_transformer.py: -------------------------------------------------------------------------------- 1 | """Doc transformer.""" 2 | from langchain.text_splitter import TokenTextSplitter 3 | 4 | from config import logger 5 | 6 | 7 | class TextSplitter: 8 | """Text splitter.""" 9 | def __init__(self, chunk_size=1000, chunk_overlap=0): 10 | self.chunk_size = chunk_size 11 | self.chunk_overlap = chunk_overlap 12 | self.text_splitter = TokenTextSplitter( 13 | chunk_size=chunk_size, chunk_overlap=chunk_overlap) 14 | 15 | def split_text(self, text): 16 | """Split text.""" 17 | chunk_list = self.text_splitter.split_text(text) 18 | 19 | return chunk_list 20 | 21 | @staticmethod 22 | def check_params_dict(params_dict): 23 | """Check params dict.""" 24 | valid_key_set = set(TextSplitter.__init__.__code__.co_varnames) 25 | keys = list(params_dict.keys()) 26 | for key in keys: 27 | if key not in valid_key_set: 28 | del params_dict[key] 29 | logger.warning(f"{key} is not a valid parameter") 30 | 31 | if "chunk_size" not in params_dict: 32 | params_dict["chunk_size"] = 1000 33 | 34 | if "chunk_overlap" not in params_dict: 35 | params_dict["chunk_overlap"] = 0 36 | 37 | return params_dict 38 | -------------------------------------------------------------------------------- /back-end/src/core/doc_search/vector_store.py: -------------------------------------------------------------------------------- 1 | """Vector store.""" 2 | from langchain.vectorstores import LanceDB 3 | 4 | import lancedb 5 | 6 | 7 | class VectorStoreLanceDB: 8 | """Vector store lance DB.""" 9 | def __init__(self, db_path, table_name, mode, embedding_model): 10 | self.db = lancedb.connect(db_path) 11 | self.embedding_model = embedding_model 12 | 13 | print(db_path) 14 | 15 | hello_world_vector = self.embedding_model.embed_text("Hello world") 16 | 17 | if mode == "read": 18 | table = self.db.open_table(table_name) 19 | elif mode == "overwrite": 20 | # pylint: disable=unexpected-keyword-arg 21 | table = self.db.create_table(name=table_name, 22 | data=[ 23 | { 24 | "vector": hello_world_vector, 25 | "text": "Hello World", 26 | "id": "1" 27 | } 28 | ], 29 | mode="overwrite") 30 | else: 31 | table = self.db.create_table(name=table_name, 32 | data=[ 33 | { 34 | "vector": hello_world_vector, 35 | "text": "Hello World", 36 | "id": "1" 37 | } 38 | ]) 39 | 40 | # pylint: disable=not-callable 41 | self.vec_db = LanceDB( 42 | connection=table, embedding=self.embedding_model.embedding_model) 43 | 44 | def drop_table(self, table_name): 45 | """Drop table.""" 46 | self.db.drop_table(table_name) 47 | 48 | def add_text(self, text): 49 | """Add text.""" 50 | self.vec_db.add_texts([text]) 51 | 52 | def add_text_list(self, text_list): 53 | """Add text list.""" 54 | self.vec_db.add_texts(text_list) 55 | 56 | def add_document(self, doc): 57 | """Add document.""" 58 | self.vec_db.add_documents([doc]) 59 | 60 | def add_document_list(self, doc_list): 61 | """Add document list.""" 62 | self.vec_db.add_documents(doc_list) 63 | 64 | def similarity_search(self, query, k=3): 65 | """Similarity search.""" 66 | docs = self.vec_db.similarity_search(query, k=k) 67 | text_list = [doc.page_content for doc in docs] 68 | return text_list 69 | -------------------------------------------------------------------------------- /back-end/src/core/interface/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnchoringAI/anchoring-ai/7fb3973590fe2c316fdf0159174dfc9dd57928ec/back-end/src/core/interface/__init__.py -------------------------------------------------------------------------------- /back-end/src/core/llm_processor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnchoringAI/anchoring-ai/7fb3973590fe2c316fdf0159174dfc9dd57928ec/back-end/src/core/llm_processor/__init__.py -------------------------------------------------------------------------------- /back-end/src/core/llm_processor/anthropic_processor.py: -------------------------------------------------------------------------------- 1 | from langchain.chat_models import ChatAnthropic 2 | from langchain.schema import HumanMessage 3 | 4 | class AnthropicProcessor: 5 | # Constructor (initialize object) 6 | def __init__( 7 | self, 8 | model_name="claude-2", 9 | temperature=0.7, 10 | max_tokens=256, 11 | top_p=1, 12 | api_key="", 13 | cache_enable=True, 14 | ): 15 | self.llm = ChatAnthropic( 16 | model=model_name, 17 | temperature=temperature, 18 | max_tokens_to_sample=max_tokens, 19 | top_p=top_p, 20 | anthropic_api_key=api_key, 21 | cache=cache_enable, 22 | ) 23 | 24 | def complete(self, text): 25 | """Complete.""" 26 | chat = self.llm 27 | messages = [ 28 | HumanMessage(content=text), 29 | ] 30 | response = chat(messages) 31 | 32 | return { 33 | "result": response.content, 34 | } 35 | 36 | @staticmethod 37 | def check_params_dict(params_dict): 38 | valid_key_set = set(AnthropicProcessor.__init__.__code__.co_varnames) 39 | keys = list(params_dict.keys()) 40 | for key in keys: 41 | if key not in valid_key_set: 42 | del params_dict[key] 43 | 44 | if "temperature" not in params_dict: 45 | params_dict["temperature"] = 0.7 46 | 47 | if "max_tokens" not in params_dict: 48 | params_dict["max_tokens"] = 1000 49 | 50 | if "top_p" in params_dict: 51 | params_dict["top_p"] = 1 52 | 53 | if "api_key" not in params_dict: 54 | params_dict["api_key"] = "" 55 | 56 | return params_dict 57 | -------------------------------------------------------------------------------- /back-end/src/core/llm_processor/openai.py: -------------------------------------------------------------------------------- 1 | """Open AI.""" 2 | from langchain.chat_models import ChatOpenAI 3 | from langchain.schema import HumanMessage 4 | from langchain.embeddings import OpenAIEmbeddings 5 | from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler 6 | 7 | from config import logger 8 | 9 | 10 | class OpenAIProcessor: 11 | """Open AI processor.""" 12 | 13 | # pylint: disable=too-many-arguments 14 | def __init__( 15 | self, 16 | model_name="gpt-3.5-turbo", 17 | temperature=0.7, 18 | max_tokens=256, 19 | n=1, 20 | request_timeout=120, 21 | openai_api_key="", 22 | cache_enable=True, 23 | ): 24 | self.model_name = model_name 25 | self.temperature = temperature 26 | self.max_tokens = max_tokens 27 | self.n = n 28 | self.request_timeout = request_timeout 29 | self.openai_api_key = openai_api_key 30 | 31 | self.llm = ChatOpenAI( 32 | model_name=model_name, 33 | temperature=temperature, 34 | max_tokens=max_tokens, 35 | n=n, 36 | request_timeout=request_timeout, 37 | openai_api_key=openai_api_key, 38 | cache=cache_enable, 39 | streaming=True, 40 | callbacks=[StreamingStdOutCallbackHandler()], 41 | ) 42 | 43 | def complete(self, text): 44 | """Complete.""" 45 | chat = self.llm 46 | messages = [ 47 | HumanMessage(content=text), 48 | ] 49 | response = chat(messages) 50 | print("response!!!!!!!!!!",response) 51 | 52 | return { 53 | "result": response.content, 54 | } 55 | 56 | @staticmethod 57 | def check_params_dict(params_dict): 58 | """Check params dict.""" 59 | valid_key_set = set(OpenAIProcessor.__init__.__code__.co_varnames) 60 | keys = list(params_dict.keys()) 61 | for key in keys: 62 | if key not in valid_key_set: 63 | del params_dict[key] 64 | logger.warning(f"{key} is not a valid parameter") 65 | 66 | if "model_name" not in params_dict: 67 | params_dict["model_name"] = "text-davinci-003" 68 | 69 | if "temperature" not in params_dict: 70 | params_dict["temperature"] = 0.7 71 | 72 | if "max_tokens" not in params_dict: 73 | params_dict["max_tokens"] = 256 74 | 75 | if "n" not in params_dict: 76 | params_dict["n"] = 1 77 | 78 | if "request_timeout" not in params_dict: 79 | params_dict["request_timeout"] = 600 80 | 81 | if "openai_api_key" not in params_dict: 82 | params_dict["openai_api_key"] = "" 83 | 84 | if "cache_enable" not in params_dict: 85 | params_dict["cache_enable"] = True 86 | 87 | return params_dict 88 | 89 | 90 | class OpenAIEmbedding: 91 | """Open AI embedding.""" 92 | 93 | # pylint: disable=too-many-arguments 94 | def __init__( 95 | self, 96 | model="text-embedding-ada-002", 97 | embedding_ctx_length=8191, 98 | chunk_size=1000, 99 | max_retries=6, 100 | request_timeout=60, 101 | openai_api_key="", 102 | ): 103 | self.embedding_model = OpenAIEmbeddings( 104 | model=model, 105 | embedding_ctx_length=embedding_ctx_length, 106 | chunk_size=chunk_size, 107 | max_retries=max_retries, 108 | request_timeout=request_timeout, 109 | openai_api_key=openai_api_key, 110 | ) 111 | 112 | def embed_text(self, text): 113 | """Embed text.""" 114 | return self.embedding_model.embed_query(text) 115 | 116 | @staticmethod 117 | def check_params_dict(params_dict): 118 | """Check params dict.""" 119 | valid_key_set = set(OpenAIEmbedding.__init__.__code__.co_varnames) 120 | keys = list(params_dict.keys()) 121 | for key in keys: 122 | if key not in valid_key_set: 123 | del params_dict[key] 124 | logger.warning(f"{key} is not a valid parameter") 125 | 126 | if "model" not in params_dict: 127 | params_dict["model"] = "text-embedding-ada-002" 128 | 129 | if "embedding_ctx_length" not in params_dict: 130 | params_dict["embedding_ctx_length"] = 8191 131 | 132 | if "chunk_size" not in params_dict: 133 | params_dict["chunk_size"] = 1000 134 | 135 | if "max_retries" not in params_dict: 136 | params_dict["max_retries"] = 6 137 | 138 | if "request_timeout" not in params_dict: 139 | params_dict["request_timeout"] = 60 140 | 141 | if "openai_api_key" not in params_dict: 142 | params_dict["openai_api_key"] = "" 143 | 144 | return params_dict 145 | -------------------------------------------------------------------------------- /back-end/src/core/task.py: -------------------------------------------------------------------------------- 1 | """Task.""" 2 | import openai 3 | 4 | 5 | def completion(prompt, parameters): 6 | """Completion.""" 7 | params = { 8 | "prompt": prompt, 9 | **parameters 10 | } 11 | resp = openai.Completion.create(**params) 12 | return resp.choice[0]['text'] 13 | -------------------------------------------------------------------------------- /back-end/src/core/task_progress.py: -------------------------------------------------------------------------------- 1 | """Task progress.""" 2 | from connection import r 3 | 4 | 5 | class TaskRedisRecords: 6 | """Task redis records.""" 7 | 8 | def __init__(self, task_id, total=0): 9 | self.task_id = task_id 10 | self.redis_name = self._assemble_task_redis_name(task_id) 11 | if total >= 0: 12 | self.total = total 13 | self._create_task_redis_records() 14 | 15 | def _create_task_redis_records(self): 16 | r.hset(name=self.redis_name, items=[ 17 | {self._total_key(): self.total}, 18 | {self._done_key(): 0}, 19 | {self._fail_key(): 0}, 20 | {self._result_key(): []}, 21 | {self._active_key(): 1}, 22 | {self._fail_key(): []}, 23 | ]) 24 | 25 | def get_task_total(self): 26 | """Get task total.""" 27 | return r.hget(self.redis_name, self._total_key()) 28 | 29 | def get_task_progress(self): 30 | """Get task progress.""" 31 | return r.hget(self.redis_name, self._done_key()) 32 | 33 | def increase_task_progress(self): 34 | """Increase task progress.""" 35 | r.hincrby(self.redis_name, self._done_key()) 36 | 37 | def get_task_fail(self): 38 | """Get task fail.""" 39 | return r.hget(self.redis_name, self._fail_key()) 40 | 41 | def increase_task_fail(self): 42 | """Increase task fail.""" 43 | r.hincrby(self.redis_name, self._fail_key()) 44 | 45 | def get_task_result(self): 46 | """Get task result.""" 47 | return r.hget(self.redis_name, self._result_key()) 48 | 49 | def append_result(self, target): 50 | """Append result.""" 51 | result = self.get_task_result() 52 | result.append(target) 53 | r.hset(self.redis_name, self._result_key(), result) 54 | 55 | def is_active(self): 56 | """Is active.""" 57 | return r.hget(self.redis_name, self._active_key()) > 0 58 | 59 | def deactivate(self): 60 | """Deactivate.""" 61 | r.hset(self.redis_name, self._active_key(), 0) 62 | 63 | def get_fail_message(self): 64 | """Get fail message.""" 65 | return r.hget(self.redis_name, self._fail_key()) 66 | 67 | def append_fail_message(self, msg): 68 | """Append fail message.""" 69 | messages = self.get_fail_message() 70 | messages.append(msg) 71 | r.hset(self.redis_name, self._fail_key(), messages) 72 | 73 | @staticmethod 74 | def _assemble_task_redis_name(task_id): 75 | return f"batch-task-{task_id}" 76 | 77 | @staticmethod 78 | def _total_key(): 79 | return "total" 80 | 81 | @staticmethod 82 | def _done_key(): 83 | return "done" 84 | 85 | @staticmethod 86 | def _fail_key(): 87 | return "fail" 88 | 89 | @staticmethod 90 | def _result_key(): 91 | return "result" 92 | 93 | @staticmethod 94 | def _active_key(): 95 | return "active" 96 | 97 | @staticmethod 98 | def _fail_key(): 99 | return "fail" 100 | -------------------------------------------------------------------------------- /back-end/src/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnchoringAI/anchoring-ai/7fb3973590fe2c316fdf0159174dfc9dd57928ec/back-end/src/model/__init__.py -------------------------------------------------------------------------------- /back-end/src/model/application.py: -------------------------------------------------------------------------------- 1 | """Application.""" 2 | from datetime import datetime 3 | from enum import Enum 4 | 5 | from sqlalchemy import String, DateTime, INTEGER, JSON 6 | from sqlalchemy.orm import relationship 7 | from sqlalchemy import ForeignKey 8 | 9 | from connection import db 10 | from util.uid_gen import gen_uuid 11 | from model.base import DbBase 12 | 13 | 14 | class TaskStatus(Enum): 15 | """Task status.""" 16 | QUEUED = 1 17 | RUNNING = 2 18 | COMPLETED = 3 19 | FAILED = 4 20 | STOPPED = 5 21 | 22 | @staticmethod 23 | def get_key_from_value(value): 24 | """Get key from value.""" 25 | for key, member in TaskStatus.__members__.items(): 26 | if member.value == value: 27 | return key 28 | return None 29 | 30 | 31 | # pylint: disable=too-few-public-methods 32 | class DbAppBuild(DbBase): 33 | """DB app build.""" 34 | __tablename__ = 't_app' 35 | 36 | id = db.Column(String(36), primary_key=True, 37 | nullable=False, unique=True, default=gen_uuid()) 38 | app_name = db.Column(String(200), nullable=False) 39 | created_by = db.Column( 40 | db.String(36), ForeignKey('t_user.id'), nullable=False) 41 | created_at = db.Column(DateTime, nullable=False, default=datetime.utcnow) 42 | updated_at = db.Column(DateTime, nullable=False, default=datetime.utcnow) 43 | tags = db.Column(JSON, nullable=True) 44 | description = db.Column(String(200), nullable=True) 45 | published = db.Column(db.Boolean, nullable=False, default=False) 46 | chain = db.Column(JSON, nullable=True) 47 | deleted_at = db.Column(DateTime, nullable=True) 48 | 49 | user = relationship("DbUser", backref='apps') 50 | 51 | # pylint: disable=too-many-arguments 52 | # pylint: disable=redefined-builtin 53 | # pylint: disable=super-init-not-called 54 | def __init__(self, id, app_name, created_by, tags, description, published, chain): 55 | self.id = id 56 | self.app_name = app_name 57 | self.created_by = created_by 58 | self.tags = tags 59 | self.description = description 60 | self.published = published 61 | self.chain = chain 62 | 63 | 64 | # pylint: disable=too-few-public-methods 65 | class DbAppTask(DbBase): 66 | """DB app task.""" 67 | __tablename__ = 't_task' 68 | 69 | id = db.Column(String(36), primary_key=True, nullable=False, unique=True) 70 | task_name = db.Column(String(200), nullable=False) 71 | created_by = db.Column( 72 | db.String(36), ForeignKey('t_user.id'), nullable=False) 73 | app_id = db.Column(String(36), ForeignKey('t_app.id'), nullable=False) 74 | file_id = db.Column(String(36), ForeignKey('t_file.id'), nullable=False) 75 | created_at = db.Column(DateTime, nullable=False, default=datetime.utcnow) 76 | status = db.Column(INTEGER, nullable=False, 77 | default=TaskStatus.QUEUED.value) 78 | completed_at = db.Column(DateTime, nullable=True, default=None) 79 | published = db.Column(db.Boolean, nullable=False, default=False) 80 | deleted_at = db.Column(DateTime, nullable=True, default=None) 81 | result = db.Column(JSON, default=None) 82 | message = db.Column(JSON, default=None) 83 | 84 | user = relationship("DbUser", backref='tasks') 85 | app = relationship("DbAppBuild") 86 | file = relationship("DbFile") 87 | 88 | # pylint: disable=too-many-arguments 89 | # pylint: disable=redefined-builtin 90 | # pylint: disable=super-init-not-called 91 | def __init__(self, id, task_name, created_by, created_at, app_id, file_id, published): 92 | self.id = id 93 | self.task_name = task_name 94 | self.created_by = created_by 95 | self.created_at = created_at 96 | self.app_id = app_id 97 | self.file_id = file_id 98 | self.published = published 99 | -------------------------------------------------------------------------------- /back-end/src/model/base.py: -------------------------------------------------------------------------------- 1 | """Base.""" 2 | from connection import db 3 | 4 | 5 | # pylint: disable=too-few-public-methods 6 | class DbBase(db.Model): 7 | """DB base.""" 8 | __abstract__ = True 9 | 10 | def as_dict(self): 11 | """Dict format.""" 12 | result = {} 13 | for c in self.__table__.columns: 14 | if getattr(self, c.name) is not None: 15 | if str(c.type) == 'JSON': 16 | result[c.name] = getattr(self, c.name) 17 | else: 18 | result[c.name] = str(getattr(self, c.name)) 19 | 20 | return result 21 | -------------------------------------------------------------------------------- /back-end/src/model/file.py: -------------------------------------------------------------------------------- 1 | """File.""" 2 | from datetime import datetime 3 | 4 | from sqlalchemy import String, DateTime, INTEGER, ForeignKey, PickleType 5 | from sqlalchemy.dialects.mysql import JSON 6 | from sqlalchemy.orm import relationship 7 | 8 | from connection import db 9 | from model.application import TaskStatus 10 | from model.base import DbBase 11 | 12 | 13 | # pylint: disable=too-few-public-methods 14 | # pylint: disable=too-many-instance-attributes 15 | class DbFile(DbBase): 16 | """DB file.""" 17 | __tablename__ = 't_file' 18 | 19 | id = db.Column(db.String(36), primary_key=True, 20 | nullable=False, unique=True) 21 | name = db.Column(db.String(120)) 22 | type = db.Column(db.String(50)) 23 | uploaded_by = db.Column( 24 | db.String(36), ForeignKey('t_user.id'), nullable=False) 25 | uploaded_at = db.Column(db.DateTime, default=datetime.utcnow) 26 | size = db.Column(db.Integer) 27 | content = db.Column(JSON) 28 | raw_content = db.Column(PickleType) 29 | published = db.Column(db.Boolean, nullable=False, default=False) 30 | deleted_at = db.Column(db.DateTime, nullable=True) 31 | 32 | user = relationship("DbUser", backref='files') 33 | 34 | # pylint: disable=too-many-arguments 35 | # pylint: disable=redefined-builtin 36 | # pylint: disable=super-init-not-called 37 | def __init__(self, 38 | id, name, type, uploaded_by, uploaded_at, size, content, raw_content, published): 39 | self.id = id 40 | self.name = name 41 | self.type = type 42 | self.uploaded_by = uploaded_by 43 | self.uploaded_at = uploaded_at 44 | self.size = size 45 | self.content = content 46 | self.raw_content = raw_content 47 | self.published = published 48 | self.deleted_at = None 49 | 50 | def as_dict(self, exclude=None): 51 | """Dict format.""" 52 | if exclude is None: 53 | exclude = [] 54 | return { 55 | c.name: getattr(self, c.name) for c in self.__table__.columns if c.name not in exclude 56 | } 57 | 58 | 59 | # pylint: disable=too-few-public-methods 60 | class DbEmbedding(DbBase): 61 | """DB embedding.""" 62 | __tablename__ = 't_embedding' 63 | 64 | id = db.Column(String(36), primary_key=True, nullable=False, unique=True) 65 | embedding_name = db.Column(String(200), nullable=False) 66 | created_by = db.Column( 67 | db.String(36), ForeignKey('t_user.id'), nullable=False) 68 | file_id = db.Column(String(36), ForeignKey('t_file.id'), nullable=False) 69 | config = db.Column(JSON, default=None) 70 | created_at = db.Column(DateTime, nullable=False, default=datetime.utcnow) 71 | status = db.Column(INTEGER, nullable=False, 72 | default=TaskStatus.QUEUED.value) 73 | completed_at = db.Column(DateTime, nullable=True, default=None) 74 | deleted_at = db.Column(DateTime, nullable=True, default=None) 75 | published = db.Column(db.Boolean, nullable=False, default=False) 76 | result = db.Column(JSON, default=None) 77 | message = db.Column(JSON, default=None) 78 | 79 | user = relationship("DbUser", backref='embeddings') 80 | file = relationship("DbFile", backref="embeddings") 81 | 82 | # pylint: disable=too-many-arguments 83 | # pylint: disable=redefined-builtin 84 | # pylint: disable=super-init-not-called 85 | def __init__(self, id, embedding_name, created_by, file_id, published, config): 86 | self.id = id 87 | self.embedding_name = embedding_name 88 | self.created_by = created_by 89 | self.file_id = file_id 90 | self.published = published 91 | self.config = config 92 | -------------------------------------------------------------------------------- /back-end/src/model/shared_link.py: -------------------------------------------------------------------------------- 1 | """Shared link.""" 2 | from datetime import datetime 3 | from sqlalchemy import String, DateTime, ForeignKey, TIMESTAMP 4 | from sqlalchemy.orm import relationship 5 | 6 | from connection import db 7 | from util.uid_gen import gen_uuid 8 | from model.base import DbBase 9 | 10 | 11 | # pylint: disable=too-few-public-methods 12 | class DbSharedLink(DbBase): 13 | """DB shared link.""" 14 | __tablename__ = 't_shared_link' 15 | 16 | id = db.Column(String(36), primary_key=True, nullable=False, 17 | unique=True, default=gen_uuid()) 18 | created_by = db.Column(String(36), ForeignKey('t_user.id'), nullable=False) 19 | resource_id = db.Column(String(36), nullable=False) 20 | resource_type = db.Column(String(20), nullable=False) 21 | created_at = db.Column(DateTime, nullable=False, default=datetime.utcnow) 22 | expires_at = db.Column(TIMESTAMP, nullable=True) 23 | 24 | user = relationship("DbUser", backref='shared_links') 25 | 26 | # pylint: disable=too-many-arguments 27 | # pylint: disable=redefined-builtin 28 | # pylint: disable=super-init-not-called 29 | def __init__(self, id, created_by, resource_id, resource_type, expires_at=None): 30 | self.id = id 31 | self.created_by = created_by 32 | self.resource_id = resource_id 33 | self.resource_type = resource_type 34 | self.expires_at = expires_at 35 | 36 | def as_dict(self, exclude=None): 37 | """Dict format.""" 38 | if exclude is None: 39 | exclude = [] 40 | return { 41 | c.name: getattr(self, c.name) for c in self.__table__.columns if c.name not in exclude 42 | } 43 | -------------------------------------------------------------------------------- /back-end/src/model/types.py: -------------------------------------------------------------------------------- 1 | """Types.""" 2 | import enum 3 | 4 | 5 | class ApiType(enum.Enum): 6 | """API type.""" 7 | OPENAI = "openai" 8 | ANTHROPIC = "anthropic" 9 | GOOGLE_SEARCH = "google_search" 10 | 11 | @staticmethod 12 | def keys(): 13 | """Keys.""" 14 | return ApiType.__members__.keys() 15 | 16 | @staticmethod 17 | def values(): 18 | """Values.""" 19 | return [member.value for member in ApiType] 20 | 21 | 22 | class TestType(enum.Enum): 23 | """Test type.""" 24 | TEST = "test" -------------------------------------------------------------------------------- /back-end/src/model/user.py: -------------------------------------------------------------------------------- 1 | """User.""" 2 | from datetime import datetime 3 | 4 | from sqlalchemy import Column, Integer, String, DateTime 5 | from werkzeug.security import generate_password_hash, check_password_hash 6 | 7 | from connection import db 8 | from util.uid_gen import gen_uuid 9 | from model.base import DbBase 10 | 11 | 12 | class DbUser(DbBase): 13 | """DB user.""" 14 | __tablename__ = 't_user' 15 | 16 | id = db.Column(String(36), primary_key=True, nullable=False, 17 | unique=True, default=gen_uuid) 18 | username = db.Column(String(20), nullable=False, unique=False) 19 | email = db.Column(String(120), nullable=False, unique=True) 20 | password_hash = db.Column(String(128), nullable=False) 21 | active = db.Column(db.Boolean, default=True) 22 | authenticated = db.Column(db.Boolean, default=True) 23 | anonymous = db.Column(db.Boolean, default=False) 24 | create_at = db.Column(DateTime, nullable=False, default=datetime.utcnow) 25 | 26 | # pylint: disable=super-init-not-called 27 | def __init__(self, username, email, password): 28 | self.username = username 29 | self.email = email 30 | self.set_password(password) 31 | 32 | def set_password(self, password): 33 | """Set password.""" 34 | self.password_hash = generate_password_hash(password) 35 | 36 | def check_password(self, password): 37 | """Check password.""" 38 | return check_password_hash(self.password_hash, password) 39 | 40 | def get_id(self): 41 | """Get ID.""" 42 | return str(self.id) 43 | 44 | def is_active(self): 45 | """Whether the user is active.""" 46 | return self.active 47 | 48 | def is_authenticated(self): 49 | """Whether the user is authenticated.""" 50 | return self.authenticated 51 | 52 | def is_anonymous(self): 53 | """Whether the user is anonymous.""" 54 | return self.anonymous 55 | 56 | 57 | # pylint: disable=too-few-public-methods 58 | class DbUserApiKey(DbBase): 59 | """DB user API key.""" 60 | __tablename__ = 't_user_api_key' 61 | 62 | id = db.Column(String(36), primary_key=True, 63 | nullable=False, default=gen_uuid()) 64 | user_id = db.Column(String(200), nullable=False, ) 65 | api_key = db.Column(String(200), nullable=False) 66 | api_type = db.Column(String(200), nullable=False) 67 | create_at = db.Column(DateTime, nullable=False, default=datetime.utcnow) 68 | valid = db.Column(db.Boolean, default=True) 69 | 70 | 71 | class DbUserQuota(DbBase): 72 | """DB user quota.""" 73 | __tablename__ = 't_user_quota' 74 | user_id = Column(String, primary_key=True, index=True) 75 | quota_available = Column(Integer, nullable=False, default=100) 76 | quota_used = Column(Integer, nullable=False, default=0) 77 | updated_at = Column(DateTime, nullable=False) 78 | 79 | def update_quota(self, amount): 80 | """Update quota.""" 81 | self.quota_used += amount 82 | self.quota_available -= amount 83 | self.updated_at = datetime.utcnow() 84 | if self.quota_available < 0: 85 | raise ValueError( 86 | "Quota limit exceeded. Please provide your API key.") 87 | -------------------------------------------------------------------------------- /back-end/src/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnchoringAI/anchoring-ai/7fb3973590fe2c316fdf0159174dfc9dd57928ec/back-end/src/services/__init__.py -------------------------------------------------------------------------------- /back-end/src/services/quota_service.py: -------------------------------------------------------------------------------- 1 | """Quota service.""" 2 | from datetime import datetime 3 | 4 | from model.user import DbUserQuota 5 | from sqlalchemy.exc import SQLAlchemyError 6 | from connection import db 7 | from services.user_api_key_service import get_selected_user_api_key_type_or_none 8 | 9 | 10 | class QuotaService: 11 | """Quota service.""" 12 | 13 | @staticmethod 14 | def check_user_quota(user_id): 15 | """Check user quota.""" 16 | user_quota = DbUserQuota.query.filter_by(user_id=user_id).first() 17 | if user_quota: 18 | return { 19 | "quota_available": user_quota.quota_available, 20 | "quota_used": user_quota.quota_used 21 | } 22 | 23 | return {"error": "Quota information not found for the user"}, 404 24 | 25 | @staticmethod 26 | def update_user_quota(user_id, amount): 27 | """Update user quota.""" 28 | try: 29 | user_quota = DbUserQuota.query.filter_by(user_id=user_id).first() 30 | if user_quota: 31 | user_quota.update_quota(amount) 32 | else: 33 | user_quota = DbUserQuota( 34 | user_id=user_id, 35 | quota_available=100 - amount, 36 | quota_used=amount, 37 | updated_at=datetime.utcnow()) 38 | db.session.add(user_quota) 39 | db.session.commit() 40 | return {"message": "Quota updated successfully"}, 200 41 | except ValueError as e: 42 | db.session.rollback() 43 | return {"error": str(e)}, 403 44 | except SQLAlchemyError: 45 | db.session.rollback() 46 | raise 47 | 48 | @staticmethod 49 | def calculate_app_quota(user_id, data): 50 | """Calcuate app quota.""" 51 | quota_needed = 0 52 | model_providers = {"openai", "google", "anthropic"} 53 | 54 | for entry in data: 55 | model_provider = entry.get("model_provider") 56 | if (model_provider in model_providers and 57 | get_selected_user_api_key_type_or_none(model_provider, user_id) is None): 58 | quota_needed += 1 59 | model_name = entry.get("parameters", {}).get("model_name") 60 | 61 | if model_name in ["gpt-4", "gpt-4-1106-preview"]: 62 | quota_needed += 4 63 | 64 | return quota_needed 65 | 66 | @staticmethod 67 | def calculate_model_quota(user_id, data): 68 | """Calculate model quota.""" 69 | quota_needed = 0 70 | model_providers = {"openai", "google", "anthropic"} 71 | 72 | model_provider = data.get("model_provider") 73 | if (model_provider in model_providers and 74 | get_selected_user_api_key_type_or_none(model_provider, user_id) is None): 75 | quota_needed += 1 76 | 77 | model_name = data.get("parameters", {}).get("model_name") 78 | 79 | if model_name in ["gpt-4", "gpt-4-1106-preview"]: 80 | quota_needed += 4 81 | 82 | return quota_needed 83 | 84 | # @staticmethod 85 | # def record_api_usage(user_id, api_name): 86 | # try: 87 | # new_api_usage_record = ApiUsage(user_id=user_id, 88 | # api_name=api_name, timestamp=datetime.utcnow()) 89 | # db.session.add(new_api_usage_record) 90 | # db.session.commit() 91 | # return {"message": "API usage recorded successfully"} 92 | # except SQLAlchemyError as e: 93 | # db.session.rollback() 94 | # return {"error": str(e)} 95 | -------------------------------------------------------------------------------- /back-end/src/services/user_api_key_service.py: -------------------------------------------------------------------------------- 1 | """User API key service.""" 2 | from flask import current_app 3 | 4 | from core.auth.authenticator import get_current_user 5 | from model.user import DbUserApiKey 6 | 7 | 8 | def get_current_user_api_keys(t=None): 9 | """Get current user API keys.""" 10 | user = get_current_user() 11 | if not user: 12 | return [] 13 | if t: 14 | api_keys = DbUserApiKey.query.filter_by(user_id=user.get_id(), api_type=t).order_by( 15 | DbUserApiKey.create_at.desc()).all() 16 | else: 17 | api_keys = DbUserApiKey.query.filter_by(user_id=user.get_id()).order_by( 18 | DbUserApiKey.create_at.desc()).all() 19 | return list(map(lambda x: x.as_dict(), api_keys)) 20 | 21 | 22 | def get_current_user_api_key_type_or_none(t): 23 | """Get current user API key type or None.""" 24 | api_keys = get_current_user_api_keys(t) 25 | if len(api_keys) == 0: 26 | return None 27 | return api_keys[-1] 28 | 29 | 30 | def get_current_user_api_key_type_or_public(t): 31 | """Get current user API key type or public.""" 32 | api_key = get_current_user_api_key_type_or_none(t) 33 | if api_key: 34 | return api_key['api_key'] 35 | elif t == "openai": 36 | return current_app.config.get("OPENAI_API_KEY") 37 | elif t == "anthropic": 38 | return current_app.config.get("ANTHROPIC_API_KEY") 39 | elif t == "google_search": 40 | return current_app.config.get("GOOGLE_SEARCH_API_KEY") 41 | else: 42 | return api_key 43 | 44 | 45 | def get_current_user_specified_api_key(t, key): 46 | """Get current user specified API key.""" 47 | user = get_current_user() 48 | return DbUserApiKey.query.filter_by( 49 | user_id=user.get_id(), api_type=t, api_key=key).first() 50 | 51 | 52 | def get_selected_user_api_key_type_or_none(t, user_id): 53 | """Get selected user API key type or none.""" 54 | api_keys = DbUserApiKey.query.filter_by(user_id=user_id, api_type=t).all() 55 | api_keys = list(map(lambda x: x.as_dict(), api_keys)) 56 | if len(api_keys) == 0: 57 | return None 58 | return api_keys[-1] 59 | -------------------------------------------------------------------------------- /back-end/src/services/user_service.py: -------------------------------------------------------------------------------- 1 | """User service.""" 2 | from model.user import DbUser 3 | 4 | 5 | def get_user_by_id(user_id): 6 | """Get user by ID.""" 7 | user = DbUser.query.filter_by(id=user_id).first() 8 | if not user: 9 | raise ValueError("invalid user id", False) 10 | return user 11 | -------------------------------------------------------------------------------- /back-end/src/test/ut_user_api_v1.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | import unittest 4 | 5 | from app import app 6 | from model.types import ApiType 7 | from util.uid_gen import gen_uuid 8 | 9 | sys.path.append("../") 10 | 11 | 12 | class TestUserAPI(unittest.TestCase): 13 | def setUp(self): 14 | self.app = app 15 | # Create a test client before each test method is executed 16 | self.app.config["TESTING"] = True 17 | self.client = app.test_client() 18 | request_obj = {"email": "test1@mail.com", "password": "123"} 19 | resp = self.client.post('/v1/user/login', json=request_obj) 20 | self.assertEqual(200, resp.status_code) 21 | res = json.loads(resp.text) 22 | self.token = res['data']['token'] 23 | 24 | def tearDown(self): 25 | # Clean up resources after each test method is executed 26 | pass 27 | 28 | def test_ping(self): 29 | resp = self.client.get('/ping') 30 | 31 | # Assert that the returned status code is 200 32 | self.assertEqual(200, resp.status_code) 33 | 34 | # Assert that the returned content meets expectations 35 | self.assertEqual('Pong!', resp.text) 36 | 37 | def test_register(self): 38 | request_obj = {"username": "test1", 39 | "email": "test1@mail.com", "password": "123"} 40 | 41 | resp = self.client.post('/v1/user/register', json=request_obj) 42 | 43 | # Assert that the returned status code is 200 44 | self.assertEqual(200, resp.status_code) 45 | 46 | def test_login(self): 47 | request_obj = {"email": "unknown", "password": "123"} 48 | 49 | resp = self.client.post('/v1/user/login', json=request_obj) 50 | 51 | # Assert that the returned status code is 401 52 | self.assertEqual(resp.status_code, 401) 53 | 54 | def test_get_api_key(self): 55 | resp = self.client.get( 56 | '/v1/user/apikey', headers={"XAuthorization": self.token}) 57 | 58 | # Assert that the returned status code is 200 59 | self.assertEqual(resp.status_code, 200) 60 | 61 | def test_update_delete_api_key(self): 62 | random_key = gen_uuid() 63 | api_type = ApiType.OPENAI.value 64 | resp = self.client.post('/v1/user/apikey', headers={"XAuthorization": self.token}, json={ 65 | "api_key": random_key, 66 | "api_type": api_type 67 | }) 68 | 69 | # Assert that the returned status code is 200 70 | self.assertEqual(resp.status_code, 200) 71 | 72 | resp = self.client.get( 73 | '/v1/user/apikey', headers={"XAuthorization": self.token}) 74 | res = json.loads(resp.text) 75 | self.assertEqual(random_key, res['data'][0]['api_key']) 76 | self.assertEqual(api_type, res['data'][0]['api_type']) 77 | resp = self.client.delete('/v1/user/apikey', headers={"XAuthorization": self.token}, json={ 78 | "api_key": random_key, 79 | "api_type": api_type 80 | }) 81 | self.assertEqual(resp.status_code, 200) 82 | resp = self.client.get( 83 | '/v1/user/apikey', headers={"XAuthorization": self.token}) 84 | res = json.loads(resp.text) 85 | exists = False 86 | for ak in res['data']: 87 | if ak == random_key: 88 | exists = True 89 | self.assertEqual(False, exists) 90 | 91 | 92 | if __name__ == "__main__": 93 | unittest.main() 94 | -------------------------------------------------------------------------------- /back-end/src/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnchoringAI/anchoring-ai/7fb3973590fe2c316fdf0159174dfc9dd57928ec/back-end/src/util/__init__.py -------------------------------------------------------------------------------- /back-end/src/util/celery_init.py: -------------------------------------------------------------------------------- 1 | """Celery initilization.""" 2 | from celery import Celery, Task 3 | from flask import Flask 4 | 5 | 6 | def celery_init_app(app: Flask) -> Celery: 7 | """Celery init app.""" 8 | # pylint: disable=too-few-public-methods 9 | # pylint: disable=abstract-method 10 | class FlaskTask(Task): 11 | """Flask task.""" 12 | 13 | def __call__(self, *args: object, **kwargs: object) -> object: 14 | with app.app_context(): 15 | return self.run(*args, **kwargs) 16 | 17 | celery_app = Celery(app.name) 18 | celery_app.config_from_object(app.config["CELERY"]) 19 | celery_app.Task = FlaskTask 20 | celery_app.set_default() 21 | app.extensions["celery"] = celery_app 22 | return celery_app 23 | -------------------------------------------------------------------------------- /back-end/src/util/logger.py: -------------------------------------------------------------------------------- 1 | """Logger.""" 2 | import logging 3 | 4 | 5 | class Logger: 6 | """Logger.""" 7 | 8 | def __init__(self, name, level="DEBUG"): 9 | self.logger = logging.getLogger(name) 10 | self.logger.setLevel(level) 11 | 12 | formatter = logging.Formatter( 13 | '%(asctime)s - %(name)s - %(levelname)s - %(message)s') 14 | 15 | # Create and configure a file handler 16 | # file_handler = logging.FileHandler('logfile.log') 17 | # file_handler.setFormatter(formatter) 18 | # self.logger.addHandler(file_handler) 19 | 20 | # Create and configure console handler 21 | console_handler = logging.StreamHandler() 22 | console_handler.setFormatter(formatter) 23 | self.logger.addHandler(console_handler) 24 | 25 | def set_level(self, level): 26 | """Set level.""" 27 | self.logger.setLevel(level) 28 | 29 | def info(self, message): 30 | """Info.""" 31 | self.logger.info(message) 32 | 33 | def debug(self, message): 34 | """Debug.""" 35 | self.logger.debug(message) 36 | 37 | def warning(self, message): 38 | """Warning.""" 39 | self.logger.warning(message) 40 | 41 | def error(self, message): 42 | """Error.""" 43 | self.logger.error(message) 44 | 45 | def critical(self, message): 46 | """Critical.""" 47 | self.logger.critical(message) 48 | -------------------------------------------------------------------------------- /back-end/src/util/resp.py: -------------------------------------------------------------------------------- 1 | """Response.""" 2 | from flask import jsonify 3 | 4 | 5 | def response(msg=None, success=True, data=None): 6 | """Formatted response.""" 7 | if data is None: 8 | data = {} 9 | resp = {'data': data, 'msg': msg, 'success': success} 10 | return jsonify(resp) 11 | -------------------------------------------------------------------------------- /back-end/src/util/timestamp_util.py: -------------------------------------------------------------------------------- 1 | """Timestamp util.""" 2 | import datetime 3 | 4 | 5 | def get_future_timestamp(secs=0, mins=0, hours=0, days=0) -> float: 6 | """Get future timestamp.""" 7 | delta_time = datetime.timedelta( 8 | days=days, seconds=secs + 60 * mins + 3600 * hours, microseconds=0 9 | ) 10 | # PyJWT has issue with utc time. use now instead 11 | cur_time = datetime.datetime.now() + delta_time 12 | return cur_time.timestamp() 13 | 14 | 15 | def has_passed_timestamp(timestamp: float) -> bool: 16 | """Whether it has passed a certain timestamp now.""" 17 | return datetime.datetime.now().timestamp() > timestamp 18 | -------------------------------------------------------------------------------- /back-end/src/util/uid_gen.py: -------------------------------------------------------------------------------- 1 | """UID generation.""" 2 | import shortuuid 3 | 4 | uid_gen = shortuuid.ShortUUID(alphabet="123456789abcdefg") 5 | 6 | 7 | def gen_uuid(length=8): 8 | """Generate UUID.""" 9 | return uid_gen.random(length=length) 10 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | backend: 4 | build: 5 | context: ./back-end 6 | dockerfile: Dockerfile-backend 7 | ports: 8 | - "5001:5001" 9 | 10 | frontend: 11 | build: 12 | context: ./front-end 13 | dockerfile: Dockerfile-frontend 14 | ports: 15 | - "3000:3000" 16 | -------------------------------------------------------------------------------- /front-end/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | .vscode/ 24 | .VSCodeCounter/ -------------------------------------------------------------------------------- /front-end/Dockerfile-frontend: -------------------------------------------------------------------------------- 1 | # Use Node.js v18.16.0 base image 2 | FROM node:18.16.0 3 | 4 | # Set the working directory in the container 5 | WORKDIR /app 6 | 7 | # Install app dependencies 8 | # A wildcard is used to ensure package.json and package-lock.json are copied 9 | COPY package*.json ./ 10 | 11 | # Install any needed packages specified in package.json 12 | RUN npm install 13 | 14 | # Bundle app source 15 | COPY . . 16 | 17 | # Make port 3000 available to the world outside this container 18 | EXPOSE 3000 19 | 20 | # Run the application 21 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /front-end/craco.config.js: -------------------------------------------------------------------------------- 1 | const CracoLessPlugin = require('craco-less'); 2 | 3 | module.exports = { 4 | plugins: [ 5 | { 6 | plugin: CracoLessPlugin, 7 | options: { 8 | lessLoaderOptions: { 9 | lessOptions: { 10 | modifyVars: { '@primary-color': '#1DA57A' }, 11 | javascriptEnabled: true, 12 | }, 13 | }, 14 | }, 15 | }, 16 | ], 17 | }; 18 | -------------------------------------------------------------------------------- /front-end/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ant-design-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "proxy": "http://127.0.0.1:5001", 6 | "dependencies": { 7 | "@craco/craco": "^7.1.0", 8 | "@reduxjs/toolkit": "^1.9.5", 9 | "@testing-library/jest-dom": "^5.16.5", 10 | "@testing-library/react": "^13.4.0", 11 | "@testing-library/user-event": "^13.5.0", 12 | "antd": "^5.4.7", 13 | "color": "^4.2.3", 14 | "craco-less": "^2.1.0-alpha.0", 15 | "highlight.js": "^11.8.0", 16 | "humps": "^2.0.1", 17 | "moment": "^2.29.4", 18 | "node-fetch": "^3.3.1", 19 | "openai": "^3.2.1", 20 | "react": "^18.2.0", 21 | "react-beautiful-dnd": "^13.1.1", 22 | "react-copy-to-clipboard": "^5.1.0", 23 | "react-dom": "^18.2.0", 24 | "react-i18next": "^13.0.2", 25 | "react-markdown": "^8.0.7", 26 | "react-router-dom": "^6.11.1", 27 | "react-scripts": "^5.0.1", 28 | "redux-thunk": "^2.4.2", 29 | "rehype-highlight": "^6.0.0", 30 | "remark-gfm": "^3.0.1", 31 | "string-to-color": "^2.2.2", 32 | "typescript": "^4.9.5", 33 | "web-vitals": "^2.1.4" 34 | }, 35 | "scripts": { 36 | "start": "craco start", 37 | "build": "craco build", 38 | "test": "craco test", 39 | "eject": "react-scripts eject" 40 | }, 41 | "eslintConfig": { 42 | "extends": [ 43 | "react-app", 44 | "react-app/jest" 45 | ] 46 | }, 47 | "browserslist": { 48 | "production": [ 49 | ">0.2%", 50 | "not dead", 51 | "not op_mini all" 52 | ], 53 | "development": [ 54 | "last 1 chrome version", 55 | "last 1 firefox version", 56 | "last 1 safari version" 57 | ] 58 | } 59 | } -------------------------------------------------------------------------------- /front-end/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | Anchoring AI 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /front-end/public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /front-end/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Anchoring AI", 3 | "name": "Make AI accessible to every team.", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } -------------------------------------------------------------------------------- /front-end/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /front-end/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | 36 | to { 37 | transform: rotate(360deg); 38 | } 39 | } -------------------------------------------------------------------------------- /front-end/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /front-end/src/api/account.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { camelizeKeys } from "humps"; 3 | import { Dispatch } from "redux"; 4 | import { setUserData } from "../redux/actions/userActions"; 5 | 6 | const BASE_URL = "/v1/user"; 7 | 8 | interface NestedLoginResponse { 9 | success: boolean; 10 | message: string; 11 | data: { 12 | token: string; 13 | username: string; 14 | id: string; 15 | }; 16 | } 17 | 18 | interface RegisterResponse { 19 | success: boolean; 20 | message: string; 21 | } 22 | 23 | interface DbUser { 24 | id: string; 25 | username: string; 26 | email: string; 27 | password_hash: string; 28 | active: boolean; 29 | authenticated: boolean; 30 | anonymous: boolean; 31 | create_at: string; 32 | } 33 | 34 | const transformResponseData = (data: any) => { 35 | if (typeof data === "string") { 36 | try { 37 | data = JSON.parse(data); 38 | } catch (e) { 39 | console.error("Parsing failed with error: ", e); 40 | return data; 41 | } 42 | } 43 | return camelizeKeys(data); 44 | }; 45 | 46 | const api = axios.create({ 47 | baseURL: BASE_URL, 48 | transformResponse: [ 49 | ...(axios.defaults.transformResponse as any[]), 50 | transformResponseData 51 | ], 52 | }); 53 | 54 | api.interceptors.request.use((config) => { 55 | const token = localStorage.getItem("token"); 56 | if (token) { 57 | config.headers = config.headers ?? {}; 58 | config.headers.XAuthorization = `Bearer ${token}`; 59 | } 60 | return config; 61 | }); 62 | 63 | export const login = async ( 64 | email: string, 65 | password: string, 66 | dispatch: Dispatch 67 | ): Promise => { 68 | const response = await api.post("/login", { email, password }); 69 | 70 | const { token, username, id } = response.data.data; 71 | 72 | if (response.data.success) { 73 | localStorage.setItem("username", username!); 74 | localStorage.setItem("token", token!); 75 | localStorage.setItem("userId", id!); 76 | 77 | dispatch( 78 | setUserData({ username: username!, id: id! }) 79 | ); 80 | } 81 | 82 | return response.data; 83 | }; 84 | 85 | export const register = async ( 86 | username: string, 87 | email: string, 88 | password: string 89 | ): Promise => { 90 | const response = await api.post("/register", { username, email, password }); 91 | 92 | return response.data; 93 | }; 94 | 95 | export const logout = async (): Promise<{ success: boolean; message: string }> => { 96 | const response = await api.post("/logout"); 97 | localStorage.removeItem("username"); 98 | localStorage.removeItem("token"); 99 | localStorage.removeItem("userId"); 100 | return response.data; 101 | }; 102 | 103 | export const checkAuthStatus = async (): Promise<{ success: boolean; message: string }> => { 104 | const token = localStorage.getItem("token"); 105 | const config = { 106 | headers: { Authorization: `Bearer ${token}` }, 107 | }; 108 | 109 | const response = await api.get("/login_required_test", config); 110 | 111 | return response.data; 112 | }; 113 | -------------------------------------------------------------------------------- /front-end/src/api/apiKeys.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const BASE_URL = "/v1/user/apikey"; 4 | 5 | const api = axios.create({ 6 | baseURL: BASE_URL, 7 | }); 8 | 9 | api.interceptors.request.use((config) => { 10 | const token = localStorage.getItem("token"); 11 | if (token) { 12 | config.headers = config.headers ?? {}; 13 | config.headers.XAuthorization = `Bearer ${token}`; 14 | } 15 | return config; 16 | }); 17 | 18 | export const registerApiKey = async ( 19 | apiKey: string, 20 | apiType: string 21 | ): Promise => { 22 | try { 23 | const response = await api.post("", { api_key: apiKey, api_type: apiType }); 24 | return response.data; 25 | } catch (error) { 26 | console.error("Failed to register API key:", error); 27 | throw error; 28 | } 29 | }; 30 | 31 | export const fetchApiKeys = async (): Promise => { 32 | try { 33 | const response = await api.get(""); 34 | return response.data; 35 | } catch (error) { 36 | console.error("Failed to fetch API keys:", error); 37 | throw error; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /front-end/src/api/applications.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse } from "axios"; 2 | import { camelizeKeys, decamelizeKeys } from "humps"; 3 | 4 | interface User { 5 | id: string; 6 | username: string; 7 | } 8 | 9 | interface App { 10 | appId: string; 11 | appName: string; 12 | createdBy: User; 13 | tags: string[]; 14 | description: string; 15 | createdAt: Date; 16 | updatedAt: Date; 17 | chain: any; 18 | deletedAt: Date | null; 19 | } 20 | 21 | interface AppListResponse { 22 | applications: App[]; 23 | totalPages: number; 24 | } 25 | 26 | interface AppResponse { 27 | application: App; 28 | } 29 | 30 | interface ApplicationData { 31 | [key: string]: any; 32 | } 33 | 34 | function fromApiResponse(apiApp: any): App { 35 | if (apiApp.id !== undefined) { 36 | const { id, ...rest } = apiApp; 37 | return { 38 | ...rest, 39 | appId: id, 40 | }; 41 | } 42 | 43 | return apiApp; 44 | } 45 | 46 | function toApiResponse(data: ApplicationData): any { 47 | if ("appId" in data) { 48 | const { appId, ...rest } = data; 49 | return { 50 | id: appId, 51 | ...rest, 52 | }; 53 | } 54 | 55 | return data; 56 | } 57 | 58 | const api = axios.create({ 59 | baseURL: "/v1/app", 60 | headers: { "Content-Type": "application/json" }, 61 | transformRequest: [ 62 | (data: ApplicationData | undefined) => 63 | data ? JSON.stringify(decamelizeKeys(toApiResponse(data))) : data, 64 | ...(axios.defaults.transformRequest 65 | ? [axios.defaults.transformRequest].flat() 66 | : []), 67 | ], 68 | 69 | transformResponse: [ 70 | ...(axios.defaults.transformResponse 71 | ? [axios.defaults.transformResponse].flat() 72 | : []), 73 | (data: any) => { 74 | if (typeof data === "string") { 75 | try { 76 | data = JSON.parse(data); 77 | } catch (e) { 78 | console.error("Parsing failed with error: ", e); 79 | return data; 80 | } 81 | } 82 | 83 | data = camelizeKeys(data); 84 | 85 | if (data.applications) { 86 | data.applications = data.applications.map(fromApiResponse); 87 | } 88 | 89 | if (data.application) { 90 | data.application = fromApiResponse(data.application); 91 | } 92 | 93 | return data; 94 | }, 95 | ], 96 | }); 97 | 98 | api.interceptors.request.use((config) => { 99 | const token = localStorage.getItem("token"); 100 | if (token) { 101 | config.headers = config.headers ?? {}; 102 | config.headers.XAuthorization = `Bearer ${token}`; 103 | } 104 | return config; 105 | }); 106 | 107 | export const saveApplication = async ( 108 | data: ApplicationData 109 | ): Promise> => { 110 | return await api.post("/modify", decamelizeKeys(toApiResponse(data))); 111 | }; 112 | 113 | export const getApplication = async (id: string): Promise => { 114 | const response: AxiosResponse = await api.get(`/load/${id}`); 115 | return { application: fromApiResponse(response.data) }; 116 | }; 117 | 118 | export const getApplications = async ( 119 | page: number, 120 | size: number, 121 | app_name?: string, 122 | created_by?: string, 123 | tags?: string[], 124 | description?: string 125 | ): Promise => { 126 | let params: any = { page, size }; 127 | 128 | if (app_name) { 129 | params.app_name = app_name; 130 | } 131 | 132 | if (created_by) { 133 | params.created_by = created_by; 134 | } 135 | 136 | if (tags) { 137 | params.tags = tags.join(","); 138 | } 139 | 140 | if (description) { 141 | params.description = description; 142 | } 143 | 144 | const response: AxiosResponse = await api.get(`/list`, { params }); 145 | return { 146 | applications: response.data.applications.map(fromApiResponse), 147 | totalPages: response.data.totalPages, 148 | }; 149 | }; 150 | 151 | export const deleteApplication = async (appId: string): Promise => { 152 | try { 153 | const response: AxiosResponse = await api.delete(`/delete/${appId}`); 154 | return camelizeKeys(response.data); 155 | } catch (error) { 156 | console.error("Delete application failed with error: ", error); 157 | throw error; 158 | } 159 | }; 160 | 161 | export const publishApplication = async (id: string): Promise => { 162 | try { 163 | const response: AxiosResponse = await api.post(`/publish/${id}`); 164 | return camelizeKeys(response.data); 165 | } catch (error) { 166 | console.error("Publish application failed with error: ", error); 167 | throw error; 168 | } 169 | }; 170 | 171 | export const autoGenerateApplication = async ( 172 | instruction: string 173 | ): Promise => { 174 | const response: AxiosResponse = await api.post("/auto_generate", { 175 | instruction: instruction, 176 | }); 177 | return { application: fromApiResponse(response.data) }; 178 | }; 179 | -------------------------------------------------------------------------------- /front-end/src/api/embedding.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { camelizeKeys, decamelizeKeys } from "humps"; 3 | 4 | const BASE_URL = "/v1/embedding"; 5 | 6 | const api = axios.create({ 7 | baseURL: BASE_URL, 8 | }); 9 | 10 | api.interceptors.request.use((config) => { 11 | const token = localStorage.getItem("token"); 12 | if (token) { 13 | config.headers = config.headers ?? {}; 14 | config.headers.XAuthorization = `Bearer ${token}`; 15 | } 16 | 17 | if (config.data) { 18 | config.data = decamelizeKeys(config.data); 19 | } 20 | 21 | return config; 22 | }); 23 | 24 | api.interceptors.response.use((response) => { 25 | if (response.data) { 26 | response.data = camelizeKeys(response.data); 27 | } 28 | return response; 29 | }); 30 | 31 | export const createEmbedding = async ( 32 | created_by: string, 33 | file_id: string, 34 | embedding_name: string 35 | ): Promise => { 36 | try { 37 | const requestData = { 38 | created_by: created_by, 39 | file_id: file_id, 40 | embedding_name: embedding_name, 41 | doc_transformer: { 42 | type: "text_splitter", 43 | parameters: { 44 | chunk_size: 256, 45 | chunk_overlap: 20, 46 | }, 47 | }, 48 | embedding_model: { 49 | model_provider: "openai", 50 | parameters: { 51 | model: "text-embedding-ada-002", 52 | embedding_ctx_length: 8191, 53 | chunk_size: 256, 54 | max_retries: 6, 55 | request_timeout: 600, 56 | }, 57 | }, 58 | vector_store: { 59 | vector_store_provider: "lancedb", 60 | parameters: { 61 | mode: "overwrite", 62 | }, 63 | }, 64 | }; 65 | 66 | const response = await api.post("/create", requestData); 67 | return response.data; 68 | } catch (error) { 69 | console.error("Failed to create embedding:", error); 70 | throw error; 71 | } 72 | }; 73 | 74 | export const listEmbeddings = async ( 75 | page: number, 76 | size: number, 77 | createdBy?: string, 78 | fileId?: string 79 | ): Promise => { 80 | try { 81 | const params = { page, size, created_by: createdBy, file_id: fileId }; 82 | const response = await api.get("/list", { params }); 83 | return response.data; 84 | } catch (error) { 85 | console.error("Failed to list embeddings:", error); 86 | throw error; 87 | } 88 | }; 89 | 90 | export const stopEmbedding = async (embeddingId: string): Promise => { 91 | try { 92 | const response = await api.get(`/stop/${embeddingId}`); 93 | return response.data; 94 | } catch (error) { 95 | console.error("Failed to stop embedding:", error); 96 | throw error; 97 | } 98 | }; 99 | 100 | export const deleteEmbedding = async (embeddingId: string): Promise => { 101 | try { 102 | const response = await api.delete(`/delete/${embeddingId}`); 103 | return response.data; 104 | } catch (error) { 105 | console.error("Failed to delete embedding:", error); 106 | throw error; 107 | } 108 | }; 109 | 110 | export const publishEmbedding = async (embeddingId: string): Promise => { 111 | try { 112 | const response = await api.post(`/publish/${embeddingId}`); 113 | return camelizeKeys(response.data); 114 | } catch (error) { 115 | console.error("Embedded file publishing failed with error: ", error); 116 | throw error; 117 | } 118 | }; 119 | 120 | export const searchRelatedDocument = async ( 121 | embeddingId: string, 122 | input: string 123 | ): Promise => { 124 | try { 125 | const requestData = { 126 | embedding_id: embeddingId, 127 | input: input, 128 | input_variables: {}, 129 | parameters: { 130 | top_n: 3, 131 | }, 132 | }; 133 | 134 | const response = await api.post("/search", requestData); 135 | return response.data; 136 | } catch (error) { 137 | console.error("Failed to search related document:", error); 138 | throw error; 139 | } 140 | }; 141 | -------------------------------------------------------------------------------- /front-end/src/api/file.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse } from "axios"; 2 | import { camelizeKeys } from "humps"; 3 | 4 | // Define the Interface 5 | interface DbFile { 6 | id: string; 7 | name: string; 8 | type: string; 9 | uploadedBy: string; 10 | uploadedByUsername: string; 11 | uploadedAt: Date; 12 | size: number; 13 | content: any; 14 | deletedAt: Date | null; 15 | } 16 | 17 | const BASE_URL = "/v1/file"; 18 | 19 | const api = axios.create({ 20 | headers: { 21 | "Content-Type": "multipart/form-data", 22 | }, 23 | transformResponse: [ 24 | ...(Array.isArray(axios.defaults.transformResponse) 25 | ? axios.defaults.transformResponse 26 | : []), 27 | (data: any) => { 28 | if (typeof data === "string") { 29 | try { 30 | data = JSON.parse(data); 31 | } catch (e) { 32 | console.error("Parsing failed with error: ", e); 33 | return data; 34 | } 35 | } 36 | return camelizeKeys(data); 37 | }, 38 | ], 39 | }); 40 | 41 | api.interceptors.request.use((config) => { 42 | const token = localStorage.getItem("token"); 43 | if (token) { 44 | config.headers = config.headers ?? {}; 45 | config.headers.XAuthorization = `Bearer ${token}`; 46 | } 47 | return config; 48 | }); 49 | 50 | export const uploadFile = ( 51 | file: File, 52 | uploadedBy: string, 53 | onUploadProgress: any 54 | ): Promise> => { 55 | const formData = new FormData(); 56 | formData.append("file", file); 57 | formData.append("uploaded_by", uploadedBy); 58 | return api.post(`${BASE_URL}/upload`, formData, { onUploadProgress }); 59 | }; 60 | 61 | export const fetchFiles = ( 62 | page: number, 63 | size: number, 64 | filterByUploader: string | null = null 65 | ): Promise> => { 66 | api.defaults.headers["Content-Type"] = "application/json"; 67 | const params: { page: number; size: number; uploaded_by?: string } = { 68 | page, 69 | size, 70 | }; 71 | if (filterByUploader) { 72 | params.uploaded_by = filterByUploader; 73 | } 74 | return api.get(`${BASE_URL}/list`, { params }).finally(() => { 75 | api.defaults.headers["Content-Type"] = "multipart/form-data"; 76 | }); 77 | }; 78 | 79 | export const fetchFile = (fileId: string): Promise> => { 80 | api.defaults.headers["Content-Type"] = "application/json"; 81 | return api.get(`${BASE_URL}/load/${fileId}`).finally(() => { 82 | api.defaults.headers["Content-Type"] = "multipart/form-data"; 83 | }); 84 | }; 85 | 86 | const fileApi = axios.create({ 87 | baseURL: BASE_URL, 88 | }); 89 | 90 | fileApi.interceptors.request.use((config) => { 91 | const token = localStorage.getItem("token"); 92 | if (token) { 93 | config.headers = config.headers ?? {}; 94 | config.headers.XAuthorization = `Bearer ${token}`; 95 | } 96 | return config; 97 | }); 98 | 99 | export const downloadFile = async (fileId: string): Promise => { 100 | try { 101 | const response = await fileApi.get(`/download/${fileId}`, { 102 | responseType: "blob", 103 | }); 104 | 105 | const blob = new Blob([response.data], { 106 | type: response.headers["content-type"], 107 | }); 108 | const url = window.URL.createObjectURL(blob); 109 | 110 | const link = document.createElement("a"); 111 | const fileName = 112 | response.headers["x-file-name"] || `file_${fileId}.extension`; 113 | 114 | link.href = url; 115 | link.setAttribute("download", fileName); 116 | 117 | document.body.appendChild(link); 118 | link.click(); 119 | 120 | document.body.removeChild(link); 121 | window.URL.revokeObjectURL(url); 122 | } catch (error) { 123 | console.error("File download failed:", error); 124 | } 125 | }; 126 | 127 | export const deleteFile = (fileId: string): Promise> => { 128 | api.defaults.headers["Content-Type"] = "application/json"; 129 | return api.delete(`${BASE_URL}/delete/${fileId}`).finally(() => { 130 | api.defaults.headers["Content-Type"] = "multipart/form-data"; 131 | }); 132 | }; 133 | 134 | export const embedText = (fileId: string): Promise> => { 135 | api.defaults.headers["Content-Type"] = "application/json"; 136 | return api.post(`${BASE_URL}/embed_text/${fileId}`).finally(() => { 137 | api.defaults.headers["Content-Type"] = "multipart/form-data"; 138 | }); 139 | }; 140 | 141 | export const publishFile = (id: string): Promise> => { 142 | api.defaults.headers["Content-Type"] = "application/json"; 143 | return api 144 | .post(`${BASE_URL}/publish/${id}`) 145 | .then((response) => { 146 | return camelizeKeys(response.data); 147 | }) 148 | .catch((error) => { 149 | console.error("File publishing failed with error: ", error); 150 | throw error; 151 | }) 152 | .finally(() => { 153 | api.defaults.headers["Content-Type"] = "multipart/form-data"; 154 | }); 155 | }; 156 | -------------------------------------------------------------------------------- /front-end/src/api/job.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse } from "axios"; 2 | import { camelizeKeys, decamelizeKeys } from "humps"; 3 | 4 | interface CreateJobRequest { 5 | createdBy: string; 6 | appId: string; 7 | fileId: string; 8 | taskName: string; 9 | inputVariables: object; 10 | } 11 | 12 | interface GetJobsRequest { 13 | page?: number; 14 | size?: number; 15 | createdBy?: string; 16 | appId?: string; 17 | fileId?: string; 18 | } 19 | 20 | interface Job { 21 | id: string; 22 | taskName: string; 23 | createdBy: string; 24 | appId: string; 25 | fileId: string; 26 | createdAt: Date; 27 | status: number; 28 | completedAt: Date | null; 29 | deletedAt: Date | null; 30 | result: object | null; 31 | message: object | null; 32 | } 33 | 34 | const BASE_URL = "/v1/task"; 35 | 36 | const api = axios.create({ 37 | headers: { 38 | "Content-Type": "application/json", 39 | }, 40 | transformRequest: [ 41 | ...(axios.defaults.transformRequest as any[] || []), 42 | (data: object) => { 43 | const decamelizedData = decamelizeKeys(data); 44 | return decamelizedData; 45 | }, 46 | ], 47 | transformResponse: [ 48 | ...(axios.defaults.transformResponse as any[] || []), 49 | (data: any) => { 50 | if (typeof data === "string") { 51 | try { 52 | data = JSON.parse(data); 53 | } catch (e) { 54 | console.error("Parsing failed with error: ", e); 55 | return data; 56 | } 57 | } 58 | 59 | if (data?.result?.result) { 60 | const originalResult = data.result.result; 61 | data = camelizeKeys(data); 62 | data.result.result = originalResult; 63 | } else { 64 | data = camelizeKeys(data); 65 | } 66 | 67 | return data; 68 | }, 69 | ], 70 | }); 71 | 72 | api.interceptors.request.use((config) => { 73 | const token = localStorage.getItem("token"); 74 | if (token) { 75 | config.headers = config.headers ?? {}; 76 | config.headers.XAuthorization = `Bearer ${token}`; 77 | } 78 | return config; 79 | }); 80 | 81 | export const createJob = (request: CreateJobRequest): Promise> => { 82 | const { inputVariables, ...restOfRequest } = request; 83 | const decamelizedData = decamelizeKeys(restOfRequest); 84 | return api.post(`${BASE_URL}/start`, { ...decamelizedData, input_variables: inputVariables }); 85 | }; 86 | 87 | export const getJobs = (request: GetJobsRequest): Promise> => { 88 | const params = decamelizeKeys(request); 89 | return api.get(`${BASE_URL}/list`, { params }); 90 | }; 91 | 92 | export const loadJob = (jobId: string): Promise> => { 93 | if (!jobId) { 94 | throw new Error("Job ID must be provided!"); 95 | } 96 | 97 | return api.get(`${BASE_URL}/load/${jobId}`); 98 | }; 99 | 100 | export const stopJob = async (jobId: string): Promise> => { 101 | try { 102 | const response = await api.get(`${BASE_URL}/stop/${jobId}`); 103 | return response.data; 104 | } catch (error) { 105 | console.error("Failed to stop job:", error); 106 | throw error; 107 | } 108 | }; 109 | 110 | export const deleteJob = async (jobId: string): Promise> => { 111 | try { 112 | const response = await api.delete(`${BASE_URL}/delete/${jobId}`); 113 | return response.data; 114 | } catch (error) { 115 | console.error("Failed to delete job:", error); 116 | throw error; 117 | } 118 | }; 119 | 120 | export const publishJob = async (jobId: string): Promise> => { 121 | try { 122 | const response = await api.post(`${BASE_URL}/publish/${jobId}`); 123 | return camelizeKeys(response.data); 124 | } catch (error) { 125 | console.error("Job publishing failed with error: ", error); 126 | throw error; 127 | } 128 | }; -------------------------------------------------------------------------------- /front-end/src/api/quota.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse } from "axios"; 2 | import { camelizeKeys, decamelizeKeys } from "humps"; 3 | 4 | interface QuotaResponse { 5 | data: number; 6 | } 7 | 8 | const BASE_URL = "/v1/quota"; 9 | 10 | const api = axios.create({ 11 | headers: { 12 | "Content-Type": "application/json", 13 | }, 14 | transformRequest: [ 15 | ...(axios.defaults.transformRequest as any[] || []), 16 | (data: object) => { 17 | const decamelizedData = decamelizeKeys(data); 18 | return decamelizedData; 19 | }, 20 | ], 21 | transformResponse: [ 22 | ...(axios.defaults.transformResponse as any[] || []), 23 | (data: any) => { 24 | if (typeof data === "string") { 25 | try { 26 | data = JSON.parse(data); 27 | } catch (e) { 28 | console.error("Parsing failed with error: ", e); 29 | return data; 30 | } 31 | } 32 | data = camelizeKeys(data); 33 | return data; 34 | }, 35 | ], 36 | }); 37 | 38 | api.interceptors.request.use((config) => { 39 | const token = localStorage.getItem("token"); 40 | if (token) { 41 | config.headers = config.headers ?? {}; 42 | config.headers.XAuthorization = `Bearer ${token}`; 43 | } 44 | return config; 45 | }); 46 | 47 | export const getQuota = (): Promise> => { 48 | return api.get(`${BASE_URL}/check`); 49 | }; 50 | -------------------------------------------------------------------------------- /front-end/src/api/sharedLink.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { camelizeKeys, decamelizeKeys } from "humps"; 3 | 4 | interface User { 5 | id: string; 6 | username: string; 7 | } 8 | 9 | interface App { 10 | appId: string; 11 | appName: string; 12 | createdBy: User; 13 | tags: string[]; 14 | description: string; 15 | createdAt: Date; 16 | updatedAt: Date; 17 | chain: any; 18 | deletedAt: Date | null; 19 | } 20 | 21 | interface AppResponse { 22 | application: App; 23 | } 24 | 25 | interface ApplicationData { 26 | [key: string]: any; 27 | } 28 | 29 | function fromApiResponse(apiApp: any): App { 30 | if (apiApp.id !== undefined) { 31 | const { id, ...rest } = apiApp; 32 | return { 33 | ...rest, 34 | appId: id, 35 | }; 36 | } 37 | 38 | return apiApp; 39 | } 40 | 41 | function toApiResponse(data: ApplicationData): any { 42 | if ("appId" in data) { 43 | const { appId, ...rest } = data; 44 | return { 45 | id: appId, 46 | ...rest, 47 | }; 48 | } 49 | 50 | return data; 51 | } 52 | 53 | const api = axios.create({ 54 | baseURL: "/v1/shared", 55 | headers: { "Content-Type": "application/json" }, 56 | transformRequest: [ 57 | (data: ApplicationData | undefined) => 58 | data ? JSON.stringify(decamelizeKeys(toApiResponse(data))) : data, 59 | ...(axios.defaults.transformRequest 60 | ? [axios.defaults.transformRequest].flat() 61 | : []), 62 | ], 63 | transformResponse: [ 64 | ...(axios.defaults.transformResponse 65 | ? [axios.defaults.transformResponse].flat() 66 | : []), 67 | (data: any) => { 68 | if (typeof data === "string") { 69 | try { 70 | data = JSON.parse(data); 71 | } catch (e) { 72 | console.error("Parsing failed with error: ", e); 73 | return data; 74 | } 75 | } 76 | 77 | data = camelizeKeys(data); 78 | 79 | if (data.application) { 80 | data.application = fromApiResponse(data.application); 81 | } 82 | 83 | return data; 84 | }, 85 | ], 86 | }); 87 | 88 | api.interceptors.request.use((config) => { 89 | const token = localStorage.getItem("token"); 90 | if (token) { 91 | config.headers = config.headers ?? {}; 92 | config.headers.XAuthorization = `Bearer ${token}`; 93 | } 94 | return config; 95 | }); 96 | 97 | export const generateShareLink = async ( 98 | resourceType: string, 99 | resourceId: string, 100 | expiresAt: string | null 101 | ): Promise => { 102 | try { 103 | const response = await api.post("/generate", { 104 | resource_type: resourceType, 105 | resource_id: resourceId, 106 | expires_at: expiresAt, 107 | }); 108 | 109 | if (response.data && response.data.shareLinkId) { 110 | const path = window.location.pathname.split('/')[1]; 111 | response.data.shareableUrl = `${window.location.origin}/shared/${path}/${response.data.shareLinkId}`; 112 | } 113 | 114 | return response.data; 115 | } catch (error) { 116 | console.error("Failed to generate share link:", error); 117 | throw error; 118 | } 119 | }; 120 | 121 | export const loadShareLinkApp = async ( 122 | linkId: string 123 | ): Promise => { 124 | try { 125 | const response = await api.get(`/app/${linkId}`); 126 | return { application: fromApiResponse(response.data) }; 127 | } catch (error) { 128 | console.error("Failed to load share link app:", error); 129 | throw error; 130 | } 131 | }; 132 | 133 | export const loadShareLinkTask = async (linkId: string): Promise => { 134 | try { 135 | const response = await api.get(`/task/${linkId}`); 136 | return response.data; 137 | } catch (error) { 138 | console.error("Failed to load share link task:", error); 139 | throw error; 140 | } 141 | }; 142 | -------------------------------------------------------------------------------- /front-end/src/assets/discord-mark-black.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /front-end/src/assets/platform_cover.svg: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /front-end/src/assets/text_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnchoringAI/anchoring-ai/7fb3973590fe2c316fdf0159174dfc9dd57928ec/front-end/src/assets/text_logo.png -------------------------------------------------------------------------------- /front-end/src/assets/text_logo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnchoringAI/anchoring-ai/7fb3973590fe2c316fdf0159174dfc9dd57928ec/front-end/src/assets/text_logo_dark.png -------------------------------------------------------------------------------- /front-end/src/assets/undraw_loading.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /front-end/src/components/AccountManagement/LoginForm.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { MailOutlined, LockOutlined } from "@ant-design/icons"; 3 | import { Form, Input, Button, message } from "antd"; 4 | import { useNavigate } from "react-router-dom"; 5 | import { login } from "../../api/account.ts"; 6 | import { useDispatch } from "react-redux"; 7 | import "./Styles.less"; 8 | 9 | const LoginForm = () => { 10 | const navigate = useNavigate(); 11 | const dispatch = useDispatch(); 12 | 13 | const [email, setEmail] = useState(""); 14 | const [password, setPassword] = useState(""); 15 | 16 | const onFinish = async (values) => { 17 | try { 18 | const data = await login(values.email, values.password, dispatch); 19 | if (data.success) { 20 | const redirectPath = localStorage.getItem("redirectPath") || "/home"; 21 | navigate(redirectPath); 22 | localStorage.removeItem("redirectPath"); 23 | } else { 24 | message.error( 25 | "The provided email or password is invalid. Please try again." 26 | ); 27 | } 28 | } catch (error) { 29 | console.error("Login failed with error: ", error); 30 | message.error( 31 | "The provided email or password is invalid. Please try again." 32 | ); 33 | } 34 | }; 35 | 36 | return ( 37 |
43 | 51 | } 53 | placeholder="Email" 54 | value={email} 55 | onChange={(e) => setEmail(e.target.value)} 56 | /> 57 | 58 | 63 | 66 | } 67 | placeholder="Password" 68 | value={password} 69 | onChange={(e) => setPassword(e.target.value)} 70 | /> 71 | 72 | 73 | 74 | 77 | 78 |
79 | ); 80 | }; 81 | 82 | export default LoginForm; 83 | -------------------------------------------------------------------------------- /front-end/src/components/AccountManagement/Styles.less: -------------------------------------------------------------------------------- 1 | .main-button { 2 | width: 100%; 3 | } -------------------------------------------------------------------------------- /front-end/src/components/MainHeader/MainHeader.less: -------------------------------------------------------------------------------- 1 | .header { 2 | background: #fff; 3 | padding: 0 30px; 4 | display: flex; 5 | justify-content: space-between; 6 | align-items: center; 7 | border-bottom: 1px solid #f0f0f0; 8 | position: fixed; 9 | top: 0; 10 | width: 100%; 11 | z-index: 10; 12 | } 13 | 14 | .logo { 15 | margin-right: 16px; 16 | color: #000; 17 | height: 48px; 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | 22 | img { 23 | width: 144px; 24 | height: auto; 25 | } 26 | } 27 | 28 | .left-header { 29 | display: flex; 30 | align-items: center; 31 | flex-grow: 1; 32 | } 33 | 34 | .menu { 35 | flex-grow: 1; 36 | } 37 | 38 | .input-group { 39 | display: flex; 40 | align-items: center; 41 | justify-content: space-between; 42 | gap: 16px; 43 | } 44 | 45 | .icon-row { 46 | display: flex; 47 | align-items: center; 48 | } 49 | 50 | .icon { 51 | margin: 0 12px; 52 | width: 20px; 53 | font-size: 20px; 54 | color: rgb(31, 31, 31); 55 | flex-shrink: 0; 56 | } 57 | 58 | .icon-container { 59 | display: flex; 60 | justify-content: center; 61 | align-items: center; 62 | } 63 | 64 | .ant-dropdown-link { 65 | cursor: pointer; 66 | } 67 | 68 | .ant-dropdown-menu-item { 69 | cursor: pointer; 70 | } 71 | 72 | .ant-menu-overflow.ant-menu.ant-menu-root.ant-menu-horizontal.ant-menu-light { 73 | border-bottom: none; 74 | } 75 | 76 | .avatar { 77 | display: flex; 78 | justify-content: center; 79 | align-items: center; 80 | height: 32px; 81 | width: 32px; 82 | border-radius: 50%; 83 | color: white; 84 | font-size: 20px; 85 | flex-shrink: 0; 86 | } 87 | 88 | .create-app-modal .ant-modal-content { 89 | padding-top: 36px; 90 | } 91 | 92 | .create-app-modal .modal-option { 93 | margin: 14px 0; 94 | } 95 | 96 | .create-app-modal .ant-card { 97 | cursor: pointer; 98 | 99 | .ant-card-body { 100 | padding: 10px 20px; 101 | } 102 | 103 | .modal-card-title { 104 | font-size: 16px; 105 | font-weight: 500; 106 | margin: 10px 0; 107 | } 108 | 109 | .modal-card-text { 110 | color: rgb(190, 190, 190); 111 | margin: 10px 0; 112 | } 113 | } 114 | 115 | .create-app-modal .ant-card:hover { 116 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); 117 | } -------------------------------------------------------------------------------- /front-end/src/components/Playground/Anthropic/AnthropicApi.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { decamelizeKeys } from "humps"; 3 | 4 | const BASE_URL = "/v1/task"; 5 | 6 | const api = axios.create({ 7 | baseURL: BASE_URL, 8 | timeout: 180000, 9 | }); 10 | 11 | api.interceptors.request.use((config) => { 12 | const token = localStorage.getItem("token"); 13 | if (token) { 14 | config.headers.XAuthorization = `Bearer ${token}`; 15 | } 16 | return config; 17 | }); 18 | 19 | export const callAnthropic = async ( 20 | input, 21 | modelName, 22 | temperature, 23 | topP, 24 | maxTokens 25 | ) => { 26 | try { 27 | const response = await api.post( 28 | "/complete", 29 | JSON.stringify( 30 | decamelizeKeys({ 31 | modelProvider: "anthropic", 32 | input: input, 33 | parameters: { 34 | modelName: modelName, 35 | maxTokens: maxTokens, 36 | temperature: temperature, 37 | topP: topP, 38 | }, 39 | }) 40 | ), 41 | { 42 | headers: { 43 | "Content-Type": "application/json", 44 | }, 45 | } 46 | ); 47 | 48 | if (response.status !== 200) { 49 | throw new Error(`Error: ${response.statusText}`); 50 | } 51 | 52 | return response.data.result; 53 | } catch (error) { 54 | if (error.response) { 55 | throw new Error(`Error: ${error.response.data.message || error.response.statusText}`); 56 | } else if (error.request) { 57 | throw new Error('Error: No response received from the server, possible timeout.'); 58 | } else { 59 | throw new Error('Error:' + error.message); 60 | } 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/Anthropic/AnthropicInput.less: -------------------------------------------------------------------------------- 1 | @alertColor: #721c24; 2 | @alertBackgroundColor: #f8d7da; 3 | @alertBorderColor: #f5c6cb; 4 | 5 | @runningColor: darkgray; 6 | 7 | .model-input { 8 | width: 100%; 9 | min-height: auto; 10 | 11 | .input-area { 12 | margin-bottom: 10px; 13 | 14 | .ant-input-textarea { 15 | width: 100%; 16 | min-height: 120px; 17 | font-size: 16px; 18 | } 19 | } 20 | 21 | .button-container { 22 | display: flex; 23 | justify-content: space-between; 24 | 25 | .left-buttons { 26 | .insert-data-button { 27 | margin-right: 10px; 28 | } 29 | } 30 | } 31 | } 32 | 33 | .copy-icon { 34 | color: inherit; 35 | transition: color 0.3s ease; 36 | cursor: pointer; 37 | size: 14px; 38 | } 39 | 40 | .text-area::-webkit-scrollbar { 41 | width: 12px; 42 | } 43 | 44 | .text-area::-webkit-scrollbar-thumb { 45 | background-color: #888; 46 | border-radius: 6px; 47 | } 48 | 49 | .text-area::-webkit-scrollbar-track { 50 | background-color: #f1f1f1; 51 | } -------------------------------------------------------------------------------- /front-end/src/components/Playground/Anthropic/AnthropicModelSettings.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Drawer, Form, Slider, Select, Tooltip } from "antd"; 3 | import "./AnthropicModelSettings.less"; 4 | 5 | const { Option } = Select; 6 | 7 | const AnthropicModelSettings = ({ 8 | open, 9 | onClose, 10 | modelName, 11 | setModelName, 12 | temperature, 13 | setTemperature, 14 | maxTokens, 15 | setMaxTokens, 16 | topP, 17 | setTopP, 18 | }) => { 19 | const handleModelNameChange = (value) => { 20 | setModelName(value); 21 | }; 22 | 23 | return ( 24 | 31 |
32 | 33 | 37 | 38 | 41 |
42 | {"Temperature"} 43 | {temperature} 44 |
45 | 46 | } 47 | > 48 | 55 |
56 | 59 |
60 | {"Maximum Tokens"} 61 | {maxTokens} 62 |
63 | 64 | } 65 | > 66 | 72 |
73 | 76 |
77 | {"Top P"} 78 | {topP} 79 |
80 | 81 | } 82 | > 83 | 90 |
91 |
92 |
93 | ); 94 | }; 95 | 96 | export default AnthropicModelSettings; 97 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/Anthropic/AnthropicModelSettings.less: -------------------------------------------------------------------------------- 1 | // .ant-slider-track { 2 | // background-color: #D9D9D9 !important; 3 | // } 4 | 5 | // .ant-slider-handle { 6 | // border-color: #D9D9D9 !important; 7 | // background-color: #D9D9D9 !important; 8 | // } 9 | 10 | // .ant-slider-handle::before, 11 | // .ant-slider-handle::after { 12 | // display: none; 13 | // } 14 | 15 | .labelContainer { 16 | display: flex; 17 | justify-content: space-between; 18 | width: 300px; 19 | } -------------------------------------------------------------------------------- /front-end/src/components/Playground/BatchInput/BatchInput.less: -------------------------------------------------------------------------------- 1 | // root elements 2 | .ant-table-row { 3 | height: 10px; 4 | } 5 | 6 | .custom-batch-input { 7 | .ant-table-cell { 8 | padding: 0 8px !important; 9 | } 10 | 11 | .ant-table-tbody>tr>td:nth-child(2) { 12 | padding: 0 !important; 13 | } 14 | 15 | .ant-input { 16 | border: none !important; 17 | border-radius: 0px !important; 18 | box-shadow: none !important; 19 | padding: 4px 8px !important; 20 | } 21 | 22 | .ant-table-thead { 23 | height: 32px !important; 24 | } 25 | } 26 | 27 | .selectable-row:hover { 28 | cursor: pointer; 29 | } 30 | 31 | .button-container { 32 | display: flex; 33 | justify-content: space-between; 34 | margin: 10px 0; 35 | } 36 | 37 | .component-button { 38 | margin-right: 10px; 39 | } 40 | 41 | .table-column { 42 | overflow: hidden; 43 | text-overflow: ellipsis; 44 | display: -webkit-box; 45 | -webkit-line-clamp: 2; 46 | -webkit-box-orient: vertical; 47 | } 48 | 49 | .upload-button { 50 | display: flex; 51 | justify-content: flex-start; 52 | align-items: center; 53 | } 54 | 55 | .pagination { 56 | margin-top: 20px; 57 | text-align: center; 58 | } -------------------------------------------------------------------------------- /front-end/src/components/Playground/BatchInput/EditableCell.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Input, Form } from "antd"; 3 | 4 | const EditableCell = ({ 5 | editing, 6 | dataIndex, 7 | title, 8 | inputType, 9 | record, 10 | index, 11 | children, 12 | setEditingKey, 13 | ...restProps 14 | }) => { 15 | const inputNode = ; 16 | return ( 17 | 18 | {editing ? ( 19 | 29 | {inputNode} 30 | 31 | ) : ( 32 | children 33 | )} 34 | 35 | ); 36 | }; 37 | 38 | export default EditableCell; 39 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/BatchInput/FileHandler.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { message } from "antd"; 3 | import { uploadFile } from "../../../api/file.ts"; 4 | import { useSelector } from "react-redux"; 5 | 6 | export const useFileHandler = ({ file, fetchFileData, setFile }) => { 7 | const [uploadedBy, setUploadedBy] = useState(""); 8 | const user = useSelector((state) => state.user.user); 9 | 10 | const logError = (error) => { 11 | console.error("Error Message: ", error.message); 12 | if (error.response) { 13 | console.error("Server Response: ", error.response); 14 | } else if (error.request) { 15 | console.error("Request: ", error.request); 16 | } 17 | }; 18 | 19 | useEffect(() => { 20 | if (user && user.id) { 21 | setUploadedBy(user.id); 22 | } 23 | }, [user]); 24 | 25 | useEffect(() => { 26 | if (file) { 27 | uploadFile(file, uploadedBy) 28 | .then((response) => { 29 | if (response.data.success) { 30 | message.success("File uploaded successfully!"); 31 | fetchFileData(response.data.fileId); 32 | setFile(null); 33 | } else { 34 | message.error(response.data.error); 35 | } 36 | }) 37 | .catch((error) => { 38 | logError(error); 39 | 40 | if (error.response?.data?.error) { 41 | message.error(error.response.data.error); 42 | } else { 43 | message.error("An error occurred while uploading the file."); 44 | } 45 | }); 46 | } 47 | }, [file, uploadedBy, fetchFileData, setFile]); 48 | }; 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/BatchInput/JobModal.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Modal, Form, Input } from "antd"; 3 | import { useEffect } from "react"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | import { setJobName, setIsJobCreated } from "../../../redux/actions/jobActions"; 6 | 7 | const JobModal = ({ isVisible, handleClose }) => { 8 | const [form] = Form.useForm(); 9 | const dispatch = useDispatch(); 10 | 11 | const jobName = useSelector((state) => state.job.jobName); 12 | 13 | const handleOk = () => { 14 | form 15 | .validateFields() 16 | .then((values) => { 17 | form.resetFields(); 18 | dispatch(setJobName(values.jobName)); 19 | handleClose(); 20 | }) 21 | .catch((info) => { 22 | console.log("Validation failed:", info); 23 | }); 24 | }; 25 | 26 | useEffect(() => { 27 | if (jobName !== "") { 28 | dispatch(setIsJobCreated(true)); 29 | } 30 | }, [jobName, dispatch]); 31 | 32 | const handleCancel = () => { 33 | dispatch(setJobName("")); 34 | dispatch(setIsJobCreated(false)); 35 | handleClose(); 36 | }; 37 | 38 | return ( 39 | 45 |

Remember to save your application before submitting batch jobs.

46 |
47 | 60 | 61 | 62 |
63 |
64 | ); 65 | }; 66 | 67 | export default JobModal; 68 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/Common/ResponseCard.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Button, Card } from "antd"; 3 | import { CopyOutlined } from "@ant-design/icons"; 4 | import ReactMarkdown from "react-markdown"; 5 | import gfm from "remark-gfm"; 6 | import rehypeHighlight from "rehype-highlight"; 7 | import "highlight.js/styles/atom-one-light.css"; 8 | import "./ResponseCard.less"; 9 | 10 | const ResponseCard = ({ 11 | isLoading, 12 | apiResponse, 13 | error, 14 | handleCopyClick, 15 | isCopied, 16 | isEditMode, 17 | title, 18 | }) => { 19 | const [isExpanded, setIsExpanded] = useState(false); 20 | 21 | const toggleExpand = () => { 22 | setIsExpanded((prev) => !prev); 23 | }; 24 | 25 | return ( 26 | 35 |
{!isEditMode ? title : "Response"}
36 | {!isLoading && apiResponse && ( 37 | 44 | )} 45 | 46 | } 47 | className={`response-card ${ 48 | !isLoading && !apiResponse && !error 49 | ? "header-only" 50 | : `${!isEditMode ? "non-editmode" : ""}` 51 | }`} 52 | > 53 | {isLoading &&

Waiting for responses...

} 54 | {!isLoading && apiResponse && ( 55 |
59 | 64 |
65 | )} 66 | {!isLoading && error &&

{error}

} 67 |
68 | ); 69 | }; 70 | 71 | export default ResponseCard; 72 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/Common/ResponseCard.less: -------------------------------------------------------------------------------- 1 | @alertColor: #721c24; 2 | @alertBackgroundColor: #f8d7da; 3 | @alertBorderColor: #f5c6cb; 4 | 5 | @runningColor: darkgray; 6 | 7 | .response-card { 8 | margin-top: 16px; 9 | margin-bottom: 10px; 10 | 11 | pre { 12 | margin: 4px 0px; 13 | white-space: pre-wrap; 14 | word-break: break-word; 15 | font-family: inherit; 16 | } 17 | 18 | .ant-card-head { 19 | min-height: 41px; 20 | padding: 4px 11px; 21 | margin-bottom: 0px; 22 | 23 | .ant-card-head-wrapper { 24 | display: flex; 25 | justify-content: space-between; 26 | align-items: center; 27 | } 28 | } 29 | 30 | .ant-card-body { 31 | padding: 4px 6px; 32 | } 33 | 34 | .hljs { 35 | border-radius: 4px; 36 | } 37 | 38 | .markdown-container { 39 | p { 40 | margin: 4px; 41 | } 42 | 43 | table { 44 | width: 100%; 45 | border-collapse: collapse; 46 | } 47 | 48 | th, 49 | td { 50 | border: 1px solid #ddd; 51 | padding: 8px; 52 | } 53 | 54 | th { 55 | background-color: #f2f2f2; 56 | color: black; 57 | } 58 | 59 | table { 60 | border-radius: 4px; 61 | border-collapse: separate; 62 | border-spacing: 0; 63 | overflow: hidden; 64 | } 65 | 66 | th:first-child { 67 | border-top-left-radius: 4px; 68 | } 69 | 70 | th:last-child { 71 | border-top-right-radius: 4px; 72 | } 73 | 74 | tr:last-child { 75 | td:first-child { 76 | border-bottom-left-radius: 4px; 77 | } 78 | 79 | td:last-child { 80 | border-bottom-right-radius: 4px; 81 | } 82 | } 83 | } 84 | } 85 | 86 | .markdown-container::-webkit-scrollbar { 87 | width: 12px; 88 | } 89 | 90 | .markdown-container::-webkit-scrollbar-thumb { 91 | background-color: #888; 92 | border-radius: 6px; 93 | } 94 | 95 | .markdown-container::-webkit-scrollbar-track { 96 | background-color: #f1f1f1; 97 | } 98 | 99 | .response-card.header-only { 100 | margin-top: 0px; 101 | display: none; 102 | } 103 | 104 | .response-card.non-editmode { 105 | margin-top: 12px; 106 | } 107 | 108 | .alert { 109 | color: @alertColor; 110 | background-color: @alertBackgroundColor; 111 | border-color: @alertBorderColor; 112 | padding: 10px 10px 10px 12px; 113 | margin: 0; 114 | border-radius: 4px; 115 | } 116 | 117 | .running { 118 | margin: 4px; 119 | color: @runningColor; 120 | } -------------------------------------------------------------------------------- /front-end/src/components/Playground/ComponentCard/ComponentCard.less: -------------------------------------------------------------------------------- 1 | .customCard { 2 | padding: 2px 12px; 3 | 4 | h3 { 5 | margin-top: 12px; 6 | margin-bottom: 12px; 7 | 8 | small { 9 | font-size: 0.8em; 10 | color: grey; 11 | font-weight: normal; 12 | } 13 | } 14 | 15 | .edit-icon { 16 | margin-right: 6px; 17 | } 18 | 19 | .flex-align-center { 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .col-left-text { 25 | text-align: left; 26 | } 27 | 28 | .text-margin { 29 | margin: 8px; 30 | } 31 | } 32 | 33 | .customCard.non-editmode { 34 | display: none; 35 | } 36 | 37 | .row-align-items { 38 | display: flex; 39 | align-items: center; 40 | } 41 | 42 | .switch-wrapper, 43 | .input-wrapper { 44 | display: inline-flex; 45 | align-items: center; 46 | height: 100%; 47 | } -------------------------------------------------------------------------------- /front-end/src/components/Playground/ComponentCard/ModelInput.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import OpenAIInput from "../OpenAI/OpenAIInput"; 3 | import AnthropicInput from "../Anthropic/AnthropicInput"; 4 | import TextInput from "../TextInput/TextInput"; 5 | import BatchInput from "../BatchInput/BatchInput"; 6 | import Output from "../Output/Output"; 7 | import TagParser from "../TagParser/TagParser"; 8 | import GoogleSearch from "../GoogleSearch/GoogleSearch"; 9 | import DocSearch from "../DocSearch/DocSearch"; 10 | import YouTubeTranscript from "../YouTubeTranscript/YouTubeTranscript"; 11 | 12 | const ModelInput = React.forwardRef((props, ref) => { 13 | switch (props.type) { 14 | case "openai": 15 | return ; 16 | case "anthropic": 17 | return ; 18 | case "text-input": 19 | return ; 20 | case "batch-input": 21 | return ; 22 | case "output": 23 | return ; 24 | case "tag-parser": 25 | return ; 26 | case "google-search": 27 | return ; 28 | case "doc-search": 29 | return ; 30 | case "youtube-transcript": 31 | return ; 32 | default: 33 | return null; 34 | } 35 | }); 36 | 37 | export default ModelInput; 38 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/DocSearch/DocSearch.less: -------------------------------------------------------------------------------- 1 | @alertColor: #721c24; 2 | @alertBackgroundColor: #f8d7da; 3 | @alertBorderColor: #f5c6cb; 4 | @runningColor: darkgray; 5 | 6 | .input-area { 7 | margin-bottom: 10px; 8 | } 9 | 10 | .button-container { 11 | display: flex; 12 | justify-content: space-between; 13 | 14 | .left-buttons { 15 | .insert-data-button { 16 | margin-right: 10px; 17 | } 18 | } 19 | } 20 | 21 | .alert { 22 | color: @alertColor; 23 | background-color: @alertBackgroundColor; 24 | border-color: @alertBorderColor; 25 | padding: 10px 10px 10px 12px; 26 | border-radius: 4px; 27 | } 28 | 29 | .running { 30 | color: @runningColor; 31 | } 32 | 33 | .table-title { 34 | display: flex; 35 | justify-content: flex-start; 36 | align-items: center; 37 | } 38 | 39 | .file-name { 40 | display: flex; 41 | align-items: center; 42 | margin: 10px 0; 43 | } 44 | 45 | .text-area::-webkit-scrollbar { 46 | width: 12px; 47 | } 48 | 49 | .text-area::-webkit-scrollbar-thumb { 50 | background-color: #888; 51 | border-radius: 6px; 52 | } 53 | 54 | .text-area::-webkit-scrollbar-track { 55 | background-color: #f1f1f1; 56 | } -------------------------------------------------------------------------------- /front-end/src/components/Playground/GoogleSearch/GoogleSearch.less: -------------------------------------------------------------------------------- 1 | @alertColor: #721c24; 2 | @alertBackgroundColor: #f8d7da; 3 | @alertBorderColor: #f5c6cb; 4 | 5 | @runningColor: darkgray; 6 | 7 | .search-input { 8 | width: 100%; 9 | min-height: auto; 10 | 11 | .input-area { 12 | margin-bottom: 10px; 13 | 14 | .ant-input-textarea { 15 | width: 100%; 16 | min-height: 120px; 17 | font-size: 16px; 18 | } 19 | } 20 | 21 | .button-container { 22 | display: flex; 23 | justify-content: space-between; 24 | 25 | .left-buttons { 26 | .insert-data-button { 27 | margin-right: 10px; 28 | } 29 | } 30 | } 31 | } 32 | 33 | .copy-icon { 34 | color: inherit; 35 | transition: color 0.3s ease; 36 | cursor: pointer; 37 | size: 14px; 38 | } 39 | 40 | .text-area::-webkit-scrollbar { 41 | width: 12px; 42 | } 43 | 44 | .text-area::-webkit-scrollbar-thumb { 45 | background-color: #888; 46 | border-radius: 6px; 47 | } 48 | 49 | .text-area::-webkit-scrollbar-track { 50 | background-color: #f1f1f1; 51 | } 52 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/GoogleSearch/GoogleSearchApi.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { decamelizeKeys } from "humps"; 3 | 4 | const BASE_URL = "/v1/task"; 5 | 6 | const api = axios.create({ 7 | baseURL: BASE_URL, 8 | timeout: 60000, 9 | }); 10 | 11 | api.interceptors.request.use((config) => { 12 | const token = localStorage.getItem("token"); 13 | if (token) { 14 | config.headers.XAuthorization = `Bearer ${token}`; 15 | } 16 | return config; 17 | }); 18 | 19 | export const callGoogleSearch = async ( 20 | input, 21 | numResults 22 | ) => { 23 | try { 24 | const response = await api.post( 25 | "/google_search", 26 | JSON.stringify( 27 | decamelizeKeys({ 28 | query: input, 29 | numResults: numResults 30 | }) 31 | ), 32 | { 33 | headers: { 34 | "Content-Type": "application/json", 35 | }, 36 | } 37 | ); 38 | if (response.status !== 200) { 39 | throw new Error(`Error: ${response.statusText}`); 40 | } 41 | return response.data.result; 42 | } catch (error) { 43 | if (error.response) { 44 | throw new Error(`Error: ${error.response.data.message || error.response.statusText}`); 45 | } else if (error.request) { 46 | throw new Error('Error: No response received from the server, possible timeout.'); 47 | } else { 48 | throw new Error('Error:' + error.message); 49 | } 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/GoogleSearch/GoogleSearchSettings.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Drawer, Form, Slider, Tooltip } from "antd"; 3 | import "./GoogleSearchSettings.less"; 4 | 5 | const GoogleSearchSettings = ({ 6 | open, 7 | onClose, 8 | numResults, 9 | setNumResults, 10 | }) => { 11 | return ( 12 | 19 |
20 | 25 |
26 | {"Number of Results"} 27 | {numResults} 28 |
29 | 30 | } 31 | > 32 | 38 |
39 |
40 |
41 | ); 42 | }; 43 | 44 | export default GoogleSearchSettings; 45 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/GoogleSearch/GoogleSearchSettings.less: -------------------------------------------------------------------------------- 1 | // .ant-slider-track { 2 | // background-color: #D9D9D9 !important; 3 | // } 4 | 5 | // .ant-slider-handle { 6 | // border-color: #D9D9D9 !important; 7 | // background-color: #D9D9D9 !important; 8 | // } 9 | 10 | // .ant-slider-handle::before, 11 | // .ant-slider-handle::after { 12 | // display: none; 13 | // } 14 | 15 | .labelContainer { 16 | display: flex; 17 | justify-content: space-between; 18 | width: 300px; 19 | } -------------------------------------------------------------------------------- /front-end/src/components/Playground/OpenAI/OpenAIApi.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { decamelizeKeys } from "humps"; 3 | 4 | const BASE_URL = "/v1/task"; 5 | 6 | const api = axios.create({ 7 | baseURL: BASE_URL, 8 | timeout: 180000, 9 | }); 10 | 11 | api.interceptors.request.use((config) => { 12 | const token = localStorage.getItem("token"); 13 | if (token) { 14 | config.headers.XAuthorization = `Bearer ${token}`; 15 | } 16 | return config; 17 | }); 18 | 19 | export const callOpenAI = async ( 20 | input, 21 | modelName, 22 | temperature, 23 | topP, 24 | maxTokens, 25 | freqPenalty, 26 | presPenalty 27 | ) => { 28 | try { 29 | const response = await api.post( 30 | "/complete", 31 | JSON.stringify( 32 | decamelizeKeys({ 33 | modelProvider: "openai", 34 | input: input, 35 | parameters: { 36 | modelName: modelName, 37 | maxTokens: maxTokens, 38 | temperature: temperature, 39 | topP: topP, 40 | frequencyPenalty: freqPenalty, 41 | presencePenalty: presPenalty, 42 | }, 43 | }) 44 | ), 45 | { 46 | headers: { 47 | "Content-Type": "application/json", 48 | }, 49 | } 50 | ); 51 | 52 | if (response.status !== 200) { 53 | throw new Error(`Error: ${response.statusText}`); 54 | } 55 | 56 | return response.data.result; 57 | } catch (error) { 58 | if (error.response) { 59 | throw new Error(`Error: ${error.response.data.message || error.response.statusText}`); 60 | } else if (error.request) { 61 | throw new Error('Error: No response received from the server, possible timeout.'); 62 | } else { 63 | throw new Error('Error:' + error.message); 64 | } 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/OpenAI/OpenAIInput.less: -------------------------------------------------------------------------------- 1 | @alertColor: #721c24; 2 | @alertBackgroundColor: #f8d7da; 3 | @alertBorderColor: #f5c6cb; 4 | 5 | @runningColor: darkgray; 6 | 7 | .openai-input { 8 | width: 100%; 9 | min-height: auto; 10 | 11 | .input-area { 12 | margin-bottom: 10px; 13 | 14 | .ant-input-textarea { 15 | width: 100%; 16 | min-height: 120px; 17 | font-size: 16px; 18 | } 19 | } 20 | 21 | .button-container { 22 | display: flex; 23 | justify-content: space-between; 24 | 25 | .left-buttons { 26 | .insert-data-button { 27 | margin-right: 10px; 28 | } 29 | } 30 | } 31 | } 32 | 33 | .copy-icon { 34 | color: inherit; 35 | transition: color 0.3s ease; 36 | cursor: pointer; 37 | size: 14px; 38 | } 39 | 40 | .text-area::-webkit-scrollbar { 41 | width: 12px; 42 | } 43 | 44 | .text-area::-webkit-scrollbar-thumb { 45 | background-color: #888; 46 | border-radius: 6px; 47 | } 48 | 49 | .text-area::-webkit-scrollbar-track { 50 | background-color: #f1f1f1; 51 | } -------------------------------------------------------------------------------- /front-end/src/components/Playground/OpenAI/OpenAIModelSettings.less: -------------------------------------------------------------------------------- 1 | // .ant-slider-track { 2 | // background-color: #D9D9D9 !important; 3 | // } 4 | 5 | // .ant-slider-handle { 6 | // border-color: #D9D9D9 !important; 7 | // background-color: #D9D9D9 !important; 8 | // } 9 | 10 | // .ant-slider-handle::before, 11 | // .ant-slider-handle::after { 12 | // display: none; 13 | // } 14 | 15 | .labelContainer { 16 | display: flex; 17 | justify-content: space-between; 18 | width: 300px; 19 | } -------------------------------------------------------------------------------- /front-end/src/components/Playground/Output/Output.less: -------------------------------------------------------------------------------- 1 | @alertColor: #721c24; 2 | @alertBackgroundColor: #f8d7da; 3 | @alertBorderColor: #f5c6cb; 4 | 5 | @runningColor: darkgray; 6 | 7 | .output-container { 8 | width: 100%; 9 | 10 | .input-area { 11 | margin-bottom: 10px; 12 | 13 | .ant-input-textarea { 14 | width: 100%; 15 | min-height: 120px; 16 | font-size: 16px; 17 | } 18 | } 19 | 20 | .button-container { 21 | display: flex; 22 | justify-content: space-between; 23 | 24 | .left-buttons { 25 | .insert-data-button { 26 | margin-right: 10px; 27 | } 28 | } 29 | } 30 | } 31 | 32 | .copy-icon { 33 | color: inherit; 34 | transition: color 0.3s ease; 35 | cursor: pointer; 36 | size: 14px; 37 | } 38 | 39 | .text-area::-webkit-scrollbar { 40 | width: 12px; 41 | } 42 | 43 | .text-area::-webkit-scrollbar-thumb { 44 | background-color: #888; 45 | border-radius: 6px; 46 | } 47 | 48 | .text-area::-webkit-scrollbar-track { 49 | background-color: #f1f1f1; 50 | } -------------------------------------------------------------------------------- /front-end/src/components/Playground/Sider/Sider.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Layout, Menu } from "antd"; 3 | import { 4 | DatabaseOutlined, 5 | AppstoreAddOutlined, 6 | CodeOutlined, 7 | } from "@ant-design/icons"; 8 | import "./Sider.less"; 9 | 10 | const { Sider } = Layout; 11 | 12 | const PlaygroundSider = ({ onMenuClick }) => { 13 | const [selectedKeys, setSelectedKeys] = React.useState([]); 14 | 15 | const items = [ 16 | { 17 | label: "Models", 18 | key: "models", 19 | icon: , 20 | children: [ 21 | { label: "OpenAI", key: "openai" }, 22 | { label: "Anthropic", key: "anthropic" }, 23 | ], 24 | }, 25 | { 26 | label: "Data", 27 | key: "data", 28 | icon: , 29 | children: [ 30 | { label: "Text Input", key: "text-input" }, 31 | { label: "Batch Input", key: "batch-input" }, 32 | { label: "Output", key: "output" }, 33 | ], 34 | }, 35 | { 36 | label: "Plug-ins", 37 | key: "plugins", 38 | icon: , 39 | children: [ 40 | { label: "Tag Parser", key: "tag-parser" }, 41 | { label: "Google Search", key: "google-search" }, 42 | { label: "Doc Search", key: "doc-search" }, 43 | { label: "YouTube Transcript", key: "youtube-transcript" }, 44 | ], 45 | }, 46 | ]; 47 | 48 | return ( 49 | 50 | { 55 | if (onMenuClick) { 56 | onMenuClick(e); 57 | } 58 | setSelectedKeys([]); 59 | }} 60 | items={items} 61 | /> 62 | 63 | ); 64 | }; 65 | 66 | export default PlaygroundSider; 67 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/Sider/Sider.less: -------------------------------------------------------------------------------- 1 | .playground-sider { 2 | width: 200px; 3 | overflow: auto; 4 | position: fixed; 5 | left: 0; 6 | top: 60px; 7 | height: calc(100vh - 60px); 8 | border-right: 1px solid #f0f0f0; 9 | } 10 | 11 | .ant-layout .ant-layout-sider { 12 | background-color: rgb(255, 255, 255); 13 | } 14 | 15 | .ant-menu.ant-menu-root.ant-menu-inline.ant-menu-light { 16 | border-right: none; 17 | } -------------------------------------------------------------------------------- /front-end/src/components/Playground/TagParser/TagParser.less: -------------------------------------------------------------------------------- 1 | .tag-parser-container { 2 | .action-row { 3 | display: flex; 4 | justify-content: space-between; 5 | margin-bottom: 1em; 6 | } 7 | 8 | .select-field { 9 | min-width: 160px; 10 | } 11 | 12 | .input-field { 13 | display: flex; 14 | flex-direction: column; 15 | margin-left: 12px; 16 | margin-right: 12px; 17 | width: 100%; 18 | } 19 | 20 | .title-padding { 21 | padding: 0px; 22 | } 23 | 24 | .noResult { 25 | color: #333333; 26 | background-color: #eeeeee; 27 | padding: 10px 10px 10px 12px; 28 | border-radius: 4px; 29 | } 30 | } -------------------------------------------------------------------------------- /front-end/src/components/Playground/TextInput/TextInput.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Input } from "antd"; 3 | import { forwardRef, useImperativeHandle } from "react"; 4 | import "./TextInput.less"; 5 | 6 | const TextInput = forwardRef( 7 | ( 8 | { 9 | components, 10 | index, 11 | title, 12 | onUpdateInput, 13 | onUpdateUserInput, 14 | onUpdateOutput, 15 | }, 16 | ref 17 | ) => { 18 | const [userInput, setUserInput] = useState(""); 19 | const [input, setInput] = useState(""); 20 | 21 | useEffect(() => { 22 | setInput(`{${title} Content}`); 23 | onUpdateInput(`{${title} Content}`); 24 | }, [title]); 25 | 26 | useEffect(() => { 27 | onUpdateOutput(userInput); 28 | onUpdateUserInput(userInput); 29 | }, [userInput]); 30 | 31 | useImperativeHandle(ref, () => ({ 32 | async run() { 33 | let processedInput = userInput; 34 | 35 | onUpdateOutput(processedInput); 36 | return processedInput; 37 | }, 38 | setInput(newInput) { 39 | setInput(newInput); 40 | }, 41 | setUserInput(newInput) { 42 | setUserInput(newInput); 43 | }, 44 | getInput() { 45 | return input; 46 | }, 47 | })); 48 | 49 | const handleInputChange = (e) => { 50 | setUserInput(e.target.value); 51 | }; 52 | 53 | const previousComponents = components.slice(0, index); 54 | 55 | return ( 56 |
57 | 64 |
65 | ); 66 | } 67 | ); 68 | 69 | export default TextInput; 70 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/TextInput/TextInput.less: -------------------------------------------------------------------------------- 1 | .text-input-container { 2 | width: 100%; 3 | margin-bottom: 10px; 4 | } 5 | 6 | .text-area::-webkit-scrollbar { 7 | width: 12px; 8 | } 9 | 10 | .text-area::-webkit-scrollbar-thumb { 11 | background-color: #888; 12 | border-radius: 6px; 13 | } 14 | 15 | .text-area::-webkit-scrollbar-track { 16 | background-color: #f1f1f1; 17 | } -------------------------------------------------------------------------------- /front-end/src/components/Playground/TitleCard/SaveModule.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { saveApplication } from "../../../redux/actions/applicationActions"; 4 | import { message } from "antd"; 5 | 6 | export default function useSaveModule( 7 | title, 8 | setTitle, 9 | tags, 10 | setTags, 11 | description, 12 | setDescription, 13 | appId, 14 | setAppId, 15 | setIsReloading, 16 | setIsChangeSaved 17 | ) { 18 | const [modalVisible, setModalVisible] = useState(false); 19 | const [saveType, setSaveType] = useState(2); 20 | const [modalTitle, setModalTitle] = useState(title); 21 | const [modalTags, setModalTags] = useState(tags); 22 | const [modalDescription, setModalDescription] = useState(description); 23 | const [saveApplicationData, setSaveApplicationData] = useState(false); 24 | const [confirmSaving, setconfirmSaving] = useState(false); 25 | 26 | const dispatch = useDispatch(); 27 | const applicationData = useSelector((state) => state.applicationData); 28 | 29 | const handleSaveButtonClick = () => { 30 | setModalVisible(true); 31 | setModalTitle(title); 32 | setModalTags(tags); 33 | setModalDescription(description); 34 | }; 35 | 36 | const handleModalCancel = () => { 37 | setModalVisible(false); 38 | }; 39 | 40 | const handleModalSave = async () => { 41 | if (modalTitle.length <= 44) { 42 | setTitle(modalTitle); 43 | setTags(modalTags); 44 | setDescription(modalDescription); 45 | setModalVisible(false); 46 | if (saveType === 2) { 47 | setAppId(null); 48 | } 49 | setSaveApplicationData(true); 50 | } else { 51 | message.error("Application name cannot exceed 44 characters."); 52 | } 53 | }; 54 | 55 | useEffect(() => { 56 | if (saveApplicationData) { 57 | setconfirmSaving(true); 58 | } 59 | }, [saveApplicationData]); 60 | 61 | useEffect(() => { 62 | if (confirmSaving) { 63 | dispatch(saveApplication(applicationData.applicationData)) 64 | .then((responseData) => { 65 | setAppId(responseData.id); 66 | setIsReloading(true); 67 | setIsChangeSaved(true); 68 | message.success("Application saved successfully"); 69 | }) 70 | .catch((error) => { 71 | console.error("Error saving application:", error); 72 | }); 73 | setSaveApplicationData(false); 74 | setconfirmSaving(false); 75 | } 76 | }, [confirmSaving]); 77 | 78 | return { 79 | modalVisible, 80 | saveType, 81 | setSaveType, 82 | modalTitle, 83 | setModalTitle, 84 | modalTags, 85 | setModalTags, 86 | modalDescription, 87 | setModalDescription, 88 | handleSaveButtonClick, 89 | handleModalCancel, 90 | handleModalSave, 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/TitleCard/ShareModule.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { message } from "antd"; 3 | import { generateShareLink } from "../../../api/sharedLink.ts"; 4 | 5 | const useShareModule = (appId) => { 6 | const [shareModalVisible, setShareModalVisible] = useState(false); 7 | const [shareLink, setShareLink] = useState(""); 8 | 9 | const handleShareClick = async () => { 10 | try { 11 | const response = await generateShareLink("APP", appId, null); 12 | setShareLink(response.shareableUrl); 13 | message.success("Link generated successfully"); 14 | } catch (error) { 15 | message.error("Failed to generate the link"); 16 | } 17 | setShareModalVisible(true); 18 | }; 19 | 20 | const handleShareModalCancel = () => { 21 | setShareModalVisible(false); 22 | }; 23 | 24 | return { 25 | shareModalVisible, 26 | shareLink, 27 | handleShareClick, 28 | handleShareModalCancel, 29 | }; 30 | }; 31 | 32 | export default useShareModule; 33 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/TitleCard/TitleCard.less: -------------------------------------------------------------------------------- 1 | .titleCard { 2 | padding: 0px 0px 0px 12px; 3 | margin: 12px 12px 0px 12px; 4 | } 5 | 6 | .edit-mode { 7 | display: flex; 8 | align-items: baseline; 9 | } 10 | 11 | .title-text { 12 | margin-right: 8px; 13 | height: auto; 14 | font-size: 24px; 15 | margin-top: 0; 16 | padding: 4px 0px; 17 | } 18 | 19 | .title-input { 20 | margin-right: 8px; 21 | max-width: 290px; 22 | height: 40px; 23 | font-size: 24px; 24 | margin-top: 0; 25 | padding: 4px 8px; 26 | } 27 | 28 | .save-icon { 29 | cursor: pointer; 30 | font-size: 16px; 31 | color: dimgray; 32 | } 33 | 34 | .edit-icon { 35 | cursor: pointer; 36 | font-size: 16px; 37 | color: dimgray; 38 | } 39 | 40 | .save-button.ant-btn { 41 | margin-left: 8px; 42 | border-color: transparent; 43 | background-color: rgb(245, 245, 245); 44 | vertical-align: bottom; 45 | } 46 | 47 | .save-button.ant-btn:hover { 48 | border-color: transparent; 49 | box-shadow: none; 50 | background-color: rgb(235, 235, 235); 51 | color: black; 52 | } 53 | 54 | .save-button.ant-btn .ant-wave { 55 | box-shadow: none; 56 | } 57 | 58 | .description-input { 59 | width: 100%; 60 | margin-top: 8px; 61 | } 62 | 63 | .tag-select { 64 | width: 100%; 65 | margin-top: 8px; 66 | margin-bottom: 12px; 67 | } 68 | 69 | .explore-button.ant-btn .ant-wave { 70 | box-shadow: none; 71 | } 72 | 73 | .explore-button.ant-btn { 74 | margin-left: 8px; 75 | border-color: transparent; 76 | background-color: rgb(245, 245, 245); 77 | vertical-align: bottom; 78 | } 79 | 80 | .explore-button.ant-btn:hover { 81 | border-color: transparent; 82 | box-shadow: none; 83 | background-color: rgb(235, 235, 235); 84 | color: black; 85 | } 86 | 87 | .more-button.ant-btn { 88 | margin-left: 8px; 89 | width: 32px; 90 | font-size: 18px; 91 | border-color: transparent; 92 | padding: 0px; 93 | background-color: rgb(245, 245, 245); 94 | vertical-align: bottom; 95 | } 96 | 97 | .more-button.ant-btn:hover { 98 | border-color: transparent; 99 | box-shadow: none; 100 | background-color: rgb(235, 235, 235); 101 | color: black; 102 | } 103 | 104 | .more-button.ant-btn .ant-wave { 105 | box-shadow: none; 106 | } 107 | 108 | .save-type-radio { 109 | margin-top: 8px; 110 | margin-bottom: 12px; 111 | } 112 | 113 | .button-row { 114 | margin-top: -4px; 115 | 116 | .ant-btn { 117 | color: dimgray; 118 | padding-left: 160px; 119 | font-size: 12px; 120 | height: 24px; 121 | } 122 | 123 | .ant-btn:first-child { 124 | border-top-right-radius: 0; 125 | border-bottom-right-radius: 0; 126 | margin-right: -1px; 127 | padding-left: 8px; 128 | padding-right: 8px; 129 | } 130 | 131 | .ant-btn:not(:first-child):not(:last-child) { 132 | border-radius: 0; 133 | margin-right: -1px; 134 | padding-left: 8px; 135 | padding-right: 8px; 136 | } 137 | 138 | .ant-btn:last-child { 139 | border-top-left-radius: 0; 140 | border-bottom-left-radius: 0; 141 | padding-left: 8px; 142 | padding-right: 8px; 143 | } 144 | } 145 | 146 | .modal-description { 147 | margin-bottom: 10px; 148 | } 149 | 150 | .share-link-container { 151 | display: flex; 152 | align-items: center; 153 | border: 1px solid #ccc; 154 | padding: 10px; 155 | border-radius: 4px; 156 | background: #f9f9f9; 157 | margin-bottom: 24px; 158 | } 159 | 160 | .copy-button { 161 | margin-left: 10px; 162 | } -------------------------------------------------------------------------------- /front-end/src/components/Playground/YouTubeTranscript/YouTubeTranscript.less: -------------------------------------------------------------------------------- 1 | @alertColor: #721c24; 2 | @alertBackgroundColor: #f8d7da; 3 | @alertBorderColor: #f5c6cb; 4 | 5 | @runningColor: darkgray; 6 | 7 | .transcript-input { 8 | width: 100%; 9 | min-height: auto; 10 | 11 | .action-row { 12 | display: flex; 13 | justify-content: space-between; 14 | margin-bottom: 1em; 15 | 16 | .input-field { 17 | display: flex; 18 | flex-direction: column; 19 | margin-left: 0; 20 | margin-right: 12px; 21 | width: 100%; 22 | } 23 | } 24 | 25 | .button-container { 26 | display: flex; 27 | justify-content: space-between; 28 | 29 | .left-buttons { 30 | .insert-data-button { 31 | margin-right: 10px; 32 | } 33 | } 34 | } 35 | } 36 | 37 | .copy-icon { 38 | color: inherit; 39 | transition: color 0.3s ease; 40 | cursor: pointer; 41 | size: 14px; 42 | } 43 | -------------------------------------------------------------------------------- /front-end/src/components/Playground/YouTubeTranscript/YouTubeTranscriptApi.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { decamelizeKeys } from "humps"; 3 | 4 | const BASE_URL = "/v1/task"; 5 | 6 | const api = axios.create({ 7 | baseURL: BASE_URL, 8 | timeout: 30000, 9 | }); 10 | 11 | api.interceptors.request.use((config) => { 12 | const token = localStorage.getItem("token"); 13 | if (token) { 14 | config.headers.XAuthorization = `Bearer ${token}`; 15 | } 16 | return config; 17 | }); 18 | 19 | export const callYouTubeTranscript = async (input) => { 20 | try { 21 | const response = await api.post( 22 | "/youtube_transcript", 23 | JSON.stringify( 24 | decamelizeKeys({ 25 | videoUrl: input 26 | }) 27 | ), 28 | { 29 | headers: { 30 | "Content-Type": "application/json", 31 | }, 32 | } 33 | ); 34 | return response.data.result; 35 | } catch (error) { 36 | let errorMessage = 'An unexpected error occurred.'; 37 | if (error.response) { 38 | const { status, data } = error.response; 39 | // Customize the message based on status code if needed 40 | switch (status) { 41 | case 400: 42 | errorMessage = 'Bad Request: The server cannot process the request due to a client error.'; 43 | break; 44 | case 401: 45 | errorMessage = 'Unauthorized: Authentication is required and has failed or has not been provided.'; 46 | break; 47 | case 403: 48 | errorMessage = 'Forbidden: You do not have the necessary permissions to access this.'; 49 | break; 50 | case 404: 51 | errorMessage = 'Not Found: The requested resource could not be found.'; 52 | break; 53 | // Add more cases as necessary 54 | } 55 | // Use the server's error message if available 56 | errorMessage = (data && data.message) || data.error || errorMessage; 57 | } else if (error.request) { 58 | errorMessage = 'No response received from the server, possible timeout.'; 59 | } else { 60 | errorMessage = error.message; 61 | } 62 | throw new Error(errorMessage); 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /front-end/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } -------------------------------------------------------------------------------- /front-end/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | import i18n from "i18next"; 7 | // import { initReactI18next } from "react-i18next"; 8 | // import translationEn from "./locales/en/translation.json"; 9 | // import other translation files here 10 | 11 | // i18n.use(initReactI18next).init({ 12 | // resources: { 13 | // en: { 14 | // translation: translationEn, 15 | // }, 16 | // // Add other languages here 17 | // }, 18 | // lng: "en", 19 | // fallbackLng: "en", 20 | // interpolation: { 21 | // escapeValue: false, 22 | // }, 23 | // }); 24 | 25 | const root = ReactDOM.createRoot(document.getElementById("root")); 26 | root.render(); 27 | reportWebVitals(); 28 | -------------------------------------------------------------------------------- /front-end/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /front-end/src/pages/AccountManagement/LoginPage.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { Row, Col, Card, Button } from "antd"; 3 | import { Link, useNavigate } from "react-router-dom"; 4 | import LoginForm from "../../components/AccountManagement/LoginForm"; 5 | import loginCover from "../../assets/platform_cover.svg"; 6 | import logo from "../../assets/text_logo_dark.png"; 7 | import "./Styles.less"; 8 | 9 | const LoginPage = () => { 10 | const navigate = useNavigate(); 11 | 12 | useEffect(() => { 13 | const token = localStorage.getItem("token"); 14 | if (token) { 15 | navigate("/home"); 16 | } 17 | }, [navigate]); 18 | 19 | return ( 20 |
24 | 27 | Logo 28 | Hello! 29 | Let's build together. 30 | 31 | 32 | 35 | 36 | 37 |
38 | ); 39 | }; 40 | 41 | export default LoginPage; 42 | -------------------------------------------------------------------------------- /front-end/src/pages/AccountManagement/RegisterPage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Row, Col, Card, Button, Typography } from "antd"; 3 | import { Link } from "react-router-dom"; 4 | import RegisterForm from "../../components/AccountManagement/RegisterForm"; 5 | import loginCover from "../../assets/platform_cover.svg"; 6 | import logo from "../../assets/text_logo_dark.png"; 7 | import "./Styles.less"; 8 | 9 | const { Text } = Typography; 10 | 11 | const RegisterPage = () => ( 12 |
16 | 17 | Logo 18 | Create Account 19 | Join our community. 20 | 21 | {/* 22 | 23 | 24 | Registration is currently closed. Please check back later. 25 | 26 | 27 | */} 28 | 29 | 30 | 33 | 34 | 35 | 36 |
37 | ); 38 | 39 | export default RegisterPage; 40 | -------------------------------------------------------------------------------- /front-end/src/pages/AccountManagement/Styles.less: -------------------------------------------------------------------------------- 1 | .card-container { 2 | height: 100vh; 3 | padding: 0px; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | background-size: cover; 8 | } 9 | 10 | .white-button { 11 | color: white; 12 | } 13 | 14 | .login-card { 15 | width: 360px; 16 | background-color: rgba(0, 0, 0, 0.5); 17 | backdrop-filter: blur(5px); 18 | border-color: transparent; 19 | display: block; 20 | flex-direction: column; 21 | align-items: center; 22 | 23 | .centered-title { 24 | text-align: center; 25 | margin-top: 12px; 26 | font-size: 36px; 27 | font-weight: 500; 28 | color: white; 29 | width: 100%; 30 | display: flex; 31 | justify-content: center; 32 | } 33 | 34 | .centered-description { 35 | text-align: center; 36 | margin-bottom: 16px; 37 | font-size: 16px; 38 | color: rgb(228, 228, 228); 39 | width: 100%; 40 | display: flex; 41 | justify-content: center; 42 | } 43 | 44 | .logo { 45 | width: 120px; 46 | margin: auto; 47 | display: flex; 48 | } 49 | 50 | .ant-card-head { 51 | border-bottom: none; 52 | } 53 | } 54 | 55 | .or-divider { 56 | text-align: center; 57 | color: rgb(192, 192, 192); 58 | margin-top: -12px; 59 | margin-bottom: 12px; 60 | } 61 | 62 | .login-button { 63 | margin-bottom: 12px; 64 | } -------------------------------------------------------------------------------- /front-end/src/pages/Application/ApplicationLoader.js: -------------------------------------------------------------------------------- 1 | import { useDispatch } from "react-redux"; 2 | import React, { useEffect, useState } from "react"; 3 | import { useParams, useMatch } from "react-router-dom"; 4 | import { Skeleton } from "antd"; 5 | import { getApplication } from "../../api/applications.ts"; 6 | import { loadShareLinkApp } from "../../api/sharedLink.ts"; 7 | import ApplicationPage from "./ApplicationPage.js"; 8 | 9 | function ApplicationLoader() { 10 | const { id } = useParams(); 11 | const dispatch = useDispatch(); 12 | const [isLoading, setIsLoading] = useState(true); 13 | 14 | const isSharedApp = useMatch("/shared/apps/:id"); 15 | 16 | useEffect(() => { 17 | const loadApplicationData = isSharedApp ? loadShareLinkApp : getApplication; 18 | 19 | loadApplicationData(id) 20 | .then((res) => { 21 | const loadedApplicationData = res; 22 | dispatch({ 23 | type: "LOAD_APPLICATION_DATA", 24 | payload: loadedApplicationData.application, 25 | }); 26 | 27 | setIsLoading(false); 28 | }) 29 | .catch((error) => { 30 | console.error(error); 31 | setIsLoading(false); 32 | }); 33 | }, [id, dispatch, isSharedApp]); 34 | 35 | if (isLoading) { 36 | return ( 37 |
38 | 39 |
40 | ); 41 | } 42 | 43 | return ; 44 | } 45 | 46 | export default ApplicationLoader; 47 | -------------------------------------------------------------------------------- /front-end/src/pages/Application/ApplicationPage.less: -------------------------------------------------------------------------------- 1 | .myLayout { 2 | min-height: 100vh; 3 | 4 | .ant-layout { 5 | background-color: white; 6 | } 7 | 8 | .applicationContent { 9 | width: 50%; 10 | margin-left: 180px; 11 | margin-top: 64px; 12 | margin-bottom: 64px; 13 | 14 | .customCard { 15 | padding: 2px 12px; 16 | margin-left: 12px; 17 | } 18 | } 19 | } 20 | 21 | .component-wrapper { 22 | padding-top: 12px; 23 | } 24 | 25 | .customCard { 26 | margin: auto; 27 | padding: 12px; 28 | background: #ffffff; 29 | } 30 | 31 | .delete-icon { 32 | color: grey; 33 | } 34 | 35 | .delete-icon:hover { 36 | color: red; 37 | } -------------------------------------------------------------------------------- /front-end/src/pages/Data/DataPage.less: -------------------------------------------------------------------------------- 1 | .actionButton { 2 | padding: 0; 3 | border-color: transparent; 4 | box-shadow: none; 5 | } 6 | 7 | .mainContent { 8 | width: 90%; 9 | margin: 20px auto; 10 | padding: 8px; 11 | } 12 | 13 | .headerRow { 14 | width: 100%; 15 | margin-bottom: 16px; 16 | margin-top: 64px; 17 | justify-content: space-between; 18 | } 19 | 20 | .rightAligned { 21 | display: flex; 22 | justify-content: flex-end; 23 | width: 100%; 24 | } 25 | 26 | .searchInput { 27 | width: 50%; 28 | margin-right: 16px; 29 | } 30 | 31 | .buttonContainer { 32 | display: flex; 33 | gap: 8px; 34 | } 35 | 36 | .buttonGroup { 37 | display: flex; 38 | align-items: center; 39 | justify-content: flex-start; 40 | } 41 | 42 | .selectableTab { 43 | margin-right: 8px; 44 | padding: 4px 14px 8px 14px; 45 | font-size: 14px; 46 | border-radius: 16px; 47 | text-decoration: none; 48 | color: #777; 49 | font-weight: 500; 50 | align-self: center; 51 | 52 | &:hover, 53 | &.selected { 54 | background-color: #f0f0f0; 55 | color: #333 !important; 56 | cursor: pointer; 57 | } 58 | } 59 | 60 | .selectedTab { 61 | margin-right: 8px; 62 | padding: 4px 14px 6px 14px; 63 | font-size: 14px; 64 | font-weight: 500; 65 | border-radius: 16px; 66 | text-decoration: none; 67 | color: #1677ff; 68 | background-color: #e6f4ff; 69 | align-self: center; 70 | } -------------------------------------------------------------------------------- /front-end/src/pages/Data/EmbeddingsPage.less: -------------------------------------------------------------------------------- 1 | .actionButton { 2 | padding: 0; 3 | border-color: transparent; 4 | box-shadow: none; 5 | } 6 | 7 | .mainContent { 8 | width: 90%; 9 | margin: 20px auto; 10 | padding: 8px; 11 | } 12 | 13 | .headerRow { 14 | width: 100%; 15 | margin-bottom: 16px; 16 | margin-top: 64px; 17 | justify-content: space-between; 18 | } 19 | 20 | .rightAligned { 21 | display: flex; 22 | justify-content: flex-end; 23 | width: 100%; 24 | } 25 | 26 | .searchInput { 27 | width: 50%; 28 | margin-right: 16px; 29 | } 30 | 31 | .buttonContainer { 32 | display: flex; 33 | gap: 8px; 34 | } 35 | 36 | .buttonGroup { 37 | display: flex; 38 | align-items: center; 39 | justify-content: flex-start; 40 | } 41 | 42 | .selectableTab { 43 | margin-right: 8px; 44 | padding: 4px 14px 8px 14px; 45 | font-size: 14px; 46 | border-radius: 16px; 47 | text-decoration: none; 48 | color: #777; 49 | font-weight: 500; 50 | align-self: center; 51 | 52 | &:hover, 53 | &.selected { 54 | background-color: #f0f0f0; 55 | color: #333 !important; 56 | cursor: pointer; 57 | } 58 | } 59 | 60 | .selectedTab { 61 | margin-right: 8px; 62 | padding: 4px 14px 8px 14px; 63 | font-size: 14px; 64 | font-weight: 500; 65 | border-radius: 16px; 66 | text-decoration: none; 67 | color: #1677ff; 68 | background-color: #e6f4ff; 69 | align-self: center; 70 | } -------------------------------------------------------------------------------- /front-end/src/pages/Home/HomePage.less: -------------------------------------------------------------------------------- 1 | .app-page { 2 | background-color: #f7f7f7; 3 | min-height: calc(100vh - 60px); 4 | } 5 | 6 | .app-card-container { 7 | column-count: 2; 8 | width: 720px; 9 | column-gap: 16px; 10 | margin: 0 auto; 11 | margin-top: 60px; 12 | padding: 20px; 13 | position: relative; 14 | overflow: auto; 15 | background-color: #f7f7f7; 16 | 17 | @media (min-width: 1260px) { 18 | width: 1080px; 19 | column-count: 3; 20 | } 21 | 22 | @media (min-width: 1670px) { 23 | column-count: 4; 24 | width: 1440px; 25 | } 26 | } 27 | 28 | .app-card-link { 29 | text-decoration: none; 30 | color: inherit; 31 | } 32 | 33 | .app-card { 34 | display: inline-block; 35 | width: 320px; 36 | margin: 16px 0; 37 | padding: 0; 38 | break-inside: avoid; 39 | 40 | .ant-card-body { 41 | padding: 16px; 42 | width: 100%; 43 | } 44 | 45 | .built-by-container { 46 | display: flex; 47 | flex-direction: column; 48 | 49 | .built-by { 50 | display: flex; 51 | align-items: center; 52 | color: rgb(163, 163, 163); 53 | 54 | .ant-dropdown-link.avatar { 55 | margin-right: 8px; 56 | height: 20px; 57 | width: 20px; 58 | font-size: 14px; 59 | } 60 | } 61 | } 62 | 63 | .card-divider { 64 | border-color: rgb(245, 245, 245); 65 | margin: 12px 0px; 66 | } 67 | 68 | .app-card-appname { 69 | display: flex; 70 | flex-direction: column; 71 | align-items: flex-start; 72 | font-size: 16px; 73 | margin-bottom: 4px; 74 | overflow: hidden; 75 | text-overflow: ellipsis; 76 | 77 | &-text { 78 | font-weight: 500; 79 | } 80 | } 81 | 82 | .app-card-description { 83 | font-size: 12px; 84 | color: rgb(163, 163, 163); 85 | } 86 | 87 | .app-card-tags { 88 | display: flex; 89 | flex-wrap: wrap; 90 | margin: 8px -4px 12px; 91 | 92 | .ant-tag { 93 | margin: 4px; 94 | padding: 2px 8px; 95 | font-weight: 500; 96 | border: none; 97 | background-color: rgb(245, 245, 245); 98 | } 99 | } 100 | 101 | .app-card-username { 102 | display: flex; 103 | justify-content: space-between; 104 | align-items: center; 105 | 106 | .app-card-createdat { 107 | display: flex; 108 | align-items: center; 109 | font-size: 12px; 110 | margin-bottom: 0; 111 | color: rgb(163, 163, 163); 112 | 113 | .app-card-createdat-icon { 114 | margin-right: 4px; 115 | } 116 | } 117 | 118 | .open-button { 119 | height: 24px; 120 | width: 60px; 121 | font-size: 12px; 122 | line-height: 22px; 123 | padding: 0; 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /front-end/src/pages/Jobs/JobDetailPage.less: -------------------------------------------------------------------------------- 1 | .loadingContainer { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | justify-content: center; 6 | height: 100vh; 7 | } 8 | 9 | .loadingImage { 10 | width: 250px; 11 | margin-bottom: 20px; 12 | } 13 | 14 | .loadingText { 15 | margin-top: 16px; 16 | font-size: 24px; 17 | background: linear-gradient(to right, #9bbaff, #3576fd); 18 | background-clip: text; 19 | -webkit-background-clip: text; 20 | color: transparent; 21 | } 22 | 23 | .subText { 24 | margin-top: 10px; 25 | font-size: 16px; 26 | color: #777; 27 | } 28 | 29 | .pageContainer { 30 | width: 90%; 31 | margin: 20px auto; 32 | padding: 8px; 33 | } 34 | 35 | .headerContainer { 36 | margin-top: 64px; 37 | margin-bottom: 16px; 38 | display: flex; 39 | justify-content: space-between; 40 | align-items: center; 41 | } 42 | 43 | .jobDetailsHeader { 44 | margin: 0; 45 | line-height: inherit; 46 | } 47 | 48 | .downloadButton { 49 | align-self: center; 50 | } 51 | 52 | .detailsContainer { 53 | display: flex; 54 | justify-content: space-between; 55 | } 56 | 57 | .innerDetailsContainer { 58 | display: flex; 59 | justify-content: space-between; 60 | width: 50%; 61 | } 62 | 63 | .card { 64 | width: 30%; 65 | height: 100%; 66 | 67 | .ant-card-head { 68 | padding: 0 12px; 69 | font-size: 14px; 70 | min-height: 36px; 71 | } 72 | 73 | .ant-card-body { 74 | display: flex; 75 | align-items: center; 76 | padding: 0 12px; 77 | min-height: 72px; 78 | } 79 | } 80 | 81 | .cardText { 82 | margin: 0; 83 | font-size: 24px; 84 | } 85 | 86 | .progressCard { 87 | width: 50%; 88 | height: 80%; 89 | margin-left: 36px; 90 | 91 | .ant-card-head { 92 | padding: 0 12px; 93 | font-size: 14px; 94 | min-height: 36px; 95 | } 96 | 97 | .ant-card-body { 98 | display: flex; 99 | align-items: center; 100 | padding: 0 12px; 101 | min-height: 72px; 102 | } 103 | 104 | .progressContainer { 105 | width: 100%; 106 | 107 | .progressStyle { 108 | margin-bottom: 0; 109 | width: 95%; 110 | } 111 | 112 | .progressText { 113 | display: flex; 114 | justify-content: space-between; 115 | margin: 0px; 116 | } 117 | } 118 | } 119 | 120 | .greyText { 121 | color: grey; 122 | } 123 | 124 | .tableStyle { 125 | margin-top: 20px; 126 | } -------------------------------------------------------------------------------- /front-end/src/pages/Jobs/JobsPage.less: -------------------------------------------------------------------------------- 1 | .custom-progress { 2 | margin-bottom: 0; 3 | width: 90%; 4 | display: flex; 5 | align-items: center; 6 | justify-content: flex-start; 7 | 8 | .ant-progress-outer { 9 | display: inline-flex; 10 | align-self: center; 11 | margin-top: 2px; 12 | } 13 | 14 | .ant-progress-text { 15 | align-self: center; 16 | } 17 | } 18 | 19 | .actionButton { 20 | padding: 0; 21 | border-color: transparent; 22 | box-shadow: none; 23 | } -------------------------------------------------------------------------------- /front-end/src/pages/Playground/PlaygroundGenerationPage.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Layout, Input, Button, message, Progress } from "antd"; 3 | import { autoGenerateApplication } from "../../api/applications.ts"; 4 | import MainHeader from "../../components/MainHeader/MainHeader"; 5 | import { useNavigate } from "react-router-dom"; 6 | import "./PlaygroundGenerationPage.less"; 7 | 8 | const { Content } = Layout; 9 | const { TextArea } = Input; 10 | 11 | const PlaygroundGenerationPage = () => { 12 | const [instruction, setInstruction] = useState(""); 13 | const [loading, setLoading] = useState(false); 14 | const [percent, setPercent] = useState(0); 15 | 16 | const navigate = useNavigate(); 17 | 18 | const handleInputChange = (e) => { 19 | if (e.target.value.length <= 1000) { 20 | setInstruction(e.target.value); 21 | } else { 22 | message.error("Max word count exceeded!"); 23 | } 24 | }; 25 | 26 | useEffect(() => { 27 | let timer; 28 | if (loading && percent < 99) { 29 | timer = setInterval(() => { 30 | setPercent((prev) => Math.min(prev + 1, 99)); 31 | }, 600); 32 | } else if (!loading && percent === 99) { 33 | setPercent(100); 34 | } 35 | return () => clearInterval(timer); 36 | }, [loading, percent]); 37 | 38 | const handleGenerateClick = async () => { 39 | if (instruction.trim().length === 0) { 40 | message.error("Please provide valid instructions."); 41 | return; 42 | } 43 | setLoading(true); 44 | try { 45 | message.success("Your application is being created."); 46 | const response = await autoGenerateApplication(instruction); 47 | if (response && response.application) { 48 | message.success("Application generated successfully!"); 49 | setLoading(false); 50 | setPercent(0); 51 | const appId = response.application.appId; 52 | navigate(`/playground/${appId}`); 53 | } else { 54 | throw new Error("No data returned"); 55 | } 56 | } catch (error) { 57 | message.error("Failed to generate application."); 58 | setLoading(false); 59 | setPercent(0); 60 | } 61 | }; 62 | 63 | return ( 64 | <> 65 | 66 |
67 | 68 |

Build with AI

69 |

70 | Tell us what tasks you'd like AI to help with. The more you share, 71 | the better we can tailor the app to your needs. After you hit 72 | submit, give us a minute to create your custom app. We'll take you 73 | right to your new app once it's ready! 74 |

75 |