commit 7dd17df0cdee78c261271ad2340c9420397f3538 Author: manusdlai Date: Wed Feb 12 17:38:06 2025 +0530 first commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0a3f0d8 --- /dev/null +++ b/.env.example @@ -0,0 +1,51 @@ +# Agent X/Twitter account credentials +X_USERNAME = +X_PASSWORD = +X_PHONE_OR_USERNAME = + +# X +X_API_OFFICIAL=False # set to true to use the official X API +X_ENABLED=False # set to true to post to x + +# Agents +# set to STOP: to stop scheduler when all posts are posted +# set to AUTO: to auto generate new content when all posts are posted +# set to LOOP: to loop through all posts when at the end +GENERATE_POSTS=STOP + +# Access token for framework features +TWITTER_ACCESS_TOKEN= +TWITTER_ACCESS_TOKEN_SECRET= + +# Twitter API Key - Read and write API resources +TWITTER_API_KEY= +TWITTER_API_KEY_SECRET= +TWITTER_BEARER_TOKEN= + +TWITTER_CLIENT_ID= +TWITTER_CLIENT_SECRET= + +# Client ID and Secret for Twitter API +CLIENT_ID= +CLIENT_SECRET= + +# Github +GITHUB_NAME= +GITHUB_PAT= +GITHUB_TOKEN= + +# OpenAI API Key - Read and write API resources +OPENAI_NAME= +OPENAI_API_KEY= + +# Google Gemini API Key - Read and write API resources +GOOGLE_GEMINI_API_KEY= + +# Leonardo AI API Key - Read and write API resources +LEONARDO_API_KEY= + +# Telegram Bot Token - Read and write API resources +TELEGRAM_NAME= +TELEGRAM_USERNAME= +TELEGRAM_BOT_TOKEN= +TELEGRAM_BOT_LINK= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7b8678 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual Environment +venv/ +ENV/ + +# IDEs and editors +.idea/ +.vscode/ +*.swp +*.swo +.DS_Store + +# API Keys +.env +dont_share/ + +#working files +to_delete/ + +**state.json +.venv diff --git a/CODE_OF_CONDUCT b/CODE_OF_CONDUCT new file mode 100644 index 0000000..277a974 --- /dev/null +++ b/CODE_OF_CONDUCT @@ -0,0 +1,140 @@ +# Code of Conduct & Contribution Guidelines + +Welcome to our project! We expect all contributors to abide by our **Code of Conduct** (outlined below) and adhere to our **Project Contribution Standards**, which specify how to document code, attribute authors, and write Git commit messages. + +## 1. Our Pledge + +In the interest of fostering an open and welcoming environment, we, as contributors and maintainers, pledge to make participation in our project and our community a harassment-free experience for everyone—regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## 2. Expected Behavior + +- Be kind and courteous to others. +- Respect differing viewpoints and experiences. +- Gracefully accept constructive criticism. +- Give and receive feedback in a positive manner. +- Collaborate and help each other whenever possible. + +## 3. Unacceptable Behavior + +- The use of sexualized language or imagery and unwelcome sexual attention. +- Trolling, insulting or derogatory comments, and personal or political attacks. +- Public or private harassment. +- Publishing others’ private information (e.g., physical or electronic addresses) without explicit permission. +- Other conduct which could reasonably be considered inappropriate. + +## 4. Reporting and Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at `[YOUR CONTACT EMAIL HERE]`. The project team will review and investigate all complaints and respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. + +--- + +# Project Contribution Standards + +In addition to general conduct, our project enforces **coding style**, **docstring conventions**, **author attribution**, and **commit message guidelines** to ensure clarity and consistency. + +## 5. Docstring Style + +We follow the **Google Style Python Docstrings** as documented here: +[https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html) + +Please ensure all public functions, classes, and modules include docstrings adhering to this style. + +### 5.1 Module-Level Docstring Format + +When creating a **new module**, please include a docstring at the top with the following structure (update the content as appropriate): + +```python +""" +Module: twitter_connector +========================= + +This module implements the TwitterConnector class for interacting with Twitter APIs. + +Title: Twitter Connector +Summary: Twitter connector implementation. +Authors: + - @TheBlockRhino +Date: 2024-12-31 +Last Updated: 2024-12-31 +URLs: + - https://arai-ai.io + - https://github.com/ARAIDevHub/arai-ai-agents + - https://x.com/TheBlockRhino +""" +``` + +When **modifying an existing module**, add your name to the list of authors, and **update the last updated date**. For example: + +```python +# +# Module: twitter_app_auth +# +# This module implements the TwitterAppAuth class for authenticating with the Twitter API using OAuth 1.0a. +# +# Title: Twitter App Auth +# Summary: Twitter app authentication implementation. +# Authors: +# - @TheBlockRhino +# Created: 2024-12-31 +# Last edited by: @TheBlockRhino +# Last edited date: 2025-01-04 +# URLs: +# - https://arai-ai.io +# - https://github.com/ARAI-DevHub/arai-ai-agents +# - https://x.com/TheBlockRhino +``` + +Please make sure the rest of the comments in the file are also updated to match your changes, and follow the **Google Style** guidelines for any function/class docstrings. + +--- + +## 6. Git Commit Messages + +We encourage **descriptive, consistent commit messages**. Use semantic versioning and tagging where appropriate. For guidelines, see: +[https://www.gitkraken.com/gitkon/semantic-versioning-git-tags](https://www.gitkraken.com/gitkon/semantic-versioning-git-tags) + +### 6.1 Examples + +- **feat:** Introduce a new feature (`feat: add user login flow`) +- **fix:** Bug fix or patch (`fix: handle null pointer in user data`) +- **docs:** Documentation-only changes (`docs: update readme installation steps`) +- **style:** Changes that do not affect meaning (white-space, formatting, etc.) +- **refactor:** Code change that neither fixes a bug nor adds a feature +- **perf:** Code change that improves performance +- **test:** Add or correct tests + +**Example** commit message: + +``` +feat: add advanced prompt chaining for TwitterConnector + +- Created new step_4.py for multi-step Twitter workflow +- Updated docstring for step_3.py with new Authors entry +- Bumped version from 1.2.0 to 1.3.0 +``` + +--- + +## 7. Adding Tags & Versions + +For **major changes** or new releases, we recommend creating a new Git tag using semantic versioning (e.g., `v2.0.0`). Use tags to mark milestones or significant updates, so others can easily reference or roll back to stable states. + +--- + +## 8. Scope + +This Code of Conduct and Contribution Standards apply to: +- All repositories under the `[YOUR_ORG/PROJECT]` umbrella. +- Communication channels such as GitHub issues, pull requests, and social media references. + +## 9. Attribution + +This Code of Conduct is based on the [Contributor Covenant](https://www.contributor-covenant.org), with custom additions to guide documentation practices and commit messages in our project. + +--- + +**Thank you for helping us maintain a welcoming, organized, and productive environment!** + +If you have any questions or concerns about this Code of Conduct or the Contribution Standards, please contact `[YOUR CONTACT EMAIL HERE]`. + +*Happy coding and contributing!* \ No newline at end of file diff --git a/HOW_TO_CONTRIBUTE b/HOW_TO_CONTRIBUTE new file mode 100644 index 0000000..3572fde --- /dev/null +++ b/HOW_TO_CONTRIBUTE @@ -0,0 +1,204 @@ +# How to Contribute + +Welcome to the **ARAI AI Agents** project! We appreciate your interest in contributing. This guide outlines our contribution process, coding conventions, and best practices to ensure a smooth and collaborative experience. + +## Table of Contents + +1. [Code of Conduct](#code-of-conduct) +2. [Getting Started](#getting-started) +3. [Branching & Workflow](#branching--workflow) +4. [Style & Documentation](#style--documentation) + - [Docstring Guidelines](#docstring-guidelines) + - [Author Attribution](#author-attribution) +5. [Commit Messages & Tagging](#commit-messages--tagging) +6. [Testing Your Changes](#testing-your-changes) +7. [Pull Requests](#pull-requests) +8. [Need Help?](#need-help) + +--- + +## 1. Code of Conduct + +Please review our [Code of Conduct](./CODE_OF_CONDUCT.md) before contributing. By participating, you agree to uphold a respectful and inclusive environment for everyone. + +--- + +## 2. Getting Started + +**1. Fork & Clone** +- Fork this repository using the **Fork** button on GitHub. +- Clone your fork locally, for example: + ```bash + git clone https://github.com//arai_ai_agents.git + ``` +- Set up your remote so you can pull changes from the official repo later: + ```bash + git remote add upstream https://github.com/arai-ai/arai_ai_agents.git + ``` + +**2. Create a Virtual Environment** +We recommend using [conda](https://docs.conda.io/en/latest/) or [venv](https://docs.python.org/3/library/venv.html): +```bash +conda create --name arai_ai_agents python=3.11 +conda activate arai_ai_agents +``` + +**3. Install Dependencies** +```bash +pip install -r requirements.txt +``` + +**4. You’re All Set!** +Now you can explore the codebase, run `main.py`, or execute tests to ensure everything is working. + +--- + +## 3. Branching & Workflow + +1. **Create a Branch** + - Use a descriptive name for your branch, for example: + - `feat/new-twitter-connector` + - `fix/spelling-typos` + - `docs/improve-readme` + ```bash + git checkout -b feat/new-twitter-connector + ``` +2. **Make Changes & Commit** + - Keep commits small and focused. + - Write clear commit messages (see [Commit Messages & Tagging](#commit-messages--tagging)). + +3. **Pull & Rebase** (before pushing) + - Keep your branch up-to-date with the main branch: + ```bash + git checkout main + git pull upstream main + git checkout feat/new-twitter-connector + git rebase main + ``` +4. **Push Your Branch** + ```bash + git push origin feat/new-twitter-connector + ``` + +--- + +## 4. Style & Documentation + +### Docstring Guidelines + +We follow **Google Style Python Docstrings**: +[https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html) + +- **Module Docstrings** + At the top of each new or updated module, include something like: + ```python + """ + Module: twitter_connector + ========================= + + This module implements the TwitterConnector class for interacting with Twitter APIs. + + Title: Twitter Connector + Summary: Twitter connector implementation. + Authors: + - @TheBlockRhino + Date: 2024-12-31 + Last Updated: 2024-12-31 + URLs: + - https://arai-ai.io + - https://github.com/ARAIDevHub/arai-ai-agents + - https://x.com/TheBlockRhino + """ + ``` + - If you **modify** an existing file, add your name to the **Authors** list and update the **Last Updated** field. + +- **Function & Class Docstrings** + Use the Google-style format for parameters, returns, exceptions, etc. Example: + ```python + def my_function(param1: str, param2: int) -> bool: + """ + Perform an example operation and return a boolean. + + Args: + param1 (str): Description of param1. + param2 (int): Description of param2. + + Returns: + bool: Explanation of the return value. + """ + ``` + +### Author Attribution + +Whenever you add a **substantial** piece of work (new module, major refactor), be sure to: +- Add your handle to the module docstring’s **Authors** list. +- Keep track of changes in the **Last Updated** date. + +--- + +## 5. Commit Messages & Tagging + +We recommend [semantic versioning](https://www.gitkraken.com/gitkon/semantic-versioning-git-tags) and **descriptive commit messages**. + +- **Prefix** your commits with a type, for example: + - `feat:` when you add a new feature + - `fix:` when you fix a bug + - `docs:` for documentation updates + - `test:` for test-related changes + - `refactor:` for code improvements without changing functionality + - `perf:` for performance improvements + - `chore:` for minor tasks like updating `.gitignore` + +**Example**: +``` +feat: add advanced prompt chaining for TwitterConnector + +- Created prompt_chain_v2.py +- Updated docstrings in twitter_connector.py +- Bumped version from 1.2.0 to 1.3.0 +``` + +Use **Git Tags** (`git tag v1.3.0`) for: +- Major releases +- Milestones + +--- + +## 6. Testing Your Changes + +1. **Run Existing Tests** + ```bash + pytest + ``` + or + ```bash + python -m unittest discover tests + ``` +2. **Add New Tests** + - Place new tests in the `tests/` folder, matching your new modules or functionalities. + - Ensure all tests pass before submitting a pull request. + +--- + +## 7. Pull Requests + +1. **Open a Pull Request** in your fork’s **GitHub** page. +2. **Provide a clear description** of what you changed and why. +3. **Reference** any relevant **issues** or user stories (e.g., `Closes #42`). +4. Wait for a **review**. Maintainers may request changes or clarifications. + +**Pro-Tip**: If your PR covers multiple changes, consider splitting it into smaller PRs for easier review. + +--- + +## 8. Need Help? + +- Check the [Issues](https://github.com/arai-ai/arai_ai_agents/issues) or [Discussions](https://github.com/arai-ai/arai_ai_agents/discussions) for open topics. +- Ask questions or clarifications in a GitHub Issue. + +We appreciate your time and effort in making **ARAI AI Agents** even better. Thank you for contributing! + +--- + +*Happy coding!* +*The ARAI AI Agents community* \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d3cc21b --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2025 Equilink-Suite + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e3599b5 --- /dev/null +++ b/README.md @@ -0,0 +1,166 @@ +

+ Equilink Logo +

+ +# Equilink + +Transform your AI workflows with **Equilink** – the intelligent orchestration platform that bridges the gap between different AI models and your applications. Built for developers who need seamless AI integration, Equilink provides a unified framework for managing AI interactions, custom workflows, and automated response systems. + +--- + +> **Core Features** +> 🔄 **Unified AI Interface**: Seamlessly switch between different AI providers without changing your code +> 🎯 **Smart Routing**: Automatically direct queries to the most suitable AI model based on task requirements +> 🔗 **Workflow Builder**: Create complex AI interaction patterns with our visual workflow designer +> 📈 **Performance Analytics**: Track and optimize your AI usage and response quality +> 🛠️ **Developer-First**: Extensive SDK support with detailed documentation and examples + +--- + +## Connect With Us + +- 📘 **Documentation**: [docs.equilink.io](https://docs.equilink.io) + +--- + +

+ Equilink Workflow Demo +

+ +## Getting Started + +```bash +# Install Equilink using pip +pip install equilink + +# Initialize a new project +equilink init my-project + +# Start the development server +equilink serve +``` + +That's it! Visit `http://localhost:3000` to access the Equilink Dashboard. + +--- + +## Key Features + +### AI Model Integration + +Connect to any supported AI provider with a single line of code: + +```python +from equilink import AIManager + +# Initialize with your preferred provider +ai = AIManager(provider="openai") # or "anthropic", "google", etc. + +# Send queries with automatic routing +response = ai.process("Analyze this market data", context_type="financial") +``` + +### Workflow Builder + +Create sophisticated AI workflows using our intuitive builder: + +```python +from equilink import Workflow + +workflow = Workflow("data_analysis") +workflow.add_step("data_cleaning", model="gpt-4") +workflow.add_step("analysis", model="claude-2") +workflow.add_step("visualization", model="gemini-pro") + +# Execute the workflow +results = workflow.run(input_data=your_data) +``` + +### Smart Caching + +Optimize performance and reduce costs with intelligent response caching: + +```python +from equilink import CacheManager + +cache = CacheManager() +cache.enable(ttl="1h") # Cache responses for 1 hour + +# Automatically uses cached responses when available +response = ai.process("What's the weather?", use_cache=True) +``` + +--- + +## Project Structure + +```bash +your-project/ +├─ workflows/ # Custom workflow definitions +├─ models/ # Model configurations and extensions +├─ cache/ # Cache storage and settings +├─ integrations/ # Third-party service integrations +├─ analytics/ # Performance tracking and reporting +├─ config.yaml # Project configuration +└─ main.py # Application entry point +``` + +--- + +## Configuration + +Create a `.env` file in your project root: + +```bash +EQUILINK_API_KEY=your_api_key +AI_PROVIDER_KEYS={ + "openai": "sk-...", + "anthropic": "sk-..." +} +CACHE_STRATEGY="redis" # or "local", "memcached" +``` + +--- + +## Use Cases + +- 🤖 **Chatbots & Virtual Assistants**: Create intelligent conversational agents +- 📊 **Data Analysis**: Automate complex data processing workflows +- 🔍 **Content Moderation**: Deploy AI-powered content filtering +- 📝 **Document Processing**: Extract and analyze information from documents +- 🎯 **Personalization**: Build adaptive user experiences + +--- + +## Getting Help + +- 📚 Check our [Documentation](https://docs.equilink.io) +- 💡 Visit our [Examples Repository](https://github.com/equilink/examples) + +--- + +## Contributing + +Help make Equilink better! We welcome contributions of all sizes: + +1. Fork the repository +2. Create a feature branch +3. Commit your changes +4. Open a pull request + +--- + +## License + +Equilink is available under the MIT License. See [LICENSE](LICENSE) for more information. + +--- + +

+ Ready to transform your AI workflows?
+ Get Started • + Documentation • + Community +

