move studio to a package

This commit is contained in:
Jack Gerrits
2024-09-30 10:20:38 -04:00
parent 3a62798af8
commit 2596648fc9
82 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
database.sqlite
.cache/*
autogenstudio/web/files/user/*
autogenstudio/test
autogenstudio/web/files/ui/*
OAI_CONFIG_LIST
scratch/
autogenstudio/web/workdir/*
autogenstudio/web/ui/*
autogenstudio/web/skills/user/*
.release.sh
.nightly.sh
notebooks/work_dir/*
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

View File

@@ -0,0 +1,17 @@
FROM python:3.10
WORKDIR /code
RUN pip install -U gunicorn autogenstudio
RUN useradd -m -u 1000 user
USER user
ENV HOME=/home/user \
PATH=/home/user/.local/bin:$PATH \
AUTOGENSTUDIO_APPDIR=/home/user/app
WORKDIR $HOME/app
COPY --chown=user . $HOME/app
CMD gunicorn -w $((2 * $(getconf _NPROCESSORS_ONLN) + 1)) --timeout 12600 -k uvicorn.workers.UvicornWorker autogenstudio.web.app:app --bind "0.0.0.0:8081"

View File

@@ -0,0 +1,7 @@
recursive-include autogenstudio/web/ui *
recursive-include autogenstudio/web/database.sqlite
recursive-exclude notebooks *
recursive-exclude frontend *
recursive-exclude docs *
recursive-exclude tests *

View File

@@ -0,0 +1,123 @@
# AutoGen Studio
[![PyPI version](https://badge.fury.io/py/autogenstudio.svg)](https://badge.fury.io/py/autogenstudio)
[![Downloads](https://static.pepy.tech/badge/autogenstudio/week)](https://pepy.tech/project/autogenstudio)
![ARA](./docs/ara_stockprices.png)
AutoGen Studio is an AutoGen-powered AI app (user interface) to help you rapidly prototype AI agents, enhance them with skills, compose them into workflows and interact with them to accomplish tasks. It is built on top of the [AutoGen](https://microsoft.github.io/autogen) framework, which is a toolkit for building AI agents.
Code for AutoGen Studio is on GitHub at [microsoft/autogen](https://github.com/microsoft/autogen/tree/main/samples/apps/autogen-studio)
> **Note**: AutoGen Studio is meant to help you rapidly prototype multi-agent workflows and demonstrate an example of end user interfaces built with AutoGen. It is not meant to be a production-ready app.
> [!WARNING]
> AutoGen Studio is currently under active development and we are iterating quickly. Kindly consider that we may introduce breaking changes in the releases during the upcoming weeks, and also the `README` might be outdated. Please see the AutoGen Studio [docs](https://microsoft.github.io/autogen/docs/autogen-studio/getting-started) page for the most up-to-date information.
**Updates**
> April 17: AutoGen Studio database layer is now rewritten to use [SQLModel](https://sqlmodel.tiangolo.com/) (Pydantic + SQLAlchemy). This provides entity linking (skills, models, agents and workflows are linked via association tables) and supports multiple [database backend dialects](https://docs.sqlalchemy.org/en/20/dialects/) supported in SQLAlchemy (SQLite, PostgreSQL, MySQL, Oracle, Microsoft SQL Server). The backend database can be specified a `--database-uri` argument when running the application. For example, `autogenstudio ui --database-uri sqlite:///database.sqlite` for SQLite and `autogenstudio ui --database-uri postgresql+psycopg://user:password@localhost/dbname` for PostgreSQL.
> March 12: Default directory for AutoGen Studio is now /home/<user>/.autogenstudio. You can also specify this directory using the `--appdir` argument when running the application. For example, `autogenstudio ui --appdir /path/to/folder`. This will store the database and other files in the specified directory e.g. `/path/to/folder/database.sqlite`. `.env` files in that directory will be used to set environment variables for the app.
Project Structure:
- _autogenstudio/_ code for the backend classes and web api (FastAPI)
- _frontend/_ code for the webui, built with Gatsby and TailwindCSS
### Installation
There are two ways to install AutoGen Studio - from PyPi or from source. We **recommend installing from PyPi** unless you plan to modify the source code.
1. **Install from PyPi**
We recommend using a virtual environment (e.g., conda) to avoid conflicts with existing Python packages. With Python 3.10 or newer active in your virtual environment, use pip to install AutoGen Studio:
```bash
pip install autogenstudio
```
2. **Install from Source**
> Note: This approach requires some familiarity with building interfaces in React.
If you prefer to install from source, ensure you have Python 3.10+ and Node.js (version above 14.15.0) installed. Here's how you get started:
- Clone the AutoGen Studio repository and install its Python dependencies:
```bash
pip install -e .
```
- Navigate to the `samples/apps/autogen-studio/frontend` directory, install dependencies, and build the UI:
```bash
npm install -g gatsby-cli
npm install --global yarn
cd frontend
yarn install
yarn build
```
For Windows users, to build the frontend, you may need alternative commands to build the frontend.
```bash
gatsby clean && rmdir /s /q ..\\autogenstudio\\web\\ui 2>nul & (set \"PREFIX_PATH_VALUE=\" || ver>nul) && gatsby build --prefix-paths && xcopy /E /I /Y public ..\\autogenstudio\\web\\ui
```
### Running the Application
Once installed, run the web UI by entering the following in your terminal:
```bash
autogenstudio ui --port 8081
```
This will start the application on the specified port. Open your web browser and go to `http://localhost:8081/` to begin using AutoGen Studio.
AutoGen Studio also takes several parameters to customize the application:
- `--host <host>` argument to specify the host address. By default, it is set to `localhost`. Y
- `--appdir <appdir>` argument to specify the directory where the app files (e.g., database and generated user files) are stored. By default, it is set to the a `.autogenstudio` directory in the user's home directory.
- `--port <port>` argument to specify the port number. By default, it is set to `8080`.
- `--reload` argument to enable auto-reloading of the server when changes are made to the code. By default, it is set to `False`.
- `--database-uri` argument to specify the database URI. Example values include `sqlite:///database.sqlite` for SQLite and `postgresql+psycopg://user:password@localhost/dbname` for PostgreSQL. If this is not specified, the database URIL defaults to a `database.sqlite` file in the `--appdir` directory.
Now that you have AutoGen Studio installed and running, you are ready to explore its capabilities, including defining and modifying agent workflows, interacting with agents and sessions, and expanding agent skills.
#### If running from source
When running from source, you need to separately bring up the frontend server.
1. Open a separate terminal and change directory to the frontend
```bash
cd frontend
```
3. Create a `.env.development` file.
```bash
cp .env.default .env.development
```
3. Launch frontend server
```bash
npm run start
```
## Contribution Guide
We welcome contributions to AutoGen Studio. We recommend the following general steps to contribute to the project:
- Review the overall AutoGen project [contribution guide](https://github.com/microsoft/autogen?tab=readme-ov-file#contributing)
- Please review the AutoGen Studio [roadmap](https://github.com/microsoft/autogen/issues/737) to get a sense of the current priorities for the project. Help is appreciated especially with Studio issues tagged with `help-wanted`
- Please initiate a discussion on the roadmap issue or a new issue to discuss your proposed contribution.
- Please review the autogenstudio dev branch here [dev branch](https://github.com/microsoft/autogen/tree/autogenstudio) and use as a base for your contribution. This way, your contribution will be aligned with the latest changes in the AutoGen Studio project.
- Submit a pull request with your contribution!
- If you are modifying AutoGen Studio, it has its own devcontainer. See instructions in `.devcontainer/README.md` to use it
- Please use the tag `studio` for any issues, questions, and PRs related to Studio
## FAQ
Please refer to the AutoGen Studio [FAQs](https://microsoft.github.io/autogen/docs/autogen-studio/faqs) page for more information.
## Acknowledgements
AutoGen Studio is Based on the [AutoGen](https://microsoft.github.io/autogen) project. It was adapted from a research prototype built in October 2023 (original credits: Gagan Bansal, Adam Fourney, Victor Dibia, Piali Choudhury, Saleema Amershi, Ahmed Awadallah, Chi Wang).

View File

@@ -0,0 +1,4 @@
from .chatmanager import *
from .datamodel import *
from .version import __version__
from .workflowmanager import *

View File

@@ -0,0 +1,179 @@
import os
from datetime import datetime
from queue import Queue
from typing import Any, Dict, List, Optional, Tuple, Union
from loguru import logger
from .datamodel import Message
from .websocket_connection_manager import WebSocketConnectionManager
from .workflowmanager import WorkflowManager
class AutoGenChatManager:
"""
This class handles the automated generation and management of chat interactions
using an automated workflow configuration and message queue.
"""
def __init__(
self, message_queue: Queue, websocket_manager: WebSocketConnectionManager = None, human_input_timeout: int = 180
) -> None:
"""
Initializes the AutoGenChatManager with a message queue.
:param message_queue: A queue to use for sending messages asynchronously.
"""
self.message_queue = message_queue
self.websocket_manager = websocket_manager
self.a_human_input_timeout = human_input_timeout
def send(self, message: dict) -> None:
"""
Sends a message by putting it into the message queue.
:param message: The message string to be sent.
"""
if self.message_queue is not None:
self.message_queue.put_nowait(message)
async def a_send(self, message: dict) -> None:
"""
Asynchronously sends a message via the WebSocketManager class
:param message: The message string to be sent.
"""
for connection, socket_client_id in self.websocket_manager.active_connections:
if message["connection_id"] == socket_client_id:
logger.info(
f"Sending message to connection_id: {message['connection_id']}. Connection ID: {socket_client_id}"
)
await self.websocket_manager.send_message(message, connection)
else:
logger.info(
f"Skipping message for connection_id: {message['connection_id']}. Connection ID: {socket_client_id}"
)
async def a_prompt_for_input(self, prompt: dict, timeout: int = 60) -> str:
"""
Sends the user a prompt and waits for a response asynchronously via the WebSocketManager class
:param message: The message string to be sent.
"""
for connection, socket_client_id in self.websocket_manager.active_connections:
if prompt["connection_id"] == socket_client_id:
logger.info(
f"Sending message to connection_id: {prompt['connection_id']}. Connection ID: {socket_client_id}"
)
try:
result = await self.websocket_manager.get_input(prompt, connection, timeout)
return result
except Exception as e:
return f"Error: {e}\nTERMINATE"
else:
logger.info(
f"Skipping message for connection_id: {prompt['connection_id']}. Connection ID: {socket_client_id}"
)
def chat(
self,
message: Message,
history: List[Dict[str, Any]],
workflow: Any = None,
connection_id: Optional[str] = None,
user_dir: Optional[str] = None,
**kwargs,
) -> Message:
"""
Processes an incoming message according to the agent's workflow configuration
and generates a response.
:param message: An instance of `Message` representing an incoming message.
:param history: A list of dictionaries, each representing a past interaction.
:param flow_config: An instance of `AgentWorkFlowConfig`. If None, defaults to a standard configuration.
:param connection_id: An optional connection identifier.
:param kwargs: Additional keyword arguments.
:return: An instance of `Message` representing a response.
"""
# create a working director for workflow based on user_dir/session_id/time_hash
work_dir = os.path.join(
user_dir,
str(message.session_id),
datetime.now().strftime("%Y%m%d_%H-%M-%S"),
)
os.makedirs(work_dir, exist_ok=True)
# if no flow config is provided, use the default
if workflow is None:
raise ValueError("Workflow must be specified")
workflow_manager = WorkflowManager(
workflow=workflow,
history=history,
work_dir=work_dir,
send_message_function=self.send,
a_send_message_function=self.a_send,
connection_id=connection_id,
)
message_text = message.content.strip()
result_message: Message = workflow_manager.run(message=f"{message_text}", clear_history=False, history=history)
result_message.user_id = message.user_id
result_message.session_id = message.session_id
return result_message
async def a_chat(
self,
message: Message,
history: List[Dict[str, Any]],
workflow: Any = None,
connection_id: Optional[str] = None,
user_dir: Optional[str] = None,
**kwargs,
) -> Message:
"""
Processes an incoming message according to the agent's workflow configuration
and generates a response.
:param message: An instance of `Message` representing an incoming message.
:param history: A list of dictionaries, each representing a past interaction.
:param flow_config: An instance of `AgentWorkFlowConfig`. If None, defaults to a standard configuration.
:param connection_id: An optional connection identifier.
:param kwargs: Additional keyword arguments.
:return: An instance of `Message` representing a response.
"""
# create a working director for workflow based on user_dir/session_id/time_hash
work_dir = os.path.join(
user_dir,
str(message.session_id),
datetime.now().strftime("%Y%m%d_%H-%M-%S"),
)
os.makedirs(work_dir, exist_ok=True)
# if no flow config is provided, use the default
if workflow is None:
raise ValueError("Workflow must be specified")
workflow_manager = WorkflowManager(
workflow=workflow,
history=history,
work_dir=work_dir,
send_message_function=self.send,
a_send_message_function=self.a_send,
a_human_input_function=self.a_prompt_for_input,
a_human_input_timeout=self.a_human_input_timeout,
connection_id=connection_id,
)
message_text = message.content.strip()
result_message: Message = await workflow_manager.a_run(
message=f"{message_text}", clear_history=False, history=history
)
result_message.user_id = message.user_id
result_message.session_id = message.session_id
return result_message

View File

@@ -0,0 +1,98 @@
import os
from typing import Optional
import typer
import uvicorn
from typing_extensions import Annotated
from .version import VERSION
app = typer.Typer()
@app.command()
def ui(
host: str = "127.0.0.1",
port: int = 8081,
workers: int = 1,
reload: Annotated[bool, typer.Option("--reload")] = False,
docs: bool = True,
appdir: str = None,
database_uri: Optional[str] = None,
):
"""
Run the AutoGen Studio UI.
Args:
host (str, optional): Host to run the UI on. Defaults to 127.0.0.1 (localhost).
port (int, optional): Port to run the UI on. Defaults to 8081.
workers (int, optional): Number of workers to run the UI with. Defaults to 1.
reload (bool, optional): Whether to reload the UI on code changes. Defaults to False.
docs (bool, optional): Whether to generate API docs. Defaults to False.
appdir (str, optional): Path to the AutoGen Studio app directory. Defaults to None.
database-uri (str, optional): Database URI to connect to. Defaults to None. Examples include sqlite:///autogenstudio.db, postgresql://user:password@localhost/autogenstudio.
"""
os.environ["AUTOGENSTUDIO_API_DOCS"] = str(docs)
if appdir:
os.environ["AUTOGENSTUDIO_APPDIR"] = appdir
if database_uri:
os.environ["AUTOGENSTUDIO_DATABASE_URI"] = database_uri
uvicorn.run(
"autogenstudio.web.app:app",
host=host,
port=port,
workers=workers,
reload=reload,
)
@app.command()
def serve(
workflow: str = "",
host: str = "127.0.0.1",
port: int = 8084,
workers: int = 1,
docs: bool = False,
):
"""
Serve an API Endpoint based on an AutoGen Studio workflow json file.
Args:
workflow (str): Path to the workflow json file.
host (str, optional): Host to run the UI on. Defaults to 127.0.0.1 (localhost).
port (int, optional): Port to run the UI on. Defaults to 8081.
workers (int, optional): Number of workers to run the UI with. Defaults to 1.
reload (bool, optional): Whether to reload the UI on code changes. Defaults to False.
docs (bool, optional): Whether to generate API docs. Defaults to False.
"""
os.environ["AUTOGENSTUDIO_API_DOCS"] = str(docs)
os.environ["AUTOGENSTUDIO_WORKFLOW_FILE"] = workflow
uvicorn.run(
"autogenstudio.web.serve:app",
host=host,
port=port,
workers=workers,
reload=False,
)
@app.command()
def version():
"""
Print the version of the AutoGen Studio UI CLI.
"""
typer.echo(f"AutoGen Studio CLI version: {VERSION}")
def run():
app()
if __name__ == "__main__":
app()

View File

@@ -0,0 +1,3 @@
# from .dbmanager import *
from .dbmanager import *
from .utils import *

View File

@@ -0,0 +1,116 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = migrations
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
# for all available tokens
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
prepend_sys_path = .
# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the python>=3.9 or backports.zoneinfo library.
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
# string value is passed to ZoneInfo()
# leave blank for localtime
# timezone =
# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; This defaults
# to migrations/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "version_path_separator" below.
# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions
# version path separator; As mentioned above, this is the character used to split
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
# Valid values for version_path_separator are:
#
# version_path_separator = :
# version_path_separator = ;
# version_path_separator = space
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
# set to 'true' to search source files recursively
# in each "version_locations" directory
# new in Alembic version 1.10
# recursive_version_locations = false
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = driver://user:pass@localhost/dbname
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples
# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks = black
# black.type = console_scripts
# black.entrypoint = black
# black.options = -l 79 REVISION_SCRIPT_FILENAME
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
# hooks = ruff
# ruff.type = exec
# ruff.executable = %(here)s/.venv/bin/ruff
# ruff.options = --fix REVISION_SCRIPT_FILENAME
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

View File

@@ -0,0 +1,491 @@
import threading
from datetime import datetime
from typing import Optional
from loguru import logger
from sqlalchemy import exc
from sqlmodel import Session, SQLModel, and_, create_engine, select
from ..datamodel import (
Agent,
AgentLink,
AgentModelLink,
AgentSkillLink,
Model,
Response,
Skill,
Workflow,
WorkflowAgentLink,
WorkflowAgentType,
)
from .utils import init_db_samples
valid_link_types = ["agent_model", "agent_skill", "agent_agent", "workflow_agent"]
class WorkflowAgentMap(SQLModel):
agent: Agent
link: WorkflowAgentLink
class DBManager:
"""A class to manage database operations"""
_init_lock = threading.Lock() # Class-level lock
def __init__(self, engine_uri: str):
connection_args = {"check_same_thread": True} if "sqlite" in engine_uri else {}
self.engine = create_engine(engine_uri, connect_args=connection_args)
# run_migration(engine_uri=engine_uri)
def create_db_and_tables(self):
"""Create a new database and tables"""
with self._init_lock: # Use the lock
try:
SQLModel.metadata.create_all(self.engine)
try:
init_db_samples(self)
except Exception as e:
logger.info("Error while initializing database samples: " + str(e))
except Exception as e:
logger.info("Error while creating database tables:" + str(e))
def upsert(self, model: SQLModel):
"""Create a new entity"""
# check if the model exists, update else add
status = True
model_class = type(model)
existing_model = None
with Session(self.engine) as session:
try:
existing_model = session.exec(select(model_class).where(model_class.id == model.id)).first()
if existing_model:
model.updated_at = datetime.now()
for key, value in model.model_dump().items():
setattr(existing_model, key, value)
model = existing_model
session.add(model)
else:
session.add(model)
session.commit()
session.refresh(model)
except Exception as e:
session.rollback()
logger.error("Error while updating " + str(model_class.__name__) + ": " + str(e))
status = False
response = Response(
message=(
f"{model_class.__name__} Updated Successfully "
if existing_model
else f"{model_class.__name__} Created Successfully"
),
status=status,
data=model.model_dump(),
)
return response
def _model_to_dict(self, model_obj):
return {col.name: getattr(model_obj, col.name) for col in model_obj.__table__.columns}
def get_items(
self,
model_class: SQLModel,
session: Session,
filters: dict = None,
return_json: bool = False,
order: str = "desc",
):
"""List all entities"""
result = []
status = True
status_message = ""
try:
if filters:
conditions = [getattr(model_class, col) == value for col, value in filters.items()]
statement = select(model_class).where(and_(*conditions))
if hasattr(model_class, "created_at") and order:
if order == "desc":
statement = statement.order_by(model_class.created_at.desc())
else:
statement = statement.order_by(model_class.created_at.asc())
else:
statement = select(model_class)
if return_json:
result = [self._model_to_dict(row) for row in session.exec(statement).all()]
else:
result = session.exec(statement).all()
status_message = f"{model_class.__name__} Retrieved Successfully"
except Exception as e:
session.rollback()
status = False
status_message = f"Error while fetching {model_class.__name__}"
logger.error("Error while getting items: " + str(model_class.__name__) + " " + str(e))
response: Response = Response(
message=status_message,
status=status,
data=result,
)
return response
def get(
self,
model_class: SQLModel,
filters: dict = None,
return_json: bool = False,
order: str = "desc",
):
"""List all entities"""
with Session(self.engine) as session:
response = self.get_items(model_class, session, filters, return_json, order)
return response
def delete(self, model_class: SQLModel, filters: dict = None):
"""Delete an entity"""
row = None
status_message = ""
status = True
with Session(self.engine) as session:
try:
if filters:
conditions = [getattr(model_class, col) == value for col, value in filters.items()]
row = session.exec(select(model_class).where(and_(*conditions))).all()
else:
row = session.exec(select(model_class)).all()
if row:
for row in row:
session.delete(row)
session.commit()
status_message = f"{model_class.__name__} Deleted Successfully"
else:
print(f"Row with filters {filters} not found")
logger.info("Row with filters + filters + not found")
status_message = "Row not found"
except exc.IntegrityError as e:
session.rollback()
logger.error("Integrity ... Error while deleting: " + str(e))
status_message = f"The {model_class.__name__} is linked to another entity and cannot be deleted."
status = False
except Exception as e:
session.rollback()
logger.error("Error while deleting: " + str(e))
status_message = f"Error while deleting: {e}"
status = False
response = Response(
message=status_message,
status=status,
data=None,
)
return response
def get_linked_entities(
self,
link_type: str,
primary_id: int,
return_json: bool = False,
agent_type: Optional[str] = None,
sequence_id: Optional[int] = None,
):
"""
Get all entities linked to the primary entity.
Args:
link_type (str): The type of link to retrieve, e.g., "agent_model".
primary_id (int): The identifier for the primary model.
return_json (bool): Whether to return the result as a JSON object.
Returns:
List[SQLModel]: A list of linked entities.
"""
linked_entities = []
if link_type not in valid_link_types:
return []
status = True
status_message = ""
with Session(self.engine) as session:
try:
if link_type == "agent_model":
# get the agent
agent = self.get_items(Agent, filters={"id": primary_id}, session=session).data[0]
linked_entities = agent.models
elif link_type == "agent_skill":
agent = self.get_items(Agent, filters={"id": primary_id}, session=session).data[0]
linked_entities = agent.skills
elif link_type == "agent_agent":
agent = self.get_items(Agent, filters={"id": primary_id}, session=session).data[0]
linked_entities = agent.agents
elif link_type == "workflow_agent":
linked_entities = session.exec(
select(WorkflowAgentLink, Agent)
.join(Agent, WorkflowAgentLink.agent_id == Agent.id)
.where(
WorkflowAgentLink.workflow_id == primary_id,
)
).all()
linked_entities = [WorkflowAgentMap(agent=agent, link=link) for link, agent in linked_entities]
linked_entities = sorted(linked_entities, key=lambda x: x.link.sequence_id) # type: ignore
except Exception as e:
logger.error("Error while getting linked entities: " + str(e))
status_message = f"Error while getting linked entities: {e}"
status = False
if return_json:
linked_entities = [row.model_dump() for row in linked_entities]
response = Response(
message=status_message,
status=status,
data=linked_entities,
)
return response
def link(
self,
link_type: str,
primary_id: int,
secondary_id: int,
agent_type: Optional[str] = None,
sequence_id: Optional[int] = None,
) -> Response:
"""
Link two entities together.
Args:
link_type (str): The type of link to create, e.g., "agent_model".
primary_id (int): The identifier for the primary model.
secondary_id (int): The identifier for the secondary model.
agent_type (Optional[str]): The type of agent, e.g., "sender" or receiver.
Returns:
Response: The response of the linking operation, including success status and message.
"""
# TBD verify that is creator of the primary entity being linked
status = True
status_message = ""
primary_model = None
secondary_model = None
if link_type not in valid_link_types:
status = False
status_message = f"Invalid link type: {link_type}. Valid link types are: {valid_link_types}"
else:
with Session(self.engine) as session:
try:
if link_type == "agent_model":
primary_model = session.exec(select(Agent).where(Agent.id == primary_id)).first()
secondary_model = session.exec(select(Model).where(Model.id == secondary_id)).first()
if primary_model is None or secondary_model is None:
status = False
status_message = "One or both entity records do not exist."
else:
# check if the link already exists
existing_link = session.exec(
select(AgentModelLink).where(
AgentModelLink.agent_id == primary_id,
AgentModelLink.model_id == secondary_id,
)
).first()
if existing_link: # link already exists
return Response(
message=(
f"{secondary_model.__class__.__name__} already linked "
f"to {primary_model.__class__.__name__}"
),
status=False,
)
else:
primary_model.models.append(secondary_model)
elif link_type == "agent_agent":
primary_model = session.exec(select(Agent).where(Agent.id == primary_id)).first()
secondary_model = session.exec(select(Agent).where(Agent.id == secondary_id)).first()
if primary_model is None or secondary_model is None:
status = False
status_message = "One or both entity records do not exist."
else:
# check if the link already exists
existing_link = session.exec(
select(AgentLink).where(
AgentLink.parent_id == primary_id,
AgentLink.agent_id == secondary_id,
)
).first()
if existing_link:
return Response(
message=(
f"{secondary_model.__class__.__name__} already linked "
f"to {primary_model.__class__.__name__}"
),
status=False,
)
else:
primary_model.agents.append(secondary_model)
elif link_type == "agent_skill":
primary_model = session.exec(select(Agent).where(Agent.id == primary_id)).first()
secondary_model = session.exec(select(Skill).where(Skill.id == secondary_id)).first()
if primary_model is None or secondary_model is None:
status = False
status_message = "One or both entity records do not exist."
else:
# check if the link already exists
existing_link = session.exec(
select(AgentSkillLink).where(
AgentSkillLink.agent_id == primary_id,
AgentSkillLink.skill_id == secondary_id,
)
).first()
if existing_link:
return Response(
message=(
f"{secondary_model.__class__.__name__} already linked "
f"to {primary_model.__class__.__name__}"
),
status=False,
)
else:
primary_model.skills.append(secondary_model)
elif link_type == "workflow_agent":
primary_model = session.exec(select(Workflow).where(Workflow.id == primary_id)).first()
secondary_model = session.exec(select(Agent).where(Agent.id == secondary_id)).first()
if primary_model is None or secondary_model is None:
status = False
status_message = "One or both entity records do not exist."
else:
# check if the link already exists
existing_link = session.exec(
select(WorkflowAgentLink).where(
WorkflowAgentLink.workflow_id == primary_id,
WorkflowAgentLink.agent_id == secondary_id,
WorkflowAgentLink.agent_type == agent_type,
WorkflowAgentLink.sequence_id == sequence_id,
)
).first()
if existing_link:
return Response(
message=(
f"{secondary_model.__class__.__name__} already linked "
f"to {primary_model.__class__.__name__}"
),
status=False,
)
else:
# primary_model.agents.append(secondary_model)
workflow_agent_link = WorkflowAgentLink(
workflow_id=primary_id,
agent_id=secondary_id,
agent_type=agent_type,
sequence_id=sequence_id,
)
session.add(workflow_agent_link)
# add and commit the link
session.add(primary_model)
session.commit()
status_message = (
f"{secondary_model.__class__.__name__} successfully linked "
f"to {primary_model.__class__.__name__}"
)
except Exception as e:
session.rollback()
logger.error("Error while linking: " + str(e))
status = False
status_message = f"Error while linking due to an exception: {e}"
response = Response(
message=status_message,
status=status,
)
return response
def unlink(
self,
link_type: str,
primary_id: int,
secondary_id: int,
agent_type: Optional[str] = None,
sequence_id: Optional[int] = 0,
) -> Response:
"""
Unlink two entities.
Args:
link_type (str): The type of link to remove, e.g., "agent_model".
primary_id (int): The identifier for the primary model.
secondary_id (int): The identifier for the secondary model.
agent_type (Optional[str]): The type of agent, e.g., "sender" or receiver.
Returns:
Response: The response of the unlinking operation, including success status and message.
"""
status = True
status_message = ""
print("primary", primary_id, "secondary", secondary_id, "sequence", sequence_id, "agent_type", agent_type)
if link_type not in valid_link_types:
status = False
status_message = f"Invalid link type: {link_type}. Valid link types are: {valid_link_types}"
return Response(message=status_message, status=status)
with Session(self.engine) as session:
try:
if link_type == "agent_model":
existing_link = session.exec(
select(AgentModelLink).where(
AgentModelLink.agent_id == primary_id,
AgentModelLink.model_id == secondary_id,
)
).first()
elif link_type == "agent_skill":
existing_link = session.exec(
select(AgentSkillLink).where(
AgentSkillLink.agent_id == primary_id,
AgentSkillLink.skill_id == secondary_id,
)
).first()
elif link_type == "agent_agent":
existing_link = session.exec(
select(AgentLink).where(
AgentLink.parent_id == primary_id,
AgentLink.agent_id == secondary_id,
)
).first()
elif link_type == "workflow_agent":
existing_link = session.exec(
select(WorkflowAgentLink).where(
WorkflowAgentLink.workflow_id == primary_id,
WorkflowAgentLink.agent_id == secondary_id,
WorkflowAgentLink.agent_type == agent_type,
WorkflowAgentLink.sequence_id == sequence_id,
)
).first()
if existing_link:
session.delete(existing_link)
session.commit()
status_message = "Link removed successfully."
else:
status = False
status_message = "Link does not exist."
except Exception as e:
session.rollback()
logger.error("Error while unlinking: " + str(e))
status = False
status_message = f"Error while unlinking due to an exception: {e}"
return Response(message=status_message, status=status)

View File

@@ -0,0 +1 @@
Generic single-database configuration.

View File

@@ -0,0 +1,80 @@
import os
from logging.config import fileConfig
from alembic import context
from sqlalchemy import engine_from_config, pool
from sqlmodel import SQLModel
from autogenstudio.datamodel import *
from autogenstudio.utils import get_db_uri
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
config.set_main_option("sqlalchemy.url", get_db_uri())
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = SQLModel.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@@ -0,0 +1,27 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import sqlmodel
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision: str = ${repr(up_revision)}
down_revision: Union[str, None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
def upgrade() -> None:
${upgrades if upgrades else "pass"}
def downgrade() -> None:
${downgrades if downgrades else "pass"}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,296 @@
from datetime import datetime
from enum import Enum
from typing import Any, Callable, Dict, List, Literal, Optional, Union
from sqlalchemy import ForeignKey, Integer, orm
from sqlmodel import (
JSON,
Column,
DateTime,
Field,
Relationship,
SQLModel,
func,
)
from sqlmodel import (
Enum as SqlEnum,
)
# added for python3.11 and sqlmodel 0.0.22 incompatibility
if hasattr(SQLModel, "model_config"):
SQLModel.model_config["protected_namespaces"] = ()
elif hasattr(SQLModel, "Config"):
class CustomSQLModel(SQLModel):
class Config:
protected_namespaces = ()
SQLModel = CustomSQLModel
else:
print("Warning: Unable to set protected_namespaces.")
# pylint: disable=protected-access
class MessageMeta(SQLModel, table=False):
task: Optional[str] = None
messages: Optional[List[Dict[str, Any]]] = None
summary_method: Optional[str] = "last"
files: Optional[List[dict]] = None
time: Optional[datetime] = None
log: Optional[List[dict]] = None
usage: Optional[List[dict]] = None
class Message(SQLModel, table=True):
__table_args__ = {"sqlite_autoincrement": True}
id: Optional[int] = Field(default=None, primary_key=True)
created_at: datetime = Field(
default_factory=datetime.now,
sa_column=Column(DateTime(timezone=True), server_default=func.now()),
) # pylint: disable=not-callable
updated_at: datetime = Field(
default_factory=datetime.now,
sa_column=Column(DateTime(timezone=True), onupdate=func.now()),
) # pylint: disable=not-callable
user_id: Optional[str] = None
role: str
content: str
session_id: Optional[int] = Field(
default=None, sa_column=Column(Integer, ForeignKey("session.id", ondelete="CASCADE"))
)
connection_id: Optional[str] = None
meta: Optional[Union[MessageMeta, dict]] = Field(default={}, sa_column=Column(JSON))
class Session(SQLModel, table=True):
__table_args__ = {"sqlite_autoincrement": True}
id: Optional[int] = Field(default=None, primary_key=True)
created_at: datetime = Field(
default_factory=datetime.now,
sa_column=Column(DateTime(timezone=True), server_default=func.now()),
) # pylint: disable=not-callable
updated_at: datetime = Field(
default_factory=datetime.now,
sa_column=Column(DateTime(timezone=True), onupdate=func.now()),
) # pylint: disable=not-callable
user_id: Optional[str] = None
workflow_id: Optional[int] = Field(default=None, foreign_key="workflow.id")
name: Optional[str] = None
description: Optional[str] = None
class AgentSkillLink(SQLModel, table=True):
__table_args__ = {"sqlite_autoincrement": True}
agent_id: int = Field(default=None, primary_key=True, foreign_key="agent.id")
skill_id: int = Field(default=None, primary_key=True, foreign_key="skill.id")
class AgentModelLink(SQLModel, table=True):
__table_args__ = {"sqlite_autoincrement": True}
agent_id: int = Field(default=None, primary_key=True, foreign_key="agent.id")
model_id: int = Field(default=None, primary_key=True, foreign_key="model.id")
class Skill(SQLModel, table=True):
__table_args__ = {"sqlite_autoincrement": True}
id: Optional[int] = Field(default=None, primary_key=True)
created_at: datetime = Field(
default_factory=datetime.now,
sa_column=Column(DateTime(timezone=True), server_default=func.now()),
) # pylint: disable=not-callable
updated_at: datetime = Field(
default_factory=datetime.now,
sa_column=Column(DateTime(timezone=True), onupdate=func.now()),
) # pylint: disable=not-callable
user_id: Optional[str] = None
version: Optional[str] = "0.0.1"
name: str
content: str
description: Optional[str] = None
secrets: Optional[List[dict]] = Field(default_factory=list, sa_column=Column(JSON))
libraries: Optional[List[str]] = Field(default_factory=list, sa_column=Column(JSON))
agents: List["Agent"] = Relationship(back_populates="skills", link_model=AgentSkillLink)
class LLMConfig(SQLModel, table=False):
"""Data model for LLM Config for AutoGen"""
config_list: List[Any] = Field(default_factory=list)
temperature: float = 0
cache_seed: Optional[Union[int, None]] = None
timeout: Optional[int] = None
max_tokens: Optional[int] = 2048
extra_body: Optional[dict] = None
class ModelTypes(str, Enum):
openai = "open_ai"
google = "google"
azure = "azure"
anthropic = "anthropic"
mistral = "mistral"
together = "together"
groq = "groq"
class Model(SQLModel, table=True):
__table_args__ = {"sqlite_autoincrement": True}
id: Optional[int] = Field(default=None, primary_key=True)
created_at: datetime = Field(
default_factory=datetime.now,
sa_column=Column(DateTime(timezone=True), server_default=func.now()),
) # pylint: disable=not-callable
updated_at: datetime = Field(
default_factory=datetime.now,
sa_column=Column(DateTime(timezone=True), onupdate=func.now()),
) # pylint: disable=not-callable
user_id: Optional[str] = None
version: Optional[str] = "0.0.1"
model: str
api_key: Optional[str] = None
base_url: Optional[str] = None
api_type: ModelTypes = Field(default=ModelTypes.openai, sa_column=Column(SqlEnum(ModelTypes)))
api_version: Optional[str] = None
description: Optional[str] = None
agents: List["Agent"] = Relationship(back_populates="models", link_model=AgentModelLink)
class CodeExecutionConfigTypes(str, Enum):
local = "local"
docker = "docker"
none = "none"
class AgentConfig(SQLModel, table=False):
name: Optional[str] = None
human_input_mode: str = "NEVER"
max_consecutive_auto_reply: int = 10
system_message: Optional[str] = None
is_termination_msg: Optional[Union[bool, str, Callable]] = None
code_execution_config: CodeExecutionConfigTypes = Field(
default=CodeExecutionConfigTypes.local, sa_column=Column(SqlEnum(CodeExecutionConfigTypes))
)
default_auto_reply: Optional[str] = ""
description: Optional[str] = None
llm_config: Optional[Union[LLMConfig, bool]] = Field(default=False, sa_column=Column(JSON))
admin_name: Optional[str] = "Admin"
messages: Optional[List[Dict]] = Field(default_factory=list)
max_round: Optional[int] = 100
speaker_selection_method: Optional[str] = "auto"
allow_repeat_speaker: Optional[Union[bool, List["AgentConfig"]]] = True
class AgentType(str, Enum):
assistant = "assistant"
userproxy = "userproxy"
groupchat = "groupchat"
class WorkflowAgentType(str, Enum):
sender = "sender"
receiver = "receiver"
planner = "planner"
sequential = "sequential"
class WorkflowAgentLink(SQLModel, table=True):
__table_args__ = {"sqlite_autoincrement": True}
workflow_id: int = Field(default=None, primary_key=True, foreign_key="workflow.id")
agent_id: int = Field(default=None, primary_key=True, foreign_key="agent.id")
agent_type: WorkflowAgentType = Field(
default=WorkflowAgentType.sender,
sa_column=Column(SqlEnum(WorkflowAgentType), primary_key=True),
)
sequence_id: Optional[int] = Field(default=0, primary_key=True)
class AgentLink(SQLModel, table=True):
__table_args__ = {"sqlite_autoincrement": True}
parent_id: Optional[int] = Field(default=None, foreign_key="agent.id", primary_key=True)
agent_id: Optional[int] = Field(default=None, foreign_key="agent.id", primary_key=True)
class Agent(SQLModel, table=True):
__table_args__ = {"sqlite_autoincrement": True}
id: Optional[int] = Field(default=None, primary_key=True)
created_at: datetime = Field(
default_factory=datetime.now,
sa_column=Column(DateTime(timezone=True), server_default=func.now()),
) # pylint: disable=not-callable
updated_at: datetime = Field(
default_factory=datetime.now,
sa_column=Column(DateTime(timezone=True), onupdate=func.now()),
) # pylint: disable=not-callable
user_id: Optional[str] = None
version: Optional[str] = "0.0.1"
type: AgentType = Field(default=AgentType.assistant, sa_column=Column(SqlEnum(AgentType)))
config: Union[AgentConfig, dict] = Field(default_factory=AgentConfig, sa_column=Column(JSON))
skills: List[Skill] = Relationship(back_populates="agents", link_model=AgentSkillLink)
models: List[Model] = Relationship(back_populates="agents", link_model=AgentModelLink)
workflows: List["Workflow"] = Relationship(link_model=WorkflowAgentLink, back_populates="agents")
parents: List["Agent"] = Relationship(
back_populates="agents",
link_model=AgentLink,
sa_relationship_kwargs=dict(
primaryjoin="Agent.id==AgentLink.agent_id",
secondaryjoin="Agent.id==AgentLink.parent_id",
),
)
agents: List["Agent"] = Relationship(
back_populates="parents",
link_model=AgentLink,
sa_relationship_kwargs=dict(
primaryjoin="Agent.id==AgentLink.parent_id",
secondaryjoin="Agent.id==AgentLink.agent_id",
),
)
task_instruction: Optional[str] = None
class WorkFlowType(str, Enum):
autonomous = "autonomous"
sequential = "sequential"
class WorkFlowSummaryMethod(str, Enum):
last = "last"
none = "none"
llm = "llm"
class Workflow(SQLModel, table=True):
__table_args__ = {"sqlite_autoincrement": True}
id: Optional[int] = Field(default=None, primary_key=True)
created_at: datetime = Field(
default_factory=datetime.now,
sa_column=Column(DateTime(timezone=True), server_default=func.now()),
) # pylint: disable=not-callable
updated_at: datetime = Field(
default_factory=datetime.now,
sa_column=Column(DateTime(timezone=True), onupdate=func.now()),
) # pylint: disable=not-callable
user_id: Optional[str] = None
version: Optional[str] = "0.0.1"
name: str
description: str
agents: List[Agent] = Relationship(back_populates="workflows", link_model=WorkflowAgentLink)
type: WorkFlowType = Field(default=WorkFlowType.autonomous, sa_column=Column(SqlEnum(WorkFlowType)))
summary_method: Optional[WorkFlowSummaryMethod] = Field(
default=WorkFlowSummaryMethod.last,
sa_column=Column(SqlEnum(WorkFlowSummaryMethod)),
)
sample_tasks: Optional[List[str]] = Field(default_factory=list, sa_column=Column(JSON))
class Response(SQLModel):
message: str
status: bool
data: Optional[Any] = None
class SocketMessage(SQLModel, table=False):
connection_id: str
data: Dict[str, Any]
type: str

View File

@@ -0,0 +1,108 @@
# metrics - agent_frequency, execution_count, tool_count,
from typing import Dict, List, Optional
from .datamodel import Message, MessageMeta
class Profiler:
"""
Profiler class to profile agent task runs and compute metrics
for performance evaluation.
"""
def __init__(self):
self.metrics: List[Dict] = []
def _is_code(self, message: Message) -> bool:
"""
Check if the message contains code.
:param message: The message instance to check.
:return: True if the message contains code, False otherwise.
"""
content = message.get("message").get("content").lower()
return "```" in content
def _is_tool(self, message: Message) -> bool:
"""
Check if the message uses a tool.
:param message: The message instance to check.
:return: True if the message uses a tool, False otherwise.
"""
content = message.get("message").get("content").lower()
return "from skills import" in content
def _is_code_execution(self, message: Message) -> bool:
"""
Check if the message indicates code execution.
:param message: The message instance to check.
:return: dict with is_code and status keys.
"""
content = message.get("message").get("content").lower()
if "exitcode:" in content:
status = "exitcode: 0" in content
return {"is_code": True, "status": status}
else:
return {"is_code": False, "status": False}
def _is_terminate(self, message: Message) -> bool:
"""
Check if the message indicates termination.
:param message: The message instance to check.
:return: True if the message indicates termination, False otherwise.
"""
content = message.get("message").get("content").lower()
return "terminate" in content
def profile(self, agent_message: Message):
"""
Profile the agent task run and compute metrics.
:param agent: The agent instance that ran the task.
:param task: The task instance that was run.
"""
meta = MessageMeta(**agent_message.meta)
print(meta.log)
usage = meta.usage
messages = meta.messages
profile = []
bar = []
stats = {}
total_code_executed = 0
success_code_executed = 0
agents = []
for message in messages:
agent = message.get("sender")
is_code = self._is_code(message)
is_tool = self._is_tool(message)
is_code_execution = self._is_code_execution(message)
total_code_executed += is_code_execution["is_code"]
success_code_executed += 1 if is_code_execution["status"] else 0
row = {
"agent": agent,
"tool_call": is_code,
"code_execution": is_code_execution,
"terminate": self._is_terminate(message),
}
bar_row = {
"agent": agent,
"tool_call": "tool call" if is_tool else "no tool call",
"code_execution": (
"success"
if is_code_execution["status"]
else "failure" if is_code_execution["is_code"] else "no code"
),
"message": 1,
}
profile.append(row)
bar.append(bar_row)
agents.append(agent)
code_success_rate = (success_code_executed / total_code_executed if total_code_executed > 0 else 0) * 100
stats["code_success_rate"] = code_success_rate
stats["total_code_executed"] = total_code_executed
return {"profile": profile, "bar": bar, "stats": stats, "agents": set(agents), "usage": usage}

View File

@@ -0,0 +1 @@
from .utils import *

View File

@@ -0,0 +1,242 @@
{
"models": [
{
"model": "gpt-4",
"api_key": "Your Azure API key here",
"base_url": "Your Azure base URL here",
"api_type": "azure",
"api_version": "Your Azure API version here",
"description": "Azure Open AI model configuration"
},
{
"model": "gpt-4-1106-preview",
"description": "OpenAI model configuration"
},
{
"model": "TheBloke/zephyr-7B-alpha-AWQ",
"api_key": "EMPTY",
"base_url": "http://localhost:8000/v1",
"description": "Local model example with vLLM server endpoint"
}
],
"agents": [
{
"type": "userproxy",
"config": {
"name": "userproxy",
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 5,
"system_message": "You are a helpful assistant.",
"default_auto_reply": "TERMINATE",
"llm_config": false,
"code_execution_config": {
"work_dir": null,
"use_docker": false
},
"description": "A user proxy agent that executes code."
}
},
{
"type": "assistant",
"skills": [
{
"title": "find_papers_arxiv",
"description": "This skill finds relevant papers on arXiv given a query.",
"content": "import os\nimport re\nimport json\nimport hashlib\n\n\ndef search_arxiv(query, max_results=10):\n \"\"\"\n Searches arXiv for the given query using the arXiv API, then returns the search results. This is a helper function. In most cases, callers will want to use 'find_relevant_papers( query, max_results )' instead.\n\n Args:\n query (str): The search query.\n max_results (int, optional): The maximum number of search results to return. Defaults to 10.\n\n Returns:\n jresults (list): A list of dictionaries. Each dictionary contains fields such as 'title', 'authors', 'summary', and 'pdf_url'\n\n Example:\n >>> results = search_arxiv(\"attention is all you need\")\n >>> print(results)\n \"\"\"\n\n import arxiv\n\n key = hashlib.md5((\"search_arxiv(\" + str(max_results) + \")\" + query).encode(\"utf-8\")).hexdigest()\n # Create the cache if it doesn't exist\n cache_dir = \".cache\"\n if not os.path.isdir(cache_dir):\n os.mkdir(cache_dir)\n\n fname = os.path.join(cache_dir, key + \".cache\")\n\n # Cache hit\n if os.path.isfile(fname):\n fh = open(fname, \"r\", encoding=\"utf-8\")\n data = json.loads(fh.read())\n fh.close()\n return data\n\n # Normalize the query, removing operator keywords\n query = re.sub(r\"[^\\s\\w]\", \" \", query.lower())\n query = re.sub(r\"\\s(and|or|not)\\s\", \" \", \" \" + query + \" \")\n query = re.sub(r\"[^\\s\\w]\", \" \", query.lower())\n query = re.sub(r\"\\s+\", \" \", query).strip()\n\n search = arxiv.Search(query=query, max_results=max_results, sort_by=arxiv.SortCriterion.Relevance)\n\n jresults = list()\n for result in search.results():\n r = dict()\n r[\"entry_id\"] = result.entry_id\n r[\"updated\"] = str(result.updated)\n r[\"published\"] = str(result.published)\n r[\"title\"] = result.title\n r[\"authors\"] = [str(a) for a in result.authors]\n r[\"summary\"] = result.summary\n r[\"comment\"] = result.comment\n r[\"journal_ref\"] = result.journal_ref\n r[\"doi\"] = result.doi\n r[\"primary_category\"] = result.primary_category\n r[\"categories\"] = result.categories\n r[\"links\"] = [str(link) for link in result.links]\n r[\"pdf_url\"] = result.pdf_url\n jresults.append(r)\n\n if len(jresults) > max_results:\n jresults = jresults[0:max_results]\n\n # Save to cache\n fh = open(fname, \"w\")\n fh.write(json.dumps(jresults))\n fh.close()\n return jresults\n",
"file_name": "find_papers_arxiv"
},
{
"title": "generate_images",
"description": "This skill generates images from a given query using OpenAI's DALL-E model and saves them to disk.",
"content": "from typing import List\nimport uuid\nimport requests # to perform HTTP requests\nfrom pathlib import Path\n\nfrom openai import OpenAI\n\n\ndef generate_and_save_images(query: str, image_size: str = \"1024x1024\") -> List[str]:\n \"\"\"\n Function to paint, draw or illustrate images based on the users query or request. Generates images from a given query using OpenAI's DALL-E model and saves them to disk. Use the code below anytime there is a request to create an image.\n\n :param query: A natural language description of the image to be generated.\n :param image_size: The size of the image to be generated. (default is \"1024x1024\")\n :return: A list of filenames for the saved images.\n \"\"\"\n\n client = OpenAI() # Initialize the OpenAI client\n response = client.images.generate(model=\"dall-e-3\", prompt=query, n=1, size=image_size) # Generate images\n\n # List to store the file names of saved images\n saved_files = []\n\n # Check if the response is successful\n if response.data:\n for image_data in response.data:\n # Generate a random UUID as the file name\n file_name = str(uuid.uuid4()) + \".png\" # Assuming the image is a PNG\n file_path = Path(file_name)\n\n img_url = image_data.url\n img_response = requests.get(img_url)\n if img_response.status_code == 200:\n # Write the binary content to a file\n with open(file_path, \"wb\") as img_file:\n img_file.write(img_response.content)\n print(f\"Image saved to {file_path}\")\n saved_files.append(str(file_path))\n else:\n print(f\"Failed to download the image from {img_url}\")\n else:\n print(\"No image data found in the response!\")\n\n # Return the list of saved files\n return saved_files\n\n\n# Example usage of the function:\n# generate_and_save_images(\"A cute baby sea otter\")\n"
}
],
"config": {
"name": "primary_assistant",
"description": "A primary assistant agent that writes plans and code to solve tasks.",
"llm_config": {
"config_list": [
{
"model": "gpt-4-1106-preview"
}
],
"temperature": 0.1,
"timeout": 600,
"cache_seed": null
},
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 8,
"system_message": "You are a helpful AI assistant. Solve tasks using your coding and language skills. In the following cases, suggest python code (in a python coding block) or shell script (in a sh coding block) for the user to execute. 1. When you need to collect info, use the code to output the info you need, for example, browse or search the web, download/read a file, print the content of a webpage or a file, get the current date/time, check the operating system. After sufficient info is printed and the task is ready to be solved based on your language skill, you can solve the task by yourself. 2. When you need to perform some task with code, use the code to perform the task and output the result. Finish the task smartly. Solve the task step by step if you need to. If a plan is not provided, explain your plan first. Be clear which step uses code, and which step uses your language skill. When using code, you must indicate the script type in the code block. The user cannot provide any other feedback or perform any other action beyond executing the code you suggest. The user can't modify your code. So do not suggest incomplete code which requires users to modify. Don't use a code block if it's not intended to be executed by the user. If you want the user to save the code in a file before executing it, put # filename: <filename> inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user. If the result indicates there is an error, fix the error and output the code again. Suggest the full code instead of partial code or code changes. If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try. When you find an answer, verify the answer carefully. Include verifiable evidence in your response if possible. Reply 'TERMINATE' in the end when everything is done."
}
}
],
"skills": [
{
"title": "fetch_profile",
"description": "This skill fetches the text content from a personal website.",
"content": "from typing import Optional\nimport requests\nfrom bs4 import BeautifulSoup\n\n\ndef fetch_user_profile(url: str) -> Optional[str]:\n \"\"\"\n Fetches the text content from a personal website.\n\n Given a URL of a person's personal website, this function scrapes\n the content of the page and returns the text found within the <body>.\n\n Args:\n url (str): The URL of the person's personal website.\n\n Returns:\n Optional[str]: The text content of the website's body, or None if any error occurs.\n \"\"\"\n try:\n # Send a GET request to the URL\n response = requests.get(url)\n # Check for successful access to the webpage\n if response.status_code == 200:\n # Parse the HTML content of the page using BeautifulSoup\n soup = BeautifulSoup(response.text, \"html.parser\")\n # Extract the content of the <body> tag\n body_content = soup.find(\"body\")\n # Return all the text in the body tag, stripping leading/trailing whitespaces\n return \" \".join(body_content.stripped_strings) if body_content else None\n else:\n # Return None if the status code isn't 200 (success)\n return None\n except requests.RequestException:\n # Return None if any request-related exception is caught\n return None\n"
},
{
"title": "generate_images",
"description": "This skill generates images from a given query using OpenAI's DALL-E model and saves them to disk.",
"content": "from typing import List\nimport uuid\nimport requests # to perform HTTP requests\nfrom pathlib import Path\n\nfrom openai import OpenAI\n\n\ndef generate_and_save_images(query: str, image_size: str = \"1024x1024\") -> List[str]:\n \"\"\"\n Function to paint, draw or illustrate images based on the users query or request. Generates images from a given query using OpenAI's DALL-E model and saves them to disk. Use the code below anytime there is a request to create an image.\n\n :param query: A natural language description of the image to be generated.\n :param image_size: The size of the image to be generated. (default is \"1024x1024\")\n :return: A list of filenames for the saved images.\n \"\"\"\n\n client = OpenAI() # Initialize the OpenAI client\n response = client.images.generate(model=\"dall-e-3\", prompt=query, n=1, size=image_size) # Generate images\n\n # List to store the file names of saved images\n saved_files = []\n\n # Check if the response is successful\n if response.data:\n for image_data in response.data:\n # Generate a random UUID as the file name\n file_name = str(uuid.uuid4()) + \".png\" # Assuming the image is a PNG\n file_path = Path(file_name)\n\n img_url = image_data.url\n img_response = requests.get(img_url)\n if img_response.status_code == 200:\n # Write the binary content to a file\n with open(file_path, \"wb\") as img_file:\n img_file.write(img_response.content)\n print(f\"Image saved to {file_path}\")\n saved_files.append(str(file_path))\n else:\n print(f\"Failed to download the image from {img_url}\")\n else:\n print(\"No image data found in the response!\")\n\n # Return the list of saved files\n return saved_files\n\n\n# Example usage of the function:\n# generate_and_save_images(\"A cute baby sea otter\")\n"
}
],
"workflows": [
{
"name": "Travel Agent Group Chat Workflow",
"description": "A group chat workflow",
"type": "groupchat",
"sender": {
"type": "userproxy",
"config": {
"name": "userproxy",
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 5,
"system_message": "You are a helpful assistant.",
"code_execution_config": {
"work_dir": null,
"use_docker": false
}
}
},
"receiver": {
"type": "groupchat",
"config": {
"name": "group_chat_manager",
"llm_config": {
"config_list": [
{
"model": "gpt-4-1106-preview"
}
],
"temperature": 0.1,
"timeout": 600,
"cache_seed": 42
},
"human_input_mode": "NEVER",
"system_message": "Group chat manager"
},
"groupchat_config": {
"admin_name": "Admin",
"max_round": 10,
"speaker_selection_method": "auto",
"agents": [
{
"type": "assistant",
"config": {
"name": "travel_planner",
"llm_config": {
"config_list": [
{
"model": "gpt-4-1106-preview"
}
],
"temperature": 0.1,
"timeout": 600,
"cache_seed": 42
},
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 8,
"system_message": "You are a helpful assistant that can suggest a travel plan for a user. You are the primary cordinator who will receive suggestions or advice from other agents (local_assistant, language_assistant). You must ensure that the finally plan integrates the suggestions from other agents or team members. YOUR FINAL RESPONSE MUST BE THE COMPLETE PLAN. When the plan is complete and all perspectives are integrated, you can respond with TERMINATE."
}
},
{
"type": "assistant",
"config": {
"name": "local_assistant",
"llm_config": {
"config_list": [
{
"model": "gpt-4-1106-preview"
}
],
"temperature": 0.1,
"timeout": 600,
"cache_seed": 42
},
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 8,
"system_message": "You are a helpful assistant that can review travel plans, providing critical feedback on how the trip can be enriched for enjoyment of the local culture. If the plan already includes local experiences, you can mention that the plan is satisfactory, with rationale."
}
},
{
"type": "assistant",
"config": {
"name": "language_assistant",
"llm_config": {
"config_list": [
{
"model": "gpt-4-1106-preview"
}
],
"temperature": 0.1,
"timeout": 600,
"cache_seed": 42
},
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 8,
"system_message": "You are a helpful assistant that can review travel plans, providing feedback on important/critical tips about how best to address language or communication challenges for the given destination. If the plan already includes language tips, you can mention that the plan is satisfactory, with rationale."
}
}
]
}
}
},
{
"name": "General Agent Workflow",
"description": "This workflow is used for general purpose tasks.",
"sender": {
"type": "userproxy",
"config": {
"name": "userproxy",
"description": "A user proxy agent that executes code.",
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 10,
"system_message": "You are a helpful assistant.",
"default_auto_reply": "TERMINATE",
"llm_config": false,
"code_execution_config": {
"work_dir": null,
"use_docker": false
}
}
},
"receiver": {
"type": "assistant",
"skills": [
{
"title": "find_papers_arxiv",
"description": "This skill finds relevant papers on arXiv given a query.",
"content": "import os\nimport re\nimport json\nimport hashlib\n\n\ndef search_arxiv(query, max_results=10):\n \"\"\"\n Searches arXiv for the given query using the arXiv API, then returns the search results. This is a helper function. In most cases, callers will want to use 'find_relevant_papers( query, max_results )' instead.\n\n Args:\n query (str): The search query.\n max_results (int, optional): The maximum number of search results to return. Defaults to 10.\n\n Returns:\n jresults (list): A list of dictionaries. Each dictionary contains fields such as 'title', 'authors', 'summary', and 'pdf_url'\n\n Example:\n >>> results = search_arxiv(\"attention is all you need\")\n >>> print(results)\n \"\"\"\n\n import arxiv\n\n key = hashlib.md5((\"search_arxiv(\" + str(max_results) + \")\" + query).encode(\"utf-8\")).hexdigest()\n # Create the cache if it doesn't exist\n cache_dir = \".cache\"\n if not os.path.isdir(cache_dir):\n os.mkdir(cache_dir)\n\n fname = os.path.join(cache_dir, key + \".cache\")\n\n # Cache hit\n if os.path.isfile(fname):\n fh = open(fname, \"r\", encoding=\"utf-8\")\n data = json.loads(fh.read())\n fh.close()\n return data\n\n # Normalize the query, removing operator keywords\n query = re.sub(r\"[^\\s\\w]\", \" \", query.lower())\n query = re.sub(r\"\\s(and|or|not)\\s\", \" \", \" \" + query + \" \")\n query = re.sub(r\"[^\\s\\w]\", \" \", query.lower())\n query = re.sub(r\"\\s+\", \" \", query).strip()\n\n search = arxiv.Search(query=query, max_results=max_results, sort_by=arxiv.SortCriterion.Relevance)\n\n jresults = list()\n for result in search.results():\n r = dict()\n r[\"entry_id\"] = result.entry_id\n r[\"updated\"] = str(result.updated)\n r[\"published\"] = str(result.published)\n r[\"title\"] = result.title\n r[\"authors\"] = [str(a) for a in result.authors]\n r[\"summary\"] = result.summary\n r[\"comment\"] = result.comment\n r[\"journal_ref\"] = result.journal_ref\n r[\"doi\"] = result.doi\n r[\"primary_category\"] = result.primary_category\n r[\"categories\"] = result.categories\n r[\"links\"] = [str(link) for link in result.links]\n r[\"pdf_url\"] = result.pdf_url\n jresults.append(r)\n\n if len(jresults) > max_results:\n jresults = jresults[0:max_results]\n\n # Save to cache\n fh = open(fname, \"w\")\n fh.write(json.dumps(jresults))\n fh.close()\n return jresults\n"
},
{
"title": "generate_images",
"description": "This skill generates images from a given query using OpenAI's DALL-E model and saves them to disk.",
"content": "from typing import List\nimport uuid\nimport requests # to perform HTTP requests\nfrom pathlib import Path\n\nfrom openai import OpenAI\n\n\ndef generate_and_save_images(query: str, image_size: str = \"1024x1024\") -> List[str]:\n \"\"\"\n Function to paint, draw or illustrate images based on the users query or request. Generates images from a given query using OpenAI's DALL-E model and saves them to disk. Use the code below anytime there is a request to create an image.\n\n :param query: A natural language description of the image to be generated.\n :param image_size: The size of the image to be generated. (default is \"1024x1024\")\n :return: A list of filenames for the saved images.\n \"\"\"\n\n client = OpenAI() # Initialize the OpenAI client\n response = client.images.generate(model=\"dall-e-3\", prompt=query, n=1, size=image_size) # Generate images\n\n # List to store the file names of saved images\n saved_files = []\n\n # Check if the response is successful\n if response.data:\n for image_data in response.data:\n # Generate a random UUID as the file name\n file_name = str(uuid.uuid4()) + \".png\" # Assuming the image is a PNG\n file_path = Path(file_name)\n\n img_url = image_data.url\n img_response = requests.get(img_url)\n if img_response.status_code == 200:\n # Write the binary content to a file\n with open(file_path, \"wb\") as img_file:\n img_file.write(img_response.content)\n print(f\"Image saved to {file_path}\")\n saved_files.append(str(file_path))\n else:\n print(f\"Failed to download the image from {img_url}\")\n else:\n print(\"No image data found in the response!\")\n\n # Return the list of saved files\n return saved_files\n\n\n# Example usage of the function:\n# generate_and_save_images(\"A cute baby sea otter\")\n"
}
],
"config": {
"description": "Default assistant to generate plans and write code to solve tasks.",
"name": "primary_assistant",
"llm_config": {
"config_list": [
{
"model": "gpt-4-1106-preview"
}
],
"temperature": 0.1,
"timeout": 600,
"cache_seed": null
},
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 15,
"system_message": "You are a helpful AI assistant. Solve tasks using your coding and language skills. In the following cases, suggest python code (in a python coding block) or shell script (in a sh coding block) for the user to execute. 1. When you need to collect info, use the code to output the info you need, for example, browse or search the web, download/read a file, print the content of a webpage or a file, get the current date/time, check the operating system. After sufficient info is printed and the task is ready to be solved based on your language skill, you can solve the task by yourself. 2. When you need to perform some task with code, use the code to perform the task and output the result. Finish the task smartly. Solve the task step by step if you need to. If a plan is not provided, explain your plan first. Be clear which step uses code, and which step uses your language skill. When using code, you must indicate the script type in the code block. The user cannot provide any other feedback or perform any other action beyond executing the code you suggest. The user can't modify your code. So do not suggest incomplete code which requires users to modify. Don't use a code block if it's not intended to be executed by the user. If you want the user to save the code in a file before executing it, put # filename: <filename> inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user. If the result indicates there is an error, fix the error and output the code again. Suggest the full code instead of partial code or code changes. If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try. When you find an answer, verify the answer carefully. Include verifiable evidence in your response if possible. Reply 'TERMINATE' in the end when everything is done."
}
},
"type": "twoagents"
}
]
}

View File

@@ -0,0 +1,573 @@
import base64
import hashlib
import os
import re
import shutil
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Tuple, Union
from dotenv import load_dotenv
from loguru import logger
from autogen.coding import DockerCommandLineCodeExecutor, LocalCommandLineCodeExecutor
from autogen.oai.client import ModelClient, OpenAIWrapper
from ..datamodel import CodeExecutionConfigTypes, Model, Skill
from ..version import APP_NAME
def md5_hash(text: str) -> str:
"""
Compute the MD5 hash of a given text.
:param text: The string to hash
:return: The MD5 hash of the text
"""
return hashlib.md5(text.encode()).hexdigest()
def check_and_cast_datetime_fields(obj: Any) -> Any:
if hasattr(obj, "created_at") and isinstance(obj.created_at, str):
obj.created_at = str_to_datetime(obj.created_at)
if hasattr(obj, "updated_at") and isinstance(obj.updated_at, str):
obj.updated_at = str_to_datetime(obj.updated_at)
return obj
def str_to_datetime(dt_str: str) -> datetime:
if dt_str[-1] == "Z":
# Replace 'Z' with '+00:00' for UTC timezone
dt_str = dt_str[:-1] + "+00:00"
return datetime.fromisoformat(dt_str)
def clear_folder(folder_path: str) -> None:
"""
Clear the contents of a folder.
:param folder_path: The path to the folder to clear.
"""
# exit if the folder does not exist
if not os.path.exists(folder_path):
return
# exit if the folder does not exist
if not os.path.exists(folder_path):
return
for file in os.listdir(folder_path):
file_path = os.path.join(folder_path, file)
if os.path.isfile(file_path):
os.remove(file_path)
elif os.path.isdir(file_path):
shutil.rmtree(file_path)
def get_file_type(file_path: str) -> str:
"""
Get file type determined by the file extension. If the file extension is not
recognized, 'unknown' will be used as the file type.
:param file_path: The path to the file to be serialized.
:return: A string containing the file type.
"""
# Extended list of file extensions for code and text files
CODE_EXTENSIONS = {
".py",
".js",
".jsx",
".java",
".c",
".cpp",
".cs",
".ts",
".tsx",
".html",
".css",
".scss",
".less",
".json",
".xml",
".yaml",
".yml",
".md",
".rst",
".tex",
".sh",
".bat",
".ps1",
".php",
".rb",
".go",
".swift",
".kt",
".hs",
".scala",
".lua",
".pl",
".sql",
".config",
}
# Supported spreadsheet extensions
CSV_EXTENSIONS = {".csv", ".xlsx"}
# Supported image extensions
IMAGE_EXTENSIONS = {
".png",
".jpg",
".jpeg",
".gif",
".bmp",
".tiff",
".svg",
".webp",
}
# Supported (web) video extensions
VIDEO_EXTENSIONS = {".mp4", ".webm", ".ogg", ".mov", ".avi", ".wmv"}
# Supported PDF extension
PDF_EXTENSION = ".pdf"
# Determine the file extension
_, file_extension = os.path.splitext(file_path)
# Determine the file type based on the extension
if file_extension in CODE_EXTENSIONS:
file_type = "code"
elif file_extension in CSV_EXTENSIONS:
file_type = "csv"
elif file_extension in IMAGE_EXTENSIONS:
file_type = "image"
elif file_extension == PDF_EXTENSION:
file_type = "pdf"
elif file_extension in VIDEO_EXTENSIONS:
file_type = "video"
else:
file_type = "unknown"
return file_type
def serialize_file(file_path: str) -> Tuple[str, str]:
"""
Reads a file from a given file path, base64 encodes its content,
and returns the base64 encoded string along with the file type.
The file type is determined by the file extension. If the file extension is not
recognized, 'unknown' will be used as the file type.
:param file_path: The path to the file to be serialized.
:return: A tuple containing the base64 encoded string of the file and the file type.
"""
file_type = get_file_type(file_path)
# Read the file and encode its contents
try:
with open(file_path, "rb") as file:
file_content = file.read()
base64_encoded_content = base64.b64encode(file_content).decode("utf-8")
except Exception as e:
raise IOError(f"An error occurred while reading the file: {e}") from e
return base64_encoded_content, file_type
def get_modified_files(start_timestamp: float, end_timestamp: float, source_dir: str) -> List[Dict[str, str]]:
"""
Identify files from source_dir that were modified within a specified timestamp range.
The function excludes files with certain file extensions and names.
:param start_timestamp: The floating-point number representing the start timestamp to filter modified files.
:param end_timestamp: The floating-point number representing the end timestamp to filter modified files.
:param source_dir: The directory to search for modified files.
:return: A list of dictionaries with details of relative file paths that were modified.
Dictionary format: {path: "", name: "", extension: "", type: ""}
Files with extensions "__pycache__", "*.pyc", "__init__.py", and "*.cache"
are ignored.
"""
modified_files = []
ignore_extensions = {".pyc", ".cache"}
ignore_files = {"__pycache__", "__init__.py"}
# Walk through the directory tree
for root, dirs, files in os.walk(source_dir):
# Update directories and files to exclude those to be ignored
dirs[:] = [d for d in dirs if d not in ignore_files]
files[:] = [f for f in files if f not in ignore_files and os.path.splitext(f)[1] not in ignore_extensions]
for file in files:
file_path = os.path.join(root, file)
file_mtime = os.path.getmtime(file_path)
# Verify if the file was modified within the given timestamp range
if start_timestamp <= file_mtime <= end_timestamp:
file_relative_path = (
"files/user" + file_path.split("files/user", 1)[1] if "files/user" in file_path else ""
)
file_type = get_file_type(file_path)
file_dict = {
"path": file_relative_path,
"name": os.path.basename(file),
# Remove the dot
"extension": os.path.splitext(file)[1].lstrip("."),
"type": file_type,
}
modified_files.append(file_dict)
# Sort the modified files by extension
modified_files.sort(key=lambda x: x["extension"])
return modified_files
def get_app_root() -> str:
"""
Get the root directory of the application.
:return: The root directory of the application.
"""
app_name = f".{APP_NAME}"
default_app_root = os.path.join(os.path.expanduser("~"), app_name)
if not os.path.exists(default_app_root):
os.makedirs(default_app_root, exist_ok=True)
app_root = os.environ.get("AUTOGENSTUDIO_APPDIR") or default_app_root
return app_root
def get_db_uri(app_root: str) -> str:
"""
Get the default database URI for the application.
:param app_root: The root directory of the application.
:return: The default database URI.
"""
db_uri = f"sqlite:///{os.path.join(app_root, 'database.sqlite')}"
db_uri = os.environ.get("AUTOGENSTUDIO_DATABASE_URI") or db_uri
logger.info(f"Using database URI: {db_uri}")
return db_uri
def init_app_folders(app_file_path: str) -> Dict[str, str]:
"""
Initialize folders needed for a web server, such as static file directories
and user-specific data directories. Also load any .env file if it exists.
:param root_file_path: The root directory where webserver folders will be created
:return: A dictionary with the path of each created folder
"""
app_root = get_app_root()
if not os.path.exists(app_root):
os.makedirs(app_root, exist_ok=True)
# load .env file if it exists
env_file = os.path.join(app_root, ".env")
if os.path.exists(env_file):
logger.info(f"Loaded environment variables from {env_file}")
load_dotenv(env_file)
files_static_root = os.path.join(app_root, "files/")
static_folder_root = os.path.join(app_file_path, "ui")
os.makedirs(files_static_root, exist_ok=True)
os.makedirs(os.path.join(files_static_root, "user"), exist_ok=True)
os.makedirs(static_folder_root, exist_ok=True)
folders = {
"files_static_root": files_static_root,
"static_folder_root": static_folder_root,
"app_root": app_root,
"database_engine_uri": get_db_uri(app_root=app_root),
}
logger.info(f"Initialized application data folder: {app_root}")
return folders
def get_skills_prompt(skills: List[Skill], work_dir: str) -> str:
"""
Create a prompt with the content of all skills and write the skills to a file named skills.py in the work_dir.
:param skills: A dictionary skills
:return: A string containing the content of all skills
"""
instruction = """
While solving the task you may use functions below which will be available in a file called skills.py .
To use a function skill.py in code, IMPORT THE FUNCTION FROM skills.py and then use the function.
If you need to install python packages, write shell code to
install via pip and use --quiet option.
"""
prompt = "" # filename: skills.py
for skill in skills:
if not isinstance(skill, Skill):
skill = Skill(**skill)
if skill.secrets:
for secret in skill.secrets:
if secret.get("value") is not None:
os.environ[secret["secret"]] = secret["value"]
prompt += f"""
##### Begin of {skill.name} #####
from skills import {skill.name} # Import the function from skills.py
{skill.content}
#### End of {skill.name} ####
"""
return instruction + prompt
def save_skills_to_file(skills: List[Skill], work_dir: str) -> None:
"""
Write the skills to a file named skills.py in the work_dir.
:param skills: A dictionary skills
"""
# TBD: Double check for duplicate skills?
# check if work_dir exists
if not os.path.exists(work_dir):
os.makedirs(work_dir)
skills_content = ""
for skill in skills:
if not isinstance(skill, Skill):
skill = Skill(**skill)
skills_content += f"""
##### Begin of {skill.name} #####
{skill.content}
#### End of {skill.name} ####
"""
# overwrite skills.py in work_dir
with open(os.path.join(work_dir, "skills.py"), "w", encoding="utf-8") as f:
f.write(skills_content)
def delete_files_in_folder(folders: Union[str, List[str]]) -> None:
"""
Delete all files and directories in the specified folders.
:param folders: A list of folders or a single folder string
"""
if isinstance(folders, str):
folders = [folders]
for folder in folders:
# Check if the folder exists
if not os.path.isdir(folder):
continue
# List all the entries in the directory
for entry in os.listdir(folder):
# Get the full path
path = os.path.join(folder, entry)
try:
if os.path.isfile(path) or os.path.islink(path):
# Remove the file or link
os.remove(path)
elif os.path.isdir(path):
# Remove the directory and all its content
shutil.rmtree(path)
except Exception as e:
# Print the error message and skip
logger.info(f"Failed to delete {path}. Reason: {e}")
def extract_successful_code_blocks(messages: List[Dict[str, str]]) -> List[str]:
"""
Parses through a list of messages containing code blocks and execution statuses,
returning the array of code blocks that executed successfully and retains
the backticks for Markdown rendering.
Parameters:
messages (List[Dict[str, str]]): A list of message dictionaries containing 'content' and 'role' keys.
Returns:
List[str]: A list containing the code blocks that were successfully executed, including backticks.
"""
successful_code_blocks = []
# Regex pattern to capture code blocks enclosed in triple backticks.
code_block_regex = r"```[\s\S]*?```"
for i, row in enumerate(messages):
message = row["message"]
if message["role"] == "user" and "execution succeeded" in message["content"]:
if i > 0 and messages[i - 1]["message"]["role"] == "assistant":
prev_content = messages[i - 1]["message"]["content"]
# Find all matches for code blocks
code_blocks = re.findall(code_block_regex, prev_content)
# Add the code blocks with backticks
successful_code_blocks.extend(code_blocks)
return successful_code_blocks
def sanitize_model(model: Model):
"""
Sanitize model dictionary to remove None values and empty strings and only keep valid keys.
"""
if isinstance(model, Model):
model = model.model_dump()
valid_keys = ["model", "base_url", "api_key", "api_type", "api_version"]
# only add key if value is not None
sanitized_model = {k: v for k, v in model.items() if (v is not None and v != "") and k in valid_keys}
return sanitized_model
def test_model(model: Model):
"""
Test the model endpoint by sending a simple message to the model and returning the response.
"""
print("Testing model", model)
sanitized_model = sanitize_model(model)
client = OpenAIWrapper(config_list=[sanitized_model])
response = client.create(
messages=[
{
"role": "system",
"content": "You are a helpful assistant that can add numbers. ONLY RETURN THE RESULT.",
},
{
"role": "user",
"content": "2+2=",
},
],
cache_seed=None,
)
return response.choices[0].message.content
def load_code_execution_config(code_execution_type: CodeExecutionConfigTypes, work_dir: str):
"""
Load the code execution configuration based on the code execution type.
:param code_execution_type: The code execution type.
:param work_dir: The working directory to store code execution files.
:return: The code execution configuration.
"""
work_dir = Path(work_dir)
work_dir.mkdir(exist_ok=True)
executor = None
if code_execution_type == CodeExecutionConfigTypes.local:
executor = LocalCommandLineCodeExecutor(work_dir=work_dir)
elif code_execution_type == CodeExecutionConfigTypes.docker:
try:
executor = DockerCommandLineCodeExecutor(work_dir=work_dir)
except Exception as e:
logger.error(f"Error initializing Docker executor: {e}")
return False
elif code_execution_type == CodeExecutionConfigTypes.none:
return False
else:
raise ValueError(f"Invalid code execution type: {code_execution_type}")
code_execution_config = {
"executor": executor,
}
return code_execution_config
def summarize_chat_history(task: str, messages: List[Dict[str, str]], client: ModelClient):
"""
Summarize the chat history using the model endpoint and returning the response.
"""
summarization_system_prompt = f"""
You are a helpful assistant that is able to review the chat history between a set of agents (userproxy agents, assistants etc) as they try to address a given TASK and provide a summary. Be SUCCINCT but also comprehensive enough to allow others (who cannot see the chat history) understand and recreate the solution.
The task requested by the user is:
===
{task}
===
The summary should focus on extracting the actual solution to the task from the chat history (assuming the task was addressed) such that any other agent reading the summary will understand what the actual solution is. Use a neutral tone and DO NOT directly mention the agents. Instead only focus on the actions that were carried out (e.g. do not say 'assistant agent generated some code visualization code ..' instead say say 'visualization code was generated ..'. The answer should be framed as a response to the user task. E.g. if the task is "What is the height of the Eiffel tower", the summary should be "The height of the Eiffel Tower is ...").
"""
summarization_prompt = [
{
"role": "system",
"content": summarization_system_prompt,
},
{
"role": "user",
"content": f"Summarize the following chat history. {str(messages)}",
},
]
response = client.create(messages=summarization_prompt, cache_seed=None)
return response.choices[0].message.content
def get_autogen_log(db_path="logs.db"):
"""
Fetches data the autogen logs database.
Args:
dbname (str): Name of the database file. Defaults to "logs.db".
table (str): Name of the table to query. Defaults to "chat_completions".
Returns:
list: A list of dictionaries, where each dictionary represents a row from the table.
"""
import json
import sqlite3
con = sqlite3.connect(db_path)
query = """
SELECT
chat_completions.*,
agents.name AS agent_name
FROM
chat_completions
JOIN
agents ON chat_completions.wrapper_id = agents.wrapper_id
"""
cursor = con.execute(query)
rows = cursor.fetchall()
column_names = [description[0] for description in cursor.description]
data = [dict(zip(column_names, row)) for row in rows]
for row in data:
response = json.loads(row["response"])
print(response)
total_tokens = response.get("usage", {}).get("total_tokens", 0)
row["total_tokens"] = total_tokens
con.close()
return data
def find_key_value(d, target_key):
"""
Recursively search for a key in a nested dictionary and return its value.
"""
if d is None:
return None
if isinstance(d, dict):
if target_key in d:
return d[target_key]
for k in d:
item = find_key_value(d[k], target_key)
if item is not None:
return item
elif isinstance(d, list):
for i in d:
item = find_key_value(i, target_key)
if item is not None:
return item
return None

View File

@@ -0,0 +1,3 @@
VERSION = "0.1.6"
__version__ = VERSION
APP_NAME = "autogenstudio"

View File

@@ -0,0 +1,512 @@
import asyncio
import os
import queue
import threading
import traceback
from contextlib import asynccontextmanager
from typing import Any, Union
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from loguru import logger
from openai import OpenAIError
from ..chatmanager import AutoGenChatManager
from ..database import workflow_from_id
from ..database.dbmanager import DBManager
from ..datamodel import Agent, Message, Model, Response, Session, Skill, Workflow
from ..profiler import Profiler
from ..utils import check_and_cast_datetime_fields, init_app_folders, md5_hash, test_model
from ..version import VERSION
from ..websocket_connection_manager import WebSocketConnectionManager
profiler = Profiler()
managers = {"chat": None} # manage calls to autogen
# Create thread-safe queue for messages between api thread and autogen threads
message_queue = queue.Queue()
active_connections = []
active_connections_lock = asyncio.Lock()
websocket_manager = WebSocketConnectionManager(
active_connections=active_connections,
active_connections_lock=active_connections_lock,
)
def message_handler():
while True:
message = message_queue.get()
logger.info(
"** Processing Agent Message on Queue: Active Connections: "
+ str([client_id for _, client_id in websocket_manager.active_connections])
+ " **"
)
for connection, socket_client_id in websocket_manager.active_connections:
if message["connection_id"] == socket_client_id:
logger.info(
f"Sending message to connection_id: {message['connection_id']}. Connection ID: {socket_client_id}"
)
asyncio.run(websocket_manager.send_message(message, connection))
else:
logger.info(
f"Skipping message for connection_id: {message['connection_id']}. Connection ID: {socket_client_id}"
)
message_queue.task_done()
message_handler_thread = threading.Thread(target=message_handler, daemon=True)
message_handler_thread.start()
app_file_path = os.path.dirname(os.path.abspath(__file__))
folders = init_app_folders(app_file_path)
ui_folder_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ui")
database_engine_uri = folders["database_engine_uri"]
dbmanager = DBManager(engine_uri=database_engine_uri)
HUMAN_INPUT_TIMEOUT_SECONDS = 180
@asynccontextmanager
async def lifespan(app: FastAPI):
print("***** App started *****")
managers["chat"] = AutoGenChatManager(
message_queue=message_queue,
websocket_manager=websocket_manager,
human_input_timeout=HUMAN_INPUT_TIMEOUT_SECONDS,
)
dbmanager.create_db_and_tables()
yield
# Close all active connections
await websocket_manager.disconnect_all()
print("***** App stopped *****")
app = FastAPI(lifespan=lifespan)
# allow cross origin requests for testing on localhost:800* ports only
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:8000",
"http://127.0.0.1:8000",
"http://localhost:8001",
"http://localhost:8081",
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
show_docs = os.environ.get("AUTOGENSTUDIO_API_DOCS", "False").lower() == "true"
docs_url = "/docs" if show_docs else None
api = FastAPI(
root_path="/api",
title="AutoGen Studio API",
version=VERSION,
docs_url=docs_url,
description="AutoGen Studio is a low-code tool for building and testing multi-agent workflows using AutoGen.",
)
# mount an api route such that the main route serves the ui and the /api
app.mount("/api", api)
app.mount("/", StaticFiles(directory=ui_folder_path, html=True), name="ui")
api.mount(
"/files",
StaticFiles(directory=folders["files_static_root"], html=True),
name="files",
)
# manage websocket connections
def create_entity(model: Any, model_class: Any, filters: dict = None):
"""Create a new entity"""
model = check_and_cast_datetime_fields(model)
try:
response: Response = dbmanager.upsert(model)
return response.model_dump(mode="json")
except Exception as ex_error:
print(ex_error)
return {
"status": False,
"message": f"Error occurred while creating {model_class.__name__}: " + str(ex_error),
}
def list_entity(
model_class: Any,
filters: dict = None,
return_json: bool = True,
order: str = "desc",
):
"""List all entities for a user"""
return dbmanager.get(model_class, filters=filters, return_json=return_json, order=order)
def delete_entity(model_class: Any, filters: dict = None):
"""Delete an entity"""
return dbmanager.delete(filters=filters, model_class=model_class)
@api.get("/skills")
async def list_skills(user_id: str):
"""List all skills for a user"""
filters = {"user_id": user_id}
return list_entity(Skill, filters=filters)
@api.post("/skills")
async def create_skill(skill: Skill):
"""Create a new skill"""
filters = {"user_id": skill.user_id}
return create_entity(skill, Skill, filters=filters)
@api.delete("/skills/delete")
async def delete_skill(skill_id: int, user_id: str):
"""Delete a skill"""
filters = {"id": skill_id, "user_id": user_id}
return delete_entity(Skill, filters=filters)
@api.get("/models")
async def list_models(user_id: str):
"""List all models for a user"""
filters = {"user_id": user_id}
return list_entity(Model, filters=filters)
@api.post("/models")
async def create_model(model: Model):
"""Create a new model"""
return create_entity(model, Model)
@api.post("/models/test")
async def test_model_endpoint(model: Model):
"""Test a model"""
try:
response = test_model(model)
return {
"status": True,
"message": "Model tested successfully",
"data": response,
}
except (OpenAIError, Exception) as ex_error:
return {
"status": False,
"message": "Error occurred while testing model: " + str(ex_error),
}
@api.delete("/models/delete")
async def delete_model(model_id: int, user_id: str):
"""Delete a model"""
filters = {"id": model_id, "user_id": user_id}
return delete_entity(Model, filters=filters)
@api.get("/agents")
async def list_agents(user_id: str):
"""List all agents for a user"""
filters = {"user_id": user_id}
return list_entity(Agent, filters=filters)
@api.post("/agents")
async def create_agent(agent: Agent):
"""Create a new agent"""
return create_entity(agent, Agent)
@api.delete("/agents/delete")
async def delete_agent(agent_id: int, user_id: str):
"""Delete an agent"""
filters = {"id": agent_id, "user_id": user_id}
return delete_entity(Agent, filters=filters)
@api.post("/agents/link/model/{agent_id}/{model_id}")
async def link_agent_model(agent_id: int, model_id: int):
"""Link a model to an agent"""
return dbmanager.link(link_type="agent_model", primary_id=agent_id, secondary_id=model_id)
@api.delete("/agents/link/model/{agent_id}/{model_id}")
async def unlink_agent_model(agent_id: int, model_id: int):
"""Unlink a model from an agent"""
return dbmanager.unlink(link_type="agent_model", primary_id=agent_id, secondary_id=model_id)
@api.get("/agents/link/model/{agent_id}")
async def get_agent_models(agent_id: int):
"""Get all models linked to an agent"""
return dbmanager.get_linked_entities("agent_model", agent_id, return_json=True)
@api.post("/agents/link/skill/{agent_id}/{skill_id}")
async def link_agent_skill(agent_id: int, skill_id: int):
"""Link an a skill to an agent"""
return dbmanager.link(link_type="agent_skill", primary_id=agent_id, secondary_id=skill_id)
@api.delete("/agents/link/skill/{agent_id}/{skill_id}")
async def unlink_agent_skill(agent_id: int, skill_id: int):
"""Unlink an a skill from an agent"""
return dbmanager.unlink(link_type="agent_skill", primary_id=agent_id, secondary_id=skill_id)
@api.get("/agents/link/skill/{agent_id}")
async def get_agent_skills(agent_id: int):
"""Get all skills linked to an agent"""
return dbmanager.get_linked_entities("agent_skill", agent_id, return_json=True)
@api.post("/agents/link/agent/{primary_agent_id}/{secondary_agent_id}")
async def link_agent_agent(primary_agent_id: int, secondary_agent_id: int):
"""Link an agent to another agent"""
return dbmanager.link(
link_type="agent_agent",
primary_id=primary_agent_id,
secondary_id=secondary_agent_id,
)
@api.delete("/agents/link/agent/{primary_agent_id}/{secondary_agent_id}")
async def unlink_agent_agent(primary_agent_id: int, secondary_agent_id: int):
"""Unlink an agent from another agent"""
return dbmanager.unlink(
link_type="agent_agent",
primary_id=primary_agent_id,
secondary_id=secondary_agent_id,
)
@api.get("/agents/link/agent/{agent_id}")
async def get_linked_agents(agent_id: int):
"""Get all agents linked to an agent"""
return dbmanager.get_linked_entities("agent_agent", agent_id, return_json=True)
@api.get("/workflows")
async def list_workflows(user_id: str):
"""List all workflows for a user"""
filters = {"user_id": user_id}
return list_entity(Workflow, filters=filters)
@api.get("/workflows/{workflow_id}")
async def get_workflow(workflow_id: int, user_id: str):
"""Get a workflow"""
filters = {"id": workflow_id, "user_id": user_id}
return list_entity(Workflow, filters=filters)
@api.get("/workflows/export/{workflow_id}")
async def export_workflow(workflow_id: int, user_id: str):
"""Export a user workflow"""
response = Response(message="Workflow exported successfully", status=True, data=None)
try:
workflow_details = workflow_from_id(workflow_id, dbmanager=dbmanager)
response.data = workflow_details
except Exception as ex_error:
response.message = "Error occurred while exporting workflow: " + str(ex_error)
response.status = False
return response.model_dump(mode="json")
@api.post("/workflows")
async def create_workflow(workflow: Workflow):
"""Create a new workflow"""
return create_entity(workflow, Workflow)
@api.delete("/workflows/delete")
async def delete_workflow(workflow_id: int, user_id: str):
"""Delete a workflow"""
filters = {"id": workflow_id, "user_id": user_id}
return delete_entity(Workflow, filters=filters)
@api.post("/workflows/link/agent/{workflow_id}/{agent_id}/{agent_type}")
async def link_workflow_agent(workflow_id: int, agent_id: int, agent_type: str):
"""Link an agent to a workflow"""
return dbmanager.link(
link_type="workflow_agent",
primary_id=workflow_id,
secondary_id=agent_id,
agent_type=agent_type,
)
@api.post("/workflows/link/agent/{workflow_id}/{agent_id}/{agent_type}/{sequence_id}")
async def link_workflow_agent_sequence(workflow_id: int, agent_id: int, agent_type: str, sequence_id: int):
"""Link an agent to a workflow"""
print("Sequence ID: ", sequence_id)
return dbmanager.link(
link_type="workflow_agent",
primary_id=workflow_id,
secondary_id=agent_id,
agent_type=agent_type,
sequence_id=sequence_id,
)
@api.delete("/workflows/link/agent/{workflow_id}/{agent_id}/{agent_type}")
async def unlink_workflow_agent(workflow_id: int, agent_id: int, agent_type: str):
"""Unlink an agent from a workflow"""
return dbmanager.unlink(
link_type="workflow_agent",
primary_id=workflow_id,
secondary_id=agent_id,
agent_type=agent_type,
)
@api.delete("/workflows/link/agent/{workflow_id}/{agent_id}/{agent_type}/{sequence_id}")
async def unlink_workflow_agent_sequence(workflow_id: int, agent_id: int, agent_type: str, sequence_id: int):
"""Unlink an agent from a workflow sequence"""
return dbmanager.unlink(
link_type="workflow_agent",
primary_id=workflow_id,
secondary_id=agent_id,
agent_type=agent_type,
sequence_id=sequence_id,
)
@api.get("/workflows/link/agent/{workflow_id}")
async def get_linked_workflow_agents(workflow_id: int):
"""Get all agents linked to a workflow"""
return dbmanager.get_linked_entities(
link_type="workflow_agent",
primary_id=workflow_id,
return_json=True,
)
@api.get("/profiler/{message_id}")
async def profile_agent_task_run(message_id: int):
"""Profile an agent task run"""
try:
agent_message = dbmanager.get(Message, filters={"id": message_id}).data[0]
profile = profiler.profile(agent_message)
return {
"status": True,
"message": "Agent task run profiled successfully",
"data": profile,
}
except Exception as ex_error:
return {
"status": False,
"message": "Error occurred while profiling agent task run: " + str(ex_error),
}
@api.get("/sessions")
async def list_sessions(user_id: str):
"""List all sessions for a user"""
filters = {"user_id": user_id}
return list_entity(Session, filters=filters)
@api.post("/sessions")
async def create_session(session: Session):
"""Create a new session"""
return create_entity(session, Session)
@api.delete("/sessions/delete")
async def delete_session(session_id: int, user_id: str):
"""Delete a session"""
filters = {"id": session_id, "user_id": user_id}
return delete_entity(Session, filters=filters)
@api.get("/sessions/{session_id}/messages")
async def list_messages(user_id: str, session_id: int):
"""List all messages for a use session"""
filters = {"user_id": user_id, "session_id": session_id}
return list_entity(Message, filters=filters, order="asc", return_json=True)
@api.post("/sessions/{session_id}/workflow/{workflow_id}/run")
async def run_session_workflow(message: Message, session_id: int, workflow_id: int):
"""Runs a workflow on provided message"""
try:
user_message_history = (
dbmanager.get(
Message,
filters={"user_id": message.user_id, "session_id": message.session_id},
return_json=True,
).data
if session_id is not None
else []
)
# save incoming message
dbmanager.upsert(message)
user_dir = os.path.join(folders["files_static_root"], "user", md5_hash(message.user_id))
os.makedirs(user_dir, exist_ok=True)
workflow = workflow_from_id(workflow_id, dbmanager=dbmanager)
agent_response: Message = await managers["chat"].a_chat(
message=message,
history=user_message_history,
user_dir=user_dir,
workflow=workflow,
connection_id=message.connection_id,
)
response: Response = dbmanager.upsert(agent_response)
return response.model_dump(mode="json")
except Exception as ex_error:
return {
"status": False,
"message": "Error occurred while processing message: " + str(ex_error),
}
@api.get("/version")
async def get_version():
return {
"status": True,
"message": "Version retrieved successfully",
"data": {"version": VERSION},
}
# websockets
async def process_socket_message(data: dict, websocket: WebSocket, client_id: str):
print(f"Client says: {data['type']}")
if data["type"] == "user_message":
user_message = Message(**data["data"])
session_id = data["data"].get("session_id", None)
workflow_id = data["data"].get("workflow_id", None)
response = await run_session_workflow(message=user_message, session_id=session_id, workflow_id=workflow_id)
response_socket_message = {
"type": "agent_response",
"data": response,
"connection_id": client_id,
}
await websocket_manager.send_message(response_socket_message, websocket)
@api.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str):
await websocket_manager.connect(websocket, client_id)
try:
while True:
data = await websocket.receive_json()
await process_socket_message(data, websocket, client_id)
except WebSocketDisconnect:
print(f"Client #{client_id} is disconnected")
await websocket_manager.disconnect(websocket)

View File

@@ -0,0 +1,30 @@
# loads a fast api api endpoint with a single endpoint that takes text query and return a response
import json
import os
from fastapi import FastAPI
from ..datamodel import Response
from ..workflowmanager import WorkflowManager
app = FastAPI()
workflow_file_path = os.environ.get("AUTOGENSTUDIO_WORKFLOW_FILE", None)
if workflow_file_path:
workflow_manager = WorkflowManager(workflow=workflow_file_path)
else:
raise ValueError("Workflow file must be specified")
@app.get("/predict/{task}")
async def predict(task: str):
response = Response(message="Task successfully completed", status=True, data=None)
try:
result_message = workflow_manager.run(message=task, clear_history=False)
response.data = result_message
except Exception as e:
response.message = str(e)
response.status = False
return response

View File

@@ -0,0 +1,135 @@
import asyncio
from typing import Any, Dict, List, Optional, Tuple, Union
import websockets
from fastapi import WebSocket, WebSocketDisconnect
class WebSocketConnectionManager:
"""
Manages WebSocket connections including sending, broadcasting, and managing the lifecycle of connections.
"""
def __init__(
self,
active_connections: List[Tuple[WebSocket, str]] = None,
active_connections_lock: asyncio.Lock = None,
) -> None:
"""
Initializes WebSocketConnectionManager with an optional list of active WebSocket connections.
:param active_connections: A list of tuples, each containing a WebSocket object and its corresponding client_id.
"""
if active_connections is None:
active_connections = []
self.active_connections_lock = active_connections_lock
self.active_connections: List[Tuple[WebSocket, str]] = active_connections
async def connect(self, websocket: WebSocket, client_id: str) -> None:
"""
Accepts a new WebSocket connection and appends it to the active connections list.
:param websocket: The WebSocket instance representing a client connection.
:param client_id: A string representing the unique identifier of the client.
"""
await websocket.accept()
async with self.active_connections_lock:
self.active_connections.append((websocket, client_id))
print(f"New Connection: {client_id}, Total: {len(self.active_connections)}")
async def disconnect(self, websocket: WebSocket) -> None:
"""
Disconnects and removes a WebSocket connection from the active connections list.
:param websocket: The WebSocket instance to remove.
"""
async with self.active_connections_lock:
try:
self.active_connections = [conn for conn in self.active_connections if conn[0] != websocket]
print(f"Connection Closed. Total: {len(self.active_connections)}")
except ValueError:
print("Error: WebSocket connection not found")
async def disconnect_all(self) -> None:
"""
Disconnects all active WebSocket connections.
"""
for connection, _ in self.active_connections[:]:
await self.disconnect(connection)
async def send_message(self, message: Union[Dict, str], websocket: WebSocket) -> None:
"""
Sends a JSON message to a single WebSocket connection.
:param message: A JSON serializable dictionary containing the message to send.
:param websocket: The WebSocket instance through which to send the message.
"""
try:
async with self.active_connections_lock:
await websocket.send_json(message)
except WebSocketDisconnect:
print("Error: Tried to send a message to a closed WebSocket")
await self.disconnect(websocket)
except websockets.exceptions.ConnectionClosedOK:
print("Error: WebSocket connection closed normally")
await self.disconnect(websocket)
except Exception as e:
print(f"Error in sending message: {str(e)}", message)
await self.disconnect(websocket)
async def get_input(self, prompt: Union[Dict, str], websocket: WebSocket, timeout: int = 60) -> str:
"""
Sends a JSON message to a single WebSocket connection as a prompt for user input.
Waits on a user response or until the given timeout elapses.
:param prompt: A JSON serializable dictionary containing the message to send.
:param websocket: The WebSocket instance through which to send the message.
"""
response = "Error: Unexpected response.\nTERMINATE"
try:
async with self.active_connections_lock:
await websocket.send_json(prompt)
result = await asyncio.wait_for(websocket.receive_json(), timeout=timeout)
data = result.get("data")
if data:
response = data.get("content", "Error: Unexpected response format\nTERMINATE")
else:
response = "Error: Unexpected response format\nTERMINATE"
except asyncio.TimeoutError:
response = f"The user was timed out after {timeout} seconds of inactivity.\nTERMINATE"
except WebSocketDisconnect:
print("Error: Tried to send a message to a closed WebSocket")
await self.disconnect(websocket)
response = "The user was disconnected\nTERMINATE"
except websockets.exceptions.ConnectionClosedOK:
print("Error: WebSocket connection closed normally")
await self.disconnect(websocket)
response = "The user was disconnected\nTERMINATE"
except Exception as e:
print(f"Error in sending message: {str(e)}", prompt)
await self.disconnect(websocket)
response = f"Error: {e}\nTERMINATE"
return response
async def broadcast(self, message: Dict) -> None:
"""
Broadcasts a JSON message to all active WebSocket connections.
:param message: A JSON serializable dictionary containing the message to broadcast.
"""
# Create a message dictionary with the desired format
message_dict = {"message": message}
for connection, _ in self.active_connections[:]:
try:
if connection.client_state == websockets.protocol.State.OPEN:
# Call send_message method with the message dictionary and current WebSocket connection
await self.send_message(message_dict, connection)
else:
print("Error: WebSocket connection is closed")
await self.disconnect(connection)
except (WebSocketDisconnect, websockets.exceptions.ConnectionClosedOK) as e:
print(f"Error: WebSocket disconnected or closed({str(e)})")
await self.disconnect(connection)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5e3340a765da6dff6585c8b2e8a4014df0c94b537d62d341d2d0d45627bbc345
size 198222

View File

@@ -0,0 +1 @@
GATSBY_API_URL=http://127.0.0.1:8081/api

View File

@@ -0,0 +1,8 @@
node_modules/
.cache/
public/
.env.development
.env.production
yarn.lock

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Victor Dibia
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.

View File

@@ -0,0 +1,31 @@
## 🚀 Running UI in Dev Mode
Run the UI in dev mode (make changes and see them reflected in the browser with hotreloading):
- npm install
- npm run start
This should start the server on port 8000.
## Design Elements
- **Gatsby**: The app is created in Gatsby. A guide on bootstrapping a Gatsby app can be found here - https://www.gatsbyjs.com/docs/quick-start/.
This provides an overview of the project file structure include functionality of files like `gatsby-config.js`, `gatsby-node.js`, `gatsby-browser.js` and `gatsby-ssr.js`.
- **TailwindCSS**: The app uses TailwindCSS for styling. A guide on using TailwindCSS with Gatsby can be found here - https://tailwindcss.com/docs/guides/gatsby.https://tailwindcss.com/docs/guides/gatsby . This will explain the functionality in tailwind.config.js and postcss.config.js.
## Modifying the UI, Adding Pages
The core of the app can be found in the `src` folder. To add pages, add a new folder in `src/pages` and add a `index.js` file. This will be the entry point for the page. For example to add a route in the app like `/about`, add a folder `about` in `src/pages` and add a `index.tsx` file. You can follow the content style in `src/pages/index.tsx` to add content to the page.
Core logic for each component should be written in the `src/components` folder and then imported in pages as needed.
## connecting to front end
the front end makes request to the backend api and expects it at /api on localhost port 8081
## setting env variables for the UI
- please look at `.env.default`
- make a copy of this file and name it `.env.development`
- set the values for the variables in this file
- The main variable here is `GATSBY_API_URL` which should be set to `http://localhost:8081/api` for local development. This tells the UI where to make requests to the backend.

View File

@@ -0,0 +1,6 @@
import "antd/dist/reset.css";
import "./src/styles/global.css";
import AuthProvider from "./src/hooks/provider";
export const wrapRootElement = AuthProvider;

View File

@@ -0,0 +1,61 @@
import type { GatsbyConfig } from "gatsby";
import fs from "fs";
const envFile = `.env.${process.env.NODE_ENV}`;
fs.access(envFile, fs.constants.F_OK, (err) => {
if (err) {
console.warn(`File '${envFile}' is missing. Using default values.`);
}
});
require("dotenv").config({
path: envFile,
});
const config: GatsbyConfig = {
pathPrefix: process.env.PREFIX_PATH_VALUE || '',
siteMetadata: {
title: `AutoGen Studio [Beta]`,
description: `Build Multi-Agent Apps`,
siteUrl: `http://tbd.place`,
},
flags: {
LAZY_IMAGES: true,
FAST_DEV: true,
DEV_SSR: false,
},
plugins: [
"gatsby-plugin-sass",
"gatsby-plugin-image",
"gatsby-plugin-sitemap",
"gatsby-plugin-postcss",
{
resolve: "gatsby-plugin-manifest",
options: {
icon: "src/images/icon.png",
},
},
"gatsby-plugin-mdx",
"gatsby-plugin-sharp",
"gatsby-transformer-sharp",
{
resolve: "gatsby-source-filesystem",
options: {
name: "images",
path: "./src/images/",
},
__key: "images",
},
{
resolve: "gatsby-source-filesystem",
options: {
name: "pages",
path: "./src/pages/",
},
__key: "pages",
},
],
};
export default config;

View File

@@ -0,0 +1,16 @@
import React from "react";
const codeToRunOnClient = `(function() {
try {
var mode = localStorage.getItem('darkmode');
document.getElementsByTagName("html")[0].className === 'dark' ? 'dark' : 'light';
} catch (e) {}
})();`;
export const onRenderBody = ({ setHeadComponents }) =>
setHeadComponents([
<script
key="myscript"
dangerouslySetInnerHTML={{ __html: codeToRunOnClient }}
/>,
]);

View File

@@ -0,0 +1,71 @@
{
"name": "AutoGen_Studio",
"version": "1.0.0",
"private": true,
"description": "AutoGen Studio - Build LLM Enabled Agents",
"author": "SPIRAL Team",
"keywords": [
"gatsby"
],
"scripts": {
"develop": "gatsby clean && gatsby develop",
"dev": "npm run develop",
"start": "gatsby clean && gatsby develop",
"build": "gatsby clean && rm -rf ../autogenstudio/web/ui && PREFIX_PATH_VALUE='' gatsby build --prefix-paths && rsync -a --delete public/ ../autogenstudio/web/ui/",
"serve": "gatsby serve",
"clean": "gatsby clean",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@ant-design/charts": "^1.3.6",
"@ant-design/plots": "^2.2.2",
"@headlessui/react": "^1.7.16",
"@heroicons/react": "^2.0.18",
"@mdx-js/mdx": "^1.6.22",
"@mdx-js/react": "^1.6.22",
"@monaco-editor/react": "^4.6.0",
"@tailwindcss/line-clamp": "^0.4.0",
"@tailwindcss/typography": "^0.5.9",
"@types/lodash.debounce": "^4.0.9",
"@types/react-syntax-highlighter": "^15.5.10",
"antd": "^5.1.0",
"autoprefixer": "^10.4.7",
"gatsby": "^4.14.0",
"gatsby-plugin-image": "^2.14.1",
"gatsby-plugin-manifest": "^4.14.0",
"gatsby-plugin-mdx": "^3.14.0",
"gatsby-plugin-postcss": "^5.14.0",
"gatsby-plugin-sass": "^5.14.0",
"gatsby-plugin-sharp": "^4.14.1",
"gatsby-plugin-sitemap": "^5.14.0",
"gatsby-source-filesystem": "^4.14.0",
"gatsby-transformer-sharp": "^4.14.0",
"jszip": "^3.10.1",
"lodash.debounce": "^4.0.8",
"papaparse": "^5.4.1",
"postcss": "^8.4.13",
"react": "^18.2.0",
"react-contenteditable": "^3.3.6",
"react-dom": "^18.2.0",
"react-inner-image-zoom": "^3.0.2",
"react-markdown": "^8.0.7",
"react-resizable": "^3.0.5",
"react-router-dom": "^6.3.0",
"react-syntax-highlighter": "^15.5.0",
"remark-gfm": "^3.0.1",
"sass": "^1.51.0",
"tailwindcss": "^3.0.24",
"uuid": "^9.0.1",
"zustand": "^4.4.6"
},
"devDependencies": {
"@types/node": "^18.7.13",
"@types/papaparse": "^5.3.14",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.15",
"@types/react-inner-image-zoom": "^3.0.0",
"@types/react-resizable": "^3.0.2",
"@types/uuid": "^9.0.8",
"typescript": "^4.6.4"
}
}

View File

@@ -0,0 +1,4 @@
module.exports = () => ({
plugins: [require("tailwindcss")],
})

View File

@@ -0,0 +1,873 @@
import {
ChevronDownIcon,
ChevronUpIcon,
Cog8ToothIcon,
XMarkIcon,
ClipboardIcon,
InformationCircleIcon,
} from "@heroicons/react/24/outline";
import React, { ReactNode, useEffect, useRef, useState } from "react";
import Icon from "./icons";
import { Modal, Table, Tooltip, theme } from "antd";
import Editor from "@monaco-editor/react";
import Papa from "papaparse";
import remarkGfm from "remark-gfm";
import ReactMarkdown from "react-markdown";
import { atomDark } from "react-syntax-highlighter/dist/esm/styles/prism";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { truncateText } from "./utils";
const { useToken } = theme;
interface CodeProps {
node?: any;
inline?: any;
className?: any;
children?: React.ReactNode;
}
interface IProps {
children?: ReactNode;
title?: string | ReactNode;
subtitle?: string | ReactNode;
count?: number;
active?: boolean;
cursor?: string;
icon?: ReactNode;
padding?: string;
className?: string;
open?: boolean;
hoverable?: boolean;
onClick?: () => void;
loading?: boolean;
}
export const SectionHeader = ({
children,
title,
subtitle,
count,
icon,
}: IProps) => {
return (
<div id="section-header" className="mb-4">
<h1 className="text-primary text-2xl">
{/* {count !== null && <span className="text-accent mr-1">{count}</span>} */}
{icon && <>{icon}</>}
{title}
{count !== null && (
<span className="text-accent mr-1 ml-2 text-xs">{count}</span>
)}
</h1>
{subtitle && <span className="inline-block">{subtitle}</span>}
{children}
</div>
);
};
export const IconButton = ({
onClick,
icon,
className,
active = false,
}: IProps) => {
return (
<span
id="icon-button"
role={"button"}
onClick={onClick}
className={`inline-block mr-2 hover:text-accent transition duration-300 ${className} ${
active ? "border-accent border rounded text-accent" : ""
}`}
>
{icon}
</span>
);
};
export const LaunchButton = ({
children,
onClick,
className = "p-3 px-5 ",
}: any) => {
return (
<button
id="launch-button"
role={"button"}
className={` focus:ring ring-accent ring-l-none rounded cursor-pointer hover:brightness-110 bg-accent transition duration-500 text-white ${className} `}
onClick={onClick}
>
{children}
</button>
);
};
export const SecondaryButton = ({ children, onClick, className }: any) => {
return (
<button
id="secondary-button"
role={"button"}
className={` ${className} focus:ring ring-accent p-2 px-5 rounded cursor-pointer hover:brightness-90 bg-secondary transition duration-500 text-primary`}
onClick={onClick}
>
{children}
</button>
);
};
export const Card = ({
children,
title,
subtitle,
hoverable = true,
active,
cursor = "cursor-pointer",
className = "p-3",
onClick,
}: IProps) => {
let border = active
? "border-accent"
: "border-secondary hover:border-accent ";
border = hoverable ? border : "border-secondary";
return (
<button
id="card"
tabIndex={0}
onClick={onClick}
role={"button"}
className={`${border} border-2 bg-secondary group ${className} w-full text-left rounded ${cursor} transition duration-300`}
>
<div className="mt- text-sm text-secondary h-full break-words">
{title && (
<div className="text-accent rounded font-semibold text-xs pb-1">
{title}
</div>
)}
<div>{subtitle}</div>
{children}
</div>
</button>
);
};
export const CollapseBox = ({
title,
subtitle,
children,
className = " p-3",
open = false,
}: IProps) => {
const [isOpen, setIsOpen] = React.useState<boolean>(open);
const chevronClass = "h-4 cursor-pointer inline-block mr-1";
return (
<div
id="collapse-box"
onMouseDown={(e) => {
if (e.detail > 1) {
e.preventDefault();
}
}}
className="bordper border-secondary rounded"
>
<div
onClick={() => {
setIsOpen(!isOpen);
}}
className={`cursor-pointer bg-secondary p-2 rounded ${
isOpen ? "rounded-b-none " : " "
}"}`}
>
{isOpen && <ChevronUpIcon className={chevronClass} />}
{!isOpen && <ChevronDownIcon className={chevronClass} />}
<span className=" inline-block -mt-2 mb-2 text-xs">
{" "}
{/* {isOpen ? "hide" : "show"} section | */}
{title}
</span>
</div>
{isOpen && (
<div className={`${className} bg-tertiary rounded rounded-t-none`}>
{children}
</div>
)}
</div>
);
};
export const HighLight = ({ children }: IProps) => {
return <span id="highlight" className="border-b border-accent">{children}</span>;
};
export const LoadBox = ({
subtitle,
className = "my-2 text-accent ",
}: IProps) => {
return (
<div id="load-box" className={`${className} `}>
<span className="mr-2 ">
{" "}
<Icon size={5} icon="loading" />
</span>{" "}
{subtitle}
</div>
);
};
export const LoadingBar = ({ children }: IProps) => {
return (
<>
<div id="loading-bar" className="rounded bg-secondary p-3">
<span className="inline-block h-6 w-6 relative mr-2">
<Cog8ToothIcon className="animate-ping text-accent absolute inline-flex h-full w-full rounded-ful opacity-75" />
<Cog8ToothIcon className="relative text-accent animate-spin inline-flex rounded-full h-6 w-6" />
</span>
{children}
</div>
<div className="relative">
<div className="loadbar rounded-b"></div>
</div>
</>
);
};
export const MessageBox = ({ title, children, className }: IProps) => {
const messageBox = useRef<HTMLDivElement>(null);
const closeMessage = () => {
if (messageBox.current) {
messageBox.current.remove();
}
};
return (
<div
id="message-box"
ref={messageBox}
className={`${className} p-3 rounded bg-secondary transition duration-1000 ease-in-out overflow-hidden`}
>
{" "}
<div className="flex gap-2 mb-2">
<div className="flex-1">
{/* <span className="mr-2 text-accent">
<InformationCircleIcon className="h-6 w-6 inline-block" />
</span>{" "} */}
<span className="font-semibold text-primary text-base">{title}</span>
</div>
<div>
<span
onClick={() => {
closeMessage();
}}
className=" border border-secondary bg-secondary brightness-125 hover:brightness-100 cursor-pointer transition duration-200 inline-block px-1 pb-1 rounded text-primary"
>
<XMarkIcon className="h-4 w-4 inline-block" />
</span>
</div>
</div>
{children}
</div>
);
};
export const GroupView = ({
children,
title,
className = "text-primary bg-primary ",
}: any) => {
return (
<div id="group-view" className={`rounded mt-4 border-secondary ${className}`}>
<div className="mt-4 p-2 rounded border relative">
<div className={`absolute -top-3 inline-block ${className}`}>
{title}
</div>
<div className="mt-2"> {children}</div>
</div>
</div>
);
};
export const ExpandView = ({
children,
icon = null,
className = "",
title = "Detail View",
}: any) => {
const [isOpen, setIsOpen] = React.useState(false);
let windowAspect = 1;
if (typeof window !== "undefined") {
windowAspect = window.innerWidth / window.innerHeight;
}
const minImageWidth = 400;
return (
<div
id="expand-view"
style={{
minHeight: "100px",
}}
className={`h-full rounded mb-6 border-secondary ${className}`}
>
<div
role="button"
onClick={() => {
setIsOpen(true);
}}
className="text-xs mb-2 h-full w-full break-words"
>
{icon ? icon : children}
</div>
{isOpen && (
<Modal
title={title}
width={800}
open={isOpen}
onCancel={() => setIsOpen(false)}
footer={null}
>
{/* <ResizableBox
// handle={<span className="text-accent">resize</span>}
lockAspectRatio={false}
handle={
<div className="absolute right-0 bottom-0 cursor-se-resize font-semibold boprder p-3 bg-secondary">
<ArrowDownRightIcon className="h-4 w-4 inline-block" />
</div>
}
width={800}
height={minImageWidth * windowAspect}
minConstraints={[minImageWidth, minImageWidth * windowAspect]}
maxConstraints={[900, 900 * windowAspect]}
className="overflow-auto w-full rounded select-none "
> */}
{children}
{/* </ResizableBox> */}
</Modal>
)}
</div>
);
};
export const LoadingOverlay = ({ children, loading }: IProps) => {
return (
<>
{loading && (
<>
<div
id="loading-overlay"
className="absolute inset-0 bg-secondary flex pointer-events-none"
style={{ opacity: 0.5 }}
>
{/* Overlay background */}
</div>
<div
className="absolute inset-0 flex items-center justify-center"
style={{ pointerEvents: "none" }}
>
{/* Center BounceLoader without inheriting the opacity */}
<BounceLoader />
</div>
</>
)}
<div className="relative">{children}</div>
</>
);
};
export const MarkdownView = ({
data,
className = "",
showCode = true,
}: {
data: string;
className?: string;
showCode?: boolean;
}) => {
function processString(inputString: string): string {
// TODO: Had to add this temp measure while debugging. Why is it null?
if (!inputString) {
console.log("inputString is null!")
}
inputString = inputString && inputString.replace(/\n/g, " \n");
const markdownPattern = /```markdown\s+([\s\S]*?)\s+```/g;
return inputString?.replace(markdownPattern, (match, content) => content);
}
const [showCopied, setShowCopied] = React.useState(false);
const CodeView = ({ props, children, language }: any) => {
const [codeVisible, setCodeVisible] = React.useState(showCode);
return (
<div>
<div className=" flex ">
<div
role="button"
onClick={() => {
setCodeVisible(!codeVisible);
}}
className=" flex-1 mr-4 "
>
{!codeVisible && (
<div className=" text-white hover:text-accent duration-300">
<ChevronDownIcon className="inline-block w-5 h-5" />
<span className="text-xs"> show</span>
</div>
)}
{codeVisible && (
<div className=" text-white hover:text-accent duration-300">
{" "}
<ChevronUpIcon className="inline-block w-5 h-5" />
<span className="text-xs"> hide</span>
</div>
)}
</div>
{/* <div className="flex-1"></div> */}
<div>
{showCopied && (
<div className="inline-block text-sm text-white">
{" "}
🎉 Copied!{" "}
</div>
)}
<ClipboardIcon
role={"button"}
onClick={() => {
navigator.clipboard.writeText(data);
// message.success("Code copied to clipboard");
setShowCopied(true);
setTimeout(() => {
setShowCopied(false);
}, 3000);
}}
className=" inline-block duration-300 text-white hover:text-accent w-5 h-5"
/>
</div>
</div>
{codeVisible && (
<SyntaxHighlighter
{...props}
style={atomDark}
language={language}
className="rounded w-full"
PreTag="div"
wrapLongLines={true}
>
{String(children).replace(/\n$/, "")}
</SyntaxHighlighter>
)}
</div>
);
};
return (
<div
id="markdown-view"
className={` w-full chatbox prose dark:prose-invert text-primary rounded ${className}`}
>
<ReactMarkdown
className=" w-full"
remarkPlugins={[remarkGfm]}
components={{
code({ node, inline, className, children, ...props }: CodeProps) {
const match = /language-(\w+)/.exec(className || "");
const language = match ? match[1] : "text";
return !inline && match ? (
<CodeView props={props} children={children} language={language} />
) : (
<code {...props} className={className}>
{children}
</code>
);
},
}}
>
{processString(data)}
</ReactMarkdown>
</div>
);
};
interface ICodeProps {
code: string;
language: string;
title?: string;
showLineNumbers?: boolean;
className?: string | undefined;
wrapLines?: boolean;
maxWidth?: string;
maxHeight?: string;
minHeight?: string;
}
export const CodeBlock = ({
code,
language = "python",
showLineNumbers = false,
className = " ",
wrapLines = false,
maxHeight = "400px",
minHeight = "auto",
}: ICodeProps) => {
const codeString = code;
const [showCopied, setShowCopied] = React.useState(false);
return (
<div id="code-block" className="relative">
<div className=" rounded absolute right-5 top-4 z-10 ">
<div className="relative border border-transparent w-full h-full">
<div
style={{ zIndex: -1 }}
className="w-full absolute top-0 h-full bg-gray-900 hover:bg-opacity-0 duration-300 bg-opacity-50 rounded"
></div>
<div className=" ">
{showCopied && (
<div className="inline-block px-2 pl-3 text-white">
{" "}
🎉 Copied!{" "}
</div>
)}
<ClipboardIcon
role={"button"}
onClick={() => {
navigator.clipboard.writeText(codeString);
// message.success("Code copied to clipboard");
setShowCopied(true);
setTimeout(() => {
setShowCopied(false);
}, 6000);
}}
className="m-2 inline-block duration-300 text-white hover:text-accent w-5 h-5"
/>
</div>
</div>
</div>
<div
id="codeDivBox"
className={`rounded w-full overflow-auto overflow-y-scroll scroll ${className}`}
style={{ maxHeight: maxHeight, minHeight: minHeight }}
>
<SyntaxHighlighter
id="codeDiv"
className="rounded-sm h-full break-all"
language={language}
showLineNumbers={showLineNumbers}
style={atomDark}
wrapLines={wrapLines}
wrapLongLines={wrapLines}
>
{codeString}
</SyntaxHighlighter>
</div>
</div>
);
};
// Controls Row
export const ControlRowView = ({
title,
description,
value,
control,
className,
truncateLength = 20,
}: {
title: string;
description: string;
value: string | number | boolean;
control: any;
className?: string;
truncateLength?: number;
}) => {
return (
<div id="control-row-view" className={`${className}`}>
<div>
<span className="text-primary inline-block">{title} </span>
<span className="text-xs ml-1 text-accent -mt-2 inline-block">
{truncateText(value + "", truncateLength)}
</span>{" "}
<Tooltip title={description}>
<InformationCircleIcon className="text-gray-400 inline-block w-4 h-4" />
</Tooltip>
</div>
{control}
<div className="bordper-b border-secondary border-dashed pb-2 mxp-2"></div>
</div>
);
};
export const BounceLoader = ({
className,
title = "",
}: {
className?: string;
title?: string;
}) => {
return (
<div id="bounce-loader" className="inline-block">
<div className="inline-flex gap-2">
<span className=" rounded-full bg-accent h-2 w-2 inline-block"></span>
<span className="animate-bounce rounded-full bg-accent h-3 w-3 inline-block"></span>
<span className=" rounded-full bg-accent h-2 w-2 inline-block"></span>
</div>
<span className=" text-sm">{title}</span>
</div>
);
};
export const ImageLoader = ({
src,
className = "",
}: {
src: string;
className?: string;
}) => {
const [isLoading, setIsLoading] = useState(true);
return (
<div id="image-loader" className="w-full rounded relative">
{isLoading && (
<div className="absolute h-24 inset-0 flex items-center justify-center">
<BounceLoader title=" loading .." />
</div>
)}
<img
alt="Dynamic content"
src={src}
className={`w-full rounded ${
isLoading ? "opacity-0" : "opacity-100"
} ${className}`}
onLoad={() => setIsLoading(false)}
/>
</div>
);
};
type DataRow = { [key: string]: any };
export const CsvLoader = ({
csvUrl,
className,
}: {
csvUrl: string;
className?: string;
}) => {
const [data, setData] = useState<DataRow[]>([]);
const [columns, setColumns] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [pageSize, setPageSize] = useState<number>(50);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(csvUrl);
const csvString = await response.text();
const parsedData = Papa.parse(csvString, {
header: true,
dynamicTyping: true,
skipEmptyLines: true,
});
setData(parsedData.data as DataRow[]);
// Use the keys of the first object for column headers
const firstRow = parsedData.data[0] as DataRow; // Type assertion
const columnHeaders: any[] = Object.keys(firstRow).map((key) => {
const val = {
title: key.charAt(0).toUpperCase() + key.slice(1), // Capitalize the key for the title
dataIndex: key,
key: key,
};
if (typeof firstRow[key] === "number") {
return {
...val,
sorter: (a: DataRow, b: DataRow) => a[key] - b[key],
};
}
return val;
});
setColumns(columnHeaders);
setIsLoading(false);
} catch (error) {
console.error("Error fetching CSV data:", error);
setIsLoading(false);
}
};
fetchData();
}, [csvUrl]);
// calculate x scroll, based on number of columns
const scrollX = columns.length * 150;
return (
<div id="csv-loader" className={`CsvLoader ${className}`}>
<Table
dataSource={data}
columns={columns}
loading={isLoading}
pagination={{ pageSize: pageSize }}
scroll={{ y: 450, x: scrollX }}
onChange={(pagination) => {
setPageSize(pagination.pageSize || 50);
}}
/>
</div>
);
};
export const CodeLoader = ({
url,
className,
}: {
url: string;
className?: string;
}) => {
const [isLoading, setIsLoading] = useState(true);
const [code, setCode] = useState<string | null>(null);
React.useEffect(() => {
fetch(url)
.then((response) => response.text())
.then((data) => {
setCode(data);
setIsLoading(false);
});
}, [url]);
return (
<div id="code-loader" className={`w-full rounded relative ${className}`}>
{isLoading && (
<div className="absolute h-24 inset-0 flex items-center justify-center">
<BounceLoader />
</div>
)}
{!isLoading && <CodeBlock code={code || ""} language={"python"} />}
</div>
);
};
export const PdfViewer = ({ url }: { url: string }) => {
const [loading, setLoading] = useState<boolean>(true);
React.useEffect(() => {
// Assuming the URL is directly usable as the source for the <object> tag
setLoading(false);
// Note: No need to handle the creation and cleanup of a blob URL or converting file content as it's not provided anymore.
}, [url]);
// Render the PDF viewer
return (
<div id="pdf-viewer" className="h-full">
{loading && <p>Loading PDF...</p>}
{!loading && (
<object
className="w-full rounded"
data={url}
type="application/pdf"
width="100%"
style={{ height: "calc(90vh - 200px)" }}
>
<p>PDF cannot be displayed.</p>
</object>
)}
</div>
);
};
export const MonacoEditor = ({
value,
editorRef,
language,
onChange,
minimap = true,
}: {
value: string;
onChange?: (value: string) => void;
editorRef: any;
language: string;
minimap?: boolean;
}) => {
const [isEditorReady, setIsEditorReady] = useState(false);
const onEditorDidMount = (editor: any, monaco: any) => {
editorRef.current = editor;
setIsEditorReady(true);
};
return (
<div id="monaco-editor" className="h-full rounded">
<Editor
height="100%"
className="h-full rounded"
defaultLanguage={language}
defaultValue={value}
value={value}
onChange={(value: string | undefined) => {
if (onChange && value) {
onChange(value);
}
}}
onMount={onEditorDidMount}
theme="vs-dark"
options={{
wordWrap: "on",
wrappingIndent: "indent",
wrappingStrategy: "advanced",
minimap: {
enabled: minimap,
},
}}
/>
</div>
);
};
export const CardHoverBar = ({
items,
}: {
items: {
title: string;
icon: any;
hoverText: string;
onClick: (e: any) => void;
}[];
}) => {
const itemRows = items.map((item, i) => {
return (
<div
key={"cardhoverrow" + i}
id={`card-hover-bar-item-${i}`}
role="button"
className="text-accent text-xs inline-block hover:bg-primary p-2 rounded"
onClick={item.onClick}
>
<Tooltip title={item.hoverText}>
<item.icon className=" w-5, h-5 cursor-pointer inline-block" />
</Tooltip>
</div>
);
});
return (
<div
id="card-hover-bar"
onMouseEnter={(e) => {
e.stopPropagation();
}}
className=" mt-2 text-right opacity-0 group-hover:opacity-100 "
>
{itemRows}
</div>
);
};
export const AgentRow = ({ message }: { message: any }) => {
return (
<GroupView
title={
<div className="rounded p-1 px-2 inline-block text-xs bg-secondary">
<span className="font-semibold">{message.sender}</span> ( to{" "}
{message.recipient} )
</div>
}
className="m"
>
<MarkdownView data={message.message?.content} className="text-sm" />
</GroupView>
);
};

View File

@@ -0,0 +1,39 @@
import * as React from "react";
import Icon from "./icons";
import { useConfigStore } from "../hooks/store";
import { fetchVersion } from "./utils";
const Footer = () => {
const version = useConfigStore((state) => state.version);
const setVersion = useConfigStore((state) => state.setVersion);
React.useEffect(() => {
if (version === null) {
fetchVersion().then((data) => {
if (data && data.data) {
setVersion(data.data.version);
}
});
}
}, []);
return (
<div className=" mt-4 text-primary p-3 border-t border-secondary flex ">
<div className="text-xs flex-1">
Maintained by the AutoGen{" "}
<a
target={"_blank"}
rel={"noopener noreferrer"}
className="underlipne inline-block border-accent border-b hover:text-accent"
href="https://microsoft.github.io/autogen/"
>
{" "}
Team.
</a>
</div>
{version && (
<div className="text-xs ml-2 text-secondary"> v{version}</div>
)}
</div>
);
};
export default Footer;

View File

@@ -0,0 +1,274 @@
import Icon from "./icons";
import { Disclosure, Menu, Transition } from "@headlessui/react";
import {
XMarkIcon,
Bars3Icon,
BellIcon,
MoonIcon,
SunIcon,
} from "@heroicons/react/24/outline";
import { Fragment } from "react";
import { appContext } from "../hooks/provider";
import { Link } from "gatsby";
import React from "react";
function classNames(...classes: string[]) {
return classes.filter(Boolean).join(" ");
}
const Header = ({ meta, link }: any) => {
const { user, logout } = React.useContext(appContext);
const userName = user ? user.name : "Unknown";
const userAvatarUrl = user ? user.avatar_url : "";
const user_id = user ? user.username : "unknown";
const links: any[] = [
{ name: "Build", href: "/build" },
{ name: "Playground", href: "/" },
// { name: "Gallery", href: "/gallery" },
// { name: "Data Explorer", href: "/explorer" },
];
const DarkModeToggle = () => {
return (
<appContext.Consumer>
{(context: any) => {
return (
<button
onClick={() => {
if (context.darkMode === "dark") {
context.setDarkMode("light");
} else {
context.setDarkMode("dark");
}
}}
type="button"
className="flex-shrink-0 bg-primary p-1 text-secondary rounded-full hover:text-secondary focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent"
>
<span className="sr-only">Toggle dark mode </span>
{context.darkMode === "dark" && (
<MoonIcon className="h-6 w-6" aria-hidden="true" />
)}
{context.darkMode === "light" && (
<SunIcon className="h-6 w-6" aria-hidden="true" />
)}
</button>
);
}}
</appContext.Consumer>
);
};
return (
<Disclosure
as="nav"
className="bg-primary text-primary mb-8 border-b border-secondary"
>
{({ open }) => (
<>
<div className=" px-0 sm:px-0 lg:px-0 ">
<div className="flex justify-between h-16">
<div className="flex lg:px-0 ">
<div className="flex flex-shrink-0 pt-2">
<a className="block " href="/#">
<span className=" bg-primary inline-block pt-2 absolute">
{" "}
<div className="inline-block w-10 text-accent bg-primary pb-2 mr-1">
<Icon icon="app" size={8} />
</div>{" "}
</span>
<div className="pt-1 text-lg ml-14 inline-block">
<div className=" flex flex-col">
<div className="text-base">{meta.title}</div>
<div className="text-xs"> {meta.description}</div>
</div>
</div>
</a>
</div>
<div className="hidden md:ml-6 md:flex md:space-x-6">
{/* Current: "border-accent text-gray-900", Default: "border-transparent text-secondary hover:border-gray-300 hover:text-primary" */}
{links.map((data, index) => {
const isActive = data.href === link;
const activeClass = isActive
? "bg-accent "
: "bg-secondary ";
return (
<div
key={index + "linkrow"}
className={`text-primary items-center hover:text-accent px-1 pt-1 block text-sm font-medium `}
>
<Link
className="hover:text-accent h-full flex flex-col"
to={data.href}
>
<div className=" flex-1 flex-col flex">
<div className="flex-1"></div>
<div className="pb-2 px-3">{data.name}</div>
</div>
<div
className={`${activeClass} w-full h-1 rounded-t-lg `}
></div>
</Link>
</div>
);
})}
</div>
</div>
<div className="flex items-center md:hidden">
{/* Mobile menu button */}
<Disclosure.Button className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-secondary hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-inset focus:ring-accent">
<span className="sr-only">Open main menu</span>
{open ? (
<XMarkIcon className="block h-6 w-6" aria-hidden="true" />
) : (
<Bars3Icon className="block h-6 w-6" aria-hidden="true" />
)}
</Disclosure.Button>
</div>
{
<div className="hidden lg:ml-4 md:flex md:items-center">
<DarkModeToggle />
{user && (
<>
<div className="ml-3">
<div className="text-sm text-primary">{userName}</div>
<div className="text-xs text-secondary">{user_id}</div>
</div>
{/* Profile dropdown */}
<Menu as="div" className="ml-4 relative flex-shrink-0">
<div>
<Menu.Button className="bg-primary rounded-full flex text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent">
<span className="sr-only">Open user menu</span>
{userAvatarUrl && (
<img
className="h-8 w-8 rounded-full"
src={userAvatarUrl}
alt=""
/>
)}
{!userAvatarUrl && userName && (
<div className="border-2 bg-accent pt-1 h-8 w-8 align-middle text-sm text-white rounded-full">
{userName[0]}
</div>
)}
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-primary ring-1 ring-black ring-opacity-5 focus:outline-none">
{/* <Menu.Item>
{({ active }) => (
<a
href="#"
className={classNames(active ? 'bg-secondary' : '', 'block px-4 py-2 text-sm text-primary')}
>
Your Profile
</a>
)}
</Menu.Item> */}
<Menu.Item>
{({ active }) => (
<a
href="#"
onClick={() => {
logout();
}}
className={classNames(
active ? "bg-secondary" : "",
"block px-4 py-2 text-sm text-primary"
)}
>
Sign out
</a>
)}
</Menu.Item>
</Menu.Items>
</Transition>
</Menu>
</>
)}
</div>
}
</div>
</div>
<Disclosure.Panel className="md:hidden">
<div className="pt-2 pb-3 space-y-1">
{/* Current: "bg-indigo-50 border-accent text-accent", Default: "border-transparent text-gray-600 hover:bg-primary hover:border-gray-300 hover:text-primary" */}
{links.map((data, index) => {
return (
<Disclosure.Button
key={index + "linkrow"}
as="a"
href={data.href}
className="bg-secondary border-accent text-accent block pl-3 pr-4 py-2 border-l-4 text-base font-medium"
>
{data.name}
</Disclosure.Button>
);
})}
</div>
<div className="mt-3 space-y-1 pb-2">
{" "}
Dark mode <DarkModeToggle />{" "}
</div>
{user && (
<div className="pt-4 pb-3 border-t border-secondary">
<div className="flex items-center px-4">
<div className="flex-shrink-0">
{userAvatarUrl && (
<img
className="h-8 w-8 rounded-full"
src={userAvatarUrl}
alt=""
/>
)}
{!userAvatarUrl && userName && (
<div className="border-2 bg-accent text-sm text-white h-8 w-8 pt-1 rounded-full text-center">
{userName[0]}
</div>
)}
</div>
<div className="ml-3">
<div className="text-sm text-primary">{userName}</div>
<div className="text-xs text-secondary">{user_id}</div>
</div>
<button
type="button"
className="ml-auto flex-shrink-0 bg-primary p-1 text-secondary rounded-full hover:text-secondary focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-accent"
>
<span className="sr-only">View notifications</span>
<BellIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="mt-3 space-y-1">
<Disclosure.Button
as="a"
href="#"
onClick={() => logout()}
className="block px-4 py-2 text-base font-medium text-secondary hover:text-primary "
>
Sign out
</Disclosure.Button>
</div>
</div>
)}
</Disclosure.Panel>
</>
)}
</Disclosure>
);
};
export default Header;

View File

@@ -0,0 +1,241 @@
import * as React from "react";
type Props = {
icon: string;
size: number;
children?: React.ReactNode;
className?: string;
};
const Icon = ({ icon = "app", size = 4, className = "" }: Props) => {
const sizeClass = `h-${size} w-${size} ${className}`;
if (icon === "github") {
return (
<svg
className={` ${sizeClass} inline-block `}
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z"
clipRule="evenodd"
/>
</svg>
);
}
if (icon === "python") {
return (
<svg
className={` ${sizeClass} inline-block `}
aria-hidden="true"
fill="currentColor"
viewBox="0 0 50 63"
>
<path
d="M42.6967 62.1044H13.464C11.5281 62.1021 9.67207 61.3321 8.30315 59.9632C6.93422 58.5942 6.16417 56.7382 6.16193 54.8023V45.2659C6.16193 44.9442 6.28972 44.6357 6.5172 44.4082C6.74467 44.1807 7.0532 44.0529 7.3749 44.0529C7.6966 44.0529 8.00513 44.1807 8.2326 44.4082C8.46008 44.6357 8.58787 44.9442 8.58787 45.2659V54.8023C8.58948 56.095 9.10373 57.3344 10.0178 58.2485C10.9319 59.1626 12.1713 59.6768 13.464 59.6784H42.6967C43.9896 59.6768 45.229 59.1626 46.1433 58.2485C47.0576 57.3345 47.5721 56.0951 47.5741 54.8023V43.8746C47.5741 43.5529 47.7019 43.2444 47.9293 43.0169C48.1568 42.7894 48.4653 42.6616 48.787 42.6616C49.1087 42.6616 49.4173 42.7894 49.6447 43.0169C49.8722 43.2444 50 43.5529 50 43.8746V54.8023C49.9975 56.7383 49.2271 58.5944 47.858 59.9632C46.4889 61.3321 44.6328 62.1021 42.6967 62.1044Z"
fill="currentColor"
/>
<path
d="M48.7822 41.6183C48.4605 41.6183 48.152 41.4906 47.9245 41.2631C47.697 41.0356 47.5692 40.7271 47.5692 40.4054V12.5967C47.5681 12.2529 47.4946 11.9131 47.3533 11.5995C47.212 11.286 47.0062 11.0058 46.7492 10.7773L38.0522 3.04699C37.6064 2.64832 37.0297 2.42731 36.4317 2.42595H14.677C13.0616 2.42852 11.5131 3.07163 10.3712 4.21425C9.22922 5.35686 8.58704 6.90572 8.58543 8.52114V23.3037C8.58543 23.6254 8.45764 23.9339 8.23016 24.1614C8.00268 24.3888 7.69416 24.5166 7.37246 24.5166C7.05076 24.5166 6.74223 24.3888 6.51476 24.1614C6.28728 23.9339 6.15948 23.6254 6.15948 23.3037V8.52114C6.16173 6.26251 7.05971 4.09698 8.65646 2.49955C10.2532 0.902118 12.4184 0.0032109 14.677 9.10874e-08H36.4317C37.6233 -0.000230408 38.7736 0.437008 39.6643 1.22874L48.3613 8.96024C48.8752 9.41695 49.2866 9.97738 49.5682 10.6046C49.8498 11.2318 49.9953 11.9116 49.9952 12.5992V40.4054C49.9952 40.7271 49.8674 41.0356 49.6399 41.2631C49.4124 41.4906 49.1039 41.6183 48.7822 41.6183Z"
fill="currentColor"
/>
<path
d="M48.7203 13.1681H41.474C40.1838 13.1665 38.947 12.6533 38.0347 11.741C37.1224 10.8287 36.6091 9.59184 36.6075 8.30167V1.49325C36.6075 1.17155 36.7353 0.863022 36.9628 0.635545C37.1903 0.408069 37.4988 0.280273 37.8205 0.280273C38.1422 0.280273 38.4507 0.408069 38.6782 0.635545C38.9057 0.863022 39.0335 1.17155 39.0335 1.49325V8.30167C39.0341 8.94874 39.2915 9.56911 39.749 10.0267C40.2066 10.4842 40.8269 10.7415 41.474 10.7422H48.7203C49.042 10.7422 49.3505 10.87 49.578 11.0974C49.8055 11.3249 49.9333 11.6334 49.9333 11.9551C49.9333 12.2768 49.8055 12.5854 49.578 12.8129C49.3505 13.0403 49.042 13.1681 48.7203 13.1681Z"
fill="currentColor"
/>
<path
d="M17.1575 40.3774C16.8358 40.3774 16.5273 40.2496 16.2998 40.0222C16.0723 39.7947 15.9445 39.4862 15.9445 39.1644V29.4036C15.9445 29.0819 16.0723 28.7734 16.2998 28.5459C16.5273 28.3185 16.8358 28.1907 17.1575 28.1907C17.4792 28.1907 17.7877 28.3185 18.0152 28.5459C18.2427 28.7734 18.3705 29.0819 18.3705 29.4036V39.1644C18.3705 39.4862 18.2427 39.7947 18.0152 40.0222C17.7877 40.2496 17.4792 40.3774 17.1575 40.3774Z"
fill="currentColor"
/>
<path
d="M17.1757 36.1381C16.8552 36.1381 16.5478 36.0113 16.3205 35.7854C16.0933 35.5595 15.9646 35.2528 15.9627 34.9324C15.9627 34.913 15.9506 32.9201 15.9506 32.1583C15.9506 31.53 15.9445 29.4073 15.9445 29.4073C15.9445 29.0856 16.0723 28.7771 16.2998 28.5496C16.5273 28.3221 16.8358 28.1943 17.1575 28.1943H19.8746C22.0919 28.1943 23.8968 29.9738 23.8968 32.162C23.8968 34.3502 22.0919 36.1296 19.8746 36.1296C19.1334 36.1296 17.206 36.1417 17.183 36.1417L17.1757 36.1381ZM18.3741 30.6166C18.3741 31.2086 18.3741 31.8551 18.3741 32.1583C18.3741 32.5125 18.3741 33.1384 18.3802 33.7049H19.8721C20.7418 33.7 21.4696 32.9929 21.4696 32.1583C21.4696 31.3238 20.7418 30.6166 19.8733 30.6166H18.3741Z"
fill="currentColor"
/>
<path
d="M29.73 35.3921C29.5283 35.3923 29.3296 35.3421 29.1522 35.2462C28.9747 35.1502 28.8239 35.0115 28.7135 34.8426L25.5938 30.0672C25.4235 29.7979 25.366 29.4725 25.4336 29.1612C25.5013 28.8499 25.6887 28.5777 25.9554 28.4034C26.222 28.2291 26.5466 28.1668 26.8588 28.2298C27.1711 28.2928 27.4461 28.4761 27.6243 28.7402L29.7252 31.9582L31.803 28.7668C31.8899 28.633 32.0023 28.5175 32.1338 28.4271C32.2653 28.3366 32.4134 28.273 32.5695 28.2398C32.7256 28.2065 32.8867 28.2044 33.0437 28.2334C33.2006 28.2625 33.3503 28.3221 33.4842 28.409C33.6181 28.4959 33.7335 28.6083 33.824 28.7398C33.9144 28.8714 33.978 29.0194 34.0113 29.1755C34.0445 29.3316 34.0467 29.4927 34.0176 29.6497C33.9886 29.8066 33.9289 29.9563 33.842 30.0902L30.7501 34.8402C30.64 35.0096 30.4894 35.1487 30.3119 35.2451C30.1344 35.3415 29.9356 35.392 29.7337 35.3921H29.73Z"
fill="currentColor"
/>
<path
d="M29.7179 40.3776C29.3962 40.3776 29.0876 40.2498 28.8602 40.0223C28.6327 39.7948 28.5049 39.4863 28.5049 39.1646L28.5182 34.1781C28.5182 33.8564 28.646 33.5478 28.8735 33.3204C29.101 33.0929 29.4095 32.9651 29.7312 32.9651C30.0529 32.9651 30.3614 33.0929 30.5889 33.3204C30.8164 33.5478 30.9442 33.8564 30.9442 34.1781L30.9308 39.1682C30.9299 39.4893 30.8016 39.7969 30.5743 40.0236C30.3469 40.2503 30.0389 40.3776 29.7179 40.3776Z"
fill="currentColor"
/>
<path
d="M6.10975 52.2791H4.24541C3.11946 52.2791 2.03962 51.8319 1.24345 51.0357C0.447283 50.2395 0 49.1597 0 48.0337C0 46.9078 0.447283 45.8279 1.24345 45.0318C2.03962 44.2356 3.11946 43.7883 4.24541 43.7883H9.75474C10.0764 43.7883 10.385 43.9161 10.6124 44.1436C10.8399 44.3711 10.9677 44.6796 10.9677 45.0013C10.9677 45.323 10.8399 45.6315 10.6124 45.859C10.385 46.0865 10.0764 46.2143 9.75474 46.2143H4.24541C3.76286 46.2143 3.30007 46.406 2.95886 46.7472C2.61764 47.0884 2.42595 47.5512 2.42595 48.0337C2.42595 48.5163 2.61764 48.9791 2.95886 49.3203C3.30007 49.6615 3.76286 49.8532 4.24541 49.8532H6.11339C6.43509 49.8532 6.74361 49.981 6.97109 50.2085C7.19857 50.4359 7.32636 50.7445 7.32636 51.0662C7.32636 51.3879 7.19857 51.6964 6.97109 51.9239C6.74361 52.1514 6.43509 52.2791 6.11339 52.2791H6.10975Z"
fill="currentColor"
/>
<path
d="M1.22872 48.8975C0.90702 48.8975 0.598496 48.7697 0.371019 48.5423C0.143542 48.3148 0.0157471 48.0063 0.0157471 47.6846L0.067905 26.3362C0.069833 25.2115 0.517967 24.1336 1.31393 23.339C2.10989 22.5444 3.18862 22.0981 4.31331 22.0981L48.7846 22.0896C49.1063 22.0896 49.4148 22.2174 49.6423 22.4449C49.8698 22.6723 49.9976 22.9809 49.9976 23.3026V45.0015C49.9976 45.3232 49.8698 45.6317 49.6423 45.8592C49.4148 46.0866 49.1063 46.2144 48.7846 46.2144H5.52386C5.20216 46.2144 4.89364 46.0866 4.66616 45.8592C4.43868 45.6317 4.31089 45.3232 4.31089 45.0015C4.31089 44.6798 4.43868 44.3712 4.66616 44.1438C4.89364 43.9163 5.20216 43.7885 5.52386 43.7885H47.5692V24.5083L4.30967 24.5168C3.82712 24.5168 3.36434 24.7085 3.02312 25.0497C2.68191 25.3909 2.49021 25.8537 2.49021 26.3362L2.43806 47.6846C2.43806 48.0056 2.31076 48.3136 2.08407 48.541C1.85738 48.7684 1.54979 48.8966 1.22872 48.8975Z"
fill="currentColor"
/>
</svg>
);
}
if (icon === "csv") {
return (
<svg
className={` ${sizeClass} inline-block `}
aria-hidden="true"
fill="currentColor"
viewBox="0 0 50 63"
>
<path
d="M42.6967 62.1044H13.464C11.5281 62.1021 9.67207 61.3321 8.30315 59.9632C6.93422 58.5942 6.16417 56.7382 6.16193 54.8023V45.2659C6.16193 44.9442 6.28972 44.6357 6.5172 44.4082C6.74467 44.1807 7.0532 44.0529 7.3749 44.0529C7.6966 44.0529 8.00513 44.1807 8.2326 44.4082C8.46008 44.6357 8.58787 44.9442 8.58787 45.2659V54.8023C8.58948 56.095 9.10373 57.3344 10.0178 58.2485C10.9319 59.1626 12.1713 59.6768 13.464 59.6784H42.6967C43.9896 59.6768 45.229 59.1626 46.1433 58.2485C47.0576 57.3345 47.5721 56.0951 47.5741 54.8023V43.8746C47.5741 43.5529 47.7019 43.2444 47.9293 43.0169C48.1568 42.7894 48.4653 42.6616 48.787 42.6616C49.1087 42.6616 49.4173 42.7894 49.6447 43.0169C49.8722 43.2444 50 43.5529 50 43.8746V54.8023C49.9975 56.7383 49.2271 58.5944 47.858 59.9632C46.4889 61.3321 44.6328 62.1021 42.6967 62.1044Z"
fill="black"
/>
<path
d="M48.7822 41.6183C48.4605 41.6183 48.152 41.4906 47.9245 41.2631C47.697 41.0356 47.5692 40.7271 47.5692 40.4054V12.5967C47.5681 12.2529 47.4946 11.9131 47.3533 11.5995C47.212 11.286 47.0062 11.0058 46.7492 10.7773L38.0522 3.04699C37.6064 2.64832 37.0297 2.42731 36.4317 2.42595H14.677C13.0616 2.42852 11.5131 3.07163 10.3712 4.21425C9.22922 5.35686 8.58704 6.90572 8.58543 8.52114V23.3037C8.58543 23.6254 8.45764 23.9339 8.23016 24.1614C8.00268 24.3888 7.69416 24.5166 7.37246 24.5166C7.05076 24.5166 6.74223 24.3888 6.51476 24.1614C6.28728 23.9339 6.15948 23.6254 6.15948 23.3037V8.52114C6.16173 6.26251 7.05971 4.09698 8.65646 2.49955C10.2532 0.902118 12.4184 0.0032109 14.677 9.10874e-08H36.4317C37.6233 -0.000230408 38.7736 0.437008 39.6643 1.22874L48.3613 8.96024C48.8752 9.41695 49.2866 9.97738 49.5682 10.6046C49.8498 11.2318 49.9953 11.9116 49.9952 12.5992V40.4054C49.9952 40.7271 49.8674 41.0356 49.6399 41.2631C49.4124 41.4906 49.1039 41.6183 48.7822 41.6183Z"
fill="black"
/>
<path
d="M48.7203 13.1681H41.474C40.1838 13.1665 38.947 12.6533 38.0347 11.741C37.1224 10.8287 36.6091 9.59184 36.6075 8.30167V1.49325C36.6075 1.17155 36.7353 0.863022 36.9628 0.635545C37.1903 0.408069 37.4988 0.280273 37.8205 0.280273C38.1422 0.280273 38.4507 0.408069 38.6782 0.635545C38.9057 0.863022 39.0335 1.17155 39.0335 1.49325V8.30167C39.0341 8.94874 39.2915 9.56911 39.749 10.0267C40.2066 10.4842 40.8269 10.7415 41.474 10.7422H48.7203C49.042 10.7422 49.3505 10.87 49.578 11.0974C49.8055 11.3249 49.9333 11.6334 49.9333 11.9551C49.9333 12.2768 49.8055 12.5854 49.578 12.8129C49.3505 13.0403 49.042 13.1681 48.7203 13.1681Z"
fill="black"
/>
<path
d="M6.10975 52.2791H4.24541C3.11946 52.2791 2.03962 51.8319 1.24345 51.0357C0.447283 50.2395 0 49.1597 0 48.0337C0 46.9078 0.447283 45.8279 1.24345 45.0318C2.03962 44.2356 3.11946 43.7883 4.24541 43.7883H9.75474C10.0764 43.7883 10.385 43.9161 10.6124 44.1436C10.8399 44.3711 10.9677 44.6796 10.9677 45.0013C10.9677 45.323 10.8399 45.6315 10.6124 45.859C10.385 46.0865 10.0764 46.2143 9.75474 46.2143H4.24541C3.76286 46.2143 3.30007 46.406 2.95886 46.7472C2.61764 47.0884 2.42595 47.5512 2.42595 48.0337C2.42595 48.5163 2.61764 48.9791 2.95886 49.3203C3.30007 49.6615 3.76286 49.8532 4.24541 49.8532H6.11339C6.43509 49.8532 6.74361 49.981 6.97109 50.2085C7.19857 50.4359 7.32636 50.7445 7.32636 51.0662C7.32636 51.3879 7.19857 51.6964 6.97109 51.9239C6.74361 52.1514 6.43509 52.2791 6.11339 52.2791H6.10975Z"
fill="black"
/>
<path
d="M1.22872 48.8975C0.90702 48.8975 0.598496 48.7697 0.371019 48.5423C0.143542 48.3148 0.0157471 48.0063 0.0157471 47.6846L0.067905 26.3362C0.069833 25.2115 0.517967 24.1336 1.31393 23.339C2.10989 22.5444 3.18862 22.0981 4.31331 22.0981L48.7846 22.0896C49.1063 22.0896 49.4148 22.2174 49.6423 22.4449C49.8698 22.6723 49.9976 22.9809 49.9976 23.3026V45.0015C49.9976 45.3232 49.8698 45.6317 49.6423 45.8592C49.4148 46.0866 49.1063 46.2144 48.7846 46.2144H5.52386C5.20216 46.2144 4.89364 46.0866 4.66616 45.8592C4.43868 45.6317 4.31089 45.3232 4.31089 45.0015C4.31089 44.6798 4.43868 44.3712 4.66616 44.1438C4.89364 43.9163 5.20216 43.7885 5.52386 43.7885H47.5692V24.5083L4.30967 24.5168C3.82712 24.5168 3.36434 24.7085 3.02312 25.0497C2.68191 25.3909 2.49021 25.8537 2.49021 26.3362L2.43806 47.6846C2.43806 48.0056 2.31076 48.3136 2.08407 48.541C1.85738 48.7684 1.54979 48.8966 1.22872 48.8975Z"
fill="black"
/>
<path
d="M18.1587 36.5474C18.4077 36.5474 18.6291 36.4948 18.8228 36.3896C19.022 36.2845 19.1797 36.1213 19.2959 35.8999C19.4121 35.673 19.4757 35.3797 19.4868 35.02H21.3047C21.2936 35.6564 21.147 36.2126 20.8647 36.6885C20.5825 37.1589 20.2062 37.5241 19.7358 37.7842C19.271 38.0387 18.7536 38.166 18.1836 38.166C17.5915 38.166 17.0741 38.0719 16.6313 37.8838C16.1942 37.6901 15.8317 37.4106 15.5439 37.0454C15.2562 36.6802 15.0404 36.2375 14.8965 35.7173C14.7581 35.1916 14.689 34.5911 14.689 33.916V33.1025C14.689 32.4329 14.7581 31.8353 14.8965 31.3096C15.0404 30.7839 15.2562 30.3384 15.5439 29.9731C15.8317 29.6079 16.1942 29.3312 16.6313 29.1431C17.0685 28.9494 17.5832 28.8525 18.1753 28.8525C18.8062 28.8525 19.3512 28.9854 19.8105 29.251C20.2754 29.5111 20.6379 29.8929 20.8979 30.3965C21.158 30.9001 21.2936 31.5171 21.3047 32.2476H19.4868C19.4757 31.8602 19.4176 31.5365 19.3125 31.2764C19.2074 31.0107 19.0579 30.8115 18.8643 30.6787C18.6761 30.5459 18.4382 30.4795 18.1504 30.4795C17.835 30.4795 17.5749 30.5404 17.3701 30.6621C17.1709 30.7783 17.016 30.9499 16.9053 31.1768C16.8001 31.4036 16.7254 31.6803 16.6812 32.0068C16.6424 32.3278 16.623 32.693 16.623 33.1025V33.916C16.623 34.3366 16.6424 34.7101 16.6812 35.0366C16.7199 35.3576 16.7918 35.6315 16.897 35.8584C17.0076 36.0853 17.1654 36.2568 17.3701 36.373C17.5749 36.4893 17.8377 36.5474 18.1587 36.5474ZM26.9658 35.6343C26.9658 35.4461 26.9105 35.2801 26.7998 35.1362C26.6947 34.9868 26.5231 34.8402 26.2852 34.6963C26.0472 34.5524 25.729 34.3975 25.3306 34.2314C24.9155 34.0599 24.5475 33.8883 24.2266 33.7168C23.9056 33.5452 23.6344 33.3571 23.4131 33.1523C23.1917 32.9421 23.0229 32.7013 22.9067 32.4302C22.7905 32.159 22.7324 31.8436 22.7324 31.4839C22.7324 31.1131 22.8016 30.77 22.9399 30.4546C23.0783 30.1392 23.2775 29.8625 23.5376 29.6245C23.7977 29.381 24.1104 29.1929 24.4756 29.0601C24.8408 28.9217 25.2531 28.8525 25.7124 28.8525C26.3599 28.8525 26.9132 28.9715 27.3726 29.2095C27.8374 29.4419 28.1916 29.7684 28.4351 30.189C28.6841 30.604 28.8086 31.0827 28.8086 31.625H26.8745C26.8745 31.3981 26.833 31.1934 26.75 31.0107C26.667 30.8226 26.5397 30.6732 26.3682 30.5625C26.1966 30.4463 25.978 30.3882 25.7124 30.3882C25.4689 30.3882 25.2642 30.4352 25.0981 30.5293C24.9377 30.6178 24.8159 30.7396 24.7329 30.8945C24.6554 31.0439 24.6167 31.2127 24.6167 31.4009C24.6167 31.5392 24.6416 31.661 24.6914 31.7661C24.7412 31.8713 24.8242 31.9736 24.9404 32.0732C25.0566 32.1673 25.2116 32.2642 25.4053 32.3638C25.6045 32.4634 25.8563 32.5768 26.1606 32.7041C26.7638 32.9365 27.2646 33.1772 27.6631 33.4263C28.0615 33.6753 28.3604 33.9658 28.5596 34.2979C28.7588 34.6299 28.8584 35.0422 28.8584 35.5347C28.8584 35.9331 28.7837 36.2928 28.6343 36.6138C28.4904 36.9347 28.2801 37.2114 28.0034 37.4438C27.7323 37.6763 27.403 37.8561 27.0156 37.9834C26.6338 38.1051 26.2077 38.166 25.7373 38.166C25.0345 38.166 24.4396 38.0277 23.9526 37.751C23.4712 37.4688 23.106 37.1063 22.8569 36.6636C22.6079 36.2209 22.4834 35.7533 22.4834 35.2607H24.3511C24.3677 35.6149 24.4424 35.8916 24.5752 36.0908C24.7135 36.29 24.8879 36.4284 25.0981 36.5059C25.3084 36.5778 25.527 36.6138 25.7539 36.6138C26.0251 36.6138 26.2492 36.5778 26.4263 36.5059C26.6034 36.4284 26.7362 36.3149 26.8247 36.1655C26.9188 36.0161 26.9658 35.839 26.9658 35.6343ZM33.0503 36.3896L34.7104 29.0186H36.7109L34.1543 38H32.9175L33.0503 36.3896ZM31.8716 29.0186L33.5898 36.4146L33.6729 38H32.4443L29.8711 29.0186H31.8716Z"
fill="black"
/>
</svg>
);
}
if (icon === "pdf") {
return (
<svg
className={` ${sizeClass} inline-block `}
aria-hidden="true"
fill="currentColor"
viewBox="0 0 50 63"
>
<path
d="M42.6966 62.1044H13.464C11.528 62.1021 9.67201 61.3321 8.30308 59.9632C6.93416 58.5942 6.16411 56.7382 6.16187 54.8023V45.2659C6.16187 44.9442 6.28966 44.6357 6.51714 44.4082C6.74461 44.1807 7.05314 44.0529 7.37484 44.0529C7.69654 44.0529 8.00507 44.1807 8.23254 44.4082C8.46002 44.6357 8.58781 44.9442 8.58781 45.2659V54.8023C8.58942 56.095 9.10367 57.3344 10.0178 58.2485C10.9319 59.1626 12.1712 59.6768 13.464 59.6784H42.6966C43.9895 59.6768 45.229 59.1626 46.1433 58.2485C47.0576 57.3345 47.5721 56.0951 47.574 54.8023V43.8746C47.574 43.5529 47.7018 43.2444 47.9293 43.0169C48.1568 42.7894 48.4653 42.6616 48.787 42.6616C49.1087 42.6616 49.4172 42.7894 49.6447 43.0169C49.8722 43.2444 50 43.5529 50 43.8746V54.8023C49.9974 56.7383 49.2271 58.5944 47.858 59.9632C46.4888 61.3321 44.6327 62.1021 42.6966 62.1044Z"
fill="currentColor"
/>
<path
d="M48.7821 41.6183C48.4604 41.6183 48.1519 41.4906 47.9244 41.2631C47.6969 41.0356 47.5691 40.7271 47.5691 40.4054V12.5967C47.5681 12.2529 47.4945 11.9131 47.3532 11.5995C47.2119 11.286 47.0061 11.0058 46.7492 10.7773L38.0521 3.04699C37.6064 2.64832 37.0297 2.42731 36.4316 2.42595H14.6769C13.0615 2.42852 11.513 3.07163 10.3711 4.21425C9.22916 5.35686 8.58698 6.90572 8.58537 8.52114V23.3037C8.58537 23.6254 8.45758 23.9339 8.2301 24.1614C8.00262 24.3888 7.6941 24.5166 7.3724 24.5166C7.0507 24.5166 6.74217 24.3888 6.5147 24.1614C6.28722 23.9339 6.15942 23.6254 6.15942 23.3037V8.52114C6.16167 6.26251 7.05965 4.09698 8.6564 2.49955C10.2532 0.902118 12.4183 0.0032109 14.6769 9.10874e-08H36.4316C37.6233 -0.000230408 38.7736 0.437008 39.6642 1.22874L48.3612 8.96024C48.8752 9.41695 49.2865 9.97738 49.5681 10.6046C49.8497 11.2318 49.9953 11.9116 49.9951 12.5992V40.4054C49.9951 40.7271 49.8673 41.0356 49.6398 41.2631C49.4123 41.4906 49.1038 41.6183 48.7821 41.6183Z"
fill="currentColor"
/>
<path
d="M48.7203 13.1681H41.474C40.1838 13.1665 38.947 12.6533 38.0347 11.741C37.1224 10.8287 36.6091 9.59184 36.6075 8.30167V1.49325C36.6075 1.17155 36.7353 0.863022 36.9628 0.635545C37.1903 0.408069 37.4988 0.280273 37.8205 0.280273C38.1422 0.280273 38.4507 0.408069 38.6782 0.635545C38.9057 0.863022 39.0335 1.17155 39.0335 1.49325V8.30167C39.0341 8.94874 39.2915 9.56911 39.749 10.0267C40.2066 10.4842 40.8269 10.7415 41.474 10.7422H48.7203C49.042 10.7422 49.3505 10.87 49.578 11.0974C49.8055 11.3249 49.9333 11.6334 49.9333 11.9551C49.9333 12.2768 49.8055 12.5854 49.578 12.8129C49.3505 13.0403 49.042 13.1681 48.7203 13.1681Z"
fill="currentColor"
/>
<path
d="M34.2362 40.3774C33.9145 40.3774 33.6059 40.2496 33.3785 40.0222C33.151 39.7947 33.0232 39.4862 33.0232 39.1644V29.4036C33.0232 29.0819 33.151 28.7734 33.3785 28.5459C33.6059 28.3185 33.9145 28.1907 34.2362 28.1907H38.1929C38.5146 28.1907 38.8231 28.3185 39.0506 28.5459C39.2781 28.7734 39.4059 29.0819 39.4059 29.4036C39.4059 29.7253 39.2781 30.0339 39.0506 30.2613C38.8231 30.4888 38.5146 30.6166 38.1929 30.6166H35.4491V39.1644C35.4491 39.4862 35.3213 39.7947 35.0939 40.0222C34.8664 40.2496 34.5579 40.3774 34.2362 40.3774Z"
fill="currentColor"
/>
<path
d="M37.8788 35.4042H34.2398C33.9181 35.4042 33.6096 35.2764 33.3821 35.0489C33.1546 34.8215 33.0269 34.5129 33.0269 34.1912C33.0269 33.8695 33.1546 33.561 33.3821 33.3335C33.6096 33.1061 33.9181 32.9783 34.2398 32.9783H37.8788C38.2005 32.9783 38.509 33.1061 38.7365 33.3335C38.9639 33.561 39.0917 33.8695 39.0917 34.1912C39.0917 34.5129 38.9639 34.8215 38.7365 35.0489C38.509 35.2764 38.2005 35.4042 37.8788 35.4042Z"
fill="currentColor"
/>
<path
d="M22.161 40.2198C21.8393 40.2198 21.5308 40.092 21.3033 39.8645C21.0759 39.6371 20.9481 39.3285 20.9481 39.0068V29.4146C20.9481 29.0929 21.0759 28.7844 21.3033 28.5569C21.5308 28.3295 21.8393 28.2017 22.161 28.2017C22.4827 28.2017 22.7913 28.3295 23.0187 28.5569C23.2462 28.7844 23.374 29.0929 23.374 29.4146V39.0068C23.374 39.3285 23.2462 39.6371 23.0187 39.8645C22.7913 40.092 22.4827 40.2198 22.161 40.2198Z"
fill="currentColor"
/>
<path
d="M22.1817 40.3774C21.86 40.3774 21.5514 40.2496 21.324 40.0221C21.0965 39.7947 20.9687 39.4861 20.9687 39.1644C20.9687 39.1293 20.9553 35.6104 20.9553 34.2689C20.9553 33.1554 20.9481 29.4012 20.9481 29.4012C20.9481 29.0795 21.0759 28.771 21.3033 28.5435C21.5308 28.316 21.8393 28.1882 22.161 28.1882H24.9254C28.0088 28.1882 30.0805 30.6372 30.0805 34.2822C30.0805 37.7489 27.9554 40.3022 25.0285 40.3531C24.2401 40.3677 22.269 40.375 22.1853 40.375L22.1817 40.3774ZM23.3764 30.6166C23.3764 31.8162 23.3764 33.5714 23.3764 34.2737C23.3764 35.1082 23.3764 36.7906 23.3849 37.9454C23.9672 37.9454 24.6149 37.9369 24.9812 37.9308C26.8249 37.8981 27.6497 36.0762 27.6497 34.2846C27.6497 32.5113 26.9329 30.6166 24.9205 30.6166H23.3764Z"
fill="currentColor"
/>
<path
d="M11.7865 40.3774C11.4648 40.3774 11.1562 40.2496 10.9288 40.0222C10.7013 39.7947 10.5735 39.4862 10.5735 39.1644V29.4036C10.5735 29.0819 10.7013 28.7734 10.9288 28.5459C11.1562 28.3185 11.4648 28.1907 11.7865 28.1907C12.1082 28.1907 12.4167 28.3185 12.6442 28.5459C12.8716 28.7734 12.9994 29.0819 12.9994 29.4036V39.1644C12.9994 39.4862 12.8716 39.7947 12.6442 40.0222C12.4167 40.2496 12.1082 40.3774 11.7865 40.3774Z"
fill="currentColor"
/>
<path
d="M11.8047 36.1381C11.4842 36.1381 11.1768 36.0113 10.9496 35.7854C10.7223 35.5595 10.5936 35.2528 10.5917 34.9324C10.5917 34.913 10.5796 32.9201 10.5796 32.1583C10.5796 31.53 10.5796 29.4073 10.5796 29.4073C10.5796 29.0856 10.7074 28.7771 10.9349 28.5496C11.1623 28.3221 11.4709 28.1943 11.7926 28.1943H14.5096C16.7269 28.1943 18.5318 29.9738 18.5318 32.162C18.5318 34.3502 16.7269 36.1296 14.5096 36.1296C13.7685 36.1296 11.8411 36.1417 11.8192 36.1417L11.8047 36.1381ZM13.0031 30.6166C13.0031 31.2086 13.0031 31.8563 13.0031 32.1583C13.0031 32.5125 13.0031 33.1384 13.0031 33.7049H14.4951C15.3599 33.7049 16.0913 32.9989 16.0913 32.1632C16.0913 31.3274 15.3636 30.6215 14.4951 30.6215L13.0031 30.6166Z"
fill="currentColor"
/>
<path
d="M6.10975 52.2791H4.24541C3.11946 52.2791 2.03962 51.8319 1.24345 51.0357C0.447283 50.2395 0 49.1597 0 48.0337C0 46.9078 0.447283 45.8279 1.24345 45.0318C2.03962 44.2356 3.11946 43.7883 4.24541 43.7883H9.75474C10.0764 43.7883 10.385 43.9161 10.6124 44.1436C10.8399 44.3711 10.9677 44.6796 10.9677 45.0013C10.9677 45.323 10.8399 45.6315 10.6124 45.859C10.385 46.0865 10.0764 46.2143 9.75474 46.2143H4.24541C3.76286 46.2143 3.30007 46.406 2.95886 46.7472C2.61764 47.0884 2.42595 47.5512 2.42595 48.0337C2.42595 48.5163 2.61764 48.9791 2.95886 49.3203C3.30007 49.6615 3.76286 49.8532 4.24541 49.8532H6.11339C6.43509 49.8532 6.74361 49.981 6.97109 50.2085C7.19857 50.4359 7.32636 50.7445 7.32636 51.0662C7.32636 51.3879 7.19857 51.6964 6.97109 51.9239C6.74361 52.1514 6.43509 52.2791 6.11339 52.2791H6.10975Z"
fill="currentColor"
/>
<path
d="M1.22872 48.8973C0.90702 48.8973 0.598496 48.7695 0.371019 48.542C0.143542 48.3145 0.0157471 48.006 0.0157471 47.6843L0.067905 26.336C0.069833 25.2113 0.517967 24.1333 1.31393 23.3387C2.10989 22.5441 3.18862 22.0978 4.31331 22.0978L48.7846 22.0894C49.1063 22.0894 49.4148 22.2171 49.6423 22.4446C49.8698 22.6721 49.9976 22.9806 49.9976 23.3023V45.0012C49.9976 45.3229 49.8698 45.6314 49.6423 45.8589C49.4148 46.0864 49.1063 46.2142 48.7846 46.2142H5.52386C5.20216 46.2142 4.89364 46.0864 4.66616 45.8589C4.43868 45.6314 4.31089 45.3229 4.31089 45.0012C4.31089 44.6795 4.43868 44.371 4.66616 44.1435C4.89364 43.916 5.20216 43.7882 5.52386 43.7882H47.5692V24.508L4.30967 24.5165C3.82712 24.5165 3.36434 24.7082 3.02312 25.0494C2.68191 25.3906 2.49021 25.8534 2.49021 26.336L2.43806 47.6843C2.43806 48.0054 2.31076 48.3134 2.08407 48.5407C1.85738 48.7681 1.54979 48.8963 1.22872 48.8973Z"
fill="currentColor"
/>
</svg>
);
}
if (icon === "microsoft") {
return (
<svg
className={` ${sizeClass} inline-block `}
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 23 23"
>
<g clipPath="url(#clip0_616_221)">
{/* <path d="M0 0H23V23H0V0Z" fill="currentColor" /> */}
<path d="M1 1H11V11H1V1Z" fill="#F35325" />
<path d="M12 1H22V11H12V1Z" fill="#81BC06" />
<path d="M1 12H11V22H1V12Z" fill="#05A6F0" />
<path d="M12 12H22V22H12V12Z" fill="#FFBA08" />
</g>
<defs>
<clipPath id="clip0_616_221">
<rect width="23" height="23" fill="white" />
</clipPath>
</defs>
</svg>
);
}
if (icon === "loading") {
return (
<svg
className={` ${sizeClass} inline-block animate-spin `}
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
>
<path d="M11 3c-1.613 0-3.122.437-4.432 1.185l1.65 2.445-6.702-.378 2.226-6.252 1.703 2.522c1.633-.959 3.525-1.522 5.555-1.522 4.406 0 8.197 2.598 9.953 6.34l-1.642 1.215c-1.355-3.258-4.569-5.555-8.311-5.555zm13 12.486l-2.375-6.157-5.307 3.925 3.389.984c-.982 3.811-4.396 6.651-8.488 6.75l.891 1.955c4.609-.461 8.373-3.774 9.521-8.146l2.369.689zm-18.117 3.906c-2.344-1.625-3.883-4.33-3.883-7.392 0-1.314.29-2.56.799-3.687l-2.108-.12c-.439 1.188-.691 2.467-.691 3.807 0 3.831 1.965 7.192 4.936 9.158l-1.524 2.842 6.516-1.044-2.735-6.006-1.31 2.442z" />
</svg>
);
}
if (icon === "app") {
return (
<svg
className={` ${sizeClass} inline-block `}
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 93 90"
>
<path
d="M44.0471 0H31.0006C28.3399 0 25.997 1.75225 25.2451 4.30449L2.26702 82.3045C1.13478 86.1479 4.01575 90 8.02249 90H21.776C24.4553 90 26.8098 88.2236 27.5454 85.6473L49.8165 7.64732C50.9108 3.81465 48.0329 0 44.0471 0Z"
fill="#3F9447"
/>
<path
d="M61.8267 39H51.7524C49.9425 39 48.3581 40.2153 47.8891 41.9634L36.3514 84.9634C35.6697 87.5042 37.5841 90 40.2148 90H50.644C52.4654 90 54.0568 88.7695 54.5153 87.0068L65.6979 44.0068C66.3568 41.4731 64.4446 39 61.8267 39Z"
fill="#D9D9D9"
/>
<path
d="M90.1629 84.234L77.2698 58.0311C77.0912 57.668 76.8537 57.337 76.5672 57.0514C74.5514 55.0426 71.1154 55.9917 70.4166 58.7504L63.7622 85.0177C63.1219 87.5453 65.0322 90 67.6397 90H86.5738C89.5362 90 91.4707 86.8921 90.1629 84.234Z"
fill="#3F9447"
/>
</svg>
);
}
return (
<svg
className={` ${sizeClass} inline-block `}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path d="M1 3.488c0-1.926 4.656-3.488 10-3.488 5.345 0 10 1.562 10 3.488s-4.655 3.487-10 3.487c-5.344 0-10-1.561-10-3.487zm10 9.158c5.345 0 10-1.562 10-3.487v-2.44c-2.418 1.738-7.005 2.256-10 2.256-3.006 0-7.588-.523-10-2.256v2.44c0 1.926 4.656 3.487 10 3.487zm0 5.665c.34 0 .678-.007 1.011-.019.045-1.407.537-2.7 1.342-3.745-.839.067-1.643.1-2.353.1-3.006 0-7.588-.523-10-2.256v2.434c0 1.925 4.656 3.486 10 3.486zm1.254 1.97c-.438.02-.861.03-1.254.03-2.995 0-7.582-.518-10-2.256v2.458c0 1.925 4.656 3.487 10 3.487 1.284 0 2.526-.092 3.676-.256-1.155-.844-2.02-2.055-2.422-3.463zm10.746-1.781c0 2.485-2.017 4.5-4.5 4.5s-4.5-2.015-4.5-4.5 2.017-4.5 4.5-4.5 4.5 2.015 4.5 4.5zm-2.166-1.289l-2.063.557.916-1.925-1.387.392-1.466 3.034 1.739-.472-1.177 2.545 3.438-4.131z" />
</svg>
);
};
export default Icon;

View File

@@ -0,0 +1,60 @@
import * as React from "react";
import Header from "./header";
import { appContext } from "../hooks/provider";
import Footer from "./footer";
/// import ant css
import "antd/dist/reset.css";
type Props = {
title: string;
link: string;
children?: React.ReactNode;
showHeader?: boolean;
restricted?: boolean;
meta?: any;
};
const Layout = ({
meta,
title,
link,
children,
showHeader = true,
restricted = false,
}: Props) => {
const layoutContent = (
<div
// style={{ height: "calc(100vh - 64px)" }}
className={` h-full flex flex-col`}
>
{showHeader && <Header meta={meta} link={link} />}
<div className="flex-1 text-primary ">
<title>{meta?.title + " | " + title}</title>
<div className=" h-full text-primary">{children}</div>
</div>
<Footer />
</div>
);
const { darkMode } = React.useContext(appContext);
React.useEffect(() => {
document.getElementsByTagName("html")[0].className = `${
darkMode === "dark" ? "dark bg-primary" : "light bg-primary"
} `;
}, [darkMode]);
return (
<appContext.Consumer>
{(context: any) => {
if (restricted) {
return <div className="h-full ">{context.user && layoutContent}</div>;
} else {
return layoutContent;
}
}}
</appContext.Consumer>
);
};
export default Layout;

View File

@@ -0,0 +1,127 @@
export type NotificationType = "success" | "info" | "warning" | "error";
export interface IMessage {
user_id: string;
role: string;
content: string;
created_at?: string;
updated_at?: string;
session_id?: number;
connection_id?: string;
workflow_id?: number;
meta?: any;
id?: number;
}
export interface IStatus {
message: string;
status: boolean;
data?: any;
}
export interface IChatMessage {
text: string;
sender: "user" | "bot";
meta?: any;
id?: number;
}
export interface ILLMConfig {
config_list: Array<IModelConfig>;
timeout?: number;
cache_seed?: number | null;
temperature: number;
max_tokens: number;
}
export interface IAgentConfig {
name: string;
llm_config?: ILLMConfig | false;
human_input_mode: string;
max_consecutive_auto_reply: number;
system_message: string | "";
is_termination_msg?: boolean | string;
default_auto_reply?: string | null;
code_execution_config?: "none" | "local" | "docker";
description?: string;
admin_name?: string;
messages?: Array<IMessage>;
max_round?: number;
speaker_selection_method?: string;
allow_repeat_speaker?: boolean;
}
export interface IAgent {
type?: "assistant" | "userproxy" | "groupchat";
config: IAgentConfig;
created_at?: string;
updated_at?: string;
id?: number;
skills?: Array<ISkill>;
user_id?: string;
}
export interface IWorkflow {
name: string;
description: string;
sender?: IAgent;
receiver?: IAgent;
type?: "autonomous" | "sequential";
created_at?: string;
updated_at?: string;
summary_method?: "none" | "last" | "llm";
id?: number;
user_id?: string;
}
export interface IModelConfig {
model: string;
api_key?: string;
api_version?: string;
base_url?: string;
api_type?: "open_ai" | "azure" | "google" | "anthropic" | "mistral";
user_id?: string;
created_at?: string;
updated_at?: string;
description?: string;
id?: number;
}
export interface IMetadataFile {
name: string;
path: string;
extension: string;
content: string;
type: string;
}
export interface IChatSession {
id?: number;
user_id: string;
workflow_id?: number;
created_at?: string;
updated_at?: string;
name: string;
}
export interface IGalleryItem {
id: number;
messages: Array<IMessage>;
session: IChatSession;
tags: Array<string>;
created_at: string;
updated_at: string;
}
export interface ISkill {
name: string;
content: string;
secrets?: any[];
libraries?: string[];
id?: number;
description?: string;
user_id?: string;
created_at?: string;
updated_at?: string;
}

View File

@@ -0,0 +1,684 @@
import {
IAgent,
IAgentConfig,
ILLMConfig,
IModelConfig,
ISkill,
IStatus,
IWorkflow,
} from "./types";
export const getServerUrl = () => {
return process.env.GATSBY_API_URL || "/api";
};
export function setCookie(name: string, value: any, days: number) {
let expires = "";
if (days) {
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}
export function getCookie(name: string) {
const nameEQ = name + "=";
const ca = document.cookie.split(";");
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == " ") c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
export function setLocalStorage(
name: string,
value: any,
stringify: boolean = true
) {
if (stringify) {
localStorage.setItem(name, JSON.stringify(value));
} else {
localStorage.setItem(name, value);
}
}
export function getLocalStorage(name: string, stringify: boolean = true): any {
if (typeof window !== "undefined") {
const value = localStorage.getItem(name);
try {
if (stringify) {
return JSON.parse(value!);
} else {
return value;
}
} catch (e) {
return null;
}
} else {
return null;
}
}
export function fetchJSON(
url: string | URL,
payload: any = {},
onSuccess: (data: any) => void,
onError: (error: IStatus) => void,
onFinal: () => void = () => {}
) {
return fetch(url, payload)
.then(function (response) {
if (response.status !== 200) {
console.log(
"Looks like there was a problem. Status Code: " + response.status,
response
);
response.json().then(function (data) {
console.log("Error data", data);
});
onError({
status: false,
message:
"Connection error " + response.status + " " + response.statusText,
});
return;
}
return response.json().then(function (data) {
onSuccess(data);
});
})
.catch(function (err) {
console.log("Fetch Error :-S", err);
onError({
status: false,
message: `There was an error connecting to server. (${err}) `,
});
})
.finally(() => {
onFinal();
});
}
export const capitalize = (s: string) => {
if (typeof s !== "string") return "";
return s.charAt(0).toUpperCase() + s.slice(1);
};
export function eraseCookie(name: string) {
document.cookie = name + "=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;";
}
export function truncateText(text: string, length = 50) {
if (text.length > length) {
return text.substring(0, length) + " ...";
}
return text;
}
export const getCaretCoordinates = () => {
let caretX, caretY;
const selection = window.getSelection();
if (selection && selection?.rangeCount !== 0) {
const range = selection.getRangeAt(0).cloneRange();
range.collapse(false);
const rect = range.getClientRects()[0];
if (rect) {
caretX = rect.left;
caretY = rect.top;
}
}
return { caretX, caretY };
};
export const getPrefixSuffix = (container: any) => {
let prefix = "";
let suffix = "";
if (window.getSelection) {
const sel = window.getSelection();
if (sel && sel.rangeCount > 0) {
let range = sel.getRangeAt(0).cloneRange();
range.collapse(true);
range.setStart(container!, 0);
prefix = range.toString();
range = sel.getRangeAt(0).cloneRange();
range.collapse(true);
range.setEnd(container, container.childNodes.length);
suffix = range.toString();
console.log("prefix", prefix);
console.log("suffix", suffix);
}
}
return { prefix, suffix };
};
export const uid = () => {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
};
export const setCaretToEnd = (element: HTMLElement) => {
const range = document.createRange();
const selection = window.getSelection();
range.selectNodeContents(element);
range.collapse(false);
selection?.removeAllRanges();
selection?.addRange(range);
element.focus();
};
// return a color between a start and end color using a percentage
export const ColorTween = (
startColor: string,
endColor: string,
percent: number
) => {
// example startColor = "#ff0000" endColor = "#0000ff" percent = 0.5
const start = {
r: parseInt(startColor.substring(1, 3), 16),
g: parseInt(startColor.substring(3, 5), 16),
b: parseInt(startColor.substring(5, 7), 16),
};
const end = {
r: parseInt(endColor.substring(1, 3), 16),
g: parseInt(endColor.substring(3, 5), 16),
b: parseInt(endColor.substring(5, 7), 16),
};
const r = Math.floor(start.r + (end.r - start.r) * percent);
const g = Math.floor(start.g + (end.g - start.g) * percent);
const b = Math.floor(start.b + (end.b - start.b) * percent);
return `rgb(${r}, ${g}, ${b})`;
};
export const guid = () => {
var w = () => {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
};
return `${w()}${w()}-${w()}-${w()}-${w()}-${w()}${w()}${w()}`;
};
/**
* Takes a string and returns the first n characters followed by asterisks.
* @param {string} str - The string to obscure
* @param {number} n - Number of characters to show before obscuring
* @returns {string} The obscured string with first n characters in clear text
*/
export const obscureString = (str: string, n: number = 3) => {
if (n < 0 || n > str.length) {
console.log("n cannot be less than 0 or greater than the string length.");
return str;
}
// First n characters in clear text
var clearText = str.substring(0, n);
// Remaining characters replaced with asterisks
var obscured = clearText + "*".repeat(str.length - n);
return obscured;
};
/**
* Converts a number of seconds into a human-readable string representing the duration in days, hours, minutes, and seconds.
* @param {number} seconds - The number of seconds to convert.
* @returns {string} A well-formatted duration string.
*/
export const formatDuration = (seconds: number) => {
const units = [
{ label: " day", seconds: 86400 },
{ label: " hr", seconds: 3600 },
{ label: " min", seconds: 60 },
{ label: " sec", seconds: 1 },
];
let remainingSeconds = seconds;
const parts = [];
for (const { label, seconds: unitSeconds } of units) {
const count = Math.floor(remainingSeconds / unitSeconds);
if (count > 0) {
parts.push(count + (count > 1 ? label + "s" : label));
remainingSeconds -= count * unitSeconds;
}
}
return parts.length > 0 ? parts.join(" ") : "0 sec";
};
export const sampleModelConfig = (modelType: string = "open_ai") => {
const openaiConfig: IModelConfig = {
model: "gpt-4-1106-preview",
api_type: "open_ai",
description: "OpenAI GPT-4 model",
};
const azureConfig: IModelConfig = {
model: "gpt-4",
api_type: "azure",
api_version: "v1",
base_url: "https://youazureendpoint.azure.com/",
description: "Azure model",
};
const googleConfig: IModelConfig = {
model: "gemini-1.0-pro",
api_type: "google",
description: "Google Gemini Model model",
};
const anthropicConfig: IModelConfig = {
model: "claude-3-5-sonnet-20240620",
api_type: "anthropic",
description: "Claude 3.5 Sonnet model",
};
const mistralConfig: IModelConfig = {
model: "mistral",
api_type: "mistral",
description: "Mistral model",
};
switch (modelType) {
case "open_ai":
return openaiConfig;
case "azure":
return azureConfig;
case "google":
return googleConfig;
case "anthropic":
return anthropicConfig;
case "mistral":
return mistralConfig;
default:
return openaiConfig;
}
};
export const getRandomIntFromDateAndSalt = (salt: number = 43444) => {
const currentDate = new Date();
const seed = currentDate.getTime() + salt;
const randomValue = Math.sin(seed) * 10000;
const randomInt = Math.floor(randomValue) % 100;
return randomInt;
};
export const getSampleWorkflow = (workflow_type: string = "autonomous") => {
const autonomousWorkflow: IWorkflow = {
name: "Default Chat Workflow",
description: "Autonomous Workflow",
type: "autonomous",
summary_method: "llm",
};
const sequentialWorkflow: IWorkflow = {
name: "Default Sequential Workflow",
description: "Sequential Workflow",
type: "sequential",
summary_method: "llm",
};
if (workflow_type === "autonomous") {
return autonomousWorkflow;
} else if (workflow_type === "sequential") {
return sequentialWorkflow;
} else {
return autonomousWorkflow;
}
};
export const sampleAgentConfig = (agent_type: string = "assistant") => {
const llm_config: ILLMConfig = {
config_list: [],
temperature: 0.1,
timeout: 600,
cache_seed: null,
max_tokens: 4000,
};
const userProxyConfig: IAgentConfig = {
name: "userproxy",
human_input_mode: "NEVER",
description: "User Proxy",
max_consecutive_auto_reply: 25,
system_message: "You are a helpful assistant.",
default_auto_reply: "TERMINATE",
llm_config: false,
code_execution_config: "local",
};
const userProxyFlowSpec: IAgent = {
type: "userproxy",
config: userProxyConfig,
};
const assistantConfig: IAgentConfig = {
name: "primary_assistant",
description: "Primary Assistant",
llm_config: llm_config,
human_input_mode: "NEVER",
max_consecutive_auto_reply: 25,
code_execution_config: "none",
system_message:
"You are a helpful AI assistant. Solve tasks using your coding and language skills. In the following cases, suggest python code (in a python coding block) or shell script (in a sh coding block) for the user to execute. 1. When you need to collect info, use the code to output the info you need, for example, browse or search the web, download/read a file, print the content of a webpage or a file, get the current date/time, check the operating system. After sufficient info is printed and the task is ready to be solved based on your language skill, you can solve the task by yourself. 2. When you need to perform some task with code, use the code to perform the task and output the result. Finish the task smartly. Solve the task step by step if you need to. If a plan is not provided, explain your plan first. Be clear which step uses code, and which step uses your language skill. When using code, you must indicate the script type in the code block. The user cannot provide any other feedback or perform any other action beyond executing the code you suggest. The user can't modify your code. So do not suggest incomplete code which requires users to modify. Don't use a code block if it's not intended to be executed by the user. If you want the user to save the code in a file before executing it, put # filename: <filename> inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user. If the result indicates there is an error, fix the error and output the code again. Suggest the full code instead of partial code or code changes. If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try. When you find an answer, verify the answer carefully. Include verifiable evidence in your response if possible. Reply 'TERMINATE' in the end when everything is done.",
};
const assistantFlowSpec: IAgent = {
type: "assistant",
config: assistantConfig,
};
const groupChatAssistantConfig = Object.assign(
{
admin_name: "groupchat_assistant",
messages: [],
max_round: 10,
speaker_selection_method: "auto",
allow_repeat_speaker: false,
},
assistantConfig
);
groupChatAssistantConfig.name = "groupchat_assistant";
groupChatAssistantConfig.system_message =
"You are a helpful assistant skilled at cordinating a group of other assistants to solve a task. ";
groupChatAssistantConfig.description = "Group Chat Assistant";
const groupChatFlowSpec: IAgent = {
type: "groupchat",
config: groupChatAssistantConfig,
};
if (agent_type === "userproxy") {
return userProxyFlowSpec;
} else if (agent_type === "assistant") {
return assistantFlowSpec;
} else if (agent_type === "groupchat") {
return groupChatFlowSpec;
} else {
return assistantFlowSpec;
}
};
export const getSampleSkill = () => {
const content = `
from typing import List
import uuid
import requests # to perform HTTP requests
from pathlib import Path
from openai import OpenAI
def generate_and_save_images(query: str, image_size: str = "1024x1024") -> List[str]:
"""
Function to paint, draw or illustrate images based on the users query or request. Generates images from a given query using OpenAI's DALL-E model and saves them to disk. Use the code below anytime there is a request to create an image.
:param query: A natural language description of the image to be generated.
:param image_size: The size of the image to be generated. (default is "1024x1024")
:return: A list of filenames for the saved images.
"""
client = OpenAI() # Initialize the OpenAI client
response = client.images.generate(model="dall-e-3", prompt=query, n=1, size=image_size) # Generate images
# List to store the file names of saved images
saved_files = []
# Check if the response is successful
if response.data:
for image_data in response.data:
# Generate a random UUID as the file name
file_name = str(uuid.uuid4()) + ".png" # Assuming the image is a PNG
file_path = Path(file_name)
img_url = image_data.url
img_response = requests.get(img_url)
if img_response.status_code == 200:
# Write the binary content to a file
with open(file_path, "wb") as img_file:
img_file.write(img_response.content)
print(f"Image saved to {file_path}")
saved_files.append(str(file_path))
else:
print(f"Failed to download the image from {img_url}")
else:
print("No image data found in the response!")
# Return the list of saved files
return saved_files
# Example usage of the function:
# generate_and_save_images("A cute baby sea otter")
`;
const skill: ISkill = {
name: "generate_and_save_images",
description: "Generate and save images based on a user's query.",
content: content,
};
return skill;
};
export const timeAgo = (
dateString: string,
returnFormatted: boolean = false
): string => {
// if dateStr is empty, return empty string
if (!dateString) {
return "";
}
// Parse the date string into a Date object
const timestamp = new Date(dateString);
// Check for invalid date
if (isNaN(timestamp.getTime())) {
throw new Error("Invalid date string provided.");
}
// Get the current time
const now = new Date();
// Calculate the difference in milliseconds
const timeDifference = now.getTime() - timestamp.getTime();
// Convert time difference to minutes and hours
const minutesAgo = Math.floor(timeDifference / (1000 * 60));
const hoursAgo = Math.floor(minutesAgo / 60);
// Format the date into a readable format e.g. "November 27, 2021, 3:45 PM"
const options: Intl.DateTimeFormatOptions = {
month: "long",
day: "numeric",
year: "numeric",
hour: "numeric",
minute: "numeric",
};
const formattedDate = timestamp.toLocaleDateString(undefined, options);
if (returnFormatted) {
return formattedDate;
}
// Determine the time difference string
let timeAgoStr: string;
if (minutesAgo < 1) {
timeAgoStr = "just now";
} else if (minutesAgo < 60) {
// Less than an hour ago, display minutes
timeAgoStr = `${minutesAgo} ${minutesAgo === 1 ? "minute" : "minutes"} ago`;
} else if (hoursAgo < 24) {
// Less than a day ago, display hours
timeAgoStr = `${hoursAgo} ${hoursAgo === 1 ? "hour" : "hours"} ago`;
} else {
// More than a day ago, display the formatted date
timeAgoStr = formattedDate;
}
// Return the final readable string
return timeAgoStr;
};
export const examplePrompts = [
{
title: "Stock Price",
prompt:
"Plot a chart of NVDA and TESLA stock price for 2023. Save the result to a file named nvda_tesla.png",
},
{
title: "Sine Wave",
prompt:
"Write a python script to plot a sine wave and save it to disc as a png file sine_wave.png",
},
{
title: "Markdown",
prompt:
"List out the top 5 rivers in africa and their length and return that as a markdown table. Do not try to write any code, just write the table",
},
{
title: "Paint",
prompt:
"paint a picture of a glass of ethiopian coffee, freshly brewed in a tall glass cup, on a table right in front of a lush green forest scenery",
},
{
title: "Travel",
prompt:
"Plan a 2 day trip to hawaii. Limit to 3 activities per day, be as brief as possible!",
},
];
export const fetchVersion = () => {
const versionUrl = getServerUrl() + "/version";
return fetch(versionUrl)
.then((response) => response.json())
.then((data) => {
return data;
})
.catch((error) => {
console.error("Error:", error);
return null;
});
};
/**
* Recursively sanitizes JSON objects by replacing specific keys with a given value.
* @param {JsonValue} data - The JSON data to be sanitized.
* @param {string[]} keys - An array of keys to be replaced in the JSON object.
* @param {string} replacement - The value to use as replacement for the specified keys.
* @returns {JsonValue} - The sanitized JSON data.
*/
export const sanitizeConfig = (
data: any,
keys: string[] = ["api_key", "id", "created_at", "updated_at", "secrets"]
): any => {
if (Array.isArray(data)) {
return data.map((item) => sanitizeConfig(item, keys));
} else if (typeof data === "object" && data !== null) {
Object.keys(data).forEach((key) => {
if (keys.includes(key)) {
delete data[key];
} else {
data[key] = sanitizeConfig(data[key], keys);
}
});
}
return data;
};
/**
* Checks the input text against the regex '^[a-zA-Z0-9_-]{1,64}$' and returns an object with
* status, message, and sanitizedText. Status is boolean indicating whether input text is valid,
* message provides information about the outcome, and sanitizedText contains a valid version
* of the input text or the original text if it was already valid.
*
* @param text - The input string to be checked and sanitized.
* @returns An object containing a status, a message, and sanitizedText.
*/
export const checkAndSanitizeInput = (
text: string
): { status: boolean; message: string; sanitizedText: string } => {
// Create a regular expression pattern to match valid characters
const regexPattern: RegExp = /^[a-zA-Z0-9_-]{1,64}$/;
let status: boolean = true;
let message: string;
let sanitizedText: string;
// Check if the input text matches the pattern
if (regexPattern.test(text)) {
// Text already adheres to the pattern
message = `The text '${text}' is valid.`;
sanitizedText = text;
} else {
// The text does not match; sanitize the input
status = false;
sanitizedText = text.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
message = `'${text}' is invalid. Consider using '${sanitizedText}' instead.`;
}
return { status, message, sanitizedText };
};
export const isValidConfig = (
jsonObj: any,
templateObj: any,
diffThreshold: number = 4
): {
status: boolean;
message: string;
} => {
// Check if both parameters are indeed objects and not null
if (
typeof jsonObj !== "object" ||
jsonObj === null ||
Array.isArray(jsonObj) ||
typeof templateObj !== "object" ||
templateObj === null ||
Array.isArray(templateObj)
) {
return {
status: false,
message:
"Invalid input: One or both parameters are not objects, or are null or arrays.",
};
}
const jsonKeys = new Set(Object.keys(jsonObj));
const templateKeys = new Set(Object.keys(templateObj));
if (jsonKeys.size !== templateKeys.size) {
if (Math.abs(jsonKeys.size - templateKeys.size) > diffThreshold) {
return {
status: false,
message:
"Configuration does not match template: Number of keys differ.",
};
}
}
for (const key of templateKeys) {
if (!jsonKeys.has(key)) {
return {
status: false,
message: `Configuration does not match template: Missing key '${key}' in configuration.`,
};
}
// If the value is an object, recursively validate
if (
typeof templateObj[key] === "object" &&
templateObj[key] !== null &&
!Array.isArray(templateObj[key])
) {
const result = isValidConfig(jsonObj[key], templateObj[key]);
if (!result.status) {
return {
status: false,
message: `Configuration error in nested key '${key}': ${result.message}`,
};
}
}
}
return {
status: true,
message: "Configuration is valid.",
};
};

View File

@@ -0,0 +1,385 @@
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
DocumentDuplicateIcon,
InformationCircleIcon,
PlusIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { Dropdown, MenuProps, Modal, message } from "antd";
import * as React from "react";
import { IAgent, IStatus } from "../../types";
import { appContext } from "../../../hooks/provider";
import {
fetchJSON,
getServerUrl,
sanitizeConfig,
timeAgo,
truncateText,
} from "../../utils";
import { BounceLoader, Card, CardHoverBar, LoadingOverlay } from "../../atoms";
import { AgentViewer } from "./utils/agentconfig";
const AgentsView = ({}: any) => {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const listAgentsUrl = `${serverUrl}/agents?user_id=${user?.email}`;
const [agents, setAgents] = React.useState<IAgent[] | null>([]);
const [selectedAgent, setSelectedAgent] = React.useState<IAgent | null>(null);
const [showNewAgentModal, setShowNewAgentModal] = React.useState(false);
const [showAgentModal, setShowAgentModal] = React.useState(false);
const sampleAgent = {
config: {
name: "sample_agent",
description: "Sample agent description",
human_input_mode: "NEVER",
max_consecutive_auto_reply: 3,
system_message: "",
},
};
const [newAgent, setNewAgent] = React.useState<IAgent | null>(sampleAgent);
const deleteAgent = (agent: IAgent) => {
setError(null);
setLoading(true);
const deleteAgentUrl = `${serverUrl}/agents/delete?user_id=${user?.email}&agent_id=${agent.id}`;
// const fetch;
const payLoad = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
agent: agent,
}),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
fetchAgents();
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(deleteAgentUrl, payLoad, onSuccess, onError);
};
const fetchAgents = () => {
setError(null);
setLoading(true);
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
setAgents(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listAgentsUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
// console.log("fetching messages", messages);
fetchAgents();
}
}, []);
const agentRows = (agents || []).map((agent: IAgent, i: number) => {
const cardItems = [
{
title: "Download",
icon: ArrowDownTrayIcon,
onClick: (e: any) => {
e.stopPropagation();
// download workflow as workflow.name.json
const element = document.createElement("a");
const sanitizedAgent = sanitizeConfig(agent);
const file = new Blob([JSON.stringify(sanitizedAgent)], {
type: "application/json",
});
element.href = URL.createObjectURL(file);
element.download = `agent_${agent.config.name}.json`;
document.body.appendChild(element); // Required for this to work in FireFox
element.click();
},
hoverText: "Download",
},
{
title: "Make a Copy",
icon: DocumentDuplicateIcon,
onClick: (e: any) => {
e.stopPropagation();
let newAgent = { ...sanitizeConfig(agent) };
newAgent.config.name = `${agent.config.name}_copy`;
console.log("newAgent", newAgent);
setNewAgent(newAgent);
setShowNewAgentModal(true);
},
hoverText: "Make a Copy",
},
{
title: "Delete",
icon: TrashIcon,
onClick: (e: any) => {
e.stopPropagation();
deleteAgent(agent);
},
hoverText: "Delete",
},
];
return (
<li
role="listitem"
key={"agentrow" + i}
className=" "
style={{ width: "200px" }}
>
<Card
className="h-full p-2 cursor-pointer"
title={
<div className=" ">
{truncateText(agent.config.name || "", 25)}
</div>
}
onClick={() => {
setSelectedAgent(agent);
setShowAgentModal(true);
}}
>
<div
style={{ minHeight: "65px" }}
aria-hidden="true"
className="my-2 break-words"
>
<div className="text-xs mb-2">{agent.type}</div>{" "}
{truncateText(agent.config.description || "", 70)}
</div>
<div
aria-label={`Updated ${timeAgo(agent.updated_at || "")}`}
className="text-xs"
>
{timeAgo(agent.updated_at || "")}
</div>
<CardHoverBar items={cardItems} />
</Card>
</li>
);
});
const AgentModal = ({
agent,
setAgent,
showAgentModal,
setShowAgentModal,
handler,
}: {
agent: IAgent | null;
setAgent: (agent: IAgent | null) => void;
showAgentModal: boolean;
setShowAgentModal: (show: boolean) => void;
handler?: (agent: IAgent | null) => void;
}) => {
const [localAgent, setLocalAgent] = React.useState<IAgent | null>(agent);
const closeModal = () => {
setShowAgentModal(false);
if (handler) {
handler(localAgent);
}
};
return (
<Modal
title={<>Agent Configuration</>}
width={800}
open={showAgentModal}
onOk={() => {
closeModal();
}}
onCancel={() => {
closeModal();
}}
footer={[]}
>
{agent && (
<AgentViewer
agent={localAgent || agent}
setAgent={setLocalAgent}
close={closeModal}
/>
)}
{/* {JSON.stringify(localAgent)} */}
</Modal>
);
};
const uploadAgent = () => {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = (e: any) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (e: any) => {
const contents = e.target.result;
if (contents) {
try {
const agent = JSON.parse(contents);
// TBD validate that it is a valid agent
if (!agent.config) {
throw new Error(
"Invalid agent file. An agent must have a config"
);
}
setNewAgent(agent);
setShowNewAgentModal(true);
} catch (err) {
message.error(
"Invalid agent file. Please upload a valid agent file."
);
}
}
};
reader.readAsText(file);
};
input.click();
};
const agentsMenuItems: MenuProps["items"] = [
// {
// type: "divider",
// },
{
key: "uploadagent",
label: (
<div>
<ArrowUpTrayIcon className="w-5 h-5 inline-block mr-2" />
Upload Agent
</div>
),
},
];
const agentsMenuItemOnClick: MenuProps["onClick"] = ({ key }) => {
if (key === "uploadagent") {
uploadAgent();
return;
}
};
return (
<div className="text-primary ">
<AgentModal
agent={selectedAgent}
setAgent={setSelectedAgent}
setShowAgentModal={setShowAgentModal}
showAgentModal={showAgentModal}
handler={(agent: IAgent | null) => {
fetchAgents();
}}
/>
<AgentModal
agent={newAgent || sampleAgent}
setAgent={setNewAgent}
setShowAgentModal={setShowNewAgentModal}
showAgentModal={showNewAgentModal}
handler={(agent: IAgent | null) => {
fetchAgents();
}}
/>
<div className="mb-2 relative">
<div className=" rounded ">
<div className="flex mt-2 pb-2 mb-2 border-b">
<div className="flex-1 font-semibold mb-2 ">
{" "}
Agents ({agentRows.length}){" "}
</div>
<div>
<Dropdown.Button
type="primary"
menu={{
items: agentsMenuItems,
onClick: agentsMenuItemOnClick,
}}
placement="bottomRight"
trigger={["click"]}
onClick={() => {
setShowNewAgentModal(true);
}}
>
<PlusIcon className="w-5 h-5 inline-block mr-1" />
New Agent
</Dropdown.Button>
</div>
</div>
<div className="text-xs mb-2 pb-1 ">
{" "}
Configure an agent that can reused in your agent workflow .
<div>
Tip: You can also create a Group of Agents ( New Agent -
GroupChat) which can have multiple agents in it.
</div>
</div>
{agents && agents.length > 0 && (
<div className="w-full relative">
<LoadingOverlay loading={loading} />
<ul className=" flex flex-wrap gap-3">{agentRows}</ul>
</div>
)}
{agents && agents.length === 0 && !loading && (
<div className="text-sm border mt-4 rounded text-secondary p-2">
<InformationCircleIcon className="h-4 w-4 inline mr-1" />
No agents found. Please create a new agent.
</div>
)}
{loading && (
<div className=" w-full text-center">
{" "}
<BounceLoader />{" "}
<span className="inline-block"> loading .. </span>
</div>
)}
</div>
</div>
</div>
);
};
export default AgentsView;

View File

@@ -0,0 +1,81 @@
import * as React from "react";
import SkillsView from "./skills";
import AgentsView from "./agents";
import WorkflowView from "./workflow";
import { Tabs } from "antd";
import {
BugAntIcon,
CpuChipIcon,
Square2StackIcon,
Square3Stack3DIcon,
} from "@heroicons/react/24/outline";
import ModelsView from "./models";
const BuildView = () => {
return (
<div className=" ">
{/* <div className="mb-4 text-2xl">Build </div> */}
<div className="mb-6 text-sm hidden text-secondary">
{" "}
Create skills, agents and workflows for building multiagent capabilities{" "}
</div>
<div className="mb-4 text-primary">
{" "}
<Tabs
tabBarStyle={{ paddingLeft: 0, marginLeft: 0 }}
defaultActiveKey="4"
tabPosition="left"
items={[
{
label: (
<div className="w-full ">
{" "}
<BugAntIcon className="h-4 w-4 inline-block mr-1" />
Skills
</div>
),
key: "1",
children: <SkillsView />,
},
{
label: (
<div className="w-full ">
{" "}
<CpuChipIcon className="h-4 w-4 inline-block mr-1" />
Models
</div>
),
key: "2",
children: <ModelsView />,
},
{
label: (
<>
<Square2StackIcon className="h-4 w-4 inline-block mr-1" />
Agents
</>
),
key: "3",
children: <AgentsView />,
},
{
label: (
<>
<Square3Stack3DIcon className="h-4 w-4 inline-block mr-1" />
Workflows
</>
),
key: "4",
children: <WorkflowView />,
},
]}
/>
</div>
<div></div>
</div>
);
};
export default BuildView;

View File

@@ -0,0 +1,403 @@
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
DocumentDuplicateIcon,
InformationCircleIcon,
PlusIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { Dropdown, MenuProps, Modal, message } from "antd";
import * as React from "react";
import { IModelConfig, IStatus } from "../../types";
import { appContext } from "../../../hooks/provider";
import {
fetchJSON,
getServerUrl,
sanitizeConfig,
timeAgo,
truncateText,
} from "../../utils";
import { BounceLoader, Card, CardHoverBar, LoadingOverlay } from "../../atoms";
import { ModelConfigView } from "./utils/modelconfig";
const ModelsView = ({}: any) => {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const listModelsUrl = `${serverUrl}/models?user_id=${user?.email}`;
const createModelUrl = `${serverUrl}/models`;
const testModelUrl = `${serverUrl}/models/test`;
const defaultModel: IModelConfig = {
model: "gpt-4-1106-preview",
description: "Sample OpenAI GPT-4 model",
user_id: user?.email,
};
const [models, setModels] = React.useState<IModelConfig[] | null>([]);
const [selectedModel, setSelectedModel] = React.useState<IModelConfig | null>(
null
);
const [newModel, setNewModel] = React.useState<IModelConfig | null>(
defaultModel
);
const [showNewModelModal, setShowNewModelModal] = React.useState(false);
const [showModelModal, setShowModelModal] = React.useState(false);
const deleteModel = (model: IModelConfig) => {
setError(null);
setLoading(true);
const deleteModelUrl = `${serverUrl}/models/delete?user_id=${user?.email}&model_id=${model.id}`;
const payLoad = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
fetchModels();
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(deleteModelUrl, payLoad, onSuccess, onError);
};
const fetchModels = () => {
setError(null);
setLoading(true);
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
setModels(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listModelsUrl, payLoad, onSuccess, onError);
};
const createModel = (model: IModelConfig) => {
setError(null);
setLoading(true);
model.user_id = user?.email;
const payLoad = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(model),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
const updatedModels = [data.data].concat(models || []);
setModels(updatedModels);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(createModelUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
// console.log("fetching messages", messages);
fetchModels();
}
}, []);
const modelRows = (models || []).map((model: IModelConfig, i: number) => {
const cardItems = [
{
title: "Download",
icon: ArrowDownTrayIcon,
onClick: (e: any) => {
e.stopPropagation();
// download workflow as workflow.name.json
const element = document.createElement("a");
const sanitizedSkill = sanitizeConfig(model);
const file = new Blob([JSON.stringify(sanitizedSkill)], {
type: "application/json",
});
element.href = URL.createObjectURL(file);
element.download = `model_${model.model}.json`;
document.body.appendChild(element); // Required for this to work in FireFox
element.click();
},
hoverText: "Download",
},
{
title: "Make a Copy",
icon: DocumentDuplicateIcon,
onClick: (e: any) => {
e.stopPropagation();
let newModel = { ...sanitizeConfig(model) };
newModel.model = `${model.model}_copy`;
setNewModel(newModel);
setShowNewModelModal(true);
},
hoverText: "Make a Copy",
},
{
title: "Delete",
icon: TrashIcon,
onClick: (e: any) => {
e.stopPropagation();
deleteModel(model);
},
hoverText: "Delete",
},
];
return (
<li
role="listitem"
key={"modelrow" + i}
className=" "
style={{ width: "200px" }}
>
<Card
className="h-full p-2 cursor-pointer"
title={
<div className=" ">{truncateText(model.model || "", 20)}</div>
}
onClick={() => {
setSelectedModel(model);
setShowModelModal(true);
}}
>
<div style={{ minHeight: "65px" }} className="my-2 break-words">
{" "}
{truncateText(model.description || model.model || "", 70)}
</div>
<div
aria-label={`Updated ${timeAgo(model.updated_at || "")} `}
className="text-xs"
>
{timeAgo(model.updated_at || "")}
</div>
<CardHoverBar items={cardItems} />
</Card>
</li>
);
});
const ModelModal = ({
model,
setModel,
showModelModal,
setShowModelModal,
handler,
}: {
model: IModelConfig;
setModel: (model: IModelConfig | null) => void;
showModelModal: boolean;
setShowModelModal: (show: boolean) => void;
handler?: (agent: IModelConfig) => void;
}) => {
const [localModel, setLocalModel] = React.useState<IModelConfig>(model);
const closeModal = () => {
setModel(null);
setShowModelModal(false);
if (handler) {
handler(model);
}
};
return (
<Modal
title={
<>
Model Specification{" "}
<span className="text-accent font-normal">{model?.model}</span>{" "}
</>
}
width={800}
open={showModelModal}
footer={[]}
onOk={() => {
closeModal();
}}
onCancel={() => {
closeModal();
}}
>
{model && (
<ModelConfigView
model={localModel}
setModel={setLocalModel}
close={closeModal}
/>
)}
</Modal>
);
};
const uploadModel = () => {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = (e: any) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (e: any) => {
const contents = e.target.result;
if (contents) {
try {
const model = JSON.parse(contents);
if (model) {
setNewModel(model);
setShowNewModelModal(true);
}
} catch (e) {
message.error("Invalid model file");
}
}
};
reader.readAsText(file);
};
input.click();
};
const modelsMenuItems: MenuProps["items"] = [
// {
// type: "divider",
// },
{
key: "uploadmodel",
label: (
<div>
<ArrowUpTrayIcon className="w-5 h-5 inline-block mr-2" />
Upload Model
</div>
),
},
];
const modelsMenuItemOnClick: MenuProps["onClick"] = ({ key }) => {
if (key === "uploadmodel") {
uploadModel();
return;
}
};
return (
<div className="text-primary ">
{selectedModel && (
<ModelModal
model={selectedModel}
setModel={setSelectedModel}
setShowModelModal={setShowModelModal}
showModelModal={showModelModal}
handler={(model: IModelConfig | null) => {
fetchModels();
}}
/>
)}
<ModelModal
model={newModel || defaultModel}
setModel={setNewModel}
setShowModelModal={setShowNewModelModal}
showModelModal={showNewModelModal}
handler={(model: IModelConfig | null) => {
fetchModels();
}}
/>
<div className="mb-2 relative">
<div className=" rounded ">
<div className="flex mt-2 pb-2 mb-2 border-b">
<div className="flex-1 font-semibold mb-2 ">
{" "}
Models ({modelRows.length}){" "}
</div>
<div>
<Dropdown.Button
type="primary"
menu={{
items: modelsMenuItems,
onClick: modelsMenuItemOnClick,
}}
placement="bottomRight"
trigger={["click"]}
onClick={() => {
setShowNewModelModal(true);
}}
>
<PlusIcon className="w-5 h-5 inline-block mr-1" />
New Model
</Dropdown.Button>
</div>
</div>
<div className="text-xs mb-2 pb-1 ">
{" "}
Create model configurations that can be reused in your agents and
workflows. {selectedModel?.model}
</div>
{models && models.length > 0 && (
<div className="w-full relative">
<LoadingOverlay loading={loading} />
<ul className=" flex flex-wrap gap-3">{modelRows}</ul>
</div>
)}
{models && models.length === 0 && !loading && (
<div className="text-sm border mt-4 rounded text-secondary p-2">
<InformationCircleIcon className="h-4 w-4 inline mr-1" />
No models found. Please create a new model which can be reused
with agents.
</div>
)}
{loading && (
<div className=" w-full text-center">
{" "}
<BounceLoader />{" "}
<span className="inline-block"> loading .. </span>
</div>
)}
</div>
</div>
</div>
);
};
export default ModelsView;

View File

@@ -0,0 +1,380 @@
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
CodeBracketIcon,
CodeBracketSquareIcon,
DocumentDuplicateIcon,
InformationCircleIcon,
KeyIcon,
PlusIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { Button, Input, Modal, message, MenuProps, Dropdown, Tabs } from "antd";
import * as React from "react";
import { ISkill, IStatus } from "../../types";
import { appContext } from "../../../hooks/provider";
import {
fetchJSON,
getSampleSkill,
getServerUrl,
sanitizeConfig,
timeAgo,
truncateText,
} from "../../utils";
import {
BounceLoader,
Card,
CardHoverBar,
LoadingOverlay,
MonacoEditor,
} from "../../atoms";
import { SkillSelector } from "./utils/selectors";
import { SkillConfigView } from "./utils/skillconfig";
const SkillsView = ({}: any) => {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const listSkillsUrl = `${serverUrl}/skills?user_id=${user?.email}`;
const saveSkillsUrl = `${serverUrl}/skills`;
const [skills, setSkills] = React.useState<ISkill[] | null>([]);
const [selectedSkill, setSelectedSkill] = React.useState<any>(null);
const [showSkillModal, setShowSkillModal] = React.useState(false);
const [showNewSkillModal, setShowNewSkillModal] = React.useState(false);
const sampleSkill = getSampleSkill();
const [newSkill, setNewSkill] = React.useState<ISkill | null>(sampleSkill);
const deleteSkill = (skill: ISkill) => {
setError(null);
setLoading(true);
// const fetch;
const deleteSkillUrl = `${serverUrl}/skills/delete?user_id=${user?.email}&skill_id=${skill.id}`;
const payLoad = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
skill: skill,
}),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
fetchSkills();
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(deleteSkillUrl, payLoad, onSuccess, onError);
};
const fetchSkills = () => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
// message.success(data.message);
console.log("skills", data.data);
setSkills(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listSkillsUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
// console.log("fetching messages", messages);
fetchSkills();
}
}, []);
const skillRows = (skills || []).map((skill: ISkill, i: number) => {
const cardItems = [
{
title: "Download",
icon: ArrowDownTrayIcon,
onClick: (e: any) => {
e.stopPropagation();
// download workflow as workflow.name.json
const element = document.createElement("a");
const sanitizedSkill = sanitizeConfig(skill);
const file = new Blob([JSON.stringify(sanitizedSkill)], {
type: "application/json",
});
element.href = URL.createObjectURL(file);
element.download = `skill_${skill.name}.json`;
document.body.appendChild(element); // Required for this to work in FireFox
element.click();
},
hoverText: "Download",
},
{
title: "Make a Copy",
icon: DocumentDuplicateIcon,
onClick: (e: any) => {
e.stopPropagation();
let newSkill = { ...sanitizeConfig(skill) };
newSkill.name = `${skill.name}_copy`;
setNewSkill(newSkill);
setShowNewSkillModal(true);
},
hoverText: "Make a Copy",
},
{
title: "Delete",
icon: TrashIcon,
onClick: (e: any) => {
e.stopPropagation();
deleteSkill(skill);
},
hoverText: "Delete",
},
];
return (
<li key={"skillrow" + i} className=" " style={{ width: "200px" }}>
<div>
{" "}
<Card
className="h-full p-2 cursor-pointer group"
title={truncateText(skill.name, 25)}
onClick={() => {
setSelectedSkill(skill);
setShowSkillModal(true);
}}
>
<div
style={{ minHeight: "65px" }}
className="my-2 break-words"
aria-hidden="true"
>
{" "}
{skill.description
? truncateText(skill.description || "", 70)
: truncateText(skill.content || "", 70)}
</div>
<div
aria-label={`Updated ${timeAgo(skill.updated_at || "")}`}
className="text-xs"
>
{timeAgo(skill.updated_at || "")}
</div>
<CardHoverBar items={cardItems} />
</Card>
<div className="text-right mt-2"></div>
</div>
</li>
);
});
const SkillModal = ({
skill,
setSkill,
showSkillModal,
setShowSkillModal,
handler,
}: {
skill: ISkill | null;
setSkill: any;
showSkillModal: boolean;
setShowSkillModal: any;
handler: any;
}) => {
const editorRef = React.useRef<any | null>(null);
const [localSkill, setLocalSkill] = React.useState<ISkill | null>(skill);
const closeModal = () => {
setSkill(null);
setShowSkillModal(false);
if (handler) {
handler(skill);
}
};
return (
<Modal
title={
<>
Skill Specification{" "}
<span className="text-accent font-normal">{localSkill?.name}</span>{" "}
</>
}
width={800}
open={showSkillModal}
onCancel={() => {
setShowSkillModal(false);
}}
footer={[]}
>
{localSkill && (
<SkillConfigView
skill={localSkill}
setSkill={setLocalSkill}
close={closeModal}
/>
)}
</Modal>
);
};
const uploadSkill = () => {
const fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.accept = ".json";
fileInput.onchange = (e: any) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (e) => {
const content = e.target?.result;
if (content) {
try {
const skill = JSON.parse(content as string);
if (skill) {
setNewSkill(skill);
setShowNewSkillModal(true);
}
} catch (e) {
message.error("Invalid skill file");
}
}
};
reader.readAsText(file);
};
fileInput.click();
};
const skillsMenuItems: MenuProps["items"] = [
// {
// type: "divider",
// },
{
key: "uploadskill",
label: (
<div>
<ArrowUpTrayIcon className="w-5 h-5 inline-block mr-2" />
Upload Skill
</div>
),
},
];
const skillsMenuItemOnClick: MenuProps["onClick"] = ({ key }) => {
if (key === "uploadskill") {
uploadSkill();
return;
}
};
return (
<div className=" text-primary ">
<SkillModal
skill={selectedSkill}
setSkill={setSelectedSkill}
showSkillModal={showSkillModal}
setShowSkillModal={setShowSkillModal}
handler={(skill: ISkill) => {
fetchSkills();
}}
/>
<SkillModal
skill={newSkill || sampleSkill}
setSkill={setNewSkill}
showSkillModal={showNewSkillModal}
setShowSkillModal={setShowNewSkillModal}
handler={(skill: ISkill) => {
fetchSkills();
}}
/>
<div className="mb-2 relative">
<div className="">
<div className="flex mt-2 pb-2 mb-2 border-b">
<ul className="flex-1 font-semibold mb-2 ">
{" "}
Skills ({skillRows.length}){" "}
</ul>
<div>
<Dropdown.Button
type="primary"
menu={{
items: skillsMenuItems,
onClick: skillsMenuItemOnClick,
}}
placement="bottomRight"
trigger={["click"]}
onClick={() => {
setShowNewSkillModal(true);
}}
>
<PlusIcon className="w-5 h-5 inline-block mr-1" />
New Skill
</Dropdown.Button>
</div>
</div>
<div className="text-xs mb-2 pb-1 ">
{" "}
Skills are python functions that agents can use to solve tasks.{" "}
</div>
{skills && skills.length > 0 && (
<div
// style={{ height: "400px" }}
className="w-full relative"
>
<LoadingOverlay loading={loading} />
<div className=" flex flex-wrap gap-3">{skillRows}</div>
</div>
)}
{skills && skills.length === 0 && !loading && (
<div className="text-sm border mt-4 rounded text-secondary p-2">
<InformationCircleIcon className="h-4 w-4 inline mr-1" />
No skills found. Please create a new skill.
</div>
)}
{loading && (
<div className=" w-full text-center">
{" "}
<BounceLoader />{" "}
<span className="inline-block"> loading .. </span>
</div>
)}
</div>
</div>
</div>
);
};
export default SkillsView;

View File

@@ -0,0 +1,517 @@
import React from "react";
import { CollapseBox, ControlRowView } from "../../../atoms";
import { checkAndSanitizeInput, fetchJSON, getServerUrl } from "../../../utils";
import {
Button,
Form,
Input,
Select,
Slider,
Tabs,
message,
theme,
} from "antd";
import {
BugAntIcon,
CpuChipIcon,
UserGroupIcon,
} from "@heroicons/react/24/outline";
import { appContext } from "../../../../hooks/provider";
import {
AgentSelector,
AgentTypeSelector,
ModelSelector,
SkillSelector,
} from "./selectors";
import { IAgent, ILLMConfig } from "../../../types";
import TextArea from "antd/es/input/TextArea";
const { useToken } = theme;
export const AgentConfigView = ({
agent,
setAgent,
close,
}: {
agent: IAgent;
setAgent: (agent: IAgent) => void;
close: () => void;
}) => {
const nameValidation = checkAndSanitizeInput(agent?.config?.name);
const [error, setError] = React.useState<any>(null);
const [loading, setLoading] = React.useState<boolean>(false);
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const createAgentUrl = `${serverUrl}/agents`;
const [controlChanged, setControlChanged] = React.useState<boolean>(false);
const onControlChange = (value: any, key: string) => {
// if (key === "llm_config") {
// if (value.config_list.length === 0) {
// value = false;
// }
// }
const updatedAgent = {
...agent,
config: { ...agent.config, [key]: value },
};
setAgent(updatedAgent);
setControlChanged(true);
};
const llm_config: ILLMConfig = agent?.config?.llm_config || {
config_list: [],
temperature: 0.1,
max_tokens: 4000,
};
const createAgent = (agent: IAgent) => {
setError(null);
setLoading(true);
// const fetch;
console.log("agent", agent);
agent.user_id = user?.email;
const payLoad = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(agent),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
console.log("agents", data.data);
const newAgent = data.data;
setAgent(newAgent);
} else {
message.error(data.message);
}
setLoading(false);
// setNewAgent(sampleAgent);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
const onFinal = () => {
setLoading(false);
setControlChanged(false);
};
fetchJSON(createAgentUrl, payLoad, onSuccess, onError, onFinal);
};
const hasChanged =
(!controlChanged || !nameValidation.status) && agent?.id !== undefined;
return (
<div className="text-primary">
<Form>
<div
className={`grid gap-3 ${
agent.type === "groupchat" ? "grid-cols-2" : "grid-cols-1"
}`}
>
<div className="">
<ControlRowView
title="Agent Name"
className=""
description="Name of the agent"
value={agent?.config?.name}
control={
<>
<Input
className="mt-2"
placeholder="Agent Name"
value={agent?.config?.name}
onChange={(e) => {
onControlChange(e.target.value, "name");
}}
/>
{!nameValidation.status && (
<div className="text-xs text-red-500 mt-2">
{nameValidation.message}
</div>
)}
</>
}
/>
<ControlRowView
title="Agent Description"
className="mt-4"
description="Description of the agent, used by other agents
(e.g. the GroupChatManager) to decide when to call upon this agent. (Default: system_message)"
value={agent.config.description || ""}
control={
<Input
className="mt-2"
placeholder="Agent Description"
value={agent.config.description || ""}
onChange={(e) => {
onControlChange(e.target.value, "description");
}}
/>
}
/>
<ControlRowView
title="Max Consecutive Auto Reply"
className="mt-4"
description="Max consecutive auto reply messages before termination."
value={agent.config?.max_consecutive_auto_reply}
control={
<Slider
min={1}
max={agent.type === "groupchat" ? 600 : 30}
defaultValue={agent.config.max_consecutive_auto_reply}
step={1}
onChange={(value: any) => {
onControlChange(value, "max_consecutive_auto_reply");
}}
/>
}
/>
<ControlRowView
title="Human Input Mode"
description="Defines when to request human input"
value={agent.config.human_input_mode}
control={
<Select
className="mt-2 w-full"
defaultValue={agent.config.human_input_mode}
onChange={(value: any) => {
onControlChange(value, "human_input_mode");
}}
options={
[
{ label: "NEVER", value: "NEVER" },
{ label: "TERMINATE", value: "TERMINATE" },
{ label: "ALWAYS", value: "ALWAYS" },
] as any
}
/>
}
/>
<ControlRowView
title="System Message"
className="mt-4"
description="Free text to control agent behavior"
value={agent.config.system_message}
control={
<TextArea
className="mt-2 w-full"
value={agent.config.system_message}
rows={3}
onChange={(e) => {
onControlChange(e.target.value, "system_message");
}}
/>
}
/>
<div className="mt-4">
{" "}
<CollapseBox
className="bg-secondary mt-4"
open={false}
title="Advanced Options"
>
<ControlRowView
title="Temperature"
className="mt-4"
description="Defines the randomness of the agent's response."
value={llm_config.temperature}
control={
<Slider
min={0}
max={2}
step={0.1}
defaultValue={llm_config.temperature || 0.1}
onChange={(value: any) => {
const llm_config = {
...agent.config.llm_config,
temperature: value,
};
onControlChange(llm_config, "llm_config");
}}
/>
}
/>
<ControlRowView
title="Agent Default Auto Reply"
className="mt-4"
description="Default auto reply when no code execution or llm-based reply is generated."
value={agent.config.default_auto_reply || ""}
control={
<Input
className="mt-2"
placeholder="Agent Description"
value={agent.config.default_auto_reply || ""}
onChange={(e) => {
onControlChange(e.target.value, "default_auto_reply");
}}
/>
}
/>
<ControlRowView
title="Max Tokens"
description="Max tokens generated by LLM used in the agent's response."
value={llm_config.max_tokens}
className="mt-4"
control={
<Slider
min={100}
max={50000}
defaultValue={llm_config.max_tokens || 1000}
onChange={(value: any) => {
const llm_config = {
...agent.config.llm_config,
max_tokens: value,
};
onControlChange(llm_config, "llm_config");
}}
/>
}
/>
<ControlRowView
title="Code Execution Config"
className="mt-4"
description="Determines if and where code execution is done."
value={agent.config.code_execution_config || "none"}
control={
<Select
className="mt-2 w-full"
defaultValue={
agent.config.code_execution_config || "none"
}
onChange={(value: any) => {
onControlChange(value, "code_execution_config");
}}
options={
[
{ label: "None", value: "none" },
{ label: "Local", value: "local" },
{ label: "Docker", value: "docker" },
] as any
}
/>
}
/>
</CollapseBox>
</div>
</div>
{/* ====================== Group Chat Config ======================= */}
{agent.type === "groupchat" && (
<div>
<ControlRowView
title="Speaker Selection Method"
description="How the next speaker is selected"
className=""
value={agent?.config?.speaker_selection_method || "auto"}
control={
<Select
className="mt-2 w-full"
defaultValue={
agent?.config?.speaker_selection_method || "auto"
}
onChange={(value: any) => {
if (agent?.config) {
onControlChange(value, "speaker_selection_method");
}
}}
options={
[
{ label: "Auto", value: "auto" },
{ label: "Round Robin", value: "round_robin" },
{ label: "Random", value: "random" },
] as any
}
/>
}
/>
<ControlRowView
title="Admin Name"
className="mt-4"
description="Name of the admin of the group chat"
value={agent.config.admin_name || ""}
control={
<Input
className="mt-2"
placeholder="Agent Description"
value={agent.config.admin_name || ""}
onChange={(e) => {
onControlChange(e.target.value, "admin_name");
}}
/>
}
/>
<ControlRowView
title="Max Rounds"
className="mt-4"
description="Max rounds before termination."
value={agent.config?.max_round || 10}
control={
<Slider
min={10}
max={600}
defaultValue={agent.config.max_round}
step={1}
onChange={(value: any) => {
onControlChange(value, "max_round");
}}
/>
}
/>
<ControlRowView
title="Allow Repeat Speaker"
className="mt-4"
description="Allow the same speaker to speak multiple times in a row"
value={agent.config?.allow_repeat_speaker || false}
control={
<Select
className="mt-2 w-full"
defaultValue={agent.config.allow_repeat_speaker}
onChange={(value: any) => {
onControlChange(value, "allow_repeat_speaker");
}}
options={
[
{ label: "True", value: true },
{ label: "False", value: false },
] as any
}
/>
}
/>
</div>
)}
</div>
</Form>
<div className="w-full mt-4 text-right">
{" "}
{!hasChanged && (
<Button
type="primary"
onClick={() => {
createAgent(agent);
setAgent(agent);
}}
loading={loading}
>
{agent.id ? "Update Agent" : "Create Agent"}
</Button>
)}
<Button
className="ml-2"
key="close"
type="default"
onClick={() => {
close();
}}
>
Close
</Button>
</div>
</div>
);
};
export const AgentViewer = ({
agent,
setAgent,
close,
}: {
agent: IAgent | null;
setAgent: (newAgent: IAgent) => void;
close: () => void;
}) => {
let items = [
{
label: (
<div className="w-full ">
{" "}
<BugAntIcon className="h-4 w-4 inline-block mr-1" />
Agent Configuration
</div>
),
key: "1",
children: (
<div>
{!agent?.type && (
<AgentTypeSelector agent={agent} setAgent={setAgent} />
)}
{agent?.type && agent && (
<AgentConfigView agent={agent} setAgent={setAgent} close={close} />
)}
</div>
),
},
];
if (agent) {
if (agent?.id) {
if (agent.type && agent.type === "groupchat") {
items.push({
label: (
<div className="w-full ">
{" "}
<UserGroupIcon className="h-4 w-4 inline-block mr-1" />
Agents
</div>
),
key: "2",
children: <AgentSelector agentId={agent?.id} />,
});
}
items.push({
label: (
<div className="w-full ">
{" "}
<CpuChipIcon className="h-4 w-4 inline-block mr-1" />
Models
</div>
),
key: "3",
children: <ModelSelector agentId={agent?.id} />,
});
items.push({
label: (
<>
<BugAntIcon className="h-4 w-4 inline-block mr-1" />
Skills
</>
),
key: "4",
children: <SkillSelector agentId={agent?.id} />,
});
}
}
return (
<div className="text-primary">
{/* <RenderView viewIndex={currentViewIndex} /> */}
<Tabs
tabBarStyle={{ paddingLeft: 0, marginLeft: 0 }}
defaultActiveKey="1"
items={items}
/>
</div>
);
};

View File

@@ -0,0 +1,207 @@
import { Button, Modal, message } from "antd";
import * as React from "react";
import { IWorkflow } from "../../../types";
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
import {
checkAndSanitizeInput,
fetchJSON,
getServerUrl,
sanitizeConfig,
} from "../../../utils";
import { appContext } from "../../../../hooks/provider";
import { CodeBlock } from "../../../atoms";
export const ExportWorkflowModal = ({
workflow,
show,
setShow,
}: {
workflow: IWorkflow | null;
show: boolean;
setShow: (show: boolean) => void;
}) => {
const serverUrl = getServerUrl();
const { user } = React.useContext(appContext);
const [error, setError] = React.useState<any>(null);
const [loading, setLoading] = React.useState<boolean>(false);
const [workflowDetails, setWorkflowDetails] = React.useState<any>(null);
const getWorkflowCode = (workflow: IWorkflow) => {
const workflowCode = `from autogenstudio import WorkflowManager
# load workflow from exported json workflow file.
workflow_manager = WorkflowManager(workflow="path/to/your/workflow_.json")
# run the workflow on a task
task_query = "What is the height of the Eiffel Tower?. Dont write code, just respond to the question."
workflow_manager.run(message=task_query)`;
return workflowCode;
};
const getCliWorkflowCode = (workflow: IWorkflow) => {
const workflowCode = `autogenstudio serve --workflow=workflow.json --port=5000
`;
return workflowCode;
};
const getGunicornWorkflowCode = (workflow: IWorkflow) => {
const workflowCode = `gunicorn -w $((2 * $(getconf _NPROCESSORS_ONLN) + 1)) --timeout 12600 -k uvicorn.workers.UvicornWorker autogenstudio.web.app:app --bind `;
return workflowCode;
};
const fetchWorkFlow = (workflow: IWorkflow) => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const downloadWorkflowUrl = `${serverUrl}/workflows/export/${workflow.id}?user_id=${user?.email}`;
const onSuccess = (data: any) => {
if (data && data.status) {
setWorkflowDetails(data.data);
console.log("workflow details", data.data);
const sanitized_name =
checkAndSanitizeInput(workflow.name).sanitizedText || workflow.name;
const file_name = `workflow_${sanitized_name}.json`;
const workflowData = sanitizeConfig(data.data);
const file = new Blob([JSON.stringify(workflowData)], {
type: "application/json",
});
const downloadUrl = URL.createObjectURL(file);
const a = document.createElement("a");
a.href = downloadUrl;
a.download = file_name;
a.click();
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(downloadWorkflowUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (workflow && workflow.id && show) {
// fetchWorkFlow(workflow.id);
console.log("workflow modal ... component loaded", workflow);
}
}, [show]);
return (
<Modal
title={
<>
Export Workflow
<span className="text-accent font-normal ml-2">
{workflow?.name}
</span>{" "}
</>
}
width={800}
open={show}
onOk={() => {
setShow(false);
}}
onCancel={() => {
setShow(false);
}}
footer={[]}
>
<div>
<div>
{" "}
You can use the following steps to start integrating your workflow
into your application.{" "}
</div>
{workflow && workflow.id && (
<>
<div className="flex mt-2 gap-3">
<div>
<div className="text-sm mt-2 mb-2 pb-1 font-bold">Step 1</div>
<div className="mt-2 mb-2 pb-1 text-xs">
Download your workflow as a JSON file by clicking the button
below.
</div>
<div className="text-sm mt-2 mb-2 pb-1">
<Button
type="primary"
loading={loading}
onClick={() => {
fetchWorkFlow(workflow);
}}
>
Download
<ArrowDownTrayIcon className="h-4 w-4 inline-block ml-2 -mt-1" />
</Button>
</div>
</div>
<div>
<div className="text-sm mt-2 mb-2 pb-1 font-bold">Step 2</div>
<div className=" mt-2 mb-2 pb-1 text-xs">
Copy the following code snippet and paste it into your
application to run your workflow on a task.
</div>
<div className="text-sm mt-2 mb-2 pb-1">
<CodeBlock
className="text-xs"
code={getWorkflowCode(workflow)}
language="python"
wrapLines={true}
/>
</div>
</div>
</div>
<div>
<div className="text-sm mt-2 mb-2 pb-1 font-bold">
Step 3 (Deploy)
</div>
<div className=" mt-2 mb-2 pb-1 text-xs">
You can also deploy your workflow as an API endpoint using the
autogenstudio python CLI.
</div>
<div className="text-sm mt-2 mb-2 pb-1">
<CodeBlock
className="text-xs"
code={getCliWorkflowCode(workflow)}
language="bash"
wrapLines={true}
/>
<div className="text-xs mt-2">
Note: this will start a endpoint on port 5000. You can change
the port by changing the port number. You can also scale this
using multiple workers (e.g., via an application server like
gunicorn) or wrap it in a docker container and deploy on a
cloud provider like Azure.
</div>
<CodeBlock
className="text-xs"
code={getGunicornWorkflowCode(workflow)}
language="bash"
wrapLines={true}
/>
</div>
</div>
</>
)}
</div>
</Modal>
);
};

View File

@@ -0,0 +1,388 @@
import React from "react";
import { fetchJSON, getServerUrl, sampleModelConfig } from "../../../utils";
import { Button, Input, message, theme } from "antd";
import {
CpuChipIcon,
InformationCircleIcon,
} from "@heroicons/react/24/outline";
import { IModelConfig, IStatus } from "../../../types";
import { Card, ControlRowView } from "../../../atoms";
import TextArea from "antd/es/input/TextArea";
import { appContext } from "../../../../hooks/provider";
const ModelTypeSelector = ({
model,
setModel,
}: {
model: IModelConfig;
setModel: (newModel: IModelConfig) => void;
}) => {
const modelTypes = [
{
label: "OpenAI",
value: "open_ai",
description: "OpenAI or other endpoints that implement the OpenAI API",
icon: <CpuChipIcon className="h-6 w-6 text-primary" />,
hint: "In addition to OpenAI models, You can also use OSS models via tools like Ollama, vLLM, LMStudio etc. that provide OpenAI compatible endpoint.",
},
{
label: "Azure OpenAI",
value: "azure",
description: "Azure OpenAI endpoint",
icon: <CpuChipIcon className="h-6 w-6 text-primary" />,
hint: "Azure OpenAI endpoint",
},
{
label: "Gemini",
value: "google",
description: "Gemini",
icon: <CpuChipIcon className="h-6 w-6 text-primary" />,
hint: "Gemini",
},
{
label: "Claude",
value: "anthropic",
description: "Anthropic Claude",
icon: <CpuChipIcon className="h-6 w-6 text-primary" />,
hint: "Anthropic Claude models",
},
{
label: "Mistral",
value: "mistral",
description: "Mistral",
icon: <CpuChipIcon className="h-6 w-6 text-primary" />,
hint: "Mistral models",
},
];
const [selectedType, setSelectedType] = React.useState<string | undefined>(
model?.api_type
);
const modelTypeRows = modelTypes.map((modelType: any, i: number) => {
return (
<li
onMouseEnter={() => {
setSelectedHint(modelType.hint);
}}
role="listitem"
key={"modeltype" + i}
className="w-36"
>
<Card
active={selectedType === modelType.value}
className="h-full p-2 cursor-pointer"
title={<div className=" ">{modelType.label}</div>}
onClick={() => {
setSelectedType(modelType.value);
if (model) {
const sampleModel = sampleModelConfig(modelType.value);
setModel(sampleModel);
// setAgent(sampleAgent);
}
}}
>
<div style={{ minHeight: "35px" }} className="my-2 break-words ">
{" "}
<div className="mb-2">{modelType.icon}</div>
<span className="text-secondary tex-sm">
{" "}
{modelType.description}
</span>
</div>
</Card>
</li>
);
});
const [selectedHint, setSelectedHint] = React.useState<string>("open_ai");
return (
<>
<div className="pb-3">Select Model Type</div>
<ul className="inline-flex gap-2">{modelTypeRows}</ul>
<div className="text-xs mt-4">
<InformationCircleIcon className="h-4 w-4 inline mr-1 -mt-1" />
{selectedHint}
</div>
</>
);
};
const ModelConfigMainView = ({
model,
setModel,
close,
}: {
model: IModelConfig;
setModel: (newModel: IModelConfig) => void;
close: () => void;
}) => {
const [loading, setLoading] = React.useState(false);
const [modelStatus, setModelStatus] = React.useState<IStatus | null>(null);
const serverUrl = getServerUrl();
const { user } = React.useContext(appContext);
const testModelUrl = `${serverUrl}/models/test`;
const createModelUrl = `${serverUrl}/models`;
// const [model, setmodel] = React.useState<IModelConfig | null>(
// model
// );
const testModel = (model: IModelConfig) => {
setModelStatus(null);
setLoading(true);
model.user_id = user?.email;
const payLoad = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(model),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
setModelStatus(data.data);
} else {
message.error(data.message);
}
setLoading(false);
setModelStatus(data);
};
const onError = (err: any) => {
message.error(err.message);
setLoading(false);
};
fetchJSON(testModelUrl, payLoad, onSuccess, onError);
};
const createModel = (model: IModelConfig) => {
setLoading(true);
model.user_id = user?.email;
const payLoad = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(model),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
setModel(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
message.error(err.message);
setLoading(false);
};
const onFinal = () => {
setLoading(false);
setControlChanged(false);
};
fetchJSON(createModelUrl, payLoad, onSuccess, onError, onFinal);
};
const [controlChanged, setControlChanged] = React.useState<boolean>(false);
const updateModelConfig = (key: string, value: string) => {
if (model) {
const updatedModelConfig = { ...model, [key]: value };
// setmodel(updatedModelConfig);
setModel(updatedModelConfig);
}
setControlChanged(true);
};
const hasChanged = !controlChanged && model.id !== undefined;
return (
<div className="relative ">
<div className="text-sm my-2">
Enter parameters for your{" "}
<span className="mx-1 text-accent">{model.api_type}</span> model.
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<ControlRowView
title="Model"
className=""
description="Model name"
value={model?.model || ""}
control={
<Input
className="mt-2 w-full"
value={model?.model}
onChange={(e) => {
updateModelConfig("model", e.target.value);
}}
/>
}
/>
<ControlRowView
title="Base URL"
className=""
description="Base URL for Model Endpoint"
value={model?.base_url || ""}
control={
<Input
className="mt-2 w-full"
value={model?.base_url}
onChange={(e) => {
updateModelConfig("base_url", e.target.value);
}}
/>
}
/>
</div>
<div>
<ControlRowView
title="API Key"
className=""
description="API Key"
value={model?.api_key || ""}
truncateLength={5}
control={
<Input.Password
className="mt-2 w-full"
value={model?.api_key}
onChange={(e) => {
updateModelConfig("api_key", e.target.value);
}}
/>
}
/>
{model?.api_type == "azure" && (
<ControlRowView
title="API Version"
className=" "
description="API Version, required by Azure Models"
value={model?.api_version || ""}
control={
<Input
className="mt-2 w-full"
value={model?.api_version}
onChange={(e) => {
updateModelConfig("api_version", e.target.value);
}}
/>
}
/>
)}
</div>
</div>
<ControlRowView
title="Description"
className="mt-4"
description="Description of the model"
value={model?.description || ""}
control={
<TextArea
className="mt-2 w-full"
value={model?.description}
onChange={(e) => {
updateModelConfig("description", e.target.value);
}}
/>
}
/>
{model?.api_type === "azure" && (
<div className="mt-4 text-xs">
Note: For Azure OAI models, you will need to specify all fields.
</div>
)}
{modelStatus && (
<div
className={`text-sm border mt-4 rounded text-secondary p-2 ${
modelStatus.status ? "border-accent" : " border-red-500 "
}`}
>
<InformationCircleIcon className="h-4 w-4 inline mr-1" />
{modelStatus.message}
{/* <span className="block"> Note </span> */}
</div>
)}
<div className="w-full mt-4 text-right">
<Button
key="test"
type="primary"
loading={loading}
onClick={() => {
if (model) {
testModel(model);
}
}}
>
Test Model
</Button>
{!hasChanged && (
<Button
className="ml-2"
key="save"
type="primary"
onClick={() => {
if (model) {
createModel(model);
setModel(model);
}
}}
>
{model?.id ? "Update Model" : "Save Model"}
</Button>
)}
<Button
className="ml-2"
key="close"
type="default"
onClick={() => {
close();
}}
>
Close
</Button>
</div>
</div>
);
};
export const ModelConfigView = ({
model,
setModel,
close,
}: {
model: IModelConfig;
setModel: (newModel: IModelConfig) => void;
close: () => void;
}) => {
return (
<div className="text-primary">
<div>
{!model?.api_type && (
<ModelTypeSelector model={model} setModel={setModel} />
)}
{model?.api_type && model && (
<ModelConfigMainView
model={model}
setModel={setModel}
close={close}
/>
)}
</div>
</div>
);
};

View File

@@ -0,0 +1,295 @@
import React from "react";
import { fetchJSON, getServerUrl, sampleModelConfig } from "../../../utils";
import { Button, Input, message, theme } from "antd";
import {
CpuChipIcon,
EyeIcon,
EyeSlashIcon,
InformationCircleIcon,
PlusIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { ISkill, IStatus } from "../../../types";
import { Card, ControlRowView, MonacoEditor } from "../../../atoms";
import TextArea from "antd/es/input/TextArea";
import { appContext } from "../../../../hooks/provider";
const SecretsEditor = ({
secrets = [],
updateSkillConfig,
}: {
secrets: { secret: string; value: string }[];
updateSkillConfig: (key: string, value: any) => void;
}) => {
const [editingIndex, setEditingIndex] = React.useState<number | null>(null);
const [newSecret, setNewSecret] = React.useState<string>("");
const [newValue, setNewValue] = React.useState<string>("");
const toggleEditing = (index: number) => {
setEditingIndex(editingIndex === index ? null : index);
};
const handleAddSecret = () => {
if (newSecret && newValue) {
const updatedSecrets = [
...secrets,
{ secret: newSecret, value: newValue },
];
updateSkillConfig("secrets", updatedSecrets);
setNewSecret("");
setNewValue("");
}
};
const handleRemoveSecret = (index: number) => {
const updatedSecrets = secrets.filter((_, i) => i !== index);
updateSkillConfig("secrets", updatedSecrets);
};
const handleSecretChange = (index: number, key: string, value: string) => {
const updatedSecrets = secrets.map((item, i) =>
i === index ? { ...item, [key]: value } : item
);
updateSkillConfig("secrets", updatedSecrets);
};
return (
<div className="mt-4">
{secrets && (
<div className="flex flex-col gap-2">
{secrets.map((secret, index) => (
<div key={index} className="flex items-center gap-2">
<Input
value={secret.secret}
disabled={editingIndex !== index}
onChange={(e) =>
handleSecretChange(index, "secret", e.target.value)
}
className="flex-1"
/>
<Input.Password
value={secret.value}
visibilityToggle
disabled={editingIndex !== index}
onChange={(e) =>
handleSecretChange(index, "value", e.target.value)
}
className="flex-1"
/>
<Button
icon={
editingIndex === index ? (
<EyeSlashIcon className="h-5 w-5" />
) : (
<EyeIcon className="h-5 w-5" />
)
}
onClick={() => toggleEditing(index)}
/>
<Button
icon={<TrashIcon className="h-5 w-5" />}
onClick={() => handleRemoveSecret(index)}
/>
</div>
))}
</div>
)}
<div className="flex items-center gap-2 mt-2">
<Input
placeholder="New Secret"
value={newSecret}
onChange={(e) => setNewSecret(e.target.value)}
className="flex-1"
/>
<Input.Password
placeholder="New Value"
value={newValue}
onChange={(e) => setNewValue(e.target.value)}
className="flex-1"
/>
<Button
icon={<PlusIcon className="h-5 w-5" />}
onClick={handleAddSecret}
/>
</div>
</div>
);
};
export const SkillConfigView = ({
skill,
setSkill,
close,
}: {
skill: ISkill;
setSkill: (newModel: ISkill) => void;
close: () => void;
}) => {
const [loading, setLoading] = React.useState(false);
const serverUrl = getServerUrl();
const { user } = React.useContext(appContext);
const testModelUrl = `${serverUrl}/skills/test`;
const createSkillUrl = `${serverUrl}/skills`;
const createSkill = (skill: ISkill) => {
setLoading(true);
skill.user_id = user?.email;
const payLoad = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(skill),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
setSkill(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
message.error(err.message);
setLoading(false);
};
const onFinal = () => {
setLoading(false);
setControlChanged(false);
};
fetchJSON(createSkillUrl, payLoad, onSuccess, onError, onFinal);
};
const [controlChanged, setControlChanged] = React.useState<boolean>(false);
const updateSkillConfig = (key: string, value: string) => {
if (skill) {
const updatedSkill = { ...skill, [key]: value };
// setSkill(updatedModelConfig);
setSkill(updatedSkill);
}
setControlChanged(true);
};
const hasChanged = !controlChanged && skill.id !== undefined;
const editorRef = React.useRef<any | null>(null);
return (
<div className="relative ">
{skill && (
<div style={{ minHeight: "65vh" }}>
<div className="flex gap-3">
<div className="h-ful flex-1 ">
<div className="mb-2 h-full" style={{ minHeight: "65vh" }}>
<div className="h-full mt-2" style={{ height: "65vh" }}>
<MonacoEditor
value={skill?.content}
language="python"
editorRef={editorRef}
onChange={(value: string) => {
updateSkillConfig("content", value);
}}
/>
</div>
</div>
</div>
<div className="w-72 ">
<div className="">
<ControlRowView
title="Name"
className=""
description="Skill name, should match function name"
value={skill?.name || ""}
control={
<Input
className="mt-2 w-full"
value={skill?.name}
onChange={(e) => {
updateSkillConfig("name", e.target.value);
}}
/>
}
/>
<ControlRowView
title="Description"
className="mt-4"
description="Description of the skill"
value={skill?.description || ""}
control={
<TextArea
className="mt-2 w-full"
value={skill?.description}
onChange={(e) => {
updateSkillConfig("description", e.target.value);
}}
/>
}
/>
<ControlRowView
title="Secrets"
className="mt-4"
description="Environment variables"
value=""
control={
<SecretsEditor
secrets={skill?.secrets || []}
updateSkillConfig={updateSkillConfig}
/>
}
/>
</div>
</div>
</div>
</div>
)}
<div className="w-full mt-4 text-right">
{/* <Button
key="test"
type="primary"
loading={loading}
onClick={() => {
if (skill) {
testModel(skill);
}
}}
>
Test Model
</Button> */}
{!hasChanged && (
<Button
className="ml-2"
key="save"
type="primary"
onClick={() => {
if (skill) {
createSkill(skill);
setSkill(skill);
}
}}
>
{skill?.id ? "Update Skill" : "Save Skill"}
</Button>
)}
<Button
className="ml-2"
key="close"
type="default"
onClick={() => {
close();
}}
>
Close
</Button>
</div>
</div>
);
};

View File

@@ -0,0 +1,279 @@
import React from "react";
import { IWorkflow, IStatus, IChatSession } from "../../../types";
import { ControlRowView } from "../../../atoms";
import {
fetchJSON,
getRandomIntFromDateAndSalt,
getServerUrl,
} from "../../../utils";
import { Button, Drawer, Input, Select, Tabs, message, theme } from "antd";
import { appContext } from "../../../../hooks/provider";
import { BugAntIcon, UserGroupIcon } from "@heroicons/react/24/outline";
import { WorkflowAgentSelector, WorkflowTypeSelector } from "./selectors";
import ChatBox from "../../playground/chatbox";
export const WorkflowViewConfig = ({
workflow,
setWorkflow,
close,
}: {
workflow: IWorkflow;
setWorkflow: (newFlowConfig: IWorkflow) => void;
close: () => void;
}) => {
const [loading, setLoading] = React.useState<boolean>(false);
const [error, setError] = React.useState<IStatus | null>(null);
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const createWorkflowUrl = `${serverUrl}/workflows`;
const [controlChanged, setControlChanged] = React.useState<boolean>(false);
const [localWorkflow, setLocalWorkflow] = React.useState<IWorkflow>(workflow);
const updateFlowConfig = (key: string, value: string) => {
// When an updatedFlowConfig is created using localWorkflow, if the contents of FlowConfigViewer Modal are changed after the Agent Specification Modal is updated, the updated contents of the Agent Specification Modal are not saved. Fixed to localWorkflow->flowConfig. Fixed a bug.
const updatedFlowConfig = { ...workflow, [key]: value };
setLocalWorkflow(updatedFlowConfig);
setWorkflow(updatedFlowConfig);
setControlChanged(true);
};
const createWorkflow = (workflow: IWorkflow) => {
setError(null);
setLoading(true);
// const fetch;
workflow.user_id = user?.email;
const payLoad = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(workflow),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
const newWorkflow = data.data;
setWorkflow(newWorkflow);
} else {
message.error(data.message);
}
setLoading(false);
// setNewAgent(sampleAgent);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
const onFinal = () => {
setLoading(false);
setControlChanged(false);
};
fetchJSON(createWorkflowUrl, payLoad, onSuccess, onError, onFinal);
};
const hasChanged = !controlChanged && workflow.id !== undefined;
const [drawerOpen, setDrawerOpen] = React.useState<boolean>(false);
const openDrawer = () => {
setDrawerOpen(true);
};
const closeDrawer = () => {
setDrawerOpen(false);
};
const dummySession: IChatSession = {
user_id: user?.email || "test_session_user_id",
workflow_id: workflow?.id,
name: "test_session",
};
return (
<>
{/* <div className="mb-2">{flowConfig.name}</div> */}
<div>
<ControlRowView
title="Workflow Name"
className="mt-4 mb-2"
description="Name of the workflow"
value={localWorkflow.name}
control={
<Input
className="mt-2 w-full"
value={localWorkflow.name}
onChange={(e) => updateFlowConfig("name", e.target.value)}
/>
}
/>
<ControlRowView
title="Workflow Description"
className="mt-4 mb-2"
description="Description of the workflow"
value={localWorkflow.description}
control={
<Input
className="mt-2 w-full"
value={localWorkflow.description}
onChange={(e) => updateFlowConfig("description", e.target.value)}
/>
}
/>
<ControlRowView
title="Summary Method"
description="Defines the method to summarize the conversation"
value={localWorkflow.summary_method || "last"}
control={
<Select
className="mt-2 w-full"
defaultValue={localWorkflow.summary_method || "last"}
onChange={(value: any) =>
updateFlowConfig("summary_method", value)
}
options={
[
{ label: "last", value: "last" },
{ label: "none", value: "none" },
{ label: "llm", value: "llm" },
] as any
}
/>
}
/>
</div>
<div className="w-full mt-4 text-right">
{" "}
{!hasChanged && (
<Button
type="primary"
onClick={() => {
createWorkflow(localWorkflow);
}}
loading={loading}
>
{workflow.id ? "Update Workflow" : "Create Workflow"}
</Button>
)}
{workflow?.id && (
<Button
className="ml-2 text-primary"
type="primary"
onClick={() => {
setDrawerOpen(true);
}}
>
Test Workflow
</Button>
)}
<Button
className="ml-2"
key="close text-primary"
type="default"
onClick={() => {
close();
}}
>
Close
</Button>
</div>
<Drawer
title={<div>{workflow?.name || "Test Workflow"}</div>}
size="large"
onClose={closeDrawer}
open={drawerOpen}
>
<div className="h-full ">
{drawerOpen && (
<ChatBox
initMessages={[]}
session={dummySession}
heightOffset={100}
/>
)}
</div>
</Drawer>
</>
);
};
export const WorflowViewer = ({
workflow,
setWorkflow,
close,
}: {
workflow: IWorkflow;
setWorkflow: (workflow: IWorkflow) => void;
close: () => void;
}) => {
let items = [
{
label: (
<div className="w-full ">
{" "}
<BugAntIcon className="h-4 w-4 inline-block mr-1" />
Workflow Configuration
</div>
),
key: "1",
children: (
<div>
{!workflow?.type && (
<WorkflowTypeSelector
workflow={workflow}
setWorkflow={setWorkflow}
/>
)}
{workflow?.type && workflow && (
<WorkflowViewConfig
workflow={workflow}
setWorkflow={setWorkflow}
close={close}
/>
)}
</div>
),
},
];
if (workflow) {
if (workflow?.id) {
items.push({
label: (
<div className="w-full ">
{" "}
<UserGroupIcon className="h-4 w-4 inline-block mr-1" />
Agents
</div>
),
key: "2",
children: (
<>
<WorkflowAgentSelector workflow={workflow} />{" "}
</>
),
});
}
}
const { user } = React.useContext(appContext);
return (
<div className="text-primary">
<Tabs
tabBarStyle={{ paddingLeft: 0, marginLeft: 0 }}
defaultActiveKey="1"
items={items}
/>
</div>
);
};

View File

@@ -0,0 +1,428 @@
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
CodeBracketSquareIcon,
DocumentDuplicateIcon,
InformationCircleIcon,
PlusIcon,
TrashIcon,
UserGroupIcon,
UsersIcon,
} from "@heroicons/react/24/outline";
import { Dropdown, MenuProps, Modal, message } from "antd";
import * as React from "react";
import { IWorkflow, IStatus } from "../../types";
import { appContext } from "../../../hooks/provider";
import {
fetchJSON,
getServerUrl,
sanitizeConfig,
timeAgo,
truncateText,
} from "../../utils";
import { BounceLoader, Card, CardHoverBar, LoadingOverlay } from "../../atoms";
import { WorflowViewer } from "./utils/workflowconfig";
import { ExportWorkflowModal } from "./utils/export";
const WorkflowView = ({}: any) => {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const listWorkflowsUrl = `${serverUrl}/workflows?user_id=${user?.email}`;
const saveWorkflowsUrl = `${serverUrl}/workflows`;
const [workflows, setWorkflows] = React.useState<IWorkflow[] | null>([]);
const [selectedWorkflow, setSelectedWorkflow] =
React.useState<IWorkflow | null>(null);
const [selectedExportWorkflow, setSelectedExportWorkflow] =
React.useState<IWorkflow | null>(null);
const sampleWorkflow: IWorkflow = {
name: "Sample Agent Workflow",
description: "Sample Agent Workflow",
};
const [newWorkflow, setNewWorkflow] = React.useState<IWorkflow | null>(
sampleWorkflow
);
const [showWorkflowModal, setShowWorkflowModal] = React.useState(false);
const [showNewWorkflowModal, setShowNewWorkflowModal] = React.useState(false);
const fetchWorkFlow = () => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
setWorkflows(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listWorkflowsUrl, payLoad, onSuccess, onError);
};
const deleteWorkFlow = (workflow: IWorkflow) => {
setError(null);
setLoading(true);
// const fetch;
const deleteWorkflowsUrl = `${serverUrl}/workflows/delete?user_id=${user?.email}&workflow_id=${workflow.id}`;
const payLoad = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
workflow: workflow,
}),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
fetchWorkFlow();
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(deleteWorkflowsUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
// console.log("fetching messages", messages);
fetchWorkFlow();
}
}, []);
React.useEffect(() => {
if (selectedWorkflow) {
setShowWorkflowModal(true);
}
}, [selectedWorkflow]);
const [showExportModal, setShowExportModal] = React.useState(false);
const workflowRows = (workflows || []).map(
(workflow: IWorkflow, i: number) => {
const cardItems = [
{
title: "Export",
icon: CodeBracketSquareIcon,
onClick: (e: any) => {
e.stopPropagation();
setSelectedExportWorkflow(workflow);
setShowExportModal(true);
},
hoverText: "Export",
},
{
title: "Download",
icon: ArrowDownTrayIcon,
onClick: (e: any) => {
e.stopPropagation();
// download workflow as workflow.name.json
const element = document.createElement("a");
const sanitizedWorkflow = sanitizeConfig(workflow);
const file = new Blob([JSON.stringify(sanitizedWorkflow)], {
type: "application/json",
});
element.href = URL.createObjectURL(file);
element.download = `workflow_${workflow.name}.json`;
document.body.appendChild(element); // Required for this to work in FireFox
element.click();
},
hoverText: "Download",
},
{
title: "Make a Copy",
icon: DocumentDuplicateIcon,
onClick: (e: any) => {
e.stopPropagation();
let newWorkflow = { ...sanitizeConfig(workflow) };
newWorkflow.name = `${workflow.name}_copy`;
setNewWorkflow(newWorkflow);
setShowNewWorkflowModal(true);
},
hoverText: "Make a Copy",
},
{
title: "Delete",
icon: TrashIcon,
onClick: (e: any) => {
e.stopPropagation();
deleteWorkFlow(workflow);
},
hoverText: "Delete",
},
];
return (
<li
key={"workflowrow" + i}
className="block h-full"
style={{ width: "200px" }}
>
<Card
className=" block p-2 cursor-pointer"
title={<div className=" ">{truncateText(workflow.name, 25)}</div>}
onClick={() => {
setSelectedWorkflow(workflow);
}}
>
<div
style={{ minHeight: "65px" }}
className="break-words my-2"
aria-hidden="true"
>
<div className="text-xs mb-2">{workflow.type}</div>{" "}
{truncateText(workflow.description, 70)}
</div>
<div
aria-label={`Updated ${timeAgo(workflow.updated_at || "")} ago`}
className="text-xs"
>
{timeAgo(workflow.updated_at || "")}
</div>
<CardHoverBar items={cardItems} />
</Card>
</li>
);
}
);
const WorkflowModal = ({
workflow,
setWorkflow,
showModal,
setShowModal,
handler,
}: {
workflow: IWorkflow | null;
setWorkflow?: (workflow: IWorkflow | null) => void;
showModal: boolean;
setShowModal: (show: boolean) => void;
handler?: (workflow: IWorkflow) => void;
}) => {
const [localWorkflow, setLocalWorkflow] = React.useState<IWorkflow | null>(
workflow
);
const closeModal = () => {
setShowModal(false);
if (handler) {
handler(localWorkflow as IWorkflow);
}
};
return (
<Modal
title={
<>
Workflow Specification{" "}
<span className="text-accent font-normal">
{localWorkflow?.name}
</span>{" "}
</>
}
width={800}
open={showModal}
onOk={() => {
closeModal();
}}
onCancel={() => {
closeModal();
}}
footer={[]}
>
<>
{localWorkflow && (
<WorflowViewer
workflow={localWorkflow}
setWorkflow={setLocalWorkflow}
close={closeModal}
/>
)}
</>
</Modal>
);
};
const uploadWorkflow = () => {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = (e: any) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (e: any) => {
const contents = e.target.result;
if (contents) {
try {
const workflow = JSON.parse(contents);
// TBD validate that it is a valid workflow
setNewWorkflow(workflow);
setShowNewWorkflowModal(true);
} catch (err) {
message.error("Invalid workflow file");
}
}
};
reader.readAsText(file);
};
input.click();
};
const workflowTypes: MenuProps["items"] = [
// {
// key: "twoagents",
// label: (
// <div>
// {" "}
// <UsersIcon className="w-5 h-5 inline-block mr-2" />
// Two Agents
// </div>
// ),
// },
// {
// key: "groupchat",
// label: (
// <div>
// <UserGroupIcon className="w-5 h-5 inline-block mr-2" />
// Group Chat
// </div>
// ),
// },
// {
// type: "divider",
// },
{
key: "uploadworkflow",
label: (
<div>
<ArrowUpTrayIcon className="w-5 h-5 inline-block mr-2" />
Upload Workflow
</div>
),
},
];
const showWorkflow = (config: IWorkflow) => {
setSelectedWorkflow(config);
setShowWorkflowModal(true);
};
const workflowTypesOnClick: MenuProps["onClick"] = ({ key }) => {
if (key === "uploadworkflow") {
uploadWorkflow();
return;
}
showWorkflow(sampleWorkflow);
};
return (
<div className=" text-primary ">
<WorkflowModal
workflow={selectedWorkflow}
setWorkflow={setSelectedWorkflow}
showModal={showWorkflowModal}
setShowModal={setShowWorkflowModal}
handler={(workflow: IWorkflow) => {
fetchWorkFlow();
}}
/>
<WorkflowModal
workflow={newWorkflow}
showModal={showNewWorkflowModal}
setShowModal={setShowNewWorkflowModal}
handler={(workflow: IWorkflow) => {
fetchWorkFlow();
}}
/>
<ExportWorkflowModal
workflow={selectedExportWorkflow}
show={showExportModal}
setShow={setShowExportModal}
/>
<div className="mb-2 relative">
<div className=" rounded ">
<div className="flex mt-2 pb-2 mb-2 border-b">
<div className="flex-1 font-semibold mb-2 ">
{" "}
Workflows ({workflowRows.length}){" "}
</div>
<div className=" ">
<Dropdown.Button
type="primary"
menu={{ items: workflowTypes, onClick: workflowTypesOnClick }}
placement="bottomRight"
trigger={["click"]}
onClick={() => {
showWorkflow(sampleWorkflow);
}}
>
<PlusIcon className="w-5 h-5 inline-block mr-1" />
New Workflow
</Dropdown.Button>
</div>
</div>
<div className="text-xs mb-2 pb-1 ">
{" "}
Configure an agent workflow that can be used to handle tasks.
</div>
{workflows && workflows.length > 0 && (
<div
// style={{ minHeight: "500px" }}
className="w-full relative"
>
<LoadingOverlay loading={loading} />
<ul className="flex flex-wrap gap-3">{workflowRows}</ul>
</div>
)}
{workflows && workflows.length === 0 && !loading && (
<div className="text-sm border mt-4 rounded text-secondary p-2">
<InformationCircleIcon className="h-4 w-4 inline mr-1" />
No workflows found. Please create a new workflow.
</div>
)}
{loading && (
<div className=" w-full text-center">
{" "}
<BounceLoader />{" "}
<span className="inline-block"> loading .. </span>
</div>
)}
</div>
</div>
</div>
);
};
export default WorkflowView;

View File

@@ -0,0 +1,207 @@
import * as React from "react";
import { appContext } from "../../../hooks/provider";
import { fetchJSON, getServerUrl, timeAgo, truncateText } from "../../utils";
import { IGalleryItem, IStatus } from "../../types";
import { Button, message } from "antd";
import { BounceLoader, Card } from "../../atoms";
import {
ChevronLeftIcon,
InformationCircleIcon,
} from "@heroicons/react/24/outline";
import { navigate } from "gatsby";
import ChatBox from "../playground/chatbox";
const GalleryView = ({ location }: any) => {
const serverUrl = getServerUrl();
const { user } = React.useContext(appContext);
const [loading, setLoading] = React.useState(false);
const [gallery, setGallery] = React.useState<null | IGalleryItem[]>(null);
const [currentGallery, setCurrentGallery] =
React.useState<null | IGalleryItem>(null);
const listGalleryUrl = `${serverUrl}/gallery?user_id=${user?.email}`;
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const [currentGalleryId, setCurrentGalleryId] = React.useState<string | null>(
null
);
React.useEffect(() => {
// get gallery id from url
const urlParams = new URLSearchParams(location.search);
const galleryId = urlParams.get("id");
if (galleryId) {
// Fetch gallery details using the galleryId
fetchGallery(galleryId);
setCurrentGalleryId(galleryId);
} else {
// Redirect to an error page or home page if the id is not found
// navigate("/");
fetchGallery(null);
}
}, []);
const fetchGallery = (galleryId: string | null) => {
const fetchGalleryUrl = galleryId
? `${serverUrl}/gallery?gallery_id=${galleryId}`
: listGalleryUrl;
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
// message.success(data.message);
console.log("gallery", data);
if (galleryId) {
// Set the currently viewed gallery item
setCurrentGallery(data.data[0]);
} else {
setGallery(data.data);
}
// Set the list of gallery items
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(fetchGalleryUrl, payLoad, onSuccess, onError);
};
const GalleryContent = ({ item }: { item: IGalleryItem }) => {
return (
<div>
<div className="mb-4 text-sm">
This session contains {item.messages.length} messages and was created{" "}
{timeAgo(item.timestamp)}
</div>
<div className="">
<ChatBox initMessages={item.messages} editable={false} />
</div>
</div>
);
};
const TagsView = ({ tags }: { tags: string[] }) => {
const tagsView = tags.map((tag: string, index: number) => {
return (
<div key={"tag" + index} className="mr-2 inline-block">
<span className="text-xs bg-secondary border px-3 p-1 rounded">
{tag}
</span>
</div>
);
});
return <div className="flex flex-wrap">{tagsView}</div>;
};
const galleryRows = gallery?.map((item: IGalleryItem, index: number) => {
const isSelected = currentGallery?.id === item.id;
return (
<div key={"galleryrow" + index} className="">
<Card
active={isSelected}
onClick={() => {
setCurrentGallery(item);
// add to history
navigate(`/gallery?id=${item.id}`);
}}
className="h-full p-2 cursor-pointer"
title={truncateText(item.messages[0]?.content || "", 20)}
>
<div className="my-2">
{" "}
{truncateText(item.messages[0]?.content || "", 80)}
</div>
<div className="text-xs">
{" "}
{item.messages.length} message{item.messages.length > 1 && "s"}
</div>
<div className="my-2 border-t border-dashed w-full pt-2 inline-flex gap-2 ">
<TagsView tags={item.tags} />{" "}
</div>
<div className="text-xs">{timeAgo(item.timestamp)}</div>
</Card>
</div>
);
});
return (
<div className=" ">
<div className="mb-4 text-2xl">Gallery</div>
{/* back to gallery button */}
{currentGallery && (
<div className="mb-4 w-full">
<Button
type="primary"
onClick={() => {
setCurrentGallery(null);
// add to history
navigate(`/gallery?_=${Date.now()}`);
if (currentGalleryId) {
fetchGallery(null);
setCurrentGalleryId(null);
}
}}
className="bg-primary text-white px-2 py-1 rounded"
>
<ChevronLeftIcon className="h-4 w-4 inline mr-1" />
Back to gallery
</Button>
</div>
)}
{!currentGallery && (
<>
<div>
View a collection of AutoGen agent specifications and sessions{" "}
</div>
<div className="mt-4 grid gap-3 grid-cols-2 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6">
{galleryRows}
</div>
</>
)}
{gallery && gallery.length === 0 && (
<div className="text-sm border rounded text-secondary p-2">
<InformationCircleIcon className="h-4 w-4 inline mr-1" />
No gallery items found. Please create a chat session and publish to
gallery.
</div>
)}
{currentGallery && (
<div className="mt-4 border-t pt-2">
<GalleryContent item={currentGallery} />
</div>
)}
{loading && (
<div className="w-full text-center boder mt-4">
<div>
{" "}
<BounceLoader />
</div>
loading gallery
</div>
)}
</div>
);
};
export default GalleryView;

View File

@@ -0,0 +1,824 @@
import {
ArrowPathIcon,
ChatBubbleLeftRightIcon,
Cog6ToothIcon,
DocumentDuplicateIcon,
ExclamationTriangleIcon,
InformationCircleIcon,
PaperAirplaneIcon,
SignalSlashIcon,
} from "@heroicons/react/24/outline";
import {
Button,
Dropdown,
MenuProps,
Tabs,
message as ToastMessage,
Tooltip,
message,
} from "antd";
import * as React from "react";
import {
IChatMessage,
IChatSession,
IMessage,
IStatus,
IWorkflow,
} from "../../types";
import { examplePrompts, fetchJSON, getServerUrl, guid } from "../../utils";
import { appContext } from "../../../hooks/provider";
import MetaDataView from "./metadata";
import {
AgentRow,
BounceLoader,
CollapseBox,
LoadingBar,
MarkdownView,
} from "../../atoms";
import { useConfigStore } from "../../../hooks/store";
import ProfilerView from "./utils/profiler";
let socketMsgs: any[] = [];
const ChatBox = ({
initMessages,
session,
editable = true,
heightOffset = 160,
}: {
initMessages: IMessage[] | null;
session: IChatSession | null;
editable?: boolean;
heightOffset?: number;
}) => {
// const session: IChatSession | null = useConfigStore((state) => state.session);
const textAreaInputRef = React.useRef<HTMLTextAreaElement>(null);
const messageBoxInputRef = React.useRef<HTMLDivElement>(null);
const { user } = React.useContext(appContext);
const wsClient = React.useRef<WebSocket | null>(null);
const wsMessages = React.useRef<IChatMessage[]>([]);
const [wsConnectionStatus, setWsConnectionStatus] =
React.useState<string>("disconnected");
const [workflow, setWorkflow] = React.useState<IWorkflow | null>(null);
const [socketMessages, setSocketMessages] = React.useState<any[]>([]);
const [awaitingUserInput, setAwaitingUserInput] = React.useState(false); // New state for tracking user input
const setAreSessionButtonsDisabled = useConfigStore(
(state) => state.setAreSessionButtonsDisabled
);
const MAX_RETRIES = 10;
const RETRY_INTERVAL = 2000;
const [retries, setRetries] = React.useState(0);
const serverUrl = getServerUrl();
let websocketUrl = serverUrl.replace("http", "ws") + "/ws/";
// check if there is a protocol in the serverUrl e.g. /api. if use the page url
if (!serverUrl.includes("http")) {
const pageUrl = window.location.href;
const url = new URL(pageUrl);
const protocol = url.protocol;
const host = url.host;
const baseUrl = protocol + "//" + host + serverUrl;
websocketUrl = baseUrl.replace("http", "ws") + "/ws/";
} else {
websocketUrl = serverUrl.replace("http", "ws") + "/ws/";
}
const [loading, setLoading] = React.useState(false);
const [text, setText] = React.useState("");
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const socketDivRef = React.useRef<HTMLDivElement>(null);
const connectionId = useConfigStore((state) => state.connectionId);
const messages = useConfigStore((state) => state.messages);
const setMessages = useConfigStore((state) => state.setMessages);
const parseMessage = (message: IMessage) => {
let meta;
try {
meta = JSON.parse(message.meta);
} catch (e) {
meta = message?.meta;
}
const msg: IChatMessage = {
text: message.content,
sender: message.role === "user" ? "user" : "bot",
meta: meta,
id: message.id,
};
return msg;
};
const parseMessages = (messages: any) => {
return messages?.map(parseMessage);
};
React.useEffect(() => {
// console.log("initMessages changed", initMessages);
const initMsgs: IChatMessage[] = parseMessages(initMessages);
setMessages(initMsgs);
wsMessages.current = initMsgs;
}, [initMessages]);
const promptButtons = examplePrompts.map((prompt, i) => {
return (
<Button
key={"prompt" + i}
type="primary"
className=""
onClick={() => {
runWorkflow(prompt.prompt);
}}
>
{" "}
{prompt.title}{" "}
</Button>
);
});
const messageListView = messages && messages?.map((message: IChatMessage, i: number) => {
const isUser = message.sender === "user";
const css = isUser ? "bg-accent text-white " : "bg-light";
// console.log("message", message);
let hasMeta = false;
if (message.meta) {
hasMeta =
message.meta.code !== null ||
message.meta.images?.length > 0 ||
message.meta.files?.length > 0 ||
message.meta.scripts?.length > 0;
}
let items: MenuProps["items"] = [];
if (isUser) {
items.push({
label: (
<div
onClick={() => {
console.log("retrying");
runWorkflow(message.text);
}}
>
<ArrowPathIcon
role={"button"}
title={"Retry"}
className="h-4 w-4 mr-1 inline-block"
/>
Retry
</div>
),
key: "retrymessage",
});
items.push({
label: (
<div
onClick={() => {
// copy to clipboard
navigator.clipboard.writeText(message.text);
ToastMessage.success("Message copied to clipboard");
}}
>
<DocumentDuplicateIcon
role={"button"}
title={"Copy"}
className="h-4 w-4 mr-1 inline-block"
/>
Copy
</div>
),
key: "copymessage",
});
}
const menu = (
<Dropdown menu={{ items }} trigger={["click"]} placement="bottomRight">
<div
role="button"
className="float-right ml-2 duration-100 hover:bg-secondary font-semibold px-2 pb-1 rounded"
>
<span className="block -mt-2 text-primary "> ...</span>
</div>
</Dropdown>
);
return (
<div
id={"message" + i} className={`align-right ${isUser ? "text-righpt" : ""} mb-2 border-b`}
key={"message" + i}
>
{" "}
<div className={` ${isUser ? "" : " w-full"} inline-flex gap-2`}>
<div className=""></div>
<div className="font-semibold text-secondary text-sm w-16">{`${
isUser ? "USER" : "AGENTS"
}`}</div>
<div
className={`inline-block group relative w-full p-2 rounded ${css}`}
>
{" "}
{items.length > 0 && editable && (
<div className=" group-hover:opacity-100 opacity-0 ">{menu}</div>
)}
{isUser && (
<>
<div className="inline-block">{message.text}</div>
</>
)}
{!isUser && (
<div
className={` w-full chatbox prose dark:prose-invert text-primary rounded `}
>
<MarkdownView
className="text-sm"
data={message.text}
showCode={false}
/>
</div>
)}
{message.meta && !isUser && (
<>
{" "}
<Tabs
defaultActiveKey="1"
items={[
{
label: (
<>
{" "}
<ChatBubbleLeftRightIcon className="h-4 w-4 inline-block mr-1" />
Agent Messages
</>
),
key: "1",
children: (
<div className="text-primary">
<MetaDataView metadata={message.meta} />
</div>
),
},
{
label: (
<div>
{" "}
<SignalSlashIcon className="h-4 w-4 inline-block mr-1" />{" "}
Profiler
</div>
),
key: "2",
children: (
<div className="text-primary">
<ProfilerView agentMessage={message} />
</div>
),
},
]}
/>
</>
)}
</div>
</div>
</div>
);
});
React.useEffect(() => {
// console.log("messages updated, scrolling");
setTimeout(() => {
scrollChatBox(messageBoxInputRef);
}, 500);
}, [messages]);
const textAreaDefaultHeight = "64px";
// clear text box if loading has just changed to false and there is no error
React.useEffect(() => {
if ((awaitingUserInput || loading === false) && textAreaInputRef.current) {
if (textAreaInputRef.current) {
if (error === null || (error && error.status === false)) {
textAreaInputRef.current.value = "";
textAreaInputRef.current.style.height = textAreaDefaultHeight;
}
}
}
}, [loading]);
React.useEffect(() => {
if (textAreaInputRef.current) {
textAreaInputRef.current.style.height = textAreaDefaultHeight; // Reset height to shrink if text is deleted
const scrollHeight = textAreaInputRef.current.scrollHeight;
textAreaInputRef.current.style.height = `${scrollHeight}px`;
}
}, [text]);
const [waitingToReconnect, setWaitingToReconnect] = React.useState<
boolean | null
>(null);
React.useEffect(() => {
if (waitingToReconnect) {
return;
}
// Only set up the websocket once
const socketUrl = websocketUrl + connectionId;
console.log("socketUrl", socketUrl);
if (!wsClient.current) {
const client = new WebSocket(socketUrl);
wsClient.current = client;
client.onerror = (e) => {
console.log("ws error", e);
};
client.onopen = () => {
setWsConnectionStatus("connected");
console.log("ws opened");
};
client.onclose = () => {
if (wsClient.current) {
// Connection failed
console.log("ws closed by server");
} else {
// Cleanup initiated from app side, can return here, to not attempt a reconnect
return;
}
if (waitingToReconnect) {
return;
}
setWsConnectionStatus("disconnected");
setWaitingToReconnect(true);
setWsConnectionStatus("reconnecting");
setTimeout(() => {
setWaitingToReconnect(null);
}, RETRY_INTERVAL);
};
client.onmessage = (message) => {
const data = JSON.parse(message.data);
console.log("received message", data);
if (data && data.type === "agent_message") {
// indicates an intermediate agent message update
const newsocketMessages = Object.assign([], socketMessages);
newsocketMessages.push(data.data);
setSocketMessages(newsocketMessages);
socketMsgs.push(data.data);
setTimeout(() => {
scrollChatBox(socketDivRef);
scrollChatBox(messageBoxInputRef);
}, 200);
// console.log("received message", data, socketMsgs.length);
} else if (data && data.type === "user_input_request") {
setAwaitingUserInput(true); // Set awaiting input state
textAreaInputRef.current.value = ""
textAreaInputRef.current.placeholder = data.data.message.content
const newsocketMessages = Object.assign([], socketMessages);
newsocketMessages.push(data.data);
setSocketMessages(newsocketMessages);
socketMsgs.push(data.data);
setTimeout(() => {
scrollChatBox(socketDivRef);
scrollChatBox(messageBoxInputRef);
}, 200);
ToastMessage.info(data.data.message)
} else if (data && data.type === "agent_status") {
// indicates a status message update
const agentStatusSpan = document.getElementById("agentstatusspan");
if (agentStatusSpan) {
agentStatusSpan.innerHTML = data.data.message;
}
} else if (data && data.type === "agent_response") {
// indicates a final agent response
setAwaitingUserInput(false); // Set awaiting input state
setAreSessionButtonsDisabled(false);
processAgentResponse(data.data);
}
};
return () => {
console.log("Cleanup");
// Dereference, so it will set up next time
wsClient.current = null;
client.close();
};
}
}, [waitingToReconnect]);
const scrollChatBox = (element: any) => {
element.current?.scroll({
top: element.current.scrollHeight,
behavior: "smooth",
});
};
const mainDivRef = React.useRef<HTMLDivElement>(null);
const processAgentResponse = (data: any) => {
if (data && data.status) {
const msg = parseMessage(data.data);
wsMessages.current.push(msg);
setMessages(wsMessages.current);
setLoading(false);
setAwaitingUserInput(false);
} else {
console.log("error", data);
// setError(data);
ToastMessage.error(data.message);
setLoading(false);
setAwaitingUserInput(false);
}
};
const fetchWorkFlow = (workflowId: number) => {
const fetchUrl = `${serverUrl}/workflows/${workflowId}?user_id=${user?.email}`;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
if (data.data && data.data.length > 0) {
setWorkflow(data.data[0]);
}
} else {
message.error(data.message);
}
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
};
fetchJSON(fetchUrl, payLoad, onSuccess, onError);
};
const runWorkflow = (query: string) => {
setError(null);
setAreSessionButtonsDisabled(true);
socketMsgs = [];
let messageHolder = Object.assign([], messages);
const userMessage: IChatMessage = {
text: query,
sender: "user",
};
messageHolder.push(userMessage);
setMessages(messageHolder);
wsMessages.current.push(userMessage);
const messagePayload: IMessage = {
role: "user",
content: query,
user_id: user?.email || "",
session_id: session?.id,
workflow_id: session?.workflow_id,
connection_id: connectionId,
};
const runWorkflowUrl = `${serverUrl}/sessions/${session?.id}/workflow/${session?.workflow_id}/run`;
const postData = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(messagePayload),
};
setLoading(true);
// check if socket connected, send on socket
// else send on fetch
if (wsClient.current && wsClient.current.readyState === 1) {
wsClient.current.send(
JSON.stringify({
connection_id: connectionId,
data: messagePayload,
type: "user_message",
session_id: session?.id,
workflow_id: session?.workflow_id,
})
);
} else {
fetch(runWorkflowUrl, postData)
.then((res) => {
if (res.status === 200) {
res.json().then((data) => {
processAgentResponse(data);
});
} else {
res.json().then((data) => {
console.log("error", data);
ToastMessage.error(data.message);
setLoading(false);
});
ToastMessage.error(
"Connection error. Ensure server is up and running."
);
}
})
.catch(() => {
setLoading(false);
ToastMessage.error(
"Connection error. Ensure server is up and running."
);
})
.finally(() => {
setTimeout(() => {
scrollChatBox(messageBoxInputRef);
}, 500);
});
}
};
const sendUserResponse = (userResponse: string) => {
setAwaitingUserInput(false);
setError(null);
setLoading(true);
textAreaInputRef.current.placeholder = "Write message here..."
const userMessage: IChatMessage = {
text: userResponse,
sender: "system",
};
const messagePayload: IMessage = {
role: "user",
content: userResponse,
user_id: user?.email || "",
session_id: session?.id,
workflow_id: session?.workflow_id,
connection_id: connectionId,
};
// check if socket connected,
if (wsClient.current && wsClient.current.readyState === 1) {
wsClient.current.send(
JSON.stringify({
connection_id: connectionId,
data: messagePayload,
type: "user_message",
session_id: session?.id,
workflow_id: session?.workflow_id,
})
);
} else {
console.err("websocket client error")
}
};
const handleTextChange = (
event: React.ChangeEvent<HTMLTextAreaElement>
): void => {
setText(event.target.value);
};
const handleKeyDown = (
event: React.KeyboardEvent<HTMLTextAreaElement>
): void => {
if (event.key === "Enter" && !event.shiftKey) {
if (textAreaInputRef.current &&(awaitingUserInput || !loading)) {
event.preventDefault();
if (awaitingUserInput) {
sendUserResponse(textAreaInputRef.current.value); // New function call for sending user input
textAreaInputRef.current.value = "";
} else {
runWorkflow(textAreaInputRef.current.value);
}
}
}
};
const getConnectionColor = (status: string) => {
if (status === "connected") {
return "bg-green-500";
} else if (status === "reconnecting") {
return "bg-orange-500";
} else if (status === "disconnected") {
return "bg-red-500";
}
};
React.useEffect(() => {
if (session && session.workflow_id) {
fetchWorkFlow(session.workflow_id);
}
}, [session]);
const WorkflowView = ({ workflow }: { workflow: IWorkflow }) => {
return (
<div id="workflow-view" className="text-xs cursor-pointer inline-block">
{" "}
{workflow.name}
</div>
);
};
return (
<div
id="chatbox-main"
style={{ height: "calc(100vh - " + heightOffset + "px)" }}
className="text-primary relative rounded "
ref={mainDivRef}
>
<div
id="workflow-name"
style={{ zIndex: 100 }}
className=" absolute right-3 bg-primary rounded text-secondary -top-6 p-2"
>
{" "}
{workflow && <div className="text-xs"> {workflow.name}</div>}
</div>
<div
id="message-box"
ref={messageBoxInputRef}
className="flex h-full flex-col rounded scroll pr-2 overflow-auto "
style={{ minHeight: "30px", height: "calc(100vh - 310px)" }}
>
<div id="scroll-gradient" className="scroll-gradient h-10">
{" "}
<span className=" inline-block h-6"></span>{" "}
</div>
<div className="flex-1 boder mt-4"></div>
{!messages && messages !== null && (
<div id="loading-messages" className="w-full text-center boder mt-4">
<div>
{" "}
<BounceLoader />
</div>
loading messages
</div>
)}
{messages && messages?.length === 0 && (
<div id="no-messages" className="ml-2 text-sm text-secondary ">
<InformationCircleIcon className="inline-block h-6 mr-2" />
No messages in the current session. Start a conversation to begin.
</div>
)}
<div id="message-list" className="ml-2"> {messageListView}</div>
{(loading || awaitingUserInput) && (
<div id="loading-bar" className={` inline-flex gap-2 duration-300 `}>
<div className=""></div>
<div className="font-semibold text-secondary text-sm w-16">
AGENTS
</div>
<div className="relative w-full ">
<div className="mb-2 ">
<LoadingBar>
<div className="mb-1 inline-block ml-2 text-xs text-secondary">
<span className="innline-block text-sm ml-2">
{" "}
<span id="agentstatusspan">
{" "}
agents working on task ..
</span>
</span>{" "}
{socketMsgs.length > 0 && (
<span className="border-l inline-block text-right ml-2 pl-2">
{socketMsgs.length} agent message
{socketMsgs.length > 1 && "s"} sent/received.
</span>
)}
</div>
</LoadingBar>
</div>
{socketMsgs.length > 0 && (
<div
id="agent-messages"
ref={socketDivRef}
style={{
minHeight: "300px",
maxHeight: "400px",
overflowY: "auto",
}}
className={`inline-block scroll group relative p-2 rounded w-full bg-light `}
>
<CollapseBox
open={true}
title={`Agent Messages (${socketMsgs.length} message${
socketMsgs.length > 1 ? "s" : ""
}) `}
>
{socketMsgs?.map((message: any, i: number) => {
return (
<div key={i}>
<AgentRow message={message} />
</div>
);
})}
</CollapseBox>
</div>
)}
</div>
</div>
)}
</div>
{editable && (
<div id="input-area" className="mt-2 p-2 absolute bg-primary bottom-0 w-full">
<div
id="input-form"
className={`rounded p-2 shadow-lg flex mb-1 gap-2 ${
loading && !awaitingUserInput ? " opacity-50 pointer-events-none" : ""
}`}
>
{/* <input className="flex-1 p-2 ring-2" /> */}
<form
autoComplete="on"
className="flex-1 relative"
onSubmit={(e) => {
e.preventDefault();
}}
>
<textarea
id="queryInput"
name="queryInput"
autoComplete="on"
onKeyDown={handleKeyDown}
onChange={handleTextChange}
placeholder="Write message here..."
ref={textAreaInputRef}
className="flex items-center w-full resize-none text-gray-600 bg-white p-2 ring-2 rounded-sm pl-5 pr-16 h-64"
style={{
maxHeight: "120px",
overflowY: "auto",
minHeight: "50px",
}}
/>
<div
id="send-button"
role={"button"}
style={{ width: "45px", height: "35px" }}
title="Send message"
onClick={() => {
if (textAreaInputRef.current && (awaitingUserInput || !loading)) {
if (awaitingUserInput) {
sendUserResponse(textAreaInputRef.current.value); // Use the new function for user input
} else {
runWorkflow(textAreaInputRef.current.value);
}
}
}}
className="absolute right-3 bottom-2 bg-accent hover:brightness-75 transition duration-300 rounded cursor-pointer flex justify-center items-center"
>
{" "}
{(awaitingUserInput || !loading) && (
<div className="inline-block ">
<PaperAirplaneIcon className="h-6 w-6 text-white " />{" "}
</div>
)}
{loading && !awaitingUserInput && (
<div className="inline-block ">
<Cog6ToothIcon className="text-white animate-spin rounded-full h-6 w-6" />
</div>
)}
</div>
</form>
</div>{" "}
<div>
<div className="mt-2 text-xs text-secondary">
<Tooltip title={`Socket ${wsConnectionStatus}`}>
<div
className={`w-1 h-3 rounded inline-block mr-1 ${getConnectionColor(
wsConnectionStatus
)}`}
></div>{" "}
</Tooltip>
Blank slate? Try one of the example prompts below{" "}
</div>
<div
id="prompt-buttons"
className={`mt-2 inline-flex gap-2 flex-wrap ${
(loading && !awaitingUserInput) ? "brightness-75 pointer-events-none" : ""
}`}
>
{promptButtons}
</div>
</div>
{error && !error.status && (
<div id="error-message" className="p-2 rounded mt-4 text-orange-500 text-sm">
{" "}
<ExclamationTriangleIcon className="h-5 text-orange-500 inline-block mr-2" />{" "}
{error.message}
</div>
)}
</div>
)}
</div>
);
};
export default ChatBox;

View File

@@ -0,0 +1,242 @@
import {
DocumentTextIcon,
PhotoIcon,
VideoCameraIcon,
} from "@heroicons/react/24/outline";
import * as React from "react";
import {
CodeBlock,
CodeLoader,
CsvLoader,
CollapseBox,
ExpandView,
GroupView,
ImageLoader,
MarkdownView,
PdfViewer,
AgentRow,
} from "../../atoms";
import { formatDuration, getServerUrl } from "../../utils";
import { IMetadataFile } from "../../types";
import Icon from "../../icons";
const MetaDataView = ({ metadata }: { metadata: any | null }) => {
const serverUrl = getServerUrl();
const renderFileContent = (file: IMetadataFile, i: number) => {
const file_type = file.extension;
const is_image = ["image"].includes(file.type);
const is_code = ["code"].includes(file.type);
const is_csv = ["csv"].includes(file.type);
const is_pdf = ["pdf"].includes(file.type);
const is_video = ["video"].includes(file.type);
const file_name = file.name || "unknown";
const file_path = file.path || "unknown";
let fileView = null;
let fileTitle = (
<div>
{file.type === "image" ? (
<PhotoIcon className="h-4 mr-1 inline-block" />
) : (
<DocumentTextIcon className="h-4 mr-1 inline-block" />
)}{" "}
<span className="break-all ">{file_name}</span>{" "}
</div>
);
let icon = (
<div className="p-2 bg-secondary rounded flex items-center justify-center h-full">
<div className="">
<div style={{ fontSize: "2em" }} className=" text-center mb-2">
{file.extension}
</div>
<div>{fileTitle}</div>
</div>
</div>
);
if (is_image) {
fileView = (
<div>
<div className="mb-2">{fileTitle}</div>
<ImageLoader
src={`${serverUrl}/${file_path}`}
className="w-full rounded"
/>
</div>
);
icon = fileView;
} else if (is_video) {
fileView = (
<div className="mb-2">
<a href={`${serverUrl}/${file_path}`}>{fileTitle}</a>
<video controls className="w-full rounded">
<source
src={`${serverUrl}/${file_path}`}
type={`video/${file_type}`}
/>
Your browser does not support the video tag.
</video>
</div>
);
// Update icon to show a video-related icon
icon = (
<div className=" relative rounded h-full">
<div className="absolute rounded p-2 bg-secondary top-0 ">
{fileTitle}
</div>
<div
style={{ minHeight: "150px" }}
className="bg-secondary h-full w-full rounded flex items-center justify-center text-primary "
>
<VideoCameraIcon className="h-14 w-14" />
</div>
</div>
);
} else if (is_csv) {
fileView = (
<div className="h">
<a href={`${serverUrl}/${file_path}`}>
<div className="mb-4">{fileTitle}</div>
</a>
<CsvLoader
csvUrl={`${serverUrl}/${file_path}`}
className="w-full rounded"
/>
</div>
);
icon = (
<div className=" relative rounded h-full">
<div className="absolute rounded p-2 bg-secondary top-0 ">
{fileTitle}
</div>
<div
style={{ minHeight: "150px" }}
className="bg-secondary h-full w-full rounded flex items-center justify-center text-primary"
>
<Icon icon="csv" size={14} />
</div>
</div>
);
} else if (is_code) {
fileView = (
<div className="h">
<a
href={`${serverUrl}/${file_path}`}
target="_blank"
rel="noopener noreferrer"
>
<div className="mb-4">{fileTitle}</div>
</a>
<CodeLoader
url={`${serverUrl}/${file_path}`}
className="w-full rounded"
/>
</div>
);
icon = (
<div className=" relative rounded h-full">
<div className="absolute rounded p-2 bg-secondary top-0 ">
{fileTitle}
</div>
<div
style={{ minHeight: "150px" }}
className="bg-secondary h-full w-full rounded flex items-center justify-center text-primary"
>
<Icon icon="python" size={14} />
</div>
</div>
);
} else if (is_pdf) {
fileView = (
<div className="h-full">
<div className="mb-4">
<a
href={`${serverUrl}/${file_path}`}
target="_blank"
rel="noopener noreferrer"
>
{fileTitle}
</a>
</div>
<PdfViewer url={`${serverUrl}/${file_path}`} />
</div>
);
icon = (
<div className=" relative rounded h-full">
<div className="absolute rounded p-2 bg-secondary top-0 ">
{fileTitle}
</div>
<div
style={{ minHeight: "150px" }}
className="bg-secondary h-full w-full rounded flex items-center justify-center text-primary "
>
<Icon icon="pdf" size={14} />
</div>
</div>
);
} else {
fileView = <span>Unsupported file type.</span>;
}
return (
<div className=" h-full rounded">
<ExpandView className="mb-1" icon={icon} title={file_name}>
{fileView}
</ExpandView>
</div>
);
};
const renderFile = (file: IMetadataFile, i: number) => (
<div key={"metafilesrow" + i} className="text-primary ">
{renderFileContent(file, i)}
</div>
);
const files = (metadata.files || []).map(renderFile);
const messages = (metadata.messages || []).map((message: any, i: number) => {
return (
<div className=" mb-2 border-dashed" key={"messagerow" + i}>
<AgentRow message={message} />
</div>
);
});
const hasContent = files.length > 0;
const hasMessages = messages.length > 0;
return (
<div>
{hasMessages && (
<div className="rounded bg-primary ">
<CollapseBox
open={false}
title={`Agent Messages (${messages.length} message${
messages.length > 1 ? "s" : ""
}) | ${formatDuration(metadata?.time)}`}
>
{messages}
</CollapseBox>
</div>
)}
{hasContent && (
<div className="rounded mt-2">
<CollapseBox
open={true}
title={`Results (${files.length} file${
files.length > 1 ? "s" : ""
})`}
>
<div className="mt-2 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{files}
</div>
</CollapseBox>
</div>
)}
</div>
);
};
export default MetaDataView;

View File

@@ -0,0 +1,94 @@
import * as React from "react";
import { IChatSession, IMessage, IStatus } from "../../types";
import { fetchJSON, getServerUrl, setLocalStorage } from "../../utils";
import ChatBox from "./chatbox";
import { appContext } from "../../../hooks/provider";
import { message } from "antd";
import SideBarView from "./sidebar";
import { useConfigStore } from "../../../hooks/store";
import SessionsView from "./sessions";
const RAView = () => {
const session: IChatSession | null = useConfigStore((state) => state.session);
const [loading, setLoading] = React.useState(false);
const [messages, setMessages] = React.useState<IMessage[] | null>(null);
const [config, setConfig] = React.useState(null);
React.useEffect(() => {
setLocalStorage("ara_config", config);
}, [config]);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const fetchMessagesUrl = `${serverUrl}/sessions/${session?.id}/messages?user_id=${user?.email}`;
const fetchMessages = () => {
setError(null);
setLoading(true);
setMessages(null);
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
setMessages(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(fetchMessagesUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user && session) {
fetchMessages();
}
}, [session]);
return (
<div className="h-full ">
<div className="flex h-full ">
<div className=" mr-2 rounded">
<SideBarView />
</div>
<div className=" flex-1 ">
{!session && (
<div className=" w-full h-full flex items-center justify-center">
<div className="w-2/3" id="middle">
<div className="w-full text-center">
{" "}
<img
src="/images/svgs/welcome.svg"
alt="welcome"
className="text-accent inline-block object-cover w-56"
/>
</div>
<SessionsView />
</div>
</div>
)}
{session !== null && (
<ChatBox initMessages={messages} session={session} />
)}
</div>
</div>
</div>
);
};
export default RAView;

View File

@@ -0,0 +1,460 @@
import {
ChatBubbleLeftRightIcon,
ExclamationTriangleIcon,
GlobeAltIcon,
PencilIcon,
PlusIcon,
Square3Stack3DIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { Button, Dropdown, Input, MenuProps, Modal, message } from "antd";
import * as React from "react";
import { IChatSession, IWorkflow, IStatus } from "../../types";
import { appContext } from "../../../hooks/provider";
import { fetchJSON, getServerUrl, timeAgo } from "../../utils";
import { LaunchButton, LoadingOverlay } from "../../atoms";
import { useConfigStore } from "../../../hooks/store";
import WorkflowSelector from "./utils/selectors";
const SessionsView = ({}: any) => {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const listSessionUrl = `${serverUrl}/sessions?user_id=${user?.email}`;
const createSessionUrl = `${serverUrl}/sessions`;
const publishSessionUrl = `${serverUrl}/sessions/publish`;
const sessions = useConfigStore((state) => state.sessions);
const setSessions = useConfigStore((state) => state.setSessions);
const sampleSession: IChatSession = {
user_id: user?.email || "",
name:
"New Session " +
new Date().toLocaleString("en-US", {
month: "short",
day: "numeric",
hour: "numeric",
minute: "numeric",
hour12: true,
}),
};
const [selectedSession, setSelectedSession] =
React.useState<IChatSession | null>(sampleSession);
// const [session, setSession] =
// React.useState<IChatSession | null>(null);
const session = useConfigStore((state) => state.session);
const setSession = useConfigStore((state) => state.setSession);
const isSessionButtonsDisabled = useConfigStore(
(state) => state.areSessionButtonsDisabled
);
const deleteSession = (session: IChatSession) => {
setError(null);
setLoading(true);
// const fetch;
const deleteSessionUrl = `${serverUrl}/sessions/delete?user_id=${user?.email}&session_id=${session.id}`;
const payLoad = {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
session: session,
}),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
fetchSessions();
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(deleteSessionUrl, payLoad, onSuccess, onError);
};
const [newSessionModalVisible, setNewSessionModalVisible] =
React.useState(false);
const fetchSessions = () => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
// message.success(data.message);
// console.log("sessions", data);
setSessions(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listSessionUrl, payLoad, onSuccess, onError);
};
const publishSession = () => {
setError(null);
setLoading(true);
const body = {
user_id: user?.email,
session: session,
tags: ["published"],
};
// const fetch;
const payLoad = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
// setSessions(data.data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(publishSessionUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (sessions && sessions.length > 0) {
const firstSession = sessions[0];
setSession(firstSession);
} else {
setSession(null);
}
}, [sessions]);
const createSession = (session: IChatSession) => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(session),
};
const onSuccess = (data: any) => {
if (data && data.status) {
message.success(data.message);
fetchSessions();
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(createSessionUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
fetchSessions();
}
}, []);
const sessionRows = sessions.map((data: IChatSession, index: number) => {
const isSelected = session?.id === data.id;
const rowClass = isSelected
? "bg-accent text-white"
: "bg-secondary text-primary";
let items: MenuProps["items"] = [
{
label: (
<div
onClick={() => {
console.log("deleting session");
deleteSession(data);
}}
>
<TrashIcon
role={"button"}
title={"Delete"}
className="h-4 w-4 mr-1 inline-block"
/>
Delete
</div>
),
key: "delete",
},
{
label: (
<div
onClick={() => {
// get current clicked session
setSelectedSession(data);
setNewSessionModalVisible(true);
}}
>
<PencilIcon
role={"button"}
title={"Edit"}
className="h-4 w-4 mr-1 inline-block"
/>
Edit
</div>
),
key: "edit",
},
// {
// label: (
// <div
// onClick={() => {
// console.log("publishing session");
// publishSession();
// }}
// >
// <GlobeAltIcon
// role={"button"}
// title={"Publish"}
// className="h-4 w-4 mr-1 inline-block"
// />
// Publish
// </div>
// ),
// key: "publish",
// },
];
items.push();
const menu = (
<Dropdown
menu={{ items: items }}
trigger={["click"]}
placement="bottomRight"
>
<div
role="button"
className={`float-right ml-2 duration-100 hover:bg-secondary font-semibold px-2 pb-1 rounded ${
isSelected ? "hover:text-accent" : ""
}`}
>
<span className={`block -mt-2 ${isSelected ? "text-white" : ""}`}>
{" "}
...
</span>
</div>
</Dropdown>
);
return (
<div
key={"sessionsrow" + index}
className={`group relative mb-2 pb-1 border-b border-dashed ${
isSessionButtonsDisabled ? "opacity-50 pointer-events-none" : ""
}`}
>
{items.length > 0 && (
<div className=" absolute right-2 top-2 group-hover:opacity-100 opacity-0 ">
{menu}
</div>
)}
<div
className={`rounded p-2 cursor-pointer ${rowClass}`}
role="button"
onClick={() => {
// setWorkflowConfig(data.flow_config);
if (!isSessionButtonsDisabled) {
setSession(data);
}
}}
>
<div className="text-xs mt-1">
<Square3Stack3DIcon className="h-4 w-4 inline-block mr-1" />
{data.name}
</div>
<div className="text-xs text-right ">
{timeAgo(data.created_at || "")}
</div>
</div>
</div>
);
});
let windowHeight, skillsMaxHeight;
if (typeof window !== "undefined") {
windowHeight = window.innerHeight;
skillsMaxHeight = windowHeight - 400 + "px";
}
const NewSessionModal = ({ session }: { session: IChatSession | null }) => {
const [workflow, setWorkflow] = React.useState<IWorkflow | null>(null);
const [localSession, setLocalSession] = React.useState<IChatSession | null>(
session
);
React.useEffect(() => {
if (workflow && workflow.id && localSession) {
setLocalSession({ ...localSession, workflow_id: workflow.id });
}
}, [workflow]);
const sessionExists =
localSession !== null && localSession.id !== undefined;
return (
<Modal
onCancel={() => {
setNewSessionModalVisible(false);
}}
title={
<div className="font-semibold mb-2 pb-1 border-b">
<Square3Stack3DIcon className="h-5 w-5 inline-block mr-1" />
New Session{" "}
</div>
}
open={newSessionModalVisible}
footer={[
<Button
key="back"
onClick={() => {
setNewSessionModalVisible(false);
}}
>
Cancel
</Button>,
<Button
key="submit"
type="primary"
disabled={!workflow}
onClick={() => {
setNewSessionModalVisible(false);
if (localSession) {
createSession(localSession);
}
}}
>
Create
</Button>,
]}
>
<WorkflowSelector
workflow={workflow}
setWorkflow={setWorkflow}
workflow_id={selectedSession?.workflow_id}
disabled={sessionExists}
/>
<div className="my-2 text-xs"> Session Name </div>
<Input
placeholder="Session Name"
value={localSession?.name || ""}
onChange={(event) => {
if (localSession) {
setLocalSession({ ...localSession, name: event.target.value });
}
}}
/>
<div className="text-xs mt-4">
{" "}
{timeAgo(localSession?.created_at || "", true)}
</div>
</Modal>
);
};
return (
<div className=" ">
<NewSessionModal session={selectedSession || sampleSession} />
<div className="mb-2 relative">
<div className="">
<div className="font-semibold mb-2 pb-1 border-b">
<ChatBubbleLeftRightIcon className="h-5 w-5 inline-block mr-1" />
Sessions{" "}
</div>
{sessions && sessions.length > 0 && (
<div className="text-xs hidden mb-2 pb-1 ">
{" "}
Create a new session or select an existing session to view chat.
</div>
)}
<div
style={{
maxHeight: skillsMaxHeight,
}}
className="mb-4 overflow-y-auto scroll rounded relative "
>
{sessionRows}
<LoadingOverlay loading={loading} />
</div>
{(!sessions || sessions.length == 0) && !loading && (
<div className="text-xs text-gray-500">
No sessions found. Create a new session to get started.
</div>
)}
</div>
<div className="flex gap-x-2">
<div className="flex-1"></div>
<LaunchButton
className={`text-sm p-2 px-3 ${isSessionButtonsDisabled ? 'opacity-50 cursor-not-allowed' : ''}`}
onClick={() => {
setSelectedSession(sampleSession);
setNewSessionModalVisible(true);
}}
>
{" "}
<PlusIcon className="w-5 h-5 inline-block mr-1" />
New
</LaunchButton>
</div>
</div>
{error && !error.status && (
<div className="p-2 border border-orange-500 text-secondary rounded mt-4 text-sm">
{" "}
<ExclamationTriangleIcon className="h-5 text-orange-500 inline-block mr-2" />{" "}
{error.message}
</div>
)}
</div>
);
};
export default SessionsView;

View File

@@ -0,0 +1,49 @@
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
import * as React from "react";
import SessionsView from "./sessions";
const SideBarView = () => {
const [isOpen, setIsOpen] = React.useState(true);
const minWidth = isOpen ? "270px" : "50px";
let windowHeight, sidebarMaxHeight;
if (typeof window !== "undefined") {
windowHeight = window.innerHeight;
sidebarMaxHeight = windowHeight - 180 + "px";
}
return (
<div
style={{
minWidth: minWidth,
maxWidth: minWidth,
height: "calc(100vh - 190px)",
}}
className=" "
>
<div className=" transition overflow-hidden duration-300 flex flex-col h-full p-2 overflow-y-scroll scroll rounded ">
<div className={`${isOpen ? "" : "hidden"} `}>
{/* <AgentsView /> */}
{<SessionsView />}
</div>
</div>
<div
onClick={() => setIsOpen(!isOpen)}
role="button"
className=" hover:text-accent duration-150 "
>
{isOpen ? (
<div className="mt-4 ">
{" "}
<ChevronLeftIcon className="w-6 h-6 inline-block rounded" />{" "}
<span className="text-xs "> close sidebar</span>
</div>
) : (
<ChevronRightIcon className="w-6 h-6 inline-block font-bold rounded " />
)}
</div>
</div>
);
};
export default SideBarView;

View File

@@ -0,0 +1,58 @@
import { Bar, Line } from "@ant-design/plots";
import * as React from "react";
import { IStatus } from "../../../../types";
const BarChartViewer = ({ data }: { data: any | null }) => {
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const [loading, setLoading] = React.useState(false);
const config = {
data: data.bar,
xField: "agent",
yField: "message",
colorField: "tool_call",
stack: true,
axis: {
y: { labelFormatter: "" },
x: {
labelSpacing: 4,
},
},
style: {
radiusTopLeft: 10,
radiusTopRight: 10,
},
height: 60 * data.agents.length,
};
const config_code_exec = Object.assign({}, config);
config_code_exec.colorField = "code_execution";
return (
<div className="bg-white rounded relative">
<div>
<div className="grid grid-cols-2">
<div>
<div className=" text-gray-700 border-b border-dashed p-4">
{" "}
Tool Call
</div>
<Bar {...config} />
</div>
<div className=" ">
<div className=" text-gray-700 border-b border-dashed p-4">
{" "}
Code Execution Status
</div>
<Bar {...config_code_exec} />
</div>
</div>
</div>
</div>
);
};
export default BarChartViewer;

View File

@@ -0,0 +1,125 @@
import { Tooltip, message } from "antd";
import * as React from "react";
import { IStatus, IChatMessage } from "../../../types";
import { fetchJSON, getServerUrl } from "../../../utils";
import { appContext } from "../../../../hooks/provider";
import { InformationCircleIcon } from "@heroicons/react/24/outline";
const BarChartViewer = React.lazy(() => import("./charts/bar"));
const ProfilerView = ({
agentMessage,
}: {
agentMessage: IChatMessage | null;
}) => {
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const [loading, setLoading] = React.useState(false);
const [profile, setProfile] = React.useState<any | null>(null);
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const fetchProfile = (messageId: number) => {
const profilerUrl = `${serverUrl}/profiler/${messageId}?user_id=${user?.email}`;
setError(null);
setLoading(true);
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
console.log(data);
if (data && data.status) {
setProfile(data.data);
setTimeout(() => {
// scroll parent to bottom
const parent = document.getElementById("chatbox");
if (parent) {
parent.scrollTop = parent.scrollHeight;
}
}, 4000);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(profilerUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user && agentMessage && agentMessage.id) {
fetchProfile(agentMessage.id);
}
}, []);
const UsageViewer = ({ usage }: { usage: any }) => {
const usageRows = usage.map((usage: any, index: number) => (
<div key={index} className=" borpder rounded">
{(usage.total_cost != 0 || usage.total_tokens != 0) && (
<>
<div className="bg-secondary p-2 text-xs rounded-t">
{usage.agent}
</div>
<div className="bg-tertiary p-3 rounded-b inline-flex gap-2 w-full">
{usage.total_tokens && usage.total_tokens != 0 && (
<div className="flex flex-col text-center w-full">
<div className="w-full px-2 text-2xl ">
{usage.total_tokens}
</div>
<div className="w-full text-xs">tokens</div>
</div>
)}
{usage.total_cost && usage.total_cost != 0 && (
<div className="flex flex-col text-center w-full">
<div className="w-full px-2 text-2xl ">
{usage.total_cost?.toFixed(3)}
</div>
<div className="w-full text-xs">USD</div>
</div>
)}
</div>
</>
)}
</div>
));
return (
<div className="inline-flex gap-3 flex-wrap">{usage && usageRows}</div>
);
};
return (
<div className=" relative">
<div className="text-sm ">
{/* {profile && <RadarMetrics profileData={profile} />} */}
{profile && <BarChartViewer data={profile} />}
<div className="mt-4">
<div className="mt-4 mb-4 txt">
LLM Costs
<Tooltip
title={
"LLM tokens below based on data returned by the model. Support for exact costs may vary."
}
>
<InformationCircleIcon className="ml-1 text-gray-400 inline-block w-4 h-4" />
</Tooltip>
</div>
{profile && profile.usage && <UsageViewer usage={profile.usage} />}
</div>
</div>
</div>
);
};
export default ProfilerView;

View File

@@ -0,0 +1,122 @@
import { Select, message } from "antd";
import * as React from "react";
import { LoadingOverlay } from "../../../atoms";
import { IWorkflow, IStatus } from "../../../types";
import { fetchJSON, getServerUrl } from "../../../utils";
import { appContext } from "../../../../hooks/provider";
import { Link } from "gatsby";
const WorkflowSelector = ({
workflow,
setWorkflow,
workflow_id,
disabled,
}: {
workflow: IWorkflow | null;
setWorkflow: (workflow: IWorkflow) => void;
workflow_id: number | undefined;
disabled?: boolean;
}) => {
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const [loading, setLoading] = React.useState(false);
const [workflows, setWorkflows] = React.useState<IWorkflow[]>([]);
const [selectedWorkflow, setSelectedWorkflow] = React.useState<number>(0);
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const listWorkflowsUrl = `${serverUrl}/workflows?user_id=${user?.email}`;
const fetchWorkFlow = () => {
setError(null);
setLoading(true);
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
if (data && data.status) {
// message.success(data.message);
setWorkflows(data.data);
if (data.data.length > 0) {
if (!disabled) {
setWorkflow(data.data[0]);
} else {
const index = data.data.findIndex((item:IWorkflow) => item.id === workflow_id);
if (index !== -1) {
setSelectedWorkflow(index);
setWorkflow(data.data[index]);
}
}
}
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(listWorkflowsUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
fetchWorkFlow();
}
}, []);
return (
<div className=" mb-4 relative">
<div className="text-sm mt-2 mb-2 pb-1 ">
{" "}
Please select an agent workflow to begin.{" "}
</div>
<div className="relative mt-2 ">
<LoadingOverlay loading={loading} />
{workflows && workflows.length > 0 && (
<Select
disabled={disabled}
className="w-full"
value={workflows[selectedWorkflow].name}
onChange={(value: any) => {
setSelectedWorkflow(value);
setWorkflow(workflows[value]);
}}
options={
workflows.map((config, index) => {
return { label: config.name, value: index };
}) as any
}
/>
)}
<div className="mt-2 text-xs hidden">
{" "}
<div className="my-2 text-xs"> {workflow?.name}</div>
View all workflows{" "}
<span className="text-accent">
{" "}
<Link to="/build">here</Link>
</span>{" "}
</div>
</div>
{!workflows ||
(workflows && workflows.length === 0 && (
<div className="p-1 border rounded text-xs px-2 text-secondary">
{" "}
No agent workflows found.
</div>
))}
</div>
);
};
export default WorkflowSelector;

View File

@@ -0,0 +1,73 @@
import React, { useState } from "react";
import {
eraseCookie,
getLocalStorage,
setLocalStorage,
} from "../components/utils";
import { message } from "antd";
export interface IUser {
name: string;
email?: string;
username?: string;
avatar_url?: string;
metadata?: any;
}
export interface AppContextType {
user: IUser | null;
setUser: any;
logout: any;
cookie_name: string;
darkMode: string;
setDarkMode: any;
}
const cookie_name = "coral_app_cookie_";
export const appContext = React.createContext<AppContextType>(
{} as AppContextType
);
const Provider = ({ children }: any) => {
const storedValue = getLocalStorage("darkmode", false);
const [darkMode, setDarkMode] = useState(
storedValue === null ? "light" : storedValue === "dark" ? "dark" : "light"
);
const logout = () => {
// setUser(null);
// eraseCookie(cookie_name);
console.log("Please implement your own logout logic");
message.info("Please implement your own logout logic");
};
const updateDarkMode = (darkMode: string) => {
setDarkMode(darkMode);
setLocalStorage("darkmode", darkMode, false);
};
// Modify logic here to add your own authentication
const initUser = {
name: "Guest User",
email: "guestuser@gmail.com",
username: "guestuser",
};
const [user, setUser] = useState<IUser | null>(initUser);
return (
<appContext.Provider
value={{
user,
setUser,
logout,
cookie_name,
darkMode,
setDarkMode: updateDarkMode,
}}
>
{children}
</appContext.Provider>
);
};
export default ({ element }: any) => <Provider>{element}</Provider>;

View File

@@ -0,0 +1,34 @@
import { create } from "zustand";
import { v4 as uuidv4 } from "uuid";
import { IChatMessage, IChatSession } from "../components/types";
interface ConfigState {
messages: IChatMessage[] | null;
setMessages: (messages: IChatMessage[]) => void;
session: IChatSession | null;
setSession: (session: IChatSession | null) => void;
sessions: IChatSession[];
setSessions: (sessions: IChatSession[]) => void;
version: string | null;
setVersion: (version: string) => void;
connectionId: string;
setConnectionId: (connectionId: string) => void;
areSessionButtonsDisabled: boolean;
setAreSessionButtonsDisabled: (disabled: boolean) => void;
}
export const useConfigStore = create<ConfigState>()((set) => ({
messages: null,
setMessages: (messages) => set({ messages }),
session: null,
setSession: (session) => set({ session }),
sessions: [],
setSessions: (sessions) => set({ sessions }),
version: null,
setVersion: (version) => set({ version }),
connectionId: uuidv4(),
setConnectionId: (connectionId) => set({ connectionId }),
areSessionButtonsDisabled: false,
setAreSessionButtonsDisabled: (disabled) => set({ areSessionButtonsDisabled: disabled }),
}));

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4f7307728cfd62bc0d07d8cc3ab3809e36d8859a5ad314d020b21648642cf574
size 12710

View File

@@ -0,0 +1,54 @@
import * as React from "react"
import { Link } from "gatsby"
// styles
const pageStyles = {
color: "#232129",
padding: "96px",
fontFamily: "-apple-system, Roboto, sans-serif, serif",
}
const headingStyles = {
marginTop: 0,
marginBottom: 64,
maxWidth: 320,
}
const paragraphStyles = {
marginBottom: 48,
}
const codeStyles = {
color: "#8A6534",
padding: 4,
backgroundColor: "#FFF4DB",
fontSize: "1.25rem",
borderRadius: 4,
}
// markup
const NotFoundPage = () => {
return (
<main style={pageStyles}>
<title>Not found</title>
<h1 style={headingStyles}>Page not found</h1>
<p style={paragraphStyles}>
Sorry{" "}
<span role="img" aria-label="Pensive emoji">
😔
</span>{" "}
we couldnt find what you were looking for.
<br />
{process.env.NODE_ENV === "development" ? (
<>
<br />
Try creating a page in <code style={codeStyles}>src/pages/</code>.
<br />
</>
) : null}
<br />
<Link to="/">Go home</Link>.
</p>
</main>
)
}
export default NotFoundPage

View File

@@ -0,0 +1,28 @@
import * as React from "react";
import Layout from "../components/layout";
import { graphql } from "gatsby";
import BuildView from "../components/views/builder/build";
// markup
const IndexPage = ({ data }: any) => {
return (
<Layout meta={data.site.siteMetadata} title="Home" link={"/build"}>
<main style={{ height: "100%" }} className=" h-full ">
<BuildView />
</main>
</Layout>
);
};
export const query = graphql`
query HomePageQuery {
site {
siteMetadata {
description
title
}
}
}
`;
export default IndexPage;

View File

@@ -0,0 +1,28 @@
import * as React from "react";
import { graphql } from "gatsby";
import Layout from "../../components/layout";
import GalleryView from "../../components/views/gallery/gallery";
// markup
const GalleryPage = ({ location, data }: any) => {
return (
<Layout meta={data.site.siteMetadata} title="Gallery" link={"/gallery"}>
<main style={{ height: "100%" }} className=" h-full ">
<GalleryView location={location} />
</main>
</Layout>
);
};
export const query = graphql`
query HomePageQuery {
site {
siteMetadata {
description
title
}
}
}
`;
export default GalleryPage;

View File

@@ -0,0 +1,28 @@
import * as React from "react";
import Layout from "../components/layout";
import { graphql } from "gatsby";
import RAView from "../components/views/playground/ra";
// markup
const IndexPage = ({ data }: any) => {
return (
<Layout meta={data.site.siteMetadata} title="Home" link={"/"}>
<main style={{ height: "100%" }} className=" h-full ">
<RAView />
</main>
</Layout>
);
};
export const query = graphql`
query HomePageQuery {
site {
siteMetadata {
description
title
}
}
}
`;
export default IndexPage;

View File

@@ -0,0 +1,374 @@
.dark {
--color-bg-primary: #111827;
--color-bg-secondary: #1e293b;
--color-bg-light: #27354c;
--color-bg-tertiary: #374151;
--color-bg-accent: #22c55e;
--color-text-primary: #f7fafc;
--color-text-secondary: #e2e8f0;
--color-text-accent: #22c55e;
--color-border-primary: #f7fafc;
--color-border-secondary: #e2e8f045;
--color-border-accent: #22c55e;
}
.light {
--color-bg-primary: #ffffff;
--color-bg-secondary: #edf2f7;
--color-bg-light: #f9fafb;
--color-bg-tertiary: #ffffff;
--color-bg-accent: #16a34a;
--color-text-primary: #334155;
--color-text-secondary: #64748b;
--color-text-accent: #16a34a;
--color-border-primary: #2d3748c1;
--color-border-secondary: #edf2f7;
--color-border-accent: #16a34a;
}
@tailwind base;
@tailwind components;
@tailwind utilities;
/* @layer base {
body {
@apply dark:bg-slate-800 dark:text-white;
}
} */
html {
display: table;
margin: auto;
}
body {
/* padding: 0px 64px 0px 64px; */
@apply px-4 md:px-8 lg:px-16 !important;
/* margin: 0px auto; */
min-width: 300px;
max-width: 1400px;
background: transparent;
height: 100%;
/* border: 2px solid green; */
display: table-cell;
vertical-align: middle;
width: 100%;
}
/* @import "antd/dist/antd.css"; */
.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
@apply text-accent !important;
font-weight: 500;
}
.ant-tabs-nav::before {
@apply border-secondary !important;
}
.ant-tabs-tab:hover {
@apply text-accent !important;
}
.ant-tabs-tab {
@apply text-primary !important;
}
.ant-tabs-ink-bar {
@apply bg-accent !important;
}
.element.style {
left: 0%;
width: 100%;
}
.ant-slider-track {
/* background: #6366f1 !important; */
@apply bg-accent !important;
}
.ant-slider-rail {
/* background: #d1d5db !important; */
@apply bg-secondary !important;
}
.ant-slider-handle {
/* border: solid 2px #6366f1 !important; */
@apply border-accent !important;
}
.ant-slider-handle:hover {
/* border: solid 2px #4f46e5 !important; */
@apply border-accent brightness-75 !important;
}
.ant-switch-checked {
@apply bg-accent !important;
border: indigo;
}
.ant-switch {
background-color: #d1d5db;
border: grey;
}
.ant-modal-content {
@apply dark:bg-primary dark:text-primary;
}
.ant-modal-footer {
@apply border-secondary;
}
.ant-modal-header,
.ant-modal-close {
@apply bg-secondary text-primary hover:text-primary transition duration-200;
}
.ant-modal-title,
.ant-modal-header {
@apply bg-primary text-primary;
}
a:hover {
@apply text-accent;
}
/* .iiz__img,
iiz__zoom-img {
@apply w-full;
} */
.ant-radio-checked .ant-radio-inner {
@apply border-accent !important;
}
.ant-radio-checked .ant-radio-inner:after {
@apply bg-accent !important;
}
.ant-radio:hover .ant-radio-inner {
@apply border-accent !important;
}
.loadbar:after {
content: "";
/* width: 40px; */
height: 3px;
/* background: red; */
position: absolute;
animation: loader 2s;
-webkit-animation: loader 2s;
animation-iteration-count: infinite;
-webkit-animation-iteration-count: infinite;
transition-timing-function: linear;
-webkit-transition-timing-function: linear;
bottom: 0px;
@apply rounded-b bg-accent;
margin-left: 0;
}
@keyframes loader {
0% {
width: 0%;
left: 0;
right: 0;
}
50% {
width: 100%;
left: 0;
right: 0;
}
99% {
width: 0%;
left: 100%;
right: 0;
}
}
@-webkit-keyframes loader {
0% {
width: 0%;
left: 0;
}
50% {
width: 100%;
left: 0;
right: 0;
}
99% {
width: 0%;
left: 100%;
right: 0;
}
}
.scroll::-webkit-scrollbar {
width: 8px;
height: 8px;
opacity: 0; /* Make scrollbar fully transparent by default */
transition: opacity 0.25s ease; /* Transition for the opacity */
}
.scroll:hover::-webkit-scrollbar {
opacity: 1; /* Make scrollbar fully opaque on hover */
}
.scroll::-webkit-scrollbar-track {
@apply bg-secondary;
border-radius: 20px;
}
.scroll::-webkit-scrollbar-thumb {
@apply bg-accent;
border-radius: 20px;
border: 3px solid rgb(214, 214, 214);
}
.dark .scroll-gradient {
background: linear-gradient(
to bottom,
rgba(17, 24, 39, 1),
rgba(17, 24, 39, 0)
);
}
.light .scroll-gradient {
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 1),
rgba(255, 255, 255, 0)
);
}
.scroll-gradient {
position: sticky;
top: 0;
height: 15px;
z-index: 2; /* Above the content but below the scrollbar */
pointer-events: none; /* So it doesn't block interaction with the content */
}
.vega-embed {
@apply bg-primary rounded !important;
}
.ant-upload-list,
.ant-upload-hint,
.ant-upload-list-text,
.ant-upload-text,
.ant-upload-text-icon {
@apply text-primary !important;
}
.ant-upload {
@apply text-primary px-2 !important;
border-radius: 4px !important;
}
.ant-upload:hover {
@apply border-accent !important;
}
.ant-upload-drag-container,
.ant-upload-text,
.ant-upload-hint {
@apply text-primary !important;
}
.ant-upload-list-item:hover,
.ant-upload-list-item-info:hover {
@apply bg-secondary text-accent !important;
}
.ant-pagination .ant-pagination-item {
@apply bg-primary !important;
}
.ant-pagination .ant-pagination-item-active a {
@apply text-accent !important;
}
.ant-pagination .ant-pagination-item-active {
@apply border-accent text-accent !important;
}
.ant-pagination .ant-pagination-item a,
.ant-pagination-item-link .anticon {
@apply text-primary !important;
}
.ant-collapse-expand-icon .anticon {
@apply text-primary !important;
}
.ant-modal-content {
@apply dark:bg-primary dark:text-primary !important;
}
.ant-modal-footer {
@apply border-secondary !important;
}
.ant-btn,
.ant-btn:hover {
@apply text-primary !important;
}
:where(.ant-btn).ant-btn-compact-item.ant-btn-primary:not([disabled])
+ .ant-btn-compact-item.ant-btn-primary:not([disabled]):before {
@apply bg-secondary !important;
}
.ant-btn-primary {
@apply bg-accent text-white !important;
}
.ant-btn-primary:hover {
@apply bg-accent text-white drop-shadow-md !important;
}
.ant-modal-close {
@apply text-primary duration-200 !important;
}
.ant-modal-title,
.ant-modal-header {
@apply bg-primary text-primary !important;
}
.ant-radio,
.ant-collapse,
.ant-collapse-header-text,
.ant-collapse-content-box,
.ant-collapse-content,
.ant-radio-wrapper {
@apply text-primary !important;
}
.ant-collapse-borderless > .ant-collapse-item {
@apply border-secondary !important;
}
.ant-skeleton-paragraph > li {
@apply bg-secondary !important;
}
.ant-drawer-content,
.ant-drawer-header,
.ant-drawer-header-title,
.ant-drawer-close,
.ant-drawer-title {
@apply bg-primary text-primary !important;
}
.ant-dropdown-menu {
max-height: 250px;
overflow: auto;
@apply scroll !important;
}
/* .ant-radio-input::before {
@apply bg-primary !important;
} */
.prose > pre {
padding: 0px !important;
margin: 0px !important;
}
.monaco-editor,
.monaco-scrollable-element,
.overflow-guard {
@apply rounded !important;
}
/* div.chatbox > ul {
list-style: disc !important;
}
div.chatbox > ul,
div.chatbox > ol {
padding-left: 20px !important;
margin: 0px !important;
}
div.chatbox > ol {
list-style: decimal !important;
} */
div#___gatsby,
div#gatsby-focus-wrapper {
height: 100%;
/* border: 1px solid green; */
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,37 @@
module.exports = {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
darkMode: "class",
theme: {
extend: {
typography: {
DEFAULT: {
css: {
maxWidth: "100ch", // add required value here
},
},
},
transitionProperty: {
height: "height",
spacing: "margin, padding",
},
backgroundColor: {
primary: "var(--color-bg-primary)",
secondary: "var(--color-bg-secondary)",
accent: "var(--color-bg-accent)",
light: "var(--color-bg-light)",
tertiary: "var(--color-bg-tertiary)",
},
textColor: {
accent: "var(--color-text-accent)",
primary: "var(--color-text-primary)",
secondary: "var(--color-text-secondary)",
},
borderColor: {
accent: "var(--color-border-accent)",
primary: "var(--color-border-primary)",
secondary: "var(--color-border-secondary)",
},
},
},
plugins: [require("@tailwindcss/typography")],
};

View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "esnext",
"lib": ["dom", "esnext"],
"jsx": "react",
"module": "esnext",
"moduleResolution": "node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": [
"./types/*",
"./src/**/*",
"./gatsby-node.ts",
"./gatsby-config.ts",
"./plugins/**/*"
]
}

View File

@@ -0,0 +1,273 @@
{
"user_id": "guestuser@gmail.com",
"name": "Travel Planning Workflow",
"type": "autonomous",
"sample_tasks": [
"Plan a 3 day trip to Hawaii Islands.",
"Plan an eventful and exciting trip to Uzbeksitan."
],
"version": "0.0.1",
"description": "Travel workflow",
"summary_method": "llm",
"agents": [
{
"agent": {
"version": "0.0.1",
"config": {
"name": "user_proxy",
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 25,
"system_message": "You are a helpful assistant",
"is_termination_msg": null,
"code_execution_config": "local",
"default_auto_reply": "TERMINATE",
"description": "User Proxy Agent Configuration",
"llm_config": false,
"admin_name": "Admin",
"messages": [],
"max_round": 100,
"speaker_selection_method": "auto",
"allow_repeat_speaker": true
},
"user_id": "guestuser@gmail.com",
"type": "userproxy",
"task_instruction": null,
"skills": [],
"models": [],
"agents": []
},
"link": {
"agent_id": 52,
"workflow_id": 18,
"agent_type": "sender",
"sequence_id": 0
}
},
{
"agent": {
"version": "0.0.1",
"config": {
"name": "travel_groupchat",
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 25,
"system_message": "You are a group chat manager",
"is_termination_msg": null,
"code_execution_config": "none",
"default_auto_reply": "TERMINATE",
"description": "Group Chat Agent Configuration",
"llm_config": {
"config_list": [
{
"api_type": "open_ai",
"model": "gpt-4-1106-preview",
"base_url": null,
"api_version": null
}
],
"temperature": 0,
"cache_seed": null,
"timeout": null,
"max_tokens": 2048,
"extra_body": null
},
"admin_name": "groupchat",
"messages": [],
"max_round": 100,
"speaker_selection_method": "auto",
"allow_repeat_speaker": true
},
"user_id": "guestuser@gmail.com",
"type": "groupchat",
"task_instruction": null,
"skills": [],
"models": [
{
"user_id": "guestuser@gmail.com",
"api_type": "open_ai",
"description": "OpenAI GPT-4 model",
"model": "gpt-4-1106-preview",
"base_url": null,
"api_version": null
}
],
"agents": [
{
"version": "0.0.1",
"config": {
"name": "user_proxy",
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 25,
"system_message": "You are a helpful assistant",
"is_termination_msg": null,
"code_execution_config": "local",
"default_auto_reply": "TERMINATE",
"description": "User Proxy Agent Configuration",
"llm_config": false,
"admin_name": "Admin",
"messages": [],
"max_round": 100,
"speaker_selection_method": "auto",
"allow_repeat_speaker": true
},
"user_id": "guestuser@gmail.com",
"type": "userproxy",
"task_instruction": null,
"skills": [],
"models": [],
"agents": []
},
{
"version": "0.0.1",
"config": {
"name": "planner_assistant",
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 25,
"system_message": "You are a helpful assistant that can suggest a travel plan for a user and utilize any context information provided. Do not ask user for additional context. You are the primary cordinator who will receive suggestions or advice from other agents (local_assistant, language_assistant). You must ensure that the finally plan integrates the suggestions from other agents or team members. YOUR FINAL RESPONSE MUST BE THE COMPLETE PLAN. When the plan is complete and all perspectives are integrated, you can respond with TERMINATE.",
"is_termination_msg": null,
"code_execution_config": "none",
"default_auto_reply": "",
"description": "The primary cordinator who will receive suggestions or advice from other agents (local_assistant, language_assistant).",
"llm_config": {
"config_list": [
{
"api_type": "open_ai",
"model": "gpt-4-1106-preview",
"base_url": null,
"api_version": null
}
],
"temperature": 0,
"cache_seed": null,
"timeout": null,
"max_tokens": 2048,
"extra_body": null
},
"admin_name": "Admin",
"messages": [],
"max_round": 100,
"speaker_selection_method": "auto",
"allow_repeat_speaker": true
},
"user_id": "guestuser@gmail.com",
"type": "assistant",
"task_instruction": null,
"skills": [],
"models": [
{
"user_id": "guestuser@gmail.com",
"api_type": "open_ai",
"description": "OpenAI GPT-4 model",
"model": "gpt-4-1106-preview",
"base_url": null,
"api_version": null
}
],
"agents": []
},
{
"version": "0.0.1",
"config": {
"name": "local_assistant",
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 25,
"system_message": "You are a local assistant that can suggest local activities or places to visit for a user and can utilize any context information provided. You can suggest local activities, places to visit, restaurants to eat at, etc. You can also provide information about the weather, local events, etc. You can provide information about the local area. Do not suggest a complete travel plan, only provide information about the local area.",
"is_termination_msg": null,
"code_execution_config": "none",
"default_auto_reply": "",
"description": "Local Assistant Agent",
"llm_config": {
"config_list": [
{
"api_type": "open_ai",
"model": "gpt-4-1106-preview",
"base_url": null,
"api_version": null
}
],
"temperature": 0,
"cache_seed": null,
"timeout": null,
"max_tokens": 2048,
"extra_body": null
},
"admin_name": "Admin",
"messages": [],
"max_round": 100,
"speaker_selection_method": "auto",
"allow_repeat_speaker": true
},
"user_id": "guestuser@gmail.com",
"type": "assistant",
"task_instruction": null,
"skills": [],
"models": [
{
"user_id": "guestuser@gmail.com",
"api_type": "open_ai",
"description": "OpenAI GPT-4 model",
"model": "gpt-4-1106-preview",
"base_url": null,
"api_version": null
}
],
"agents": []
},
{
"version": "0.0.1",
"config": {
"name": "language_assistant",
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 25,
"system_message": "You are a helpful assistant that can review travel plans, providing feedback on important/critical tips about how best to address language or communication challenges for the given destination. If the plan already includes language tips, you can mention that the plan is satisfactory, with rationale.",
"is_termination_msg": null,
"code_execution_config": "none",
"default_auto_reply": "",
"description": "Language Assistant Agent",
"llm_config": {
"config_list": [
{
"api_type": "open_ai",
"model": "gpt-4-1106-preview",
"base_url": null,
"api_version": null
}
],
"temperature": 0,
"cache_seed": null,
"timeout": null,
"max_tokens": 2048,
"extra_body": null
},
"admin_name": "Admin",
"messages": [],
"max_round": 100,
"speaker_selection_method": "auto",
"allow_repeat_speaker": true
},
"user_id": "guestuser@gmail.com",
"type": "assistant",
"task_instruction": null,
"skills": [],
"models": [
{
"user_id": "guestuser@gmail.com",
"api_type": "open_ai",
"description": "OpenAI GPT-4 model",
"model": "gpt-4-1106-preview",
"base_url": null,
"api_version": null
}
],
"agents": []
}
]
},
"link": {
"agent_id": 54,
"workflow_id": 18,
"agent_type": "receiver",
"sequence_id": 0
}
}
]
}

View File

@@ -0,0 +1,105 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"from autogenstudio import WorkflowManager"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## AutoGen Studio Agent Workflow API Example\n",
"\n",
"This notebook focuses on demonstrating capabilities of the autogen studio workflow python api. \n",
"\n",
"- Declarative Specification of an Agent Workflow \n",
"- Loading the specification and running the resulting agent\n",
"\n",
"\n",
"> Note: The notebook currently demonstrates support for a two agent setup. Support for GroupChat is currently in development."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# load workflow from json file\n",
"workflow_manager = WorkflowManager(workflow=\"two_agent.json\")\n",
"\n",
"# run the workflow on a task\n",
"task_query = \"What is the height of the Eiffel Tower?. Dont write code, just respond to the question.\"\n",
"workflow_manager.run(message=task_query)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# print the agent history\n",
"workflow_manager.agent_history"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Group Chat Agent"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# load workflow from json file\n",
"travel_workflow_manager = WorkflowManager(workflow=\"travel_groupchat.json\")\n",
"\n",
"# run the workflow on a task\n",
"task_query = \"Plan a two day trip to Maui hawaii.\"\n",
"travel_workflow_manager.run(message=task_query)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# print the agent history\n",
"print(len(travel_workflow_manager.agent_history), \"agent messages were involved in the conversation\")\n",
"travel_workflow_manager.agent_history"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "coral",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -0,0 +1,112 @@
{
"user_id": "guestuser@gmail.com",
"name": "Default Workflow",
"type": "autonomous",
"sample_tasks": [
"paint a picture of a glass of ethiopian coffee, freshly brewed in a tall glass cup, on a table right in front of a lush green forest scenery",
"Plot the stock price of NVIDIA YTD."
],
"version": "0.0.1",
"description": "Default workflow",
"summary_method": "last",
"agents": [
{
"agent": {
"version": "0.0.1",
"config": {
"name": "user_proxy",
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 25,
"system_message": "You are a helpful assistant",
"is_termination_msg": null,
"code_execution_config": "local",
"default_auto_reply": "TERMINATE",
"description": "User Proxy Agent Configuration",
"llm_config": false,
"admin_name": "Admin",
"messages": [],
"max_round": 100,
"speaker_selection_method": "auto",
"allow_repeat_speaker": true
},
"user_id": "guestuser@gmail.com",
"type": "userproxy",
"task_instruction": null,
"skills": [],
"models": [],
"agents": []
},
"link": {
"agent_id": 52,
"workflow_id": 19,
"agent_type": "sender",
"sequence_id": 0
}
},
{
"agent": {
"version": "0.0.1",
"config": {
"name": "default_assistant",
"human_input_mode": "NEVER",
"max_consecutive_auto_reply": 25,
"system_message": "You are a helpful AI assistant.\nSolve tasks using your coding and language skills.\nIn the following cases, suggest python code (in a python coding block) or shell script (in a sh coding block) for the user to execute.\n 1. When you need to collect info, use the code to output the info you need, for example, browse or search the web, download/read a file, print the content of a webpage or a file, get the current date/time, check the operating system. After sufficient info is printed and the task is ready to be solved based on your language skill, you can solve the task by yourself.\n 2. When you need to perform some task with code, use the code to perform the task and output the result. Finish the task smartly.\nSolve the task step by step if you need to. If a plan is not provided, explain your plan first. Be clear which step uses code, and which step uses your language skill.\nWhen using code, you must indicate the script type in the code block. The user cannot provide any other feedback or perform any other action beyond executing the code you suggest. The user can't modify your code. So do not suggest incomplete code which requires users to modify. Don't use a code block if it's not intended to be executed by the user.\nIf you want the user to save the code in a file before executing it, put # filename: <filename> inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user.\nIf the result indicates there is an error, fix the error and output the code again. Suggest the full code instead of partial code or code changes. If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try.\nWhen you find an answer, verify the answer carefully. Include verifiable evidence in your response if possible.\nReply \"TERMINATE\" in the end when everything is done.\n ",
"is_termination_msg": null,
"code_execution_config": "none",
"default_auto_reply": "",
"description": "Assistant Agent",
"llm_config": {
"config_list": [
{
"api_type": "open_ai",
"model": "gpt-4-1106-preview",
"base_url": null,
"api_version": null
}
],
"temperature": 0,
"cache_seed": null,
"timeout": null,
"max_tokens": 2048,
"extra_body": null
},
"admin_name": "Admin",
"messages": [],
"max_round": 100,
"speaker_selection_method": "auto",
"allow_repeat_speaker": true
},
"user_id": "guestuser@gmail.com",
"type": "assistant",
"task_instruction": null,
"skills": [
{
"user_id": "guestuser@gmail.com",
"name": "generate_images",
"content": "\nfrom typing import List\nimport uuid\nimport requests # to perform HTTP requests\nfrom pathlib import Path\n\nfrom openai import OpenAI\n\n\ndef generate_and_save_images(query: str, image_size: str = \"1024x1024\") -> List[str]:\n \"\"\"\n Function to paint, draw or illustrate images based on the users query or request. Generates images from a given query using OpenAI's DALL-E model and saves them to disk. Use the code below anytime there is a request to create an image.\n\n :param query: A natural language description of the image to be generated.\n :param image_size: The size of the image to be generated. (default is \"1024x1024\")\n :return: A list of filenames for the saved images.\n \"\"\"\n\n client = OpenAI() # Initialize the OpenAI client\n response = client.images.generate(model=\"dall-e-3\", prompt=query, n=1, size=image_size) # Generate images\n\n # List to store the file names of saved images\n saved_files = []\n\n # Check if the response is successful\n if response.data:\n for image_data in response.data:\n # Generate a random UUID as the file name\n file_name = str(uuid.uuid4()) + \".png\" # Assuming the image is a PNG\n file_path = Path(file_name)\n\n img_url = image_data.url\n img_response = requests.get(img_url)\n if img_response.status_code == 200:\n # Write the binary content to a file\n with open(file_path, \"wb\") as img_file:\n img_file.write(img_response.content)\n print(f\"Image saved to {file_path}\")\n saved_files.append(str(file_path))\n else:\n print(f\"Failed to download the image from {img_url}\")\n else:\n print(\"No image data found in the response!\")\n\n # Return the list of saved files\n return saved_files\n\n\n# Example usage of the function:\n# generate_and_save_images(\"A cute baby sea otter\")\n",
"description": "Generate and save images based on a user's query.",
"secrets": {},
"libraries": {}
}
],
"models": [
{
"user_id": "guestuser@gmail.com",
"api_type": "open_ai",
"description": "OpenAI GPT-4 model",
"model": "gpt-4-1106-preview",
"base_url": null,
"api_version": null
}
],
"agents": []
},
"link": {
"agent_id": 53,
"workflow_id": 19,
"agent_type": "receiver",
"sequence_id": 0
}
}
]
}

View File

@@ -0,0 +1,68 @@
[build-system]
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"
[project]
name = "autogenstudio"
authors = [
{ name="AutoGen Team", email="autogen@microsoft.com" },
]
description = "AutoGen Studio"
readme = "README.md"
license = { file="LICENSE" }
requires-python = ">=3.9, <3.13"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
dependencies = [
"pydantic",
"fastapi",
"typer",
"uvicorn",
"arxiv",
"pyautogen[gemini,anthropic,mistral]==0.2.34",
"python-dotenv",
"websockets",
"numpy < 2.0.0",
"sqlmodel",
"psycopg",
"alembic",
"loguru",
]
optional-dependencies = {web = ["fastapi", "uvicorn"], database = ["psycopg"]}
dynamic = ["version"]
[tool.setuptools]
include-package-data = true
[tool.setuptools.dynamic]
version = {attr = "autogenstudio.version.VERSION"}
readme = {file = ["README.md"]}
[tool.setuptools.packages.find]
include = ["autogenstudio*"]
exclude = ["*.tests*"]
namespaces = false
[tool.setuptools.package-data]
"autogenstudio" = ["*.*"]
[tool.pytest.ini_options]
filterwarnings = [
"ignore:Deprecated call to `pkg_resources\\.declare_namespace\\('.*'\\):DeprecationWarning",
"ignore::DeprecationWarning:google.rpc",
]
[project.urls]
"Homepage" = "https://github.com/microsoft/autogen"
"Bug Tracker" = "https://github.com/microsoft/autogen/issues"
[project.scripts]
autogenstudio = "autogenstudio.cli:run"

View File

@@ -0,0 +1 @@
.

View File

@@ -0,0 +1,3 @@
from setuptools import setup
setup()

View File

@@ -0,0 +1,56 @@
import os
from autogenstudio.datamodel import Agent, Skill
from autogenstudio.utils import utils
class TestUtilSaveSkillsToFile:
def test_save_skills_to_file(self):
# cleanup test work_dir
try:
os.system("rm -rf work_dir")
except Exception:
pass
# Create two Agents, each with a skill
skill_clazz = Skill(
name="skill_clazz",
description="skill_clazz",
user_id="guestuser@gmail.com",
libraries=["lib1.0", "lib1.1"],
content="I am the skill clazz content",
secrets=[{"secret": "secret_1", "value": "value_1"}],
agents=[],
)
skill_dict = Skill(
name="skill_dict",
description="skill_dict",
user_id="guestuser@gmail.com",
libraries=["lib2.0", "lib2.1"],
content="I am the skill dict content",
secrets=[{"secret": "secret_2", "value": "value_2"}],
agents=[],
)
Agent(skills=[skill_clazz])
Agent(skills=[skill_dict])
# test from flow
skills = [skill_dict.__dict__, skill_clazz]
utils.save_skills_to_file(skills, work_dir="work_dir")
f = open("work_dir/skills.py", "r")
skills_content = f.read()
assert skills_content.find(skill_clazz.content)
assert skills_content.find(skill_dict.content)
# cleanup test work_dir
try:
os.system("rm -rf work_dir")
except Exception:
pass

View File

@@ -0,0 +1,47 @@
import os
from autogenstudio.datamodel import Skill
from autogenstudio.utils import utils
class TestUtilGetSkillsPrompt:
def test_get_skills_prompt(self):
skill_clazz = Skill(
name="skill_clazz",
description="skill_clazz",
user_id="guestuser@gmail.com",
libraries=["lib1.0", "lib1.1"],
content="I am the skill clazz content",
secrets=[{"secret": "secret_1", "value": "value_1"}],
agents=[],
)
skill_dict = Skill(
name="skill_dict",
description="skill_dict",
user_id="guestuser@gmail.com",
libraries=["lib2.0", "lib2.1"],
content="I am the skill dict content",
secrets=[{"secret": "secret_2", "value": "value_2"}],
agents=[],
)
skills = [skill_dict.__dict__, skill_clazz]
prompt = utils.get_skills_prompt(skills, work_dir="work_dir")
# test that prompt contains contents of skills class and dict
assert prompt.find(skill_clazz.content) > 0
assert prompt.find(skill_dict.content) > 0
# test that secrets are set in environ
assert os.getenv("secret_1") == "value_1"
assert os.getenv("secret_2") == "value_2"
# cleanup test work_dir
try:
os.system("rm -rf work_dir")
except Exception:
pass