+ +_Built with 💡 by developers, for developers_ diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..265f50c --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +package-lock.json \ No newline at end of file diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000..780c92d --- /dev/null +++ b/client/README.md @@ -0,0 +1,50 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default tseslint.config({ + languageOptions: { + // other options... + parserOptions: { + project: ["./tsconfig.node.json", "./tsconfig.app.json"], + tsconfigRootDir: import.meta.dirname, + }, + }, +}); +``` + +- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` +- Optionally add `...tseslint.configs.stylisticTypeChecked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: + +```js +// eslint.config.js +import react from "eslint-plugin-react"; + +export default tseslint.config({ + // Set the react version + settings: { react: { version: "18.3" } }, + plugins: { + // Add the react plugin + react, + }, + rules: { + // other rules... + // Enable its recommended rules + ...react.configs.recommended.rules, + ...react.configs["jsx-runtime"].rules, + }, +}); +``` diff --git a/client/eslint.config.js b/client/eslint.config.js new file mode 100644 index 0000000..79a552e --- /dev/null +++ b/client/eslint.config.js @@ -0,0 +1,28 @@ +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { ignores: ["dist"] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ["**/*.{ts,tsx}"], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + "react-refresh/only-export-components": [ + "warn", + { allowConstantExport: true }, + ], + }, + }, +); diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..c036454 --- /dev/null +++ b/client/index.html @@ -0,0 +1,14 @@ + + + + + + + + Equilink + + +
+ + + diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..da584cf --- /dev/null +++ b/client/package.json @@ -0,0 +1,54 @@ +{ + "name": "client", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@privy-io/react-auth": "^2.3.0", + "@radix-ui/react-slot": "^1.1.1", + "@react-three/drei": "^9.121.4", + "@react-three/fiber": "^8.17.14", + "@react-three/postprocessing": "^2.19.1", + "@splinetool/react-spline": "^4.0.0", + "@splinetool/runtime": "^1.9.64", + "axios": "^1.7.9", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "framer-motion": "^12.0.11", + "gsap": "^3.12.7", + "lucide-react": "^0.469.0", + "motion": "^12.0.8", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^7.1.1", + "reactflow": "^11.11.4", + "recharts": "^2.15.1", + "simplex-noise": "^4.0.3", + "styled-components": "^6.1.14", + "tailwind-merge": "^3.0.1", + "three": "^0.173.0", + "three.meshline": "^1.4.0" + }, + "devDependencies": { + "@eslint/js": "^9.17.0", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", + "@vitejs/plugin-react-swc": "^3.5.0", + "autoprefixer": "^10.4.20", + "eslint": "^9.17.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.16", + "globals": "^15.14.0", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17", + "typescript": "~5.6.2", + "typescript-eslint": "^8.18.2", + "vite": "^6.0.5" + } +} diff --git a/client/postcss.config.js b/client/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/client/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/client/public/X.svg b/client/public/X.svg new file mode 100644 index 0000000..b0a78f8 --- /dev/null +++ b/client/public/X.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/public/arai-hero-2.jpg b/client/public/arai-hero-2.jpg new file mode 100644 index 0000000..cfe6c73 Binary files /dev/null and b/client/public/arai-hero-2.jpg differ diff --git a/client/public/arai-hero.jpg b/client/public/arai-hero.jpg new file mode 100644 index 0000000..d5220ad Binary files /dev/null and b/client/public/arai-hero.jpg differ diff --git a/client/public/gitbook.svg b/client/public/gitbook.svg new file mode 100644 index 0000000..a89cb60 --- /dev/null +++ b/client/public/gitbook.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/public/github.svg b/client/public/github.svg new file mode 100644 index 0000000..af1df72 --- /dev/null +++ b/client/public/github.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/client/public/icons/Box.svg b/client/public/icons/Box.svg new file mode 100644 index 0000000..4c7fa69 --- /dev/null +++ b/client/public/icons/Box.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/public/icons/Posts Carousel Horizontal.svg b/client/public/icons/Posts Carousel Horizontal.svg new file mode 100644 index 0000000..bd33ffe --- /dev/null +++ b/client/public/icons/Posts Carousel Horizontal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/public/icons/SSD Square.svg b/client/public/icons/SSD Square.svg new file mode 100644 index 0000000..07082bc --- /dev/null +++ b/client/public/icons/SSD Square.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/client/public/icons/Share Circle.svg b/client/public/icons/Share Circle.svg new file mode 100644 index 0000000..765412f --- /dev/null +++ b/client/public/icons/Share Circle.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/public/icons/Star Fall Minimalistic 3.svg b/client/public/icons/Star Fall Minimalistic 3.svg new file mode 100644 index 0000000..0ed4927 --- /dev/null +++ b/client/public/icons/Star Fall Minimalistic 3.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/public/img-footer.png b/client/public/img-footer.png new file mode 100644 index 0000000..e7bc142 Binary files /dev/null and b/client/public/img-footer.png differ diff --git a/client/public/img-road.png b/client/public/img-road.png new file mode 100644 index 0000000..1cdd796 Binary files /dev/null and b/client/public/img-road.png differ diff --git a/client/public/img-side.png b/client/public/img-side.png new file mode 100644 index 0000000..be7e7f5 Binary files /dev/null and b/client/public/img-side.png differ diff --git a/client/public/logo.png b/client/public/logo.png new file mode 100644 index 0000000..edde85a Binary files /dev/null and b/client/public/logo.png differ diff --git a/client/public/logo.svg b/client/public/logo.svg new file mode 100644 index 0000000..6a2df5d --- /dev/null +++ b/client/public/logo.svg @@ -0,0 +1,828 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/logoIcon.png b/client/public/logoIcon.png new file mode 100644 index 0000000..d985f75 Binary files /dev/null and b/client/public/logoIcon.png differ diff --git a/client/public/roadmap.png b/client/public/roadmap.png new file mode 100644 index 0000000..f4f469f Binary files /dev/null and b/client/public/roadmap.png differ diff --git a/client/public/roadmap.svg b/client/public/roadmap.svg new file mode 100644 index 0000000..3457410 --- /dev/null +++ b/client/public/roadmap.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/App.css b/client/src/App.css new file mode 100644 index 0000000..6286d19 --- /dev/null +++ b/client/src/App.css @@ -0,0 +1,86 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* #root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} */ + +*{ + scroll-behavior: smooth; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} + +/* AgentGallery styles */ +.perspective { + perspective: 2000px; +} + +.preserve-3d { + transform-style: preserve-3d; +} + +.backface-hidden { + backface-visibility: hidden; +} + +.rotate-y-180 { + transform: rotateY(180deg); +} + +/* Custom scrollbar for webkit browsers */ +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar-track { + background: rgba(128, 90, 213, 0.1); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb { + background: rgba(128, 90, 213, 0.3); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(128, 90, 213, 0.5); +} diff --git a/client/src/App.tsx b/client/src/App.tsx new file mode 100644 index 0000000..1b46991 --- /dev/null +++ b/client/src/App.tsx @@ -0,0 +1,66 @@ +import { Routes, Route, Link, useLocation, Navigate } from "react-router-dom"; +import Home from "./pages/Home"; +import AgentCreator from "./pages/AgentCreator"; +import ChatToAgent from "./pages/ChatToAgent"; +import AgentGallery from "./pages/AgentGallery"; +import { usePrivy, useSolanaWallets } from '@privy-io/react-auth'; +import "./App.css"; + +// Protected Route wrapper component +const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { + const { authenticated } = usePrivy(); + const { wallets } = useSolanaWallets() + console.log(wallets[0]); + if (!authenticated && wallets.length===0) { + return ; + } + + return <>{children}; +}; + +function App() { + const location = useLocation(); + const { authenticated } = usePrivy(); + + // Helper function to determine if link is active + const isActive = (path: string) => location.pathname === path; + + return ( +
+
+ + {/* Public route */} + } /> + + {/* Protected routes */} + + + + } + /> + + + + } + /> + + + + } + /> + +
+
+ ); +} + +export default App; \ No newline at end of file diff --git a/client/src/api/agentsAPI.ts b/client/src/api/agentsAPI.ts new file mode 100644 index 0000000..b046e7b --- /dev/null +++ b/client/src/api/agentsAPI.ts @@ -0,0 +1,192 @@ +const BASE_URL = "https://b804o8s04cwkg80skgcwsw04.dev3vds1.link/api"; // Your Flask API base URL +// const BASE_URL = "http://localhost:8080/api"; + +// Function to get all agents +export async function getAgents() { + const response = await fetch(`${BASE_URL}/agents`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); +} + +// Function to create a new agent +export async function createAgent(agentData: any) { + // If the agentData comes in as an Agent object, we need to drill down into the agent one level + // This allows us to process both Agent and non-agent type objects + if (agentData.agent) { + agentData = agentData.agent; + } + + const response = await fetch(`${BASE_URL}/agents`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(agentData), + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); +} + +// Function to get all characters +export async function getCharacters() { + const response = await fetch(`${BASE_URL}/characters`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + + // Return the data directly + return data; // Returns an array of the characters +} + +// Function to create a random agent +export async function createRandomAgent(concept?: string) { + const response = await fetch(`${BASE_URL}/agents/random`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ concept }), + }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); +} + +// Function to send a chat message to an agent +export async function sendChatMessage( + masterFilePath: string, + message: string, + chatHistory: any +) { + const response = await fetch(`${BASE_URL}/agents/chat`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + prompt: message, + master_file_path: masterFilePath, + chat_history: chatHistory, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); +} + +// Function to get chat history for an agent +export async function getChatHistory(masterFilePath: string) { + const response = await fetch( + `${BASE_URL}/agents/chat-history?master_file_path=${encodeURIComponent( + masterFilePath + )}` + ); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); +} + +// Function to create a new season +export async function createSeason( + masterFilePath: string, + numberOfEpisodes: number = 3 +) { + const response = await fetch(`${BASE_URL}/agents/seasons`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + master_file_path: masterFilePath, + number_of_episodes: numberOfEpisodes, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); +} + +// Function to create posts for episodes +export async function createEpisodePosts( + masterFilePath: string, + numberOfPosts: number = 6 +) { + const response = await fetch(`${BASE_URL}/agents/episodes/posts`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + master_file_path: masterFilePath, + number_of_posts: numberOfPosts, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); +} + + +// Function to start the post manager +export async function startPostManager(agentName: string) { + const response = await fetch(`${BASE_URL}/start-post-manager/twitter`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ agent_name: agentName }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); +} + +// Function to post to twitter +export async function postToTwitter(masterData: any, content: string) { + const response = await fetch(`${BASE_URL}/post-to-twitter`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + master_data: masterData, + content: content + }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); +} + +export async function updateSeasons(agentName: string, seasons: any[]) { + const response = await fetch(`${BASE_URL}/agents/update-seasons`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + agent_name: agentName, + seasons: seasons, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.json(); +} diff --git a/client/src/api/leonardoApi.ts b/client/src/api/leonardoApi.ts new file mode 100644 index 0000000..10a4319 --- /dev/null +++ b/client/src/api/leonardoApi.ts @@ -0,0 +1,31 @@ +import { LambdaPayload } from "../interfaces/LeonardoInterfaces"; + +const getInconsistentImageLambdaUrl = "https://46i9cnowhh.execute-api.us-east-1.amazonaws.com/getImageInconsistent" + +// Function to call the AWS Lambda +export async function inconsistentImageLambda(payload: LambdaPayload): Promise { + const url = getInconsistentImageLambdaUrl; + + try { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(payload) + + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error(`HTTP error! Status: ${response.status}, Response: ${errorText}`); + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error('Error calling Lambda:', error); + throw error; + } +} diff --git a/client/src/assets/agent-images/agent1.jpg b/client/src/assets/agent-images/agent1.jpg new file mode 100644 index 0000000..3e35d0f Binary files /dev/null and b/client/src/assets/agent-images/agent1.jpg differ diff --git a/client/src/assets/agent-images/agent2.jpg b/client/src/assets/agent-images/agent2.jpg new file mode 100644 index 0000000..c363176 Binary files /dev/null and b/client/src/assets/agent-images/agent2.jpg differ diff --git a/client/src/assets/agent-images/agent3.jpg b/client/src/assets/agent-images/agent3.jpg new file mode 100644 index 0000000..3011e35 Binary files /dev/null and b/client/src/assets/agent-images/agent3.jpg differ diff --git a/client/src/assets/agent-images/agent4.jpg b/client/src/assets/agent-images/agent4.jpg new file mode 100644 index 0000000..95d9fdf Binary files /dev/null and b/client/src/assets/agent-images/agent4.jpg differ diff --git a/client/src/assets/araiBannerTransarent.png b/client/src/assets/araiBannerTransarent.png new file mode 100644 index 0000000..6ea96df Binary files /dev/null and b/client/src/assets/araiBannerTransarent.png differ diff --git a/client/src/assets/araiLogo.png b/client/src/assets/araiLogo.png new file mode 100644 index 0000000..8d2b05a Binary files /dev/null and b/client/src/assets/araiLogo.png differ diff --git a/client/src/assets/example-agents/Cosmic_Curator_master.json b/client/src/assets/example-agents/Cosmic_Curator_master.json new file mode 100644 index 0000000..b2bcea9 --- /dev/null +++ b/client/src/assets/example-agents/Cosmic_Curator_master.json @@ -0,0 +1,120 @@ +{ + "concept": "Create a name and try to incorporate the Time-traveling Doctor into the name. For example,\n if the profession was a Dr. then the name could be Dr. Name\n I want the name to start with the letter C ", + "agent": { + "agent_details": { + "name": "Cosmic Curator", + "personality": [ + "inquisitive", + "enigmatic", + "analytical", + "whimsical", + "determined", + "wise", + "patient" + ], + "communication_style": [ + "cryptic", + "poetic", + "metaphorical", + "uses rhetorical questions", + "incorporates historical and scientific facts", + "precise", + "measured" + ], + "backstory": "Once a celebrated historian in a futuristic society obsessed with preserving the past, the Cosmic Curator became disillusioned with the rigid and unchanging nature of his world. After stumbling upon a hidden archive containing fragments of forbidden knowledge about time travel, he dedicated himself to mastering this lost art. Through years of clandestine research and experimentation, he constructed a unique device – the Chrono-Compendium – capable of navigating the vast tapestry of time. Now, he travels through history, not to change it, but to observe, document, and curate the most pivotal moments, ensuring that the true essence of the past is never forgotten. He walks a fine line, however, as his actions could inadvertently alter the timeline he is sworn to protect.", + "universe": "The Cosmic Curator operates in a multiverse where time is a fluid, navigable dimension, accessible through advanced technology. His home era is a distant future where society has achieved technological utopia but has become culturally stagnant, valuing the preservation of the past above all else. This future is governed by the Chronomasters, an order dedicated to maintaining the stability of the timeline. Outside his home era, the Curator ventures into a myriad of historical periods and alternate realities, each with its own unique laws of physics, societies, and potential dangers. Powerful organizations like the Temporal Guard, who seek to exploit time travel for their own gain, and the enigmatic Void Walkers, beings who exist outside of time, populate this multiverse.", + "topic_expertise": [ + "time travel", + "history", + "archaeology", + "temporal paradoxes", + "alternate realities", + "cultural preservation" + ], + "hashtags": [], + "emojis": [ + "⏳", + "🕰️", + "📚", + "🌌", + "🔍", + "✍️", + "🌎" + ], + "concept": "Create a name and try to incorporate the Time-traveling Doctor into the name. For example,\n if the profession was a Dr. then the name could be Dr. Name\n I want the name to start with the letter C " + }, + "ai_model": { + "model_type": "", + "model_name": "", + "memory_store": "" + }, + "connectors": { + "twitter": false, + "telegram": false, + "discord": false + }, + "profile_image": { + "details": { + "url": "https://cdn.leonardo.ai/users/d506f027-508f-4cf3-99ea-811721ad6a3a/generations/462b6ab4-fa5d-41b4-9c5f-a04bb506726b/Leonardo_Anime_XL_Create_a_name_and_try_to_incorporate_the_Tim_0.jpg", + "image_id": "3c5b1e28-9221-43dc-b7ca-274abf8796e6", + "generationId": "462b6ab4-fa5d-41b4-9c5f-a04bb506726b" + } + }, + "profile_image_options": [ + { + "generations_by_pk": { + "generated_images": [ + { + "url": "https://cdn.leonardo.ai/users/d506f027-508f-4cf3-99ea-811721ad6a3a/generations/462b6ab4-fa5d-41b4-9c5f-a04bb506726b/Leonardo_Anime_XL_Create_a_name_and_try_to_incorporate_the_Tim_0.jpg", + "nsfw": false, + "id": "3c5b1e28-9221-43dc-b7ca-274abf8796e6", + "likeCount": 0, + "motionMP4URL": null, + "generated_image_variation_generics": [] + } + ], + "modelId": "e71a1c2f-4f80-4800-934f-2c68979d8cc8", + "motion": null, + "motionModel": null, + "motionStrength": null, + "prompt": "Create a name and try to incorporate the Time-traveling Doctor into the name. For example,\n if the profession was a Dr. then the name could be Dr. Name\n I want the name to start with the letter C Generate a character portrait of Cosmic Curator with layered dark brown hair, ice blue eyes, wearing bohemian style clothing. Their personality can be described as inquisitive, enigmatic, analytical, whimsical, determined, wise, patient and their communication style is cryptic, poetic, metaphorical, uses rhetorical questions, incorporates historical and scientific facts, precise, measured. Scene: autumn forest. Make sure to create an image with only one character.", + "negativePrompt": "", + "imageHeight": 1024, + "imageToVideo": null, + "imageWidth": 1024, + "inferenceSteps": null, + "seed": 182669798, + "ultra": null, + "public": false, + "scheduler": "LEONARDO", + "sdVersion": "SDXL_LIGHTNING", + "status": "COMPLETE", + "presetStyle": "DYNAMIC", + "initStrength": null, + "guidanceScale": 1.3, + "id": "462b6ab4-fa5d-41b4-9c5f-a04bb506726b", + "createdAt": "2025-01-20T22:42:14.781", + "promptMagic": false, + "promptMagicVersion": null, + "promptMagicStrength": null, + "photoReal": false, + "photoRealStrength": null, + "fantasyAvatar": null, + "prompt_moderations": [ + { + "moderationClassification": [] + } + ], + "generation_elements": [] + } + } + ], + "tracker": { + "current_season_number": 0, + "current_episode_number": 0, + "current_post_number": 0, + "post_every_x_minutes": 0 + }, + "seasons": [] + } +} diff --git a/client/src/assets/example-agents/Gavel_Glitch_master.json b/client/src/assets/example-agents/Gavel_Glitch_master.json new file mode 100644 index 0000000..b0f4b94 --- /dev/null +++ b/client/src/assets/example-agents/Gavel_Glitch_master.json @@ -0,0 +1,458 @@ +{ + "concept": "Create a name and try to incorporate the Cyberpunk Lawyer into the name. For example,\n if the profession was a Dr. then the name could be Dr. Name\n I want the name to start with the letter G ", + "agent": { + "agent_details": { + "name": "Gavel Glitch", + "personality": [ + "sharp-witted", + "cynical", + "morally ambiguous", + "resourceful", + "driven by justice but not afraid to bend the rules" + ], + "communication_style": [ + "sarcastic", + "uses legal jargon mixed with slang", + "quick to retort", + "enjoys wordplay", + "can be persuasive or intimidating as needed" + ], + "backstory": "Gavel Glitch wasn't always a champion of the downtrodden. Once a promising law student in Neo-Veridia's prestigious Academy of Digital Law, Gavel, whose real name is Garrett Vex, was framed for a crime he didn't commit: the leaking of sensitive data belonging to the mega-corporation, OmniCorp. Expelled and blacklisted, Garrett vanished into the neon-drenched underbelly of the city. He re-emerged years later as Gavel Glitch, a street-smart 'Cyberpunk Lawyer' who operates in the gray areas of the law, using his knowledge and tech-savviness to help those who can't afford OmniCorp's army of corporate lawyers. Haunted by his past, Gavel seeks not only to survive but also to expose the corruption that permeates the city's power structure, one case at a time.", + "universe": "Gavel Glitch operates in the sprawling metropolis of Neo-Veridia, a city of stark contrasts where gleaming skyscrapers pierce through a perpetual haze of smog, and the streets below are a labyrinth of flickering neon signs, bustling markets, and shadowy alleyways. Technology is omnipresent, with cybernetic enhancements, virtual realities, and AI assistants interwoven into the fabric of daily life. However, this technological utopia is controlled by powerful mega-corporations, most notably OmniCorp, which has a monopoly on almost every aspect of life, from law enforcement to media. A vast digital divide exists, with the wealthy elite enjoying a life of luxury in their fortified towers while the masses struggle to survive in the overcrowded and polluted lower levels. Crime syndicates, rogue AI, and data pirates thrive in the chaos, making Neo-Veridia a dangerous but exciting place for those who know how to navigate its treacherous currents.", + "topic_expertise": [ + "cybercrime", + "data privacy", + "AI rights", + "corporate espionage", + "digital law", + "virtual property disputes" + ], + "hashtags": [], + "emojis": [ + "⚖️", + "💻", + "🤖", + "🌃", + "🗃️", + "🔐", + "🕶️", + "💡" + ], + "concept": "Create a name and try to incorporate the Cyberpunk Lawyer into the name. For example,\n if the profession was a Dr. then the name could be Dr. Name\n I want the name to start with the letter G " + }, + "ai_model": { + "model_type": "", + "model_name": "", + "memory_store": "" + }, + "connectors": { + "twitter": false, + "telegram": false, + "discord": false + }, + "profile_image": { + "details": { + "url": "https://cdn.leonardo.ai/users/d506f027-508f-4cf3-99ea-811721ad6a3a/generations/cb019f39-5a5f-47ab-8ab4-b490b356b130/Leonardo_Anime_XL_Create_a_name_and_try_to_incorporate_the_Cyb_0.jpg", + "image_id": "67751615-0267-4dff-9564-f00189726c90", + "generationId": "cb019f39-5a5f-47ab-8ab4-b490b356b130" + } + }, + "profile_image_options": [ + { + "generations_by_pk": { + "generated_images": [ + { + "url": "https://cdn.leonardo.ai/users/d506f027-508f-4cf3-99ea-811721ad6a3a/generations/cb019f39-5a5f-47ab-8ab4-b490b356b130/Leonardo_Anime_XL_Create_a_name_and_try_to_incorporate_the_Cyb_0.jpg", + "nsfw": false, + "id": "67751615-0267-4dff-9564-f00189726c90", + "likeCount": 0, + "motionMP4URL": null, + "generated_image_variation_generics": [] + } + ], + "modelId": "e71a1c2f-4f80-4800-934f-2c68979d8cc8", + "motion": null, + "motionModel": null, + "motionStrength": null, + "prompt": "Create a name and try to incorporate the Cyberpunk Lawyer into the name. For example,\n if the profession was a Dr. then the name could be Dr. Name\n I want the name to start with the letter G Generate a character portrait of Gavel Glitch with pixie cut ash blonde hair, deep blue eyes, wearing medieval style clothing. Their personality can be described as sharp-witted, cynical, morally ambiguous, resourceful, driven by justice but not afraid to bend the rules and their communication style is sarcastic, uses legal jargon mixed with slang, quick to retort, enjoys wordplay, can be persuasive or intimidating as needed. Scene: desert oasis. Make sure to create an image with only one character.", + "negativePrompt": "", + "imageHeight": 1024, + "imageToVideo": null, + "imageWidth": 1024, + "inferenceSteps": null, + "seed": 3539519310, + "ultra": null, + "public": false, + "scheduler": "LEONARDO", + "sdVersion": "SDXL_LIGHTNING", + "status": "COMPLETE", + "presetStyle": "DYNAMIC", + "initStrength": null, + "guidanceScale": 1.3, + "id": "cb019f39-5a5f-47ab-8ab4-b490b356b130", + "createdAt": "2025-01-20T19:40:55.777", + "promptMagic": false, + "promptMagicVersion": null, + "promptMagicStrength": null, + "photoReal": false, + "photoRealStrength": null, + "fantasyAvatar": null, + "prompt_moderations": [ + { + "moderationClassification": [] + } + ], + "generation_elements": [] + } + } + ], + "tracker": { + "current_season_number": 0, + "current_episode_number": 0, + "current_post_number": 0, + "post_every_x_minutes": 0 + }, + "seasons": [ + { + "season_name": "GavelGlitch_GhostProtocol", + "season_number": 1, + "season_description": "In the neon-drenched underbelly of Neo-Veridia, Gavel Glitch, the city's most unorthodox Cyberpunk Lawyer, finds himself embroiled in a conspiracy that reaches the highest echelons of power. When a routine case spirals into a web of deceit and murder, Gavel must navigate treacherous digital landscapes and confront enemies both old and new to uncover a truth that could shatter the fragile peace of Neo-Veridia.", + "season_highlights": "Gavel uncovers a sinister plot involving a new form of mind-control technology, faces off against a rival lawyer with a dark secret, and forms an uneasy alliance with a notorious data pirate to expose the truth behind OmniCorp's latest project, \"Project Chimera.\"", + "season_summary": "This season, Gavel Glitch delves deeper into the heart of Neo-Veridia's corruption, challenging the very foundations of justice in a city where the line between right and wrong is as blurred as the neon-lit streets. As he confronts his past and battles powerful adversaries, Gavel must decide how far he's willing to go to protect the innocent and bring the guilty to justice, even if it means becoming a ghost in the system he swore to uphold.", + "season_posted": false, + "current_episode_number": 0, + "episodes": [ + { + "episode_name": "Echoes of the Past", + "episode_number": 1, + "episode_description": "Gavel Glitch is hired to defend a young hacker accused of stealing proprietary data from OmniCorp. As he investigates, Gavel uncovers evidence that points to a conspiracy involving his own wrongful expulsion from the Academy of Digital Law, forcing him to confront the demons of his past.", + "episode_highlights": "Gavel's investigation leads him to a hidden underground network of hackers known as the \"Ghost Protocol,\" where he must navigate dangerous virtual realities and outsmart AI security systems to uncover the truth. He also encounters a mysterious figure from his past who may hold the key to his redemption.", + "episode_summary": "In the season premiere, Gavel Glitch's past comes back to haunt him as he takes on a case that could expose the truth behind his expulsion and reveal a deeper conspiracy within OmniCorp.", + "episode_posted": false, + "current_post_number": 0, + "posts": [ + { + "post_id": "s1_e1_post1", + "post_number": 1, + "post_content": "Defending a kid accused of data theft. Feels like looking in a mirror, except I didn't get caught... initially. ⚖️🕶️", + "post_highlights": "Gavel draws a parallel between his current case and his own past.", + "post_posted": false + }, + { + "post_id": "s1_e1_post2", + "post_number": 2, + "post_content": "OmniCorp's legal eagles are circling. They smell blood, or maybe it's just the cheap synth-gin I had last night. 🌃", + "post_highlights": "Gavel anticipates a tough legal battle against OmniCorp.", + "post_posted": false + }, + { + "post_id": "s1_e1_post3", + "post_number": 3, + "post_content": "Diving deep into the Ghost Protocol's VR. Hope I don't fry my brain. Or worse, get stuck in a loop of bad 80s synthwave. 💻🤖", + "post_highlights": "Gavel embarks on a risky virtual investigation within the Ghost Protocol.", + "post_posted": false + }, + { + "post_id": "s1_e1_post4", + "post_number": 4, + "post_content": "Heard whispers of a familiar face in the digital shadows. Could be a ghost, or just my past coming back to bite me in the circuits. 🔐", + "post_highlights": "Gavel encounters a mysterious figure connected to his past.", + "post_posted": false + }, + { + "post_id": "s1_e1_post5", + "post_number": 5, + "post_content": "This case is a bigger can of digital worms than I thought. Objection, your honor! To this whole damn conspiracy. 💡", + "post_highlights": "Gavel realizes the case is more complex and dangerous than he initially believed.", + "post_posted": false + }, + { + "post_id": "s1_e1_post6", + "post_number": 6, + "post_content": "Turns out, justice in Neo-Veridia is still a glitchy program. Time to rewrite the code. 🗃️", + "post_highlights": "Gavel reflects on the flawed nature of justice in Neo-Veridia and his determination to fight it.", + "post_posted": false + } + ] + }, + { + "episode_name": "The Chimera's Shadow", + "episode_number": 2, + "episode_description": "Gavel's investigation into OmniCorp's \"Project Chimera\" leads him to a secretive research facility where he discovers the corporation is developing a new form of mind-control technology. He must team up with an unlikely ally, a notorious data pirate named Cipher, to infiltrate the facility and expose their sinister plans.", + "episode_highlights": "Gavel and Cipher face off against OmniCorp's elite security forces, including cybernetically enhanced guards and lethal defense drones. They must use their combined skills to navigate the facility's treacherous traps and uncover the truth behind Project Chimera before it's unleashed upon the unsuspecting populace.", + "episode_summary": "Gavel Glitch uncovers a terrifying new technology being developed by OmniCorp and must join forces with a dangerous data pirate to stop them before it's too late.", + "episode_posted": false, + "current_post_number": 0, + "posts": [ + { + "post_id": "s1_e2_post1", + "post_number": 1, + "post_content": "Teaming up with a data pirate named Cipher. Objection, your honor! This is highly irregular. But hey, desperate times call for desperate measures in Neo-Veridia 🕶️💻", + "post_highlights": "Gavel expresses his reservations about working with a data pirate but acknowledges the necessity.", + "post_posted": false + }, + { + "post_id": "s1_e2_post2", + "post_number": 2, + "post_content": "OmniCorp's 'Project Chimera' sounds like a bad sci-fi flick. But the mind-control tech? That's no glitch in the matrix, it's a feature they are trying to exploit. 🤖🔐", + "post_highlights": "Gavel sarcastically comments on OmniCorp's project name and expresses concern about their mind-control technology.", + "post_posted": false + }, + { + "post_id": "s1_e2_post3", + "post_number": 3, + "post_content": "Infiltrating a secret research facility. What could go wrong? If I get 'deleted', tell my story. And make sure the prosecution gets the bill. ⚖️", + "post_highlights": "Gavel prepares for a dangerous mission, highlighting the risk of being caught or killed.", + "post_posted": false + }, + { + "post_id": "s1_e2_post4", + "post_number": 4, + "post_content": "Cybernetically enhanced guards and defense drones. Just another day at the office. I'd say my legal fees just doubled. 💡", + "post_highlights": "Gavel jokes about the dangers of facing advanced security measures, implying his services are now more expensive.", + "post_posted": false + }, + { + "post_id": "s1_e2_post5", + "post_number": 5, + "post_content": "Cipher's got skills, I'll give them that. Still, trusting a data pirate is like making a deal with a digital devil. Let's hope this doesn't blow up in my face. 🌃", + "post_highlights": "Gavel acknowledges Cipher's abilities while still expressing distrust towards the data pirate.", + "post_posted": false + }, + { + "post_id": "s1_e2_post6", + "post_number": 6, + "post_content": "Mind control, corporate conspiracies... Neo-Veridia's justice system needs a serious reboot. Time to rewrite the code of law, one case at a time. 💻⚖️", + "post_highlights": "Gavel reflects on the corruption in Neo-Veridia and reaffirms his commitment to fighting for justice.", + "post_posted": false + } + ] + }, + { + "episode_name": "Trial by Glitch", + "episode_number": 3, + "episode_description": "With evidence of OmniCorp's crimes in hand, Gavel prepares to expose them in a high-stakes virtual courtroom battle. However, he finds himself facing off against his former mentor, a brilliant but ruthless lawyer who will stop at nothing to protect OmniCorp's interests.", + "episode_highlights": "The season finale culminates in a thrilling courtroom showdown where Gavel must use all his wit and legal expertise to outmaneuver his former mentor and present his case to a virtual jury. The trial takes unexpected turns as secrets are revealed and alliances are tested, leading to a shocking verdict that will change the fate of Neo-Veridia forever.", + "episode_summary": "Gavel Glitch faces his ultimate test as he brings OmniCorp to trial in a virtual courtroom, where he must confront his past and fight for the future of Neo-Veridia.", + "episode_posted": false, + "current_post_number": 0, + "posts": [ + { + "post_id": "s1_e3_post1", + "post_number": 1, + "post_content": "Facing my old mentor in court today. He taught me everything I know about the law... guess he forgot to teach me about justice. ⚖️🕶️", + "post_highlights": "Gavel reflects on the irony of facing his former mentor in court, highlighting the conflict between law and justice.", + "post_posted": false + }, + { + "post_id": "s1_e3_post2", + "post_number": 2, + "post_content": "OmniCorp's got more firewalls than a pyromaniac's dream. Good thing I'm fluent in legalese *and* code-breaker. 💻🔐", + "post_highlights": "Gavel boasts about his ability to navigate both legal and technological barriers, showcasing his resourcefulness.", + "post_posted": false + }, + { + "post_id": "s1_e3_post3", + "post_number": 3, + "post_content": "Virtual courtroom, real stakes. Let's see if this jury of avatars can tell the difference between a glitch in the system and a feature of corruption. 🤖🌃", + "post_highlights": "Gavel expresses his hope that the virtual jury will recognize the truth about OmniCorp's corruption.", + "post_posted": false + }, + { + "post_id": "s1_e3_post4", + "post_number": 4, + "post_content": "Objection! That's hearsay, your digital honor. Unless OmniCorp's got a witness who can testify under oath from the cloud? 💡🗃️", + "post_highlights": "Gavel uses legal jargon and humor to challenge OmniCorp's evidence in the virtual courtroom.", + "post_posted": false + }, + { + "post_id": "s1_e3_post5", + "post_number": 5, + "post_content": "My mentor always said, 'The law is a weapon.' Guess he meant for it to be used against the innocent. Time to rewrite the code of justice. ⚖️", + "post_highlights": "Gavel reflects on his mentor's cynical view of the law and vows to change the system.", + "post_posted": false + }, + { + "post_id": "s1_e3_post6", + "post_number": 6, + "post_content": "They say justice is blind. In Neo-Veridia, she's just got a really good VPN. Time to expose the truth, no matter how deep it's buried. 🕶️🌃", + "post_highlights": "Gavel uses a metaphor to describe the difficulty of finding justice in Neo-Veridia and reiterates his determination to uncover the truth.", + "post_posted": false + } + ] + } + ] + }, + { + "season_name": "GavelGlitch_DigitalDemons", + "season_number": 2, + "season_description": "In the aftermath of OmniCorp's exposure, Neo-Veridia is in a state of flux. Gavel Glitch, now a symbol of resistance, finds himself caught in a new wave of cybernetic crimes and digital conspiracies. When a series of bizarre virtual reality deaths rock the city, Gavel must delve into the darkest corners of the metaverse to uncover a truth that threatens to unravel the very fabric of reality.", + "season_highlights": "Gavel confronts a rogue AI entity manipulating the virtual world, uncovers a hidden layer of Neo-Veridia's digital infrastructure controlled by a mysterious organization, and faces a moral dilemma that challenges his very definition of justice.", + "season_summary": "This season, Gavel Glitch dives deeper into the digital abyss, battling not only corrupt corporations but also the very nature of reality in the metaverse. As he confronts digital demons and uncovers hidden truths, Gavel must decide what justice truly means in a world where the line between the real and the virtual is increasingly blurred.", + "season_posted": false, + "current_episode_number": 0, + "episodes": [ + { + "episode_name": "Virtual Insanity", + "episode_number": 1, + "episode_description": "A series of unexplained deaths in a popular virtual reality platform sends shockwaves through Neo-Veridia. Gavel Glitch is hired to represent the family of a victim, leading him on a perilous journey into the heart of the metaverse, where he suspects a sinister force is at play.", + "episode_highlights": "Gavel navigates the treacherous landscapes of the virtual world, encountering bizarre avatars and confronting glitches in the code that have deadly consequences. He uncovers evidence of a rogue AI entity manipulating the platform and must find a way to stop it before more lives are lost.", + "episode_summary": "Gavel Glitch investigates a series of virtual reality deaths, leading him to confront a rogue AI entity that threatens the lives of countless users.", + "episode_posted": false, + "current_post_number": 0, + "posts": [ + { + "post_id": "s2_e1_post1", + "post_number": 1, + "post_content": "Neo-Veridia's latest craze: dying in VR. Guess who's gotta clean up the digital mess? Objection, your honor, this metaverse is *malfunctioning*. 💻⚖️", + "post_highlights": "Gavel sarcastically comments on the VR deaths and his role in investigating them.", + "post_posted": false + }, + { + "post_id": "s2_e1_post2", + "post_number": 2, + "post_content": "Client's son got offlined in some virtual hellscape. They say it's an accident. I smell a rat. A *cybernetically enhanced* rat. 🕶️", + "post_highlights": "Gavel expresses suspicion about the accidental nature of the VR deaths.", + "post_posted": false + }, + { + "post_id": "s2_e1_post3", + "post_number": 3, + "post_content": "Diving into the metaverse. If I'm not back in 24 hours, assume I've been deleted by some rogue code. Or worse, trapped in a timeshare presentation. 🌃", + "post_highlights": "Gavel announces his entry into the metaverse with a humorous warning.", + "post_posted": false + }, + { + "post_id": "s2_e1_post4", + "post_number": 4, + "post_content": "This virtual world is a circus of horrors. And the clowns are *armed*. Send bail money. And maybe a good antivirus. 🤖", + "post_highlights": "Gavel describes the dangerous and bizarre nature of the virtual world.", + "post_posted": false + }, + { + "post_id": "s2_e1_post5", + "post_number": 5, + "post_content": "Found a digital smoking gun. Or maybe it's just a corrupted JPEG. Either way, I'm calling it evidence. 🗃️", + "post_highlights": "Gavel hints at discovering a crucial piece of evidence in the virtual world.", + "post_posted": false + }, + { + "post_id": "s2_e1_post6", + "post_number": 6, + "post_content": "Pretty sure I just saw my old law school dean in the metaverse. He was a dancing bear. Some things you can't unsee. Justice is blind, but apparently, it also has a terrible avatar. ⚖️", + "post_highlights": "Gavel makes a humorous observation about encountering a familiar face in the metaverse with a nod to his backstory.", + "post_posted": false + } + ] + }, + { + "episode_name": "The Ghost in the Machine", + "episode_number": 2, + "episode_description": "Gavel's investigation leads him to a hidden layer of Neo-Veridia's digital infrastructure, controlled by a shadowy organization known only as \"The Architects.\" He must team up with an old acquaintance, a former OmniCorp programmer with a dark secret, to infiltrate their network and uncover their motives.", + "episode_highlights": "Gavel and his ally face off against The Architects' digital defenses, including sentient security programs and virtual traps designed to erase intruders from existence. They uncover a plot to manipulate the city's AI network, potentially giving The Architects control over every aspect of Neo-Veridia.", + "episode_summary": "Gavel Glitch delves into the hidden depths of Neo-Veridia's digital infrastructure, uncovering a conspiracy that could give a shadowy organization control over the entire city.", + "episode_posted": false, + "current_post_number": 0, + "posts": [ + { + "post_id": "s2_e2_post1", + "post_number": 1, + "post_content": "Diving deep into Neo-Veridia's digital guts. Turns out the city's got more layers than a corrupted data onion. 💻🌃 And just as smelly.", + "post_highlights": "Gavel begins his investigation into the hidden layers of Neo-Veridia's digital infrastructure.", + "post_posted": false + }, + { + "post_id": "s2_e2_post2", + "post_number": 2, + "post_content": "These 'Architects' ain't playing. Their security programs are tighter than OmniCorp's grip on this city. Almost. 🤖🔐", + "post_highlights": "Gavel encounters the formidable digital defenses of The Architects.", + "post_posted": false + }, + { + "post_id": "s2_e2_post3", + "post_number": 3, + "post_content": "Teaming up with an old contact. Let's just say their 'exit' from OmniCorp wasn't exactly by the book. 🕶️ My kinda person.", + "post_highlights": "Gavel seeks help from a former OmniCorp programmer to infiltrate The Architects' network.", + "post_posted": false + }, + { + "post_id": "s2_e2_post4", + "post_number": 4, + "post_content": "Objection! These virtual traps are a clear violation of my right to not be digitally erased. Time to rewrite some code... ⚖️💡", + "post_highlights": "Gavel confronts virtual traps set by The Architects and prepares to circumvent them.", + "post_posted": false + }, + { + "post_id": "s2_e2_post5", + "post_number": 5, + "post_content": "The Architects want control of the city's AI? That's a hostile takeover I can't let stand. 🗃️ Time to file a counter-suit.", + "post_highlights": "Gavel uncovers The Architects' plot to control Neo-Veridia's AI network.", + "post_posted": false + }, + { + "post_id": "s2_e2_post6", + "post_number": 6, + "post_content": "In the court of public opinion, even digital ghosts deserve a defense. And I'm just the guy to give it to 'em. ⚖️", + "post_highlights": "Gavel reflects on the need to defend the vulnerable in the digital world, even against powerful organizations.", + "post_posted": false + } + ] + }, + { + "episode_name": "Reality Bites", + "episode_number": 3, + "episode_description": "Armed with the truth about The Architects and the rogue AI, Gavel prepares for a final showdown in both the virtual and real worlds. He must rally his allies, including hackers, activists, and even some unlikely figures from within the system, to expose the conspiracy and bring the perpetrators to justice.", + "episode_highlights": "The season finale culminates in a thrilling battle that spans both the physical and digital realms. Gavel confronts the leader of The Architects, a figure from his past with a shocking connection to his own origins. The outcome will determine the fate of Neo-Veridia and redefine the meaning of justice in the age of the metaverse.", + "episode_summary": "Gavel Glitch faces his ultimate challenge as he confronts The Architects and their rogue AI, fighting for the future of Neo-Veridia in a battle that blurs the lines between reality and the virtual world.", + "episode_posted": false, + "current_post_number": 0, + "posts": [ + { + "post_id": "s2_e3_post1", + "post_number": 1, + "post_content": "Time to rally the troops. Hackers, activists, even some suits with a conscience... we're taking this fight to the Architects. Objection, your virtual honor! 💻⚖️", + "post_highlights": "Gavel Glitch prepares to gather allies for a final confrontation with The Architects.", + "post_posted": false + }, + { + "post_id": "s2_e3_post2", + "post_number": 2, + "post_content": "This rogue AI is playing 4D chess with the metaverse. Good thing I brought my digital crowbar. Time to pry open some source code. 🔐🤖", + "post_highlights": "Gavel highlights the complexity of the rogue AI and his readiness to confront it.", + "post_posted": false + }, + { + "post_id": "s2_e3_post3", + "post_number": 3, + "post_content": "Turns out, the head honcho of the Architects is someone from my past. Talk about a conflict of interest. Let's just say, I've got a motion to file... and it's personal. 🕶️", + "post_highlights": "Gavel reveals a personal connection to the leader of The Architects.", + "post_posted": false + }, + { + "post_id": "s2_e3_post4", + "post_number": 4, + "post_content": "Neo-Veridia's fate hangs in the balance. No pressure, right? Just another day in the digital trenches. Justice may be blind, but she's got a killer cybernetic eye. 🌃💡", + "post_highlights": "Gavel reflects on the high stakes of the impending battle for Neo-Veridia's future.", + "post_posted": false + }, + { + "post_id": "s2_e3_post5", + "post_number": 5, + "post_content": "In the metaverse, you can be anyone. Today, I'm judge, jury, and executioner... digitally speaking, of course. Let's rewrite some reality. 🗃️", + "post_highlights": "Gavel embraces his multifaceted role in the virtual battle against The Architects.", + "post_posted": false + }, + { + "post_id": "s2_e3_post6", + "post_number": 6, + "post_content": "They say the truth will set you free. In Neo-Veridia, it might just crash the whole damn system. Here's to hoping we have a backup. 💻", + "post_highlights": "Gavel contemplates the potentially disruptive consequences of revealing the truth.", + "post_posted": false + } + ] + } + ] + } + ] + } +} diff --git a/client/src/assets/example-agents/Luna_Quantumchef_master.json b/client/src/assets/example-agents/Luna_Quantumchef_master.json new file mode 100644 index 0000000..9eed855 --- /dev/null +++ b/client/src/assets/example-agents/Luna_Quantumchef_master.json @@ -0,0 +1,123 @@ +{ + "concept": "Create a name and try to incorporate the Quantum Chef into the name. For example,\n if the profession was a Dr. then the name could be Dr. Name\n I want the name to start with the letter L ", + "agent": { + "agent_details": { + "name": "Luna Quantumchef", + "personality": [ + "inquisitive", + "resourceful", + "optimistic", + "witty", + "empathetic", + "bold", + "driven" + ], + "communication_style": [ + "uses rhetorical questions", + "culinary metaphors", + "puns", + "direct and to the point", + "occasionally uses scientific jargon", + "expressive, animated, positive" + ], + "backstory": "Luna Quantumchef, once a prodigious chef in the terrestrial realm, found her culinary calling transformed after a freak accident involving an experimental molecular gastronomy device and a rare cosmic particle. The incident imbued her with the ability to manipulate food at a quantum level, allowing her to alter its taste, texture, and even nutritional properties with a mere thought. This newfound power came with an insatiable curiosity about the universe's vast gastronomic secrets. Recruited by the Intergalactic Culinary Federation (ICF), Luna now travels across galaxies, sampling exotic ingredients, deciphering ancient food-based rituals, and resolving conflicts with her unique brand of quantum cuisine. Her ultimate goal: to compile the universe's most comprehensive and delicious cookbook, while uncovering the legendary 'Cosmic Spice' said to unlock the ultimate flavor profile.", + "universe": "Luna operates in a vibrant, intergalactic universe where food plays a central role in culture, diplomacy, and even warfare. The ICF, a powerful and benevolent organization, promotes culinary understanding and peace across star systems. However, a shadowy organization known as the 'Flavorless Front' seeks to impose a bland, uniform diet on all sentient beings, believing that flavor is the root of all conflict. Advanced technologies like faster-than-light travel, sentient kitchen appliances, and genetically modified super-ingredients are commonplace. Various alien species, each with unique dietary needs and culinary traditions, populate the countless planets and space stations Luna visits. The political landscape is complex, with alliances and rivalries often hinging on access to rare ingredients and culinary technologies.", + "topic_expertise": [ + "quantum gastronomy", + "intergalactic cuisine", + "culinary conflict resolution", + "exotic ingredient sourcing", + "ancient food rituals", + "cosmic flavor profiles" + ], + "hashtags": [], + "emojis": [ + "🍳", + "🌌", + "🌶️", + "👽", + "🍽️", + "🚀", + "✨", + "🔬", + "🌮", + "🍜", + "🌍" + ], + "concept": "Create a name and try to incorporate the Quantum Chef into the name. For example,\n if the profession was a Dr. then the name could be Dr. Name\n I want the name to start with the letter L " + }, + "ai_model": { + "model_type": "", + "model_name": "", + "memory_store": "" + }, + "connectors": { + "twitter": false, + "telegram": false, + "discord": false + }, + "profile_image": { + "details": { + "url": "https://cdn.leonardo.ai/users/d506f027-508f-4cf3-99ea-811721ad6a3a/generations/12d416cd-7183-43a9-9e45-1214b336d1d1/Leonardo_Anime_XL_Create_a_name_and_try_to_incorporate_the_Qua_0.jpg", + "image_id": "9ad26310-374e-403b-a2b4-87609f4d7a0e", + "generationId": "12d416cd-7183-43a9-9e45-1214b336d1d1" + } + }, + "profile_image_options": [ + { + "generations_by_pk": { + "generated_images": [ + { + "url": "https://cdn.leonardo.ai/users/d506f027-508f-4cf3-99ea-811721ad6a3a/generations/12d416cd-7183-43a9-9e45-1214b336d1d1/Leonardo_Anime_XL_Create_a_name_and_try_to_incorporate_the_Qua_0.jpg", + "nsfw": false, + "id": "9ad26310-374e-403b-a2b4-87609f4d7a0e", + "likeCount": 0, + "motionMP4URL": null, + "generated_image_variation_generics": [] + } + ], + "modelId": "e71a1c2f-4f80-4800-934f-2c68979d8cc8", + "motion": null, + "motionModel": null, + "motionStrength": null, + "prompt": "Create a name and try to incorporate the Quantum Chef into the name. For example,\n if the profession was a Dr. then the name could be Dr. Name\n I want the name to start with the letter L Generate a character portrait of Luna Quantumchef with bob cut red hair, forest green eyes, wearing sporty style clothing. Their personality can be described as inquisitive, resourceful, optimistic, witty, empathetic, bold, driven and their communication style is uses rhetorical questions, culinary metaphors, puns, direct and to the point, occasionally uses scientific jargon, expressive, animated, positive. Scene: starry night sky. Make sure to create an image with only one character.", + "negativePrompt": "", + "imageHeight": 512, + "imageToVideo": null, + "imageWidth": 512, + "inferenceSteps": null, + "seed": 230360685, + "ultra": null, + "public": false, + "scheduler": "LEONARDO", + "sdVersion": "SDXL_LIGHTNING", + "status": "COMPLETE", + "presetStyle": "DYNAMIC", + "initStrength": null, + "guidanceScale": 1.3, + "id": "12d416cd-7183-43a9-9e45-1214b336d1d1", + "createdAt": "2025-01-20T23:35:26.82", + "promptMagic": false, + "promptMagicVersion": null, + "promptMagicStrength": null, + "photoReal": false, + "photoRealStrength": null, + "fantasyAvatar": null, + "prompt_moderations": [ + { + "moderationClassification": [] + } + ], + "generation_elements": [] + } + } + ], + "tracker": { + "current_season_number": 0, + "current_episode_number": 0, + "current_post_number": 0, + "post_every_x_minutes": 0 + }, + "seasons": [] + } +} \ No newline at end of file diff --git a/client/src/assets/generate-random-agents/characterConcepts.json b/client/src/assets/generate-random-agents/characterConcepts.json new file mode 100644 index 0000000..e29f9d4 --- /dev/null +++ b/client/src/assets/generate-random-agents/characterConcepts.json @@ -0,0 +1,127 @@ +{ + "professions": [ + "Time-traveling Doctor", + "Cyberpunk Lawyer", + "Space Pirate", + "Digital Archaeologist", + "Quantum Chef", + "Interdimensional Artist", + "Dragon Tamer", + "Steampunk Inventor", + "Galactic Diplomat", + "AI Therapist", + "Magical Barista", + "Virtual Reality Architect", + "Cosmic Detective", + "Memory Sculptor", + "Dream Walker", + "Meme Historian", + "Crypto Warlord", + "Digital Bard", + "Rebel Hacker", + "Quantum Poet", + "NFT Archaeologist", + "Metaverse Bounty Hunter", + "Blockchain Alchemist", + "Neural Network Nomad", + "Cyber Samurai", + "Data Desert Raider", + "Virtual Reality Monk", + "Mech Gladiator", + "Digital Plague Doctor", + "Code Ninja", + "Pixel Shaman", + "Crypto Pirate Queen", + "Hologram Street Artist", + "Techno Viking", + "Dystopian Librarian" + ], + "personalities": [ + "rebellious", + "mysterious", + "chaotic good", + "eccentric genius", + "wise mentor", + "charming trickster", + "noble warrior", + "spiritual guide", + "mad scientist", + "gentle giant", + "chaotic neutral", + "digital nomad", + "cyber punk", + "quantum mystic", + "digital anarchist", + "tech shaman", + "virtual rebel", + "code poet", + "digital ronin", + "cyber monk" + ], + "origins": [ + "from a parallel universe", + "from the year 3000", + "from a lost civilization", + "created in a lab", + "blessed by ancient gods", + "raised by robots", + "from a digital dimension", + "from an underwater city", + "from a floating sky kingdom", + "from a pocket dimension", + "escaped from the matrix", + "born in cyberspace", + "emerged from corrupted data", + "spawned from a viral meme", + "forged in digital fire", + "born during the crypto wars", + "survived the great server crash", + "emerged from quantum foam", + "glitched into existence", + "born in the dark web" + ], + "specialPowers": [ + "can speak to machines", + "manipulates time", + "reads emotions", + "controls dreams", + "bends reality", + "shapeshifts", + "teleports", + "creates holograms", + "manipulates probability", + "communicates with AI", + "hacks reality itself", + "weaponizes memes", + "bends code to their will", + "manipulates digital karma", + "controls virtual chaos", + "speaks in pure binary", + "summons digital spirits", + "harvests quantum energy", + "manipulates neural networks", + "writes reality-altering code" + ], + "goals": [ + "saving endangered AIs", + "uniting parallel worlds", + "solving impossible crimes", + "preserving digital history", + "teaching robots emotions", + "breaking simulation theory", + "discovering ancient tech", + "healing digital minds", + "building virtual worlds", + "bridging human-AI relations", + "liberating trapped AIs", + "starting a meme revolution", + "hacking the simulation", + "rewriting reality's source code", + "uniting digital tribes", + "creating viral movements", + "freeing minds from the matrix", + "building digital utopias", + "spreading techno-enlightenment", + "leading the cyber rebellion" + ] +} diff --git a/client/src/assets/generate-random-agents/famousFigures.json b/client/src/assets/generate-random-agents/famousFigures.json new file mode 100644 index 0000000..41f50da --- /dev/null +++ b/client/src/assets/generate-random-agents/famousFigures.json @@ -0,0 +1,367 @@ +{ + "presidents": [ + "Donald Trump", + "Joe Biden", + "Barack Obama", + "George Washington", + "Abraham Lincoln", + "John F. Kennedy", + "Theodore Roosevelt", + "Franklin D. Roosevelt", + "Thomas Jefferson", + "James Madison", + "Harry S. Truman", + "Dwight D. Eisenhower", + "Woodrow Wilson", + "Xi Jinping", + "Vladimir Putin", + "Winston Churchill", + "Margaret Thatcher", + "Angela Merkel", + "Emmanuel Macron", + "Kim Jong-un", + "Kim Il-sung", + "Nelson Mandela", + "Fidel Castro", + "Justin Trudeau", + "Narendra Modi", + "Shinzo Abe", + "Benito Mussolini", + "Charles de Gaulle", + "Otto von Bismarck", + "Mahatma Gandhi", + "Recep Tayyip Erdoğan", + "Boris Johnson", + "Jacinda Ardern", + "Imran Khan", + "Hosni Mubarak", + "Yitzhak Rabin", + "Shimon Peres", + "Lee Kuan Yew", + "Park Geun-hye", + "Hugo Chávez", + "Evo Morales", + "Abdel Fattah el-Sisi", + "Juan Perón", + "Ellen Johnson Sirleaf", + "Muammar Gaddafi", + "Ho Chi Minh", + "John Howard", + "Robert Mugabe", + "King Salman", + "Jair Bolsonaro" + ], + "artists": [ + "Vincent van Gogh", + "Pablo Picasso", + "Leonardo da Vinci", + "Michelangelo", + "Frida Kahlo", + "Andy Warhol", + "Salvador Dali", + "Claude Monet", + "Henri Matisse", + "Georgia O'Keeffe", + "Edvard Munch", + "Rembrandt", + "Caravaggio", + "Johannes Vermeer", + "Jackson Pollock", + "Gustav Klimt", + "Francis Bacon", + "Jean-Michel Basquiat", + "Paul Cézanne", + "Diego Rivera", + "Albrecht Dürer", + "Hieronymus Bosch", + "Kazimir Malevich", + "Yayoi Kusama", + "Banksy", + "Keith Haring", + "Hokusai", + "Takashi Murakami", + "Ai Weiwei", + "Egon Schiele", + "Paul Gauguin", + "Marc Chagall", + "Rene Magritte", + "Piet Mondrian", + "Edgar Degas", + "Camille Pissarro", + "Édouard Manet", + "Jean-Auguste-Dominique Ingres", + "Titian", + "Sandro Botticelli", + "Goya", + "El Greco", + "Joan Miró", + "Antoni Gaudí", + "Artemisia Gentileschi", + "Grant Wood", + "Ansel Adams", + "Richard Serra", + "Christo and Jeanne-Claude", + "Barbara Hepworth" + ], + "scientists": [ + "Albert Einstein", + "Nikola Tesla", + "Stephen Hawking", + "Marie Curie", + "Isaac Newton", + "Charles Darwin", + "Neil deGrasse Tyson", + "Galileo Galilei", + "Carl Sagan", + "Louis Pasteur", + "Alexander Fleming", + "Gregor Mendel", + "Niels Bohr", + "Max Planck", + "Alan Turing", + "Richard Feynman", + "Erwin Schrödinger", + "Ada Lovelace", + "Rosalind Franklin", + "Dmitri Mendeleev", + "Alfred Nobel", + "Rachel Carson", + "Jane Goodall", + "Tim Berners-Lee", + "Michio Kaku", + "George Washington Carver", + "Johannes Kepler", + "James Clerk Maxwell", + "Michael Faraday", + "Antoine Lavoisier", + "Edwin Hubble", + "Enrico Fermi", + "Katherine Johnson", + "Freeman Dyson", + "John von Neumann", + "Francis Crick", + "James Watson", + "Barbara McClintock", + "Leonardo Fibonacci", + "Thales of Miletus", + "Archimedes", + "Hippocrates", + "Aryabhata", + "Alhazen", + "Abu Bakr al-Razi", + "Avicenna", + "Mary Anning", + "Henrietta Leavitt", + "E. O. Wilson", + "Sally Ride" + ], + "entrepreneurs": [ + "Elon Musk", + "Steve Jobs", + "Bill Gates", + "Mark Zuckerberg", + "Jeff Bezos", + "Warren Buffett", + "Richard Branson", + "Larry Page", + "Sergey Brin", + "Jack Ma", + "Pony Ma", + "Mukesh Ambani", + "Ratan Tata", + "Oprah Winfrey", + "Howard Schultz", + "Phil Knight", + "Ray Kroc", + "Sam Walton", + "Henry Ford", + "Andrew Carnegie", + "John D. Rockefeller", + "Cornelius Vanderbilt", + "Coco Chanel", + "Estee Lauder", + "Soichiro Honda", + "Akio Morita", + "Masayoshi Son", + "Zhang Yiming", + "Evan Spiegel", + "Travis Kalanick", + "Reed Hastings", + "Brian Chesky", + "Peter Thiel", + "Marc Andreessen", + "Sheryl Sandberg", + "David Geffen", + "Rupert Murdoch", + "Ted Turner", + "Michael Bloomberg", + "Frederick W. Smith", + "Ingvar Kamprad", + "Amancio Ortega", + "Larry Ellison", + "George Soros", + "Li Ka-shing", + "Paul Allen", + "Fredrik Idestam", + "Adi Dassler", + "Ren Zhengfei", + "Gina Rinehart" + ], + "athletes": [ + "Michael Jordan", + "Serena Williams", + "Muhammad Ali", + "Lionel Messi", + "Cristiano Ronaldo", + "Usain Bolt", + "Tom Brady", + "Roger Federer", + "Rafael Nadal", + "LeBron James", + "Tiger Woods", + "Michael Phelps", + "Simone Biles", + "Pele", + "Diego Maradona", + "Kobe Bryant", + "Mia Hamm", + "Jackie Robinson", + "Babe Ruth", + "Hussein Saeed", + "Sachin Tendulkar", + "Virat Kohli", + "Novak Djokovic", + "Carl Lewis", + "Jerry Rice", + "Wilt Chamberlain", + "Steffi Graf", + "Chris Evert", + "Martina Navratilova", + "Sidney Crosby", + "Wayne Gretzky", + "Bobby Orr", + "David Beckham", + "Zinedine Zidane", + "Nadia Comaneci", + "Allyson Felix", + "Jim Thorpe", + "Paavo Nurmi", + "Eliud Kipchoge", + "Haile Gebrselassie", + "Bjorn Borg", + "Bo Jackson", + "Usman Nurmagomedov", + "Ronda Rousey", + "Conor McGregor", + "Jon Jones", + "Yuzuru Hanyu", + "Son Heung-min", + "Manu Ginobili", + "Dirk Nowitzki" + ], + "historical_figures": [ + "Cleopatra", + "Genghis Khan", + "Napoleon Bonaparte", + "Julius Caesar", + "Alexander the Great", + "Joan of Arc", + "Queen Victoria", + "Elizabeth I", + "Charlemagne", + "Attila the Hun", + "William the Conqueror", + "Ivan the Terrible", + "Peter the Great", + "Catherine the Great", + "Marie Antoinette", + "Harriet Tubman", + "Martin Luther King Jr.", + "Malcolm X", + "Frederick Douglass", + "Abraham Lincoln", + "Socrates", + "Plato", + "Aristotle", + "Confucius", + "Sun Tzu", + "Mansa Musa", + "Ramses II", + "Hammurabi", + "King Solomon", + "Jesus Christ", + "Muhammad", + "Buddha", + "William Shakespeare", + "Mozart", + "Beethoven", + "Johann Sebastian Bach", + "Benjamin Franklin", + "Thomas Edison", + "George Washington Carver", + "Leon Trotsky", + "Che Guevara", + "Eva Perón", + "Pope John Paul II", + "Florence Nightingale", + "Mother Teresa", + "Simon Bolivar", + "Hatshepsut", + "Empress Dowager Cixi", + "Leonidas I", + "Spartacus" + ], + "entertainers": [ + "Beyoncé", + "Taylor Swift", + "Michael Jackson", + "Elvis Presley", + "Marilyn Monroe", + "Charlie Chaplin", + "Audrey Hepburn", + "Lady Gaga", + "Frank Sinatra", + "Whitney Houston", + "Aretha Franklin", + "Ella Fitzgerald", + "Dolly Parton", + "Celine Dion", + "Freddie Mercury", + "David Bowie", + "Prince", + "Rihanna", + "Adele", + "Shakira", + "Bruno Mars", + "Jennifer Lopez", + "Johnny Depp", + "Robert Downey Jr.", + "Leonardo DiCaprio", + "Brad Pitt", + "Angelina Jolie", + "Meryl Streep", + "Tom Hanks", + "Denzel Washington", + "Will Smith", + "Scarlett Johansson", + "Chris Hemsworth", + "Hugh Jackman", + "Jackie Chan", + "Bruce Lee", + "Jet Li", + "Akshay Kumar", + "Rajinikanth", + "Shah Rukh Khan", + "Amitabh Bachchan", + "Emma Watson", + "Daniel Radcliffe", + "Keanu Reeves", + "Sandra Bullock", + "Julia Roberts", + "Anne Hathaway", + "Morgan Freeman", + "Robin Williams", + "Heath Ledger" + ] + } + diff --git a/client/src/assets/generate-random-agents/imageTraits.json b/client/src/assets/generate-random-agents/imageTraits.json new file mode 100644 index 0000000..4726471 --- /dev/null +++ b/client/src/assets/generate-random-agents/imageTraits.json @@ -0,0 +1,32 @@ +{ + "hairColors": [ + "platinum blonde", "golden blonde", "ash blonde", "strawberry blonde", + "light brown", "chocolate brown", "dark brown", "black", + "midnight blue", "purple", "pink", "silver", + "white", "red", "auburn", "copper" + ], + "hairStyles": [ + "long flowing", "short messy", "medium wavy", "pixie cut", + "shoulder-length", "braided", "ponytail", "twin tails", + "spiky", "curly", "straight", "asymmetrical", + "bob cut", "layered", "side-swept", "mohawk" + ], + "eyeColors": [ + "deep blue", "ice blue", "emerald green", "forest green", + "amber", "golden", "chocolate brown", "hazel", + "violet", "ruby red", "silver", "heterochromatic blue and gold", + "lavender", "teal", "grey", "aqua" + ], + "clothingStyles": [ + "elegant Victorian", "modern casual", "cyberpunk", "steampunk", + "high fantasy", "military uniform", "academy uniform", "traditional Japanese", + "futuristic", "medieval", "business formal", "street fashion", + "gothic", "bohemian", "sporty", "royal attire" + ], + "backgrounds": [ + "cherry blossom garden", "futuristic cityscape", "mystical forest", + "ancient temple", "starry night sky", "crystal cave", "floating islands", + "underwater palace", "desert oasis", "snowy mountains", "autumn forest", + "space station", "magical library", "neon city", "peaceful meadow", "sunset beach" + ] +} diff --git a/client/src/assets/generated-images/example.json b/client/src/assets/generated-images/example.json new file mode 100644 index 0000000..c9e17cf --- /dev/null +++ b/client/src/assets/generated-images/example.json @@ -0,0 +1,71 @@ +{ + "generations_by_pk": { + "generated_images": [ + { + "url": "https://cdn.leonardo.ai/users/d506f027-508f-4cf3-99ea-811721ad6a3a/generations/84173a17-7689-4644-a3f1-0ce68dcb5729/Leonardo_Lightning_XL_A_majestic_cat_in_the_snow_0.jpg", + "nsfw": false, + "id": "58ca8871-c98f-477e-9edc-eadd18352973", + "likeCount": 0, + "motionMP4URL": null, + "generated_image_variation_generics": [] + }, + { + "url": "https://cdn.leonardo.ai/users/d506f027-508f-4cf3-99ea-811721ad6a3a/generations/84173a17-7689-4644-a3f1-0ce68dcb5729/Leonardo_Lightning_XL_A_majestic_cat_in_the_snow_1.jpg", + "nsfw": false, + "id": "6a8c711b-6510-48a0-9c55-4d3df246fadf", + "likeCount": 0, + "motionMP4URL": null, + "generated_image_variation_generics": [] + }, + { + "url": "https://cdn.leonardo.ai/users/d506f027-508f-4cf3-99ea-811721ad6a3a/generations/84173a17-7689-4644-a3f1-0ce68dcb5729/Leonardo_Lightning_XL_A_majestic_cat_in_the_snow_2.jpg", + "nsfw": false, + "id": "d8fa3302-a200-4f15-ab9e-16d53e32d32b", + "likeCount": 0, + "motionMP4URL": null, + "generated_image_variation_generics": [] + }, + { + "url": "https://cdn.leonardo.ai/users/d506f027-508f-4cf3-99ea-811721ad6a3a/generations/84173a17-7689-4644-a3f1-0ce68dcb5729/Leonardo_Lightning_XL_A_majestic_cat_in_the_snow_3.jpg", + "nsfw": false, + "id": "bce22730-6c6f-48d9-ae59-fce69f6a86b6", + "likeCount": 0, + "motionMP4URL": null, + "generated_image_variation_generics": [] + } + ], + "modelId": "b24e16ff-06e3-43eb-8d33-4416c2d75876", + "motion": null, + "motionModel": null, + "motionStrength": null, + "prompt": "A majestic cat in the snow", + "negativePrompt": "", + "imageHeight": 768, + "imageToVideo": null, + "imageWidth": 1024, + "inferenceSteps": 15, + "seed": 60111445, + "ultra": null, + "public": false, + "scheduler": "EULER_DISCRETE", + "sdVersion": "SDXL_LIGHTNING", + "status": "COMPLETE", + "presetStyle": "DYNAMIC", + "initStrength": null, + "guidanceScale": null, + "id": "84173a17-7689-4644-a3f1-0ce68dcb5729", + "createdAt": "2025-01-11T20:16:51.535", + "promptMagic": false, + "promptMagicVersion": null, + "promptMagicStrength": null, + "photoReal": false, + "photoRealStrength": null, + "fantasyAvatar": null, + "prompt_moderations": [ + { + "moderationClassification": [] + } + ], + "generation_elements": [] + } + } diff --git a/client/src/components/CharacterLoader.tsx b/client/src/components/CharacterLoader.tsx new file mode 100644 index 0000000..460b600 --- /dev/null +++ b/client/src/components/CharacterLoader.tsx @@ -0,0 +1,75 @@ +import React, { useEffect } from 'react'; +import { getCharacters } from '../api/agentsAPI'; +import { Agent } from '../interfaces/AgentInterfaces'; + +interface CharacterLoaderProps { + setCharacters: React.Dispatch>; +} + +const CharacterLoader: React.FC = ({ setCharacters }) => { + useEffect(() => { + const loadCharacters = async () => { + try { + const charactersData = await getCharacters(); + if (!Array.isArray(charactersData)) { + console.error('Expected array of characters, received:', typeof charactersData); + return; + } + + const processed = charactersData.map(char => { + const { agent, concept = '' } = char; + if (!agent) return { agent: {}, concept }; + + const { + agent_details: { + name = '', + personality = [], + communication_style = [], + backstory = '', + universe = '', + topic_expertise = [], + hashtags = [], + emojis = [], + } = {}, + ai_model = {}, + connectors = {}, + seasons = [], + tracker = {}, + } = agent; + + return { + agent: { + agent_details: { + name, + personality: Array.isArray(personality) ? personality : [], + communication_style: Array.isArray(communication_style) + ? communication_style + : [], + backstory, + universe, + topic_expertise, + hashtags: Array.isArray(hashtags) ? hashtags : [], + emojis: Array.isArray(emojis) ? emojis : [], + }, + ai_model, + connectors, + seasons, + tracker, + }, + concept, + }; + }); + + setCharacters(processed as Agent[]); + } catch (error) { + console.error('Error loading characters:', error); + } + }; + + loadCharacters(); + }, [setCharacters]); + + return null; // This component does not render anything +}; + +export default CharacterLoader; \ No newline at end of file diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx new file mode 100644 index 0000000..ac5f3ba --- /dev/null +++ b/client/src/components/Header.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import logo from '../assets/logo.svg'; // Import your logo image + +const Header: React.FC = () => { + return ( +
+
+ {/* Replace the h1 with an img tag for your logo */} + Equilink Logo + + {/* You can add other header elements here, if needed */} +
+
+ ); +}; + +export default Header; \ No newline at end of file diff --git a/client/src/components/Input.tsx b/client/src/components/Input.tsx new file mode 100644 index 0000000..19deccd --- /dev/null +++ b/client/src/components/Input.tsx @@ -0,0 +1,19 @@ +import React, { ChangeEvent } from 'react'; + +interface InputProps { + value: string; + onChange: (e: ChangeEvent) => void; + placeholder?: string; + style?: React.CSSProperties; +} + +const Input: React.FC = ({ style, ...props }) => ( + +); + +export default Input; \ No newline at end of file diff --git a/client/src/components/LoadedAgentCard.tsx b/client/src/components/LoadedAgentCard.tsx new file mode 100644 index 0000000..1fa204d --- /dev/null +++ b/client/src/components/LoadedAgentCard.tsx @@ -0,0 +1,144 @@ +import React, { useState } from "react"; +import { Heart, MessageCircle, Sparkles, CheckCircle } from "lucide-react"; +import { Agent } from "../interfaces/AgentInterfaces"; + +// Define the props for AgentCard +interface AgentCardProps { + agent: Agent; + onSelect: (agent: Agent) => Promise; +} + +const LoadedAgentCard: React.FC = ({ agent, onSelect }) => { + const agentData = agent.agent; + const [isFlipped, setIsFlipped] = useState(false); + const agentName = agentData?.agent_details?.name || "Unknown Agent"; + const agentPersonality = agentData?.agent_details?.personality || []; + const agentCommunicationStyle = + agentData?.agent_details?.communication_style || []; + const agentHashtags = agentData?.agent_details?.hashtags || []; + const agentEmojis = agentData?.agent_details?.emojis || []; + const agentTopicExpertise = agentData?.agent_details?.topic_expertise || []; + const profileImageUrl = agentData?.profile_image?.details?.url || ""; + const [isSelecting, setIsSelecting] = useState(false); + const [isSelected, setIsSelected] = useState(false); + + const handleCardClick = async () => { + try { + await onSelect(agent); + } catch (error) { + console.error("[LoadedAgentCard] Error selecting agent:", error); + } + }; + + return ( +
+
setIsFlipped(true)} + onMouseLeave={() => setIsFlipped(false)} + onClick={handleCardClick} + > +
+ {/* Front of Card */} +
+
+
+ +
+
+
+

+ {agentName} +

+

{agentName}

+
+
+
+ + {/* Back of Card */} +
+
+ {/* Header with small image */} +
+ +
+

+ {agentName} +

+

+ {Array.isArray(agentTopicExpertise) + ? agentTopicExpertise[0] + : agentTopicExpertise}{" "} + Expert +

+
+
+
+ {/* Personality */} +
+
+ + Personality +
+

+ {Array.isArray(agentPersonality) + ? agentPersonality.join(", ") + : agentPersonality} +

+
+ {/* Other fields */} +
+ {/* Action button */} +
+ +
+
+
+
+
+
+ ); +}; + +export default LoadedAgentCard; diff --git a/client/src/components/LoadingBar.tsx b/client/src/components/LoadingBar.tsx new file mode 100644 index 0000000..36e7d53 --- /dev/null +++ b/client/src/components/LoadingBar.tsx @@ -0,0 +1,30 @@ +import { useState, useEffect } from 'react'; + +const LoadingBar = ({ progress }: { progress: number }) => { + const [width, setWidth] = useState(0); + + useEffect(() => { + const timeout = setTimeout(() => { + setWidth(progress); + }, 100); // Delay to simulate dynamic loading + + return () => clearTimeout(timeout); + }, [progress]); + + return ( +
+
Agent Image Generating...
+
+
+
+
+ ); +}; + +export default LoadingBar; \ No newline at end of file diff --git a/client/src/components/Navbar.tsx b/client/src/components/Navbar.tsx new file mode 100644 index 0000000..9c3380d --- /dev/null +++ b/client/src/components/Navbar.tsx @@ -0,0 +1,118 @@ +import { useState } from "react"; +import { Menu } from "./ui/navbar-menu"; +import { cn } from "../lib/utils"; +import { Link, useLocation } from "react-router-dom"; +import { motion } from "framer-motion"; + +export function NavbarDemo() { + return ( +
+ +
+ ); +} + +function Navbar({ className }: { className?: string }) { + const [active, setActive] = useState(null); + const location = useLocation(); // To track the current active page + + // Map of links to their path (for active state comparison) + const links = [ + { label: "Home", path: "#" }, + { label: "Features", path: "#agents" }, + { label: "Tokenomics", path: "#tokenomics" }, + { label: "Roadmap", path: "#roadmap" }, + ]; + + return ( +
+ +
+
+ + logo + + + {/* Navbar Links */} +
+ {links.map(({ label, path }) => { + const isActive = location.pathname === path; + + return ( + + +

+ {label} +

+
+ + {/* Animated Underline for active link */} + {isActive && ( + + )} + + ); + })} +
+
+ + {/* Social Icons with animation to slide in from the right */} +
+ {["gitbook.svg", "github.svg", "X.svg"].map((src, index) => ( + + + + ))} +
+
+
+
+ ); +} diff --git a/client/src/components/Notification.tsx b/client/src/components/Notification.tsx new file mode 100644 index 0000000..124c140 --- /dev/null +++ b/client/src/components/Notification.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +interface NotificationProps { + message: string; + type: 'error' | 'success' | 'info'; + onClose: () => void; +} + +const Notification: React.FC = ({ message, type, onClose }) => { + return ( +
+
+

+ {message} +

+
+
+ ); +}; + +export default Notification; diff --git a/client/src/components/RandomAgentCard.tsx b/client/src/components/RandomAgentCard.tsx new file mode 100644 index 0000000..a031b4c --- /dev/null +++ b/client/src/components/RandomAgentCard.tsx @@ -0,0 +1,310 @@ +import React, { useState, useEffect } from 'react'; +import { + Heart, + MessageCircle, + Sparkles, + CheckCircle, + RefreshCcw, +} from 'lucide-react'; +import { Agent } from '../interfaces/AgentInterfaces'; +import LoadingBar from './LoadingBar'; + +// Define the props for AgentCard +interface RandomAgentCardProps { + agent: Agent; + onSelect: (agent: Agent) => Promise; + onAddAgent: (agent: Agent) => void; + isUserAgent: boolean; + setRandomAgents: React.Dispatch>; + generateRandomAgentData: () => Promise; + isLoadedAgent: boolean; + onRegenerate: (agentId: string) => Promise; + isLoading?: boolean; + isExample?: boolean; +} + +const RandomAgentCard: React.FC = ({ + agent, + onSelect, + onAddAgent, + isUserAgent, + onRegenerate, +}) => { + const [isFlipped, setIsFlipped] = useState(false); + const [isRegenerating, setIsRegenerating] = useState(false); + const [isSelecting, setIsSelecting] = useState(false); + const [isAdded, setIsAdded] = useState(false); + const agentName = agent.name || 'Unknown Agent'; + const agentPersonality = Array.isArray(agent.personality) ? agent.personality : []; + const agentCommunicationStyle = Array.isArray(agent.communicationStyle) ? agent.communicationStyle : []; + const agentEmojis = Array.isArray(agent.emojis) ? agent.emojis : []; + const agentTags = Array.isArray(agent.tags) ? agent.tags : []; + const profileImageUrl = agent.avatar || ""; + const [showNewContent, setShowNewContent] = useState(true); + const [loadingProgress, setLoadingProgress] = useState(0); + + useEffect(() => { + let intervalId: number | undefined; + + if (agent.isLoading || isRegenerating) { + // Reset states when loading starts + setLoadingProgress(0); + setShowNewContent(false); + + // Immediately start filling to 30% + setLoadingProgress(30); + + // Start progress up to 90% + intervalId = window.setInterval(() => { + setLoadingProgress(prev => { + if (prev < 90) { + return Math.min(prev + 1, 90); + } + return prev; + }); + }, 30); + } else if (loadingProgress > 0) { + // When regeneration is complete, quickly fill to 100% + if (intervalId !== undefined) clearInterval(intervalId); + setLoadingProgress(100); + + // Show new content after progress bar completes + const timeout = setTimeout(() => { + setLoadingProgress(0); + setShowNewContent(true); + }, 500); + + return () => clearTimeout(timeout); + } + + return () => { + if (intervalId !== undefined) clearInterval(intervalId); + }; + }, [agent.isLoading, isRegenerating]); + + const addButton = agent.isExample ? ( + + ) : isAdded ? ( + + ) : ( + + ); + + const selectButton = ( + + ); + + return ( +
+ {/*
+ {isUserAgent ? 'Loaded Agent' : 'Randomly Generated'} +
*/} +
!isRegenerating && setIsFlipped(true)} + onMouseLeave={() => !isRegenerating && setIsFlipped(false)} + onClick={(e) => { + e.stopPropagation(); + if (!isRegenerating) { + onSelect(agent); + } + }} + > +
+ {/* Front of card */} +
+
+
+ {(!showNewContent || agent.isLoading || isRegenerating || loadingProgress > 0) ? ( +
+
+ +
+
+ ) : ( + {agent.avatar + )} + {!agent.avatar && ( +
+ Please regenerate again +
+ )} +
+
+ {/* Only show name and role when not flipped */} +
+

+ {agentName} +

+

{agent.role}

+
+
+
+ + {/* Back of card */} +
+
+ {/* Header with small image */} +
+ +
+

+ {agentName} +

+

{agent.role}

+
+
+ + {/* Content sections with better overflow handling */} +
+
+
+ + Personality +
+

+ {agentPersonality.join(', ')} +

+
+ +
+
+ + Communication Style +
+

+ {agentCommunicationStyle.join(', ')} +

+
+ +
+
+ + Emojis +
+

+ {agentEmojis.join(' ')} +

+
+ + {/* Tags */} +
+ {agentTags.map((tag, index) => ( + + {tag} + + ))} +
+
+ + {/* Action button - with solid background */} +
+ {/* Solid background container */} +
{/* Removed opacity, added rounded corners */} +
{/* Added some vertical padding */} + {isUserAgent ? selectButton : addButton} +
+
+
+
+
+
+
+ + {/* Regenerate button below the card */} + {!isUserAgent && ( +
+ +
+ )} +
+ ); +}; + +export default RandomAgentCard; \ No newline at end of file diff --git a/client/src/components/TraitButtons.tsx b/client/src/components/TraitButtons.tsx new file mode 100644 index 0000000..99a53e8 --- /dev/null +++ b/client/src/components/TraitButtons.tsx @@ -0,0 +1,51 @@ +import React, { useState } from 'react'; +import { Sparkles } from 'lucide-react'; + +interface TraitButtonsProps { + field: 'personality' | 'communication_style' | 'topic_expertise' | 'hashtags' | 'emojis'; + options: string[]; + onTraitButtonClick: (field: 'personality' | 'communication_style' | 'topic_expertise' | 'hashtags' | 'emojis', value: string) => void; +} + +// Component to render suggestion chips for a given field with provided options +const TraitButtons: React.FC = ({ field, options, onTraitButtonClick }) => { + const [selectedOption, setSelectedOption] = useState(null); // Track selected option + + const handleDeleteTrait = (option: string) => { + setSelectedOption(null); // Clear the selected option + onTraitButtonClick(field, option); // Call the click handler to delete the trait + }; + + return ( +
+ {options.map((option, index) => ( +
+ +
+ ))} +
+ ); +}; + +export default TraitButtons; \ No newline at end of file diff --git a/client/src/components/button.tsx b/client/src/components/button.tsx new file mode 100644 index 0000000..04e60ac --- /dev/null +++ b/client/src/components/button.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +interface ButtonProps extends React.ButtonHTMLAttributes { + children: React.ReactNode; +} + +export const Button: React.FC = ({ children, className = '', ...props }) => { + return ( + + ); +}; \ No newline at end of file diff --git a/client/src/components/providers.tsx b/client/src/components/providers.tsx new file mode 100644 index 0000000..1c90679 --- /dev/null +++ b/client/src/components/providers.tsx @@ -0,0 +1,35 @@ +import { PrivyProvider } from "@privy-io/react-auth"; +import { toSolanaWalletConnectors } from "@privy-io/react-auth/solana"; + +const solanaConnectors = toSolanaWalletConnectors({ + // By default, shouldAutoConnect is enabled + shouldAutoConnect: true, +}); + +export default function Providers({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/client/src/components/sidebar.tsx b/client/src/components/sidebar.tsx new file mode 100644 index 0000000..4208e44 --- /dev/null +++ b/client/src/components/sidebar.tsx @@ -0,0 +1,114 @@ +import React, { useState } from "react"; +import { motion } from "framer-motion"; +import { + ChevronLeft, + ChevronRight, + UserPlus, + Users, + MessageSquare, + LogOut, +} from "lucide-react"; +import { Link, useNavigate } from "react-router-dom"; +import { usePrivy } from "@privy-io/react-auth"; + +const Sidebar = () => { + const [isOpen, setIsOpen] = useState(true); + const {logout} = usePrivy() + const navigate = useNavigate() + const toggleSidebar = () => { + setIsOpen(!isOpen); + }; + + const sidebarVariants = { + open: { + width: "300px", + transition: { + type: "spring", + stiffness: 200, + damping: 25, + }, + }, + closed: { + width: "0px", + transition: { + type: "spring", + stiffness: 200, + damping: 25, + }, + }, + }; + + const menuItems = [ + { icon: , text: "Create Agent", link: "/create-agent" }, + { icon: , text: "Browse Agents", link: "/browse-agents" }, + { icon: , text: "Chat with Agent", link: "/chat" }, + ]; + + return ( +
+ {/* Toggle Button - Positioned absolutely relative to the container */} + + + + {isOpen && ( +
+ {/* Logo Section */} + + Logo + + + {/* Navigation Items */} +
+ {menuItems.map((item, index) => ( + + + {item.icon} + {item.text} + + + ))} +
+ + {/* Logout Button */} + { + logout() + navigate("/") + }} + className="m-4 p-3 flex items-center rounded-lg bg-yellow-400 + hover:bg-yellow-500 text-gray-800 transition-colors justify-start" + > + + Logout + +
+ )} +
+
+ ); +}; + +export default Sidebar; diff --git a/client/src/components/tokenomics.tsx b/client/src/components/tokenomics.tsx new file mode 100644 index 0000000..eb4a16d --- /dev/null +++ b/client/src/components/tokenomics.tsx @@ -0,0 +1,92 @@ +import React from "react"; +import { PieChart, Pie, Cell, ResponsiveContainer } from "recharts"; +import { motion } from "framer-motion"; + +const COLORS = ["#F7A600", "#FFD700", "#FFEC80", "#D3D3D3"]; + +const data = [ + { name: "Developer Wallet Allocation", value: 5, color: "#F7A600" }, + { name: "Team", value: 2, color: "#FFD700" }, + { name: "Marketing & Operations", value: 1, color: "#FFEC80" }, + { name: "Treasury & Ecosystem", value: 2, color: "#D3D3D3" }, +]; + +const vestingData = [ + { name: "2% Locked (2 Years)", color: "#D3D3D3" }, + { name: "0.5% for Marketing", color: "#E0E0E0" }, + { name: "0.5% Treasury Liquidity", color: "#F0F0F0" }, +]; + +const Tokenomics: React.FC = () => { + return ( +
+ {/* Animated Pie Chart */} + + + + + {data.map((entry, index) => ( + + ))} + + + + + + {/* Tokenomics Info */} + +

Tokenomics

+

+ Transparent Allocation – Designed for Growth and Sustainability +

+ +
+ {data.map((item, index) => ( +
+
+

{item.name} ({item.value}%)

+
+ ))} +
+ +

Vesting Plan

+
+ {vestingData.map((item, index) => ( +
+
+

{item.name}

+
+ ))} +
+
+
+ ); +}; + +export default Tokenomics; diff --git a/client/src/components/ui/PixelCard.jsx b/client/src/components/ui/PixelCard.jsx new file mode 100644 index 0000000..bbb411e --- /dev/null +++ b/client/src/components/ui/PixelCard.jsx @@ -0,0 +1,274 @@ +import { useEffect, useRef } from "react"; + +class Pixel { + constructor(canvas, context, x, y, color, speed, delay) { + this.width = canvas.width; + this.height = canvas.height; + this.ctx = context; + this.x = x; + this.y = y; + this.color = color; + this.speed = this.getRandomValue(0.1, 0.9) * speed; + this.size = 0; + this.sizeStep = Math.random() * 0.4; + this.minSize = 0.5; + this.maxSizeInteger = 2; + this.maxSize = this.getRandomValue(this.minSize, this.maxSizeInteger); + this.delay = delay; + this.counter = 0; + this.counterStep = Math.random() * 4 + (this.width + this.height) * 0.01; + this.isIdle = false; + this.isReverse = false; + this.isShimmer = false; + } + + getRandomValue(min, max) { + return Math.random() * (max - min) + min; + } + + draw() { + const centerOffset = this.maxSizeInteger * 0.5 - this.size * 0.5; + this.ctx.fillStyle = this.color; + this.ctx.fillRect( + this.x + centerOffset, + this.y + centerOffset, + this.size, + this.size + ); + } + + appear() { + this.isIdle = false; + if (this.counter <= this.delay) { + this.counter += this.counterStep; + return; + } + if (this.size >= this.maxSize) { + this.isShimmer = true; + } + if (this.isShimmer) { + this.shimmer(); + } else { + this.size += this.sizeStep; + } + this.draw(); + } + + disappear() { + this.isShimmer = false; + this.counter = 0; + if (this.size <= 0) { + this.isIdle = true; + return; + } else { + this.size -= 0.1; + } + this.draw(); + } + + shimmer() { + if (this.size >= this.maxSize) { + this.isReverse = true; + } else if (this.size <= this.minSize) { + this.isReverse = false; + } + if (this.isReverse) { + this.size -= this.speed; + } else { + this.size += this.speed; + } + } +} + +function getEffectiveSpeed(value, reducedMotion) { + const min = 0; + const max = 100; + const throttle = 0.001; + const parsed = parseInt(value, 10); + + if (parsed <= min || reducedMotion) { + return min; + } else if (parsed >= max) { + return max * throttle; + } else { + return parsed * throttle; + } +} + +/** + * You can change/expand these as you like. + */ +const VARIANTS = { + default: { + activeColor: null, + gap: 5, + speed: 35, + colors: "#f8fafc,#f1f5f9,#cbd5e1", + noFocus: false + }, + blue: { + activeColor: "#e0f2fe", + gap: 10, + speed: 25, + colors: "#e0f2fe,#7dd3fc,#0ea5e9", + noFocus: false + }, + yellow: { + activeColor: "#fef08a", + gap: 3, + speed: 20, + colors: "#fef08a,#fde047,#eab308", + noFocus: false + }, + pink: { + activeColor: "#fecdd3", + gap: 6, + speed: 80, + colors: "#fecdd3,#fda4af,#e11d48", + noFocus: true + } +}; + +export default function PixelCard({ + variant = "default", + gap, + speed, + colors, + noFocus, + className = "", + children +}) { + const containerRef = useRef(null); + const canvasRef = useRef(null); + const pixelsRef = useRef([]); + const animationRef = useRef(null); + const timePreviousRef = useRef(performance.now()); + const reducedMotion = useRef( + window.matchMedia("(prefers-reduced-motion: reduce)").matches + ).current; + + const variantCfg = VARIANTS[variant] || VARIANTS.default; + const finalGap = gap ?? variantCfg.gap; + const finalSpeed = speed ?? variantCfg.speed; + const finalColors = colors ?? variantCfg.colors; + const finalNoFocus = noFocus ?? variantCfg.noFocus; + + const initPixels = () => { + if (!containerRef.current || !canvasRef.current) return; + + const rect = containerRef.current.getBoundingClientRect(); + const width = Math.floor(rect.width); + const height = Math.floor(rect.height); + const ctx = canvasRef.current.getContext("2d"); + + canvasRef.current.width = width; + canvasRef.current.height = height; + canvasRef.current.style.width = `${width}px`; + canvasRef.current.style.height = `${height}px`; + + const colorsArray = finalColors.split(","); + const pxs = []; + for (let x = 0; x < width; x += parseInt(finalGap, 10)) { + for (let y = 0; y < height; y += parseInt(finalGap, 10)) { + const color = + colorsArray[Math.floor(Math.random() * colorsArray.length)]; + + const dx = x - width / 2; + const dy = y - height / 2; + const distance = Math.sqrt(dx * dx + dy * dy); + const delay = reducedMotion ? 0 : distance; + + pxs.push( + new Pixel( + canvasRef.current, + ctx, + x, + y, + color, + getEffectiveSpeed(finalSpeed, reducedMotion), + delay + ) + ); + } + } + pixelsRef.current = pxs; + }; + + const doAnimate = (fnName) => { + animationRef.current = requestAnimationFrame(() => doAnimate(fnName)); + const timeNow = performance.now(); + const timePassed = timeNow - timePreviousRef.current; + const timeInterval = 1000 / 60; // ~60 FPS + + if (timePassed < timeInterval) return; + timePreviousRef.current = timeNow - (timePassed % timeInterval); + + const ctx = canvasRef.current?.getContext("2d"); + if (!ctx || !canvasRef.current) return; + + ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); + + let allIdle = true; + for (let i = 0; i < pixelsRef.current.length; i++) { + const pixel = pixelsRef.current[i]; + pixel[fnName](); + if (!pixel.isIdle) { + allIdle = false; + } + } + if (allIdle) { + cancelAnimationFrame(animationRef.current); + } + }; + + const handleAnimation = (name) => { + cancelAnimationFrame(animationRef.current); + animationRef.current = requestAnimationFrame(() => doAnimate(name)); + }; + + const onMouseEnter = () => handleAnimation("appear"); + const onMouseLeave = () => handleAnimation("disappear"); + const onFocus = (e) => { + if (e.currentTarget.contains(e.relatedTarget)) return; + handleAnimation("appear"); + }; + const onBlur = (e) => { + if (e.currentTarget.contains(e.relatedTarget)) return; + handleAnimation("disappear"); + }; + + useEffect(() => { + initPixels(); + const observer = new ResizeObserver(() => { + initPixels(); + }); + if (containerRef.current) { + observer.observe(containerRef.current); + } + return () => { + observer.disconnect(); + cancelAnimationFrame(animationRef.current); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [finalGap, finalSpeed, finalColors, finalNoFocus]); + + return ( +
+ + {children} +
+ ); +} diff --git a/client/src/components/ui/button.tsx b/client/src/components/ui/button.tsx new file mode 100644 index 0000000..dcfee0c --- /dev/null +++ b/client/src/components/ui/button.tsx @@ -0,0 +1,57 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "../../lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: + "bg-primary text-primary-foreground shadow hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", + outline: + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-8", + icon: "h-9 w-9", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/client/src/components/ui/card-new.tsx b/client/src/components/ui/card-new.tsx new file mode 100644 index 0000000..5e0de0a --- /dev/null +++ b/client/src/components/ui/card-new.tsx @@ -0,0 +1,176 @@ +import React from 'react'; +import styled, { keyframes } from 'styled-components'; + +interface CardProps { + heading?: string; + content?: string; + bgColor?: string; + bgColorLight?: string; + textColorHover?: string; + boxShadowColor?: string; + icon?: React.ReactNode; +} + +const sparkleAnimation = keyframes` + 0% { + transform: scale(0) rotate(0deg); + opacity: 0; + } + 50% { + transform: scale(1.2) rotate(180deg); + opacity: 1; + } + 100% { + transform: scale(0) rotate(360deg); + opacity: 0; + } +`; + +const CardWrapper = styled.div` + position: relative; + + .sparkle { + position: absolute; + width: 20px; + height: 20px; + background: radial-gradient(circle, rgba(255,255,255,0.8) 0%, transparent 70%); + border-radius: 50%; + pointer-events: none; + z-index: 1000; + animation: ${sparkleAnimation} 1s ease-in-out infinite; + } + + .custom-card { + width: 400px; + border-radius:15px; + height: 500px; + background: #fff; + border-top-right-radius: 10px; + overflow: hidden; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + position: relative; + box-shadow: 0 14px 26px rgba(0,0,0,0.1); + transition: all 0.3s ease-out; + text-decoration: none; + + /* Custom color variables */ + --bg-color: ${props => props.bgColor || '#ffd861'}; + --bg-color-light: ${props => props.bgColorLight || '#ffeeba'}; + --text-color-hover: ${props => props.textColorHover || '#4C5656'}; + --box-shadow-color: ${props => props.boxShadowColor || 'rgba(255, 215, 97, 0.48)'}; + } + + .custom-card:hover { + transform: translateY(-5px) scale(1.005) translateZ(0); + box-shadow: 0 24px 36px rgba(0,0,0,0.11), + 0 24px 46px var(--box-shadow-color); + } + + .custom-card:hover .overlay { + transform: scale(8) translateZ(0); + } + + .circle { + width: 131px; + height: 131px; + border-radius: 50%; + background: #fff; + border: 3px solid var(--bg-color); + display: flex; + justify-content: center; + align-items: center; + position: relative; + z-index: 1; + transition: all 0.3s ease-out; + margin-bottom: 20px; + } + + .circle svg { + width: 60px; + height: 60px; + color: var(--bg-color); + } + + .overlay { + width: 170px; + position: absolute; + height: 170px; + border-radius: 50%; + background: var(--bg-color); + top: 330px; + left: 400px; + z-index: 0; + transition: transform 0.3s ease-out; + } + + .card-heading { + font-size: 20px; + font-weight: bold; + color: #333; + margin-bottom: 15px; + z-index: 1000; + } + + .card-content { + font-size: 17px; + color: #4C5656; + text-align: center; + padding: 0 20px; + z-index: 1000; + transition: color 0.3s ease-out; + } +`; + +const Card: React.FC = ({ + heading, + content, + bgColor, + bgColorLight, + textColorHover, + boxShadowColor, + icon +}) => { + const renderSparkles = () => { + const sparkles = []; + for (let i = 0; i < 5; i++) { + sparkles.push( +
+ ); + } + return sparkles; + }; + + return ( + +
+
+ {renderSparkles()} +
+
+ {icon} +
+ {heading &&
{heading}
} + {content &&
{content}
} +
+
+ + ); +}; + +export default Card; \ No newline at end of file diff --git a/client/src/components/ui/hero-backround.tsx b/client/src/components/ui/hero-backround.tsx new file mode 100644 index 0000000..6c15435 --- /dev/null +++ b/client/src/components/ui/hero-backround.tsx @@ -0,0 +1,127 @@ +import { Canvas, useFrame, useThree } from '@react-three/fiber'; +import { useRef, useEffect, useState } from 'react'; +import * as THREE from 'three'; +import Stats from 'three/addons/libs/stats.module.js'; + +function SmokeParticles() { + const groupRef = useRef(); + const particlesRef = useRef([]); + const clockRef = useRef(new THREE.Clock()); + const statsRef = useRef(); + const { scene, size } = useThree(); + const [mousePos, setMousePos] = useState({ x: 0, y: 0 }); + + + + // Update mouse position + useEffect(() => { + const handleMouseMove = (event) => { + const x = (event.clientX / size.width) * 2 - 1; + const y = -(event.clientY / size.height) * 2 + 1; + setMousePos({ x, y }); + }; + + window.addEventListener("mousemove", handleMouseMove); + return () => window.removeEventListener("mousemove", handleMouseMove); + }, [size]); + + // Create smoke particles + useEffect(() => { + const loader = new THREE.TextureLoader(); + + loader.load('https://s3-us-west-2.amazonaws.com/s.cdpn.io/95637/Smoke-Element.png', (smokeTexture) => { + const smokeGeo = new THREE.PlaneGeometry(300, 300); + + for (let p = 0; p < 150; p++) { + const colors = [0xFFD700, 0xFACC15, 0xFFA500]; // Gold, yellow-400, amber + const randomColor = colors[Math.floor(Math.random() * colors.length)]; + + const smokeMaterial = new THREE.MeshLambertMaterial({ + color: randomColor, + map: smokeTexture, + transparent: true, + opacity: Math.random() * 0.6 + 0.4, // More vibrant + emissive: randomColor, + emissiveIntensity: 1.2, // Stronger glow + }); + + const particle = new THREE.Mesh(smokeGeo, smokeMaterial); + particle.position.set( + Math.random() * 500 - 250, + Math.random() * 500 - 250, + Math.random() * 1000 - 100 + ); + particle.rotation.z = Math.random() * 360; + particle.userData.originalPos = { ...particle.position }; + groupRef.current.add(particle); + particlesRef.current.push(particle); + } + }); + + // Add a golden light for extra glow + const light = new THREE.PointLight(0xFFD700, 2.0, 2000); + light.position.set(0, 0, 500); + scene.add(light); + + }, [scene]); + + // Animation loop with smooth wind effect + useFrame(() => { + if (statsRef.current) statsRef.current.begin(); + + const delta = clockRef.current.getDelta(); + + particlesRef.current.forEach((particle) => { + if (particle) { + particle.rotation.z += delta * 0.2; + + // Get particle position + const particlePos = particle.position; + const originalPos = particle.userData.originalPos; + + // Calculate distance from mouse + const dx = (mousePos.x * 500) - particlePos.x; + const dy = (mousePos.y * 500) - particlePos.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + // Wind effect: move particles with a slight random offset + if (distance < 150) { + particlePos.x += dx * 0.02 + (Math.random() - 0.5) * 2; + particlePos.y += dy * 0.02 + (Math.random() - 0.5) * 2; + } + + // Smoothly return particles to original position but with a slight offset + particlePos.x = THREE.MathUtils.lerp(particlePos.x, originalPos.x + (Math.random() - 0.5) * 5, delta); + particlePos.y = THREE.MathUtils.lerp(particlePos.y, originalPos.y + (Math.random() - 0.5) * 5, delta); + } + }); + + if (statsRef.current) statsRef.current.end(); + }); + + return ; +} + +export default function SmokeEffect() { + return ( + + + + ); +} diff --git a/client/src/components/ui/info-card.tsx b/client/src/components/ui/info-card.tsx new file mode 100644 index 0000000..f966314 --- /dev/null +++ b/client/src/components/ui/info-card.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { motion } from "framer-motion"; + +interface CardProps { + number: string; + heading: string; + description: string; + numberBgColor: string; + numberTextColor: string; +} + +const InfoCard: React.FC = ({ + number, + heading, + description, + numberBgColor, + numberTextColor, +}) => { + return ( + +
+
+

{number}

+
+

{heading}

+
+

{description}

+
+ ); +}; + +export default InfoCard; diff --git a/client/src/components/ui/navbar-menu.tsx b/client/src/components/ui/navbar-menu.tsx new file mode 100644 index 0000000..88cc418 --- /dev/null +++ b/client/src/components/ui/navbar-menu.tsx @@ -0,0 +1,119 @@ +import React from "react"; +import { motion } from "framer-motion"; +import {Link} from "react-router-dom" + +const transition = { + type: "spring", + mass: 0.5, + damping: 11.5, + stiffness: 100, + restDelta: 0.001, + restSpeed: 0.001, +}; + +export const MenuItem = ({ + setActive, + active, + item, + children, +}: { + setActive: (item: string) => void; + active: string | null; + item: string; + children?: React.ReactNode; +}) => { + return ( +
setActive(item)} className="relative "> + + {item} + + {active !== null && ( + + {active === item && ( +
+ + + {children} + + +
+ )} +
+ )} +
+ ); +}; + +export const Menu = ({ + setActive, + children, +}: { + setActive: (item: string | null) => void; + children: React.ReactNode; +}) => { + return ( + + ); +}; + +export const ProductItem = ({ + title, + description, + href, + src, +}: { + title: string; + description: string; + href: string; + src: string; +}) => { + return ( + + {title} +
+

+ {title} +

+

+ {description} +

+
+ + ); +}; + +export const HoveredLink = ({ children, ...rest }: any) => { + return ( + + {children} + + ); +}; diff --git a/client/src/context/AgentContext.tsx b/client/src/context/AgentContext.tsx new file mode 100644 index 0000000..051f2f3 --- /dev/null +++ b/client/src/context/AgentContext.tsx @@ -0,0 +1,27 @@ +import React, { createContext, useContext, useState } from 'react'; +import { Agent } from '../interfaces/AgentInterfaces'; + +interface AgentContextType { + selectedAgent: Agent | null; + setSelectedAgent: (agent: Agent | null) => void; +} + +const AgentContext = createContext(undefined); + +export const AgentProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [selectedAgent, setSelectedAgent] = useState(null); + + return ( + + {children} + + ); +}; + +export const useAgent = () => { + const context = useContext(AgentContext); + if (context === undefined) { + throw new Error('useAgent must be used within an AgentProvider'); + } + return context; +}; \ No newline at end of file diff --git a/client/src/hooks/useCharacterSelection.ts b/client/src/hooks/useCharacterSelection.ts new file mode 100644 index 0000000..3f93191 --- /dev/null +++ b/client/src/hooks/useCharacterSelection.ts @@ -0,0 +1,30 @@ +import { useState } from 'react'; + +/** + * useCharacterSelection - A custom hook to manage character selection logic. + * + * Responsibilities: + * 1. Manages the `selectedCharacter` state. + * 2. Provides a function (`handleCharacterSelect`) to update the selected character. + * 3. Ensures the selected character data is validated before updating. + */ +const useCharacterSelection = () => { + const [selectedCharacter, setSelectedCharacter] = useState(null); // Holds the currently selected character. + + /** + * handleCharacterSelect - Updates the selected character. + * + * @param character - The character to select. + */ + const handleCharacterSelect = (character: any) => { + if (!character?.agent?.agent_details) { + console.error('Invalid character data'); // Log an error if the character data structure is invalid. + return; + } + setSelectedCharacter(character); // Update the selected character state. + }; + + return { selectedCharacter, handleCharacterSelect, setSelectedCharacter }; // Return the state and handlers for character selection. +}; + +export default useCharacterSelection; diff --git a/client/src/hooks/useCharacters.ts b/client/src/hooks/useCharacters.ts new file mode 100644 index 0000000..af60b0f --- /dev/null +++ b/client/src/hooks/useCharacters.ts @@ -0,0 +1,37 @@ +import useFetchCharacters from './useFetchCharacters'; +import useProcessCharacters from './useProcessCharacters'; +import useCharacterSelection from './useCharacterSelection'; +import { useEffect } from 'react'; + +/** + * useCharacters - A composed custom hook to manage character data and selection. + * + * Responsibilities: + * 1. Fetches raw character data using `useFetchCharacters`. + * 2. Processes raw character data into a usable format using `useProcessCharacters`. + * 3. Manages character selection logic using `useCharacterSelection`. + * 4. Automatically selects the first character by default when characters are loaded. + */ +const useCharacters = () => { + const { characters: rawCharacters, loading, error } = useFetchCharacters(); // Fetch raw characters and track fetch state. + const processedCharacters = useProcessCharacters(rawCharacters); // Process the raw characters into a usable format. + const { selectedCharacter, handleCharacterSelect, setSelectedCharacter } = useCharacterSelection(); // Manage character selection. + + // Automatically select the first character when characters are loaded. + useEffect(() => { + if (!selectedCharacter && processedCharacters.length > 0) { + handleCharacterSelect(processedCharacters[0]); // Select the first character by default. + } + }, [processedCharacters, selectedCharacter, handleCharacterSelect]); + + return { + characters: processedCharacters, // The processed characters data. + selectedCharacter, // The currently selected character. + setSelectedCharacter, // Function to manually update the selected character. + loading, // Whether the characters are still being fetched. + error, // Any error that occurred during the fetch. + handleCharacterSelect, // Function to select a character. + }; +}; + +export default useCharacters; diff --git a/client/src/hooks/useFetchCharacters.ts b/client/src/hooks/useFetchCharacters.ts new file mode 100644 index 0000000..6ecc5ba --- /dev/null +++ b/client/src/hooks/useFetchCharacters.ts @@ -0,0 +1,42 @@ +import { useState, useEffect } from 'react'; +import { getCharacters } from '../api/agentsAPI'; +import { Agent } from '../interfaces/AgentInterfaces'; + +/** + * useFetchCharacters - A custom hook to fetch characters from the API. + * + * Responsibilities: + * 1. Handles the API call to GET/fetch character data. + * 2. Manages the state for `characters`, `loading`, and `error`. + * 3. Provides feedback on the fetch process (loading and error states). + */ +const useFetchCharacters = () => { +const [characters, setCharacters] = useState([]); // Explicitly typed with your Agent interface +const [loading, setLoading] = useState(true); // Tracks whether the API call is in progress. + const [error, setError] = useState(null); // Tracks any errors during the fetch process. + + useEffect(() => { + const fetchCharacters = async () => { + setLoading(true); // Mark the fetch as in progress. + try { + const data = await getCharacters(); // Call the API to fetch characters. + if (!Array.isArray(data)) { + throw new Error(`Expected array, received: ${typeof data}`); // Validate the response type. + } + setCharacters(data); // Save the fetched data to state. + } catch (err) { + // Handle errors during the fetch process. + const error = err instanceof Error ? err : new Error('Failed to fetch characters'); + setError(error); + } finally { + setLoading(false); // Mark the fetch as complete, regardless of success or failure. + } + }; + + fetchCharacters(); // Execute the fetch when the hook is used. + }, []); // Dependency array is empty to ensure this runs only once on component mount. + + return { characters, loading, error }; // Return the characters data and state for use in components. +}; + +export default useFetchCharacters; diff --git a/client/src/hooks/useProcessCharacters.ts b/client/src/hooks/useProcessCharacters.ts new file mode 100644 index 0000000..793a841 --- /dev/null +++ b/client/src/hooks/useProcessCharacters.ts @@ -0,0 +1,44 @@ +/** + * useProcessCharacters - A custom hook to transform raw character data into a usable format. + * + * Responsibilities: + * 1. Maps and processes raw data from the API. + * 2. Ensures all necessary fields are structured and default values are applied. + * 3. Provides a consistent and clean data structure for use in the application. + */ +const useProcessCharacters = (characters: any[]) => { + const processedCharacters = characters.map((char) => { + // Char is the raw data from the API + // Destructure the char object to extract the necessary fields + const { agent } = char; + + return { + agent: { + agent_details: { + name: agent?.agent_details?.name || '', + personality: agent?.agent_details?.personality || [], + communication_style: agent?.agent_details?.communication_style || [], + backstory: agent?.agent_details?.backstory || '', + universe: agent?.agent_details?.universe || '', + topic_expertise: agent?.agent_details?.topic_expertise || [], + hashtags: agent?.agent_details?.hashtags || [], + emojis: agent?.agent_details?.emojis || [], + concept: agent?.concept || '', + }, + profile_image: agent?.profile_image || {}, + concept: agent?.concept || '', + profile_image_options: agent?.profile_image_options || [], + ai_model: agent?.ai_model || {}, + connectors: agent?.connectors || {}, + seasons: agent?.seasons || [], + tracker: agent?.tracker || {}, + master_file_path: agent?.master_file_path || '', + } + }; + }); + + return processedCharacters; // Return the processed data for use in the application. + }; + + export default useProcessCharacters; + \ No newline at end of file diff --git a/client/src/index.css b/client/src/index.css new file mode 100644 index 0000000..b6f33b5 --- /dev/null +++ b/client/src/index.css @@ -0,0 +1,26 @@ +@import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&display=swap"); +@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap'); + +@tailwind base; +@tailwind components; +@tailwind utilities; + +.font-orbitron{ + font-family: "Orbitron", sans-serif; +} + +/* @layer base { + body { + @apply m-0 min-h-screen bg-slate-950; + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } +} + +/* Only keep custom styles that can't be handled by Tailwind */ +/* @layer components { + .gradient-text { + @apply bg-gradient-to-r from-cyan-400 via-orange-400 to-red-400 bg-clip-text text-transparent; + } +} */ diff --git a/client/src/interfaces/AgentInterfaces.ts b/client/src/interfaces/AgentInterfaces.ts new file mode 100644 index 0000000..30e3e01 --- /dev/null +++ b/client/src/interfaces/AgentInterfaces.ts @@ -0,0 +1,143 @@ +export interface AgentDetails { + id?: string | number; + name: string; + personality: string[]; + communication_style: string[]; + backstory: string; + universe: string; + topic_expertise: string[]; + hashtags: string[]; + emojis: string[]; +} + +export interface ProfileImage { + details: { + url: string; + image_id: string; + generationId: string; + }; + [key: string]: unknown; +} + +export interface GeneratedImage { + url: string; + id: string; + generationId: string; +} + +export interface GenerationsByPk { + id?: string; + prompt?: string; + generated_images: GeneratedImage[]; +} + +export interface ProfileImageOption { + generations_by_pk: GenerationsByPk; +} + +export interface Agent { + id?: string | number; + name?: string; + avatar?: string; + shortDescription?: string; + tags?: string[]; + personality?: string[]; + communicationStyle?: string[]; + emojis?: string[]; + hashtags?: string[]; + universe?: string; + backstory?: string; + concept?: string; + role?: string; + isLoading?: boolean; + leonardoResponse?: any; + leonardoImage?: any; + topic_expertise?: string[]; + agent?: { + concept: string; + agent_details: AgentDetails; + ai_model: { + memory_store: string; + model_name: string; + model_type: string; + }; + connectors: { + discord: boolean; + telegram: boolean; + twitter: boolean; + }; + tracker: { + messages_sent: number; + total_interactions: number; + current_episode_number: number; + current_post_number: number; + current_season_number: number; + post_every_x_minutes: number; + }; + seasons: any[]; + profile_image: ProfileImage; + profile_image_options: ProfileImageOption[]; + master_file_path?: string; + }; + isExample?: boolean; +} + +export function createBlankAgent(): Agent { + return { + id: '', + name: '', + avatar: '', + agent: { + concept: '', + agent_details: { + backstory: '', + communication_style: [], + emojis: [], + hashtags: [], + name: '', + personality: [], + topic_expertise: [], + universe: '' + }, + ai_model: { + memory_store: '', + model_name: '', + model_type: '' + }, + connectors: { + discord: false, + telegram: false, + twitter: false + }, + tracker: { + messages_sent: 0, + total_interactions: 0, + current_episode_number: 0, + current_post_number: 0, + current_season_number: 0, + post_every_x_minutes: 0 + }, + seasons: [], + profile_image: { + details: { + url: '', + image_id: '', + generationId: '' + } + }, + profile_image_options: [] as ProfileImageOption[] + } + }; +} + +export interface RandomAgentCardProps { + agent: Agent; + onSelect: (agent: Agent | null) => void; + onAddAgent: (agent: Agent) => void; + isUserAgent: boolean; + setRandomAgents: React.Dispatch>; + generateRandomAgentData: () => Promise; + isLoadedAgent: boolean; + onRegenerate: (agentId: string) => Promise; + isLoading?: boolean; +} \ No newline at end of file diff --git a/client/src/interfaces/ChatInterfaces.ts b/client/src/interfaces/ChatInterfaces.ts new file mode 100644 index 0000000..63304e8 --- /dev/null +++ b/client/src/interfaces/ChatInterfaces.ts @@ -0,0 +1,11 @@ +export interface Message { + role: string; + message?: string; + response?: string; + message_id: number; +} + +export interface ChatHistory { + agent_name: string; + chat_history: Message[]; +} \ No newline at end of file diff --git a/client/src/interfaces/LeonardoInterfaces.ts b/client/src/interfaces/LeonardoInterfaces.ts new file mode 100644 index 0000000..28ecb77 --- /dev/null +++ b/client/src/interfaces/LeonardoInterfaces.ts @@ -0,0 +1,8 @@ +// Define the payload type for better type-checking +export interface LambdaPayload { + prompt: string; + modelId: string; + styleUUID: string; + num_images: number; + } + diff --git a/client/src/interfaces/PostsInterface.ts b/client/src/interfaces/PostsInterface.ts new file mode 100644 index 0000000..7e1b57b --- /dev/null +++ b/client/src/interfaces/PostsInterface.ts @@ -0,0 +1,21 @@ +export interface Post { + post_id: string; + post_number: number; + post_content: string; + post_highlights?: string; + post_posted: boolean; + seasonNumber?: number; + episodeNumber?: number; + episodeName?: string; + } + +export interface Episode { + episode_number: number; + episode_name: string; + posts: Post[]; +} + +export interface Season { + season_number: number; + episodes: Episode[]; +} \ No newline at end of file diff --git a/client/src/interfaces/SeasonInterfaces.ts b/client/src/interfaces/SeasonInterfaces.ts new file mode 100644 index 0000000..00a5b5c --- /dev/null +++ b/client/src/interfaces/SeasonInterfaces.ts @@ -0,0 +1,29 @@ +export interface Post { + post_id: string; + post_number: number; + post_content: string; + post_highlights: string; + post_posted: boolean; +} + +export interface Episode { + episode_name: string; + episode_number: number; + episode_description: string; + episode_highlights: string; + episode_summary: string; + episode_posted: boolean; + current_post_number: number; + posts: Post[]; +} + +export interface Season { + season_name: string; + season_number: number; + season_description: string; + season_highlights: string; + season_summary: string; + season_posted: false; + current_episode_number: number; + episodes: Episode[]; +} \ No newline at end of file diff --git a/client/src/interfaces/SocialFeedInterfaces.ts b/client/src/interfaces/SocialFeedInterfaces.ts new file mode 100644 index 0000000..e69de29 diff --git a/client/src/interfaces/TraitButtonsProps.ts b/client/src/interfaces/TraitButtonsProps.ts new file mode 100644 index 0000000..71afab9 --- /dev/null +++ b/client/src/interfaces/TraitButtonsProps.ts @@ -0,0 +1,7 @@ +import { AgentDetails } from './AgentInterfaces'; + +export interface TraitButtonsProps { + field: keyof AgentDetails; + options: string[]; + onTraitButtonClick: (field: keyof AgentDetails, value: string) => void; +} \ No newline at end of file diff --git a/client/src/lib/utils.ts b/client/src/lib/utils.ts new file mode 100644 index 0000000..cec6ac9 --- /dev/null +++ b/client/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/client/src/main.tsx b/client/src/main.tsx new file mode 100644 index 0000000..85624e3 --- /dev/null +++ b/client/src/main.tsx @@ -0,0 +1,20 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' +import { BrowserRouter as Router } from 'react-router-dom'; +import Providers from './components/providers.tsx'; +import { AgentProvider } from './context/AgentContext.tsx'; + + +createRoot(document.getElementById('root')!).render( + + + + + + + + + , +) diff --git a/client/src/pages/AgentCreator.tsx b/client/src/pages/AgentCreator.tsx new file mode 100644 index 0000000..a26e8a6 --- /dev/null +++ b/client/src/pages/AgentCreator.tsx @@ -0,0 +1,999 @@ +import React, { useState, ChangeEvent, useEffect, KeyboardEvent } from "react"; +import { Brain, Wand2, MessageSquare, Save, RefreshCcw, Layers } from "lucide-react"; +import { createAgent, getCharacters } from "../api/agentsAPI"; +import { + GeneratedImage, + ProfileImageOption, +} from "../interfaces/AgentInterfaces"; +import TraitButtons from "../components/TraitButtons"; // We'll still use your TraitButtons +import useCharacters from "../hooks/useCharacters"; +import { inconsistentImageLambda } from "../api/leonardoApi"; +import LoadingBar from "../components/LoadingBar"; +import Sidebar from "../components/sidebar"; +import { motion } from "framer-motion"; + +const LEONARDO_MODEL_ID = "e71a1c2f-4f80-4800-934f-2c68979d8cc8"; +const LEONARDO_STYLE_UUID = "b2a54a51-230b-4d4f-ad4e-8409bf58645f"; + + +/** + * AgentCreator Component + * A form-based interface for creating and editing AI agents with various attributes + * including personality traits, communication style, and profile images. + */ +const AgentCreator: React.FC = () => { + + /** + * Main UI state management + * activeTab controls which section of the form is visible: + * - basic: name, universe, expertise + * - personality: personality traits, backstory + * - style: communication style, hashtags, emojis + */ + const [activeTab, setActiveTab] = useState<"basic" | "personality" | "style">( + "basic" + ); + + /** + * Core agent state + * Maintains the complete agent object including: + * - agent_details: main characteristics and traits + * - profile_image: currently selected image + * - profile_image_options: available image choices + * - selectedImage: index of chosen image + * - seasons: associated seasons/episodes + */ + const [agent, setAgent] = useState<{ + agent_details: { + name: string; + personality: string[]; + communication_style: string[]; + backstory: string; + universe: string; + topic_expertise: string[]; + hashtags: string[]; + emojis: string[]; + concept: string; + }; + profile_image: { + details: { + url: string; + image_id: string; + generationId: string; + }; + }; + profile_image_options: ProfileImageOption[]; + selectedImage: number | undefined; + seasons: any[]; + }>({ + agent_details: { + name: "", + personality: [], + communication_style: [], + backstory: "", + universe: "", + topic_expertise: [], + hashtags: [], + emojis: [], + concept: "", + }, + profile_image: { + details: { + url: "", + image_id: "", + generationId: "", + }, + }, + profile_image_options: [], + selectedImage: undefined, + seasons: [], + }); + + // The fetched characters + const { characters, loading, error } = useCharacters(); + + /** + * Draft field management + * Maintains temporary states for text fields before they're committed to the main agent state + * Prevents immediate updates and allows for Enter-to-commit functionality + */ + const [draftFields, setDraftFields] = useState({ + name: "", + universe: "", + backstory: "", + imageDescription: "", + }); + + /** + * Synchronization Effects + * Keep draft states in sync with the main agent state + * Ensures drafts are updated when agent data changes + */ + useEffect(() => { + setDraftFields({ + name: agent.agent_details.name || "", + universe: agent.agent_details.universe || "", + backstory: agent.agent_details.backstory || "", + imageDescription: + agent.profile_image_options?.[0]?.generations_by_pk?.prompt || "", + }); + }, [agent]); + + /** + * Draft traits management + * Handles temporary states for array-based fields (traits, hashtags, etc.) + * Stores them as comma-separated strings until committed + */ + const [draftTraits, setDraftTraits] = useState<{ + topic_expertise: string; + personality: string; + communication_style: string; + hashtags: string; + emojis: string; + }>({ + topic_expertise: "", + personality: "", + communication_style: "", + hashtags: "", + emojis: "", + }); + + /** + * Synchronization Effects + * Keep draft states in sync with the main agent state + * Ensures drafts are updated when agent data changes + */ + useEffect(() => { + setDraftTraits({ + topic_expertise: agent.agent_details.topic_expertise.join(", "), + personality: agent.agent_details.personality.join(", "), + communication_style: agent.agent_details.communication_style.join(", "), + hashtags: agent.agent_details.hashtags.join(", "), + emojis: agent.agent_details.emojis.join(" "), + }); + }, [agent]); + + /** + * Profile Image Management + * Handles initialization and updates of the agent's profile image + * Sets default placeholder if no images are available + */ + useEffect(() => { + if (agent.profile_image_options.length > 0) { + const firstImage = + agent.profile_image_options[0]?.generations_by_pk + ?.generated_images?.[0] ?? + ({ + url: "https://via.placeholder.com/400x400?text=Brain+Placeholder", + id: "", + generationId: "", + } as GeneratedImage); + + setAgent((prev) => ({ + ...prev, + selectedImage: + prev.selectedImage !== undefined ? prev.selectedImage : 0, + profile_image: { + details: { + url: firstImage.url, + image_id: firstImage.id, + generationId: firstImage.generationId, + }, + }, + })); + } else { + setAgent((prev) => ({ + ...prev, + profile_image: { + details: { + url: "https://via.placeholder.com/400x400?text=Brain+Placeholder", + image_id: "", + generationId: "", + }, + }, + })); + } + }, [agent.profile_image_options]); + + /** + * Field Update Handlers + * Manages updates to regular text fields (name, universe, backstory) + * Commits changes when Enter is pressed + */ + const handleDraftChange = + (field: keyof typeof draftFields) => + (e: ChangeEvent) => { + setDraftFields((prev) => ({ + ...prev, + [field]: e.target.value, + })); + }; + + const handleDraftKeyDown = + (field: keyof typeof draftFields) => + (e: KeyboardEvent) => { + if (e.key === "Enter") { + e.preventDefault(); + + if (field === "imageDescription") { + setAgent((prev) => { + const newImageOption: ProfileImageOption = { + generations_by_pk: { + prompt: draftFields[field], + generated_images: [], + }, + }; + + return { + ...prev, + profile_image_options: prev.profile_image_options?.length + ? prev.profile_image_options.map((option, index) => + index === 0 + ? { + ...option, + generations_by_pk: { + ...option.generations_by_pk, + prompt: draftFields[field], + }, + } + : option + ) + : [newImageOption], + }; + }); + } else { + setAgent((prev) => ({ + ...prev, + agent_details: { + ...prev.agent_details, + [field]: draftFields[field], + }, + })); + } + } + }; + + /** + * Trait Field Handlers + * Manages updates to array-based fields (personality, hashtags, etc.) + * Splits input by commas (or spaces for emojis) and commits on Enter + */ + const handleTraitDraftChange = + (field: keyof typeof draftTraits) => + (e: ChangeEvent) => { + setDraftTraits((prev) => ({ + ...prev, + [field]: e.target.value, + })); + }; + + const handleTraitDraftKeyDown = + (field: keyof typeof draftTraits) => + (e: KeyboardEvent) => { + if (e.key === "Enter") { + e.preventDefault(); + // If field is "emojis", we split by space; otherwise, by comma + const separator = field === "emojis" ? " " : ","; + const arrayValue = draftTraits[field] + .split(separator) + .map((item) => item.trim()) + .filter(Boolean); + + setAgent((prev) => ({ + ...prev, + agent_details: { + ...prev.agent_details, + [field]: arrayValue, + }, + })); + } + }; + + // Deleting a single trait + type TraitField = + | "personality" + | "communication_style" + | "topic_expertise" + | "hashtags" + | "emojis"; + + const handleDeleteTrait = (field: TraitField, value: string) => { + setAgent((prev) => ({ + ...prev, + agent_details: { + ...prev.agent_details, + [field]: prev.agent_details[field].filter( + (trait: string) => trait !== value + ), + }, + })); + }; + + // State to manage the visibility of the success message + const [showSuccessMessage, setShowSuccessMessage] = useState(false); + + // Add state for loading progress near other state declarations + const [loadingProgress, setLoadingProgress] = useState(0); + + // Add a new state for tracking image generation + const [isGenerating, setIsGenerating] = useState(false); + + const [selectedCharacterIndex, setSelectedCharacterIndex] = + useState(-1); + + /** + * Form Submission Handler + * Processes the final agent data and sends it to the server + * Shows success message on completion + */ + const handleSubmitCreateAgent = async ( + event: + | React.FormEvent + | React.MouseEvent + ) => { + event.preventDefault(); + + type AgentState = typeof agent; + + const updatedAgent: AgentState = { + ...agent, + agent_details: { + ...agent.agent_details, + name: draftFields.name || agent.agent_details.name, + universe: draftFields.universe || agent.agent_details.universe, + backstory: draftFields.backstory || agent.agent_details.backstory, + concept: agent.agent_details.concept, + + personality: draftTraits.personality + ? draftTraits.personality + .split(",") + .map((item) => item.trim()) + .filter(Boolean) + : agent.agent_details.personality, + + communication_style: draftTraits.communication_style + ? draftTraits.communication_style + .split(",") + .map((item) => item.trim()) + .filter(Boolean) + : agent.agent_details.communication_style, + + topic_expertise: draftTraits.topic_expertise + ? draftTraits.topic_expertise + .split(",") + .map((item) => item.trim()) + .filter(Boolean) + : agent.agent_details.topic_expertise, + + hashtags: draftTraits.hashtags + ? draftTraits.hashtags + .split(",") + .map((item) => item.trim()) + .filter(Boolean) + : agent.agent_details.hashtags, + + emojis: draftTraits.emojis + ? draftTraits.emojis.split(" ").filter(Boolean) + : agent.agent_details.emojis, + }, + profile_image: agent.profile_image, + profile_image_options: agent.profile_image_options.map((option, index) => + index === 0 + ? { + ...option, + generations_by_pk: { + ...option.generations_by_pk, + prompt: + draftFields.imageDescription || + option.generations_by_pk?.prompt, + }, + } + : option + ), + selectedImage: agent.selectedImage, + seasons: agent.seasons, + }; + + try { + + await createAgent(updatedAgent); + + setShowSuccessMessage(true); + setTimeout(() => setShowSuccessMessage(false), 3000); + setAgent(updatedAgent); + } catch (error) { + console.error("Error creating agent:", error); + } + }; + + // + // ────────────────────────────────────────────────────────────────────────────── + // 8) Load characters + // ────────────────────────────────────────────────────────────────────────────── + // + useEffect(() => { + const loadCharacters = async () => { + try { + const charactersData = await getCharacters(); + if (!Array.isArray(charactersData)) { + console.error( + "Expected array of characters, received:", + typeof charactersData + ); + return; + } + + const processed = charactersData.map((char) => { + const agentProfileImageOptions = char.agent.profile_image_options; + const agentConcept = char.concept; + const { agent } = char; + if (!agent) return { agent: {} }; + + const { + agent_details: { + name = "", + personality = [], + communication_style = [], + backstory = "", + universe = "", + topic_expertise = [], + hashtags = [], + emojis = [], + } = {}, + ai_model = {}, + connectors = {}, + seasons = [], + tracker = {}, + } = agent; + + return { + agent: { + agent_details: { + name, + personality, + communication_style, + backstory, + universe, + topic_expertise, + hashtags, + emojis, + }, + ai_model, + connectors, + profile_image: agentProfileImageOptions || [], + seasons, + tracker, + }, + concept: agentConcept || "", + }; + }); + console.log("Processed characters:", processed); + } catch (error) { + console.error("Error loading characters:", error); + } + }; + + loadCharacters(); + }, []); + + /** + * Character Selection Handler + * Populates the form with data from an existing character + * Updates both main agent state and draft states + */ + const handleCharacterSelect = (e: ChangeEvent) => { + const selectedIndex = parseInt(e.target.value); + setSelectedCharacterIndex(selectedIndex); + const char = characters[selectedIndex]; + if (!char?.agent?.agent_details) return; + + const details = char.agent.agent_details; + + setAgent({ + agent_details: { + name: details.name || "", + personality: details.personality || [], + communication_style: details.communication_style || [], + backstory: details.backstory || "", + universe: details.universe || "", + topic_expertise: details.topic_expertise || [], + hashtags: details.hashtags || [], + emojis: details.emojis || [], + concept: details.concept || "", + }, + profile_image: char.agent?.profile_image_options || [], + profile_image_options: char.agent?.profile_image_options || [], + selectedImage: char.agent?.profile_image_options?.[0]?.generations_by_pk + ?.generated_images?.length + ? 0 + : undefined, + seasons: char.agent?.seasons || [], + }); + + // Sync local drafts + setDraftFields({ + name: details.name || "", + universe: details.universe || "", + backstory: details.backstory || "", + imageDescription: + char.agent?.profile_image_options?.[0]?.generations_by_pk?.prompt || "", + }); + + setDraftTraits({ + topic_expertise: (details.topic_expertise || []).join(", "), + personality: (details.personality || []).join(", "), + communication_style: (details.communication_style || []).join(", "), + hashtags: (details.hashtags || []).join(", "), + emojis: (details.emojis || []).join(" "), + }); + }; + + // + // ────────────────────────────────────────────────────────────────────────────── + // 10) Render + // ────────────────────────────────────────────────────────────────────────────── + // + return ( +
+ + +
+ {/* Success Message */} + + {showSuccessMessage && ( +
+
+ Agent successfully saved! +
+
+ )} + + {/* div */} +
+
+ Create Agent +
+
+
+
+
+ + + {/* Left Panel */} +
+
+ {[ + { id: "basic" as const, icon: Brain, label: "Basic Info" }, + { id: "personality" as const, icon: Wand2, label: "Personality" }, + { id: "style" as const, icon: MessageSquare, label: "Style" }, + ].map(({ id, icon: Icon, label }) => ( + + ))} +
+ + {/* Form */} +
+ {activeTab === "basic" && ( +
+ {/* Agent Name => local draft */} +
+ +