Sample Web Application Built with AutoGen (#695)

* Adding research assistant code

* Adding research assistant code

* checking in RA files

* Remove used text file

* Update README.md to include Saleema's name to the Contributors list.

* remove extraneous files

* update gitignore

* improve structure on global skills

* fix linting error

* readme update

* readme update

* fix wrong function bug

* readme update

* update ui build

* cleanup, remove unused modules

* readme and docs updates

* set default user

* ui build update

* add screenshot to improve instructions

* remove logout behaviour, replace with note to developers to add their own logout logic

* Create blog and edit ARA README

* Added the stock prices example in the readme for ARA

* Include edits from review with Saleema

* fix format issues

* Cosmetic changes for betting debug messages

* edit authors

* remove references to request_timeout to support autogen v0.0.2

* update bg color for UI

* readme update

* update research assistant blog post

* omit samples folder from codecov

* ui build update + precommit refactor

* formattiing updates  fromo pre-commit

* readme update

* remove compiled source files

* update gitignore

* refactor, file removals

* refactor for improved structure - datamodel, chat and db helper

* update gitignore

* refactor, file removals

* refactor for improved structure - datamodel, chat and db helper

* refactor skills view

* general refactor

* gitignore update and general refactor

* skills update

* general refactor

* ui folder structure refactor

* improve support for skills loading

* add fetch profile default skill

* refactor chat to autogenchat

* qol refactor

* improve metadata display

* early support for autogenflow in ui

* docs update general refactor

* general refactor

* readme update

* readme update

* readme and cli update

* pre-commit updates

* precommit update

* readme update

* add steup.py for older python build versions

* add manifest.in, update app icon

* in-progress changes to agent specification

* remove use_cache refs

* update datamodel, and fix for default serverurl

* request_timeout

* readme update, fix autogen values

* fix pyautogen version

* precommit formatting and other qol items

* update folder structure

* req update

* readme and docs update

* docs update

* remove duplicate in yaml file

* add support for explicit skills addition

* readme and documentation updates

* general refactor

* remove blog post, schedule for future PR

* readme update, add info on llmconfig

* make use_cache False by default unless set

* minor ui updates

* upgrade ui to use latest uatogen lib version 0.2.0b5

* Ui refactor, support for adding arbitrary model specifications

* formatting/precommit checks

* update readme, utils default skill

---------

Co-authored-by: Piali Choudhury <pialic@microsoft.com>
Co-authored-by: Chi Wang <wang.chi@microsoft.com>
This commit is contained in:
Victor Dibia 2023-11-20 10:40:30 -08:00 committed by GitHub
parent a4d9ce8c5b
commit 143e49c6e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 5164 additions and 0 deletions

View File

@ -3,3 +3,4 @@ branch = True
source = autogen
omit =
*test*
*samples*

View File

@ -0,0 +1,24 @@
database.sqlite
.cache/*
autogenra/web/files/user/*
autogenra/web/files/ui/*
OAI_CONFIG_LIST
scratch/
autogenra/web/workdir/*
autogenra/web/ui/*
autogenra/web/skills/user/*
.release.sh
# 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,5 @@
recursive-include autogenra/web/ui *
recursive-exclude notebooks *
recursive-exclude frontend *
recursive-exclude docs *
recursive-exclude tests *

View File

@ -0,0 +1,119 @@
# AutoGen Assistant
![ARA](./docs/ara_stockprices.png)
AutoGen Assistant is an Autogen-powered AI app (user interface) that can converse with you to help you conduct research, write and execute code, run saved skills, create new skills (explicitly and by demonstration), and adapt in response to your interactions.
### Capabilities / Roadmap
Some of the capabilities supported by the app frontend include the following:
- [x] Select fron a list of agents (current support for two agent workflows - `UserProxyAgent` and `AssistantAgent`)
- [x] Modify agent configuration (e.g. temperature, model, agent system message, model etc) and chat with updated agent configurations.
- [x] View agent messages and output files in the UI from agent runs.
- [ ] Support for more complex agent workflows (e.g. `GroupChat` workflows)
- [ ] Improved user experience (e.g., streaming intermediate model output, better summarization of agent responses, etc)
Project Structure:
- _autogenra/_ code for the backend classes and web api (FastAPI)
- _frontend/_ code for the webui, built with Gatsby and Tailwind
## Getting Started
AutoGen requires access to an LLM. Please see the [AutoGen docs](https://microsoft.github.io/autogen/docs/FAQ#set-your-api-endpoints) on how to configure access to your LLM provider. In this sample, We recommend setting up your `OPENAI_API_KEY` or `AZURE_OPENAI_API_KEY` environment variable and then specifying the exact model parameters to be used in the `llm_config` that is passed to each agent specification. See the `get_default_agent_config()` method in `utils.py` to see an example of setting up `llm_config`. The example below shows how to configure access to an Azure OPENAI LLM.
```python
llm_config = LLMConfig(
config_list=[{
"model": "gpt-4",
"api_key": "<azure_api_key>",
"api_base": "<azure api base>",
"api_type": "azure",
"api_version": "2023-06-01-preview"
}],
temperature=0,
)
```
```bash
export OPENAI_API_KEY=<your_api_key>
```
### Install and Run
To install a prebuilt version of the app from PyPi. We highly recommend using a virtual environment (e.g. miniconda) and **python 3.10+** to avoid dependency conflicts.
```bash
pip install autogenra
autogenra ui --port 8081 # run the web ui on port 8081
```
### Install from Source
To install the app from source, clone the repository and install the dependencies.
```bash
pip install -e .
```
You will also need to build the app front end. Note that your Gatsby requires node > 14.15.0 . You may need to [upgrade your node](https://stackoverflow.com/questions/10075990/upgrading-node-js-to-latest-version) version as needed.
```bash
npm install --global yarn
cd frontend
yarn install
yarn build
```
The command above will build the frontend ui and copy the build artifacts to the `autogenra` web ui folder. Note that you may have to run `npm install --force --legacy-peer-deps` to force resolve some peer dependencies.
Run the web ui:
```bash
autogenra ui --port 8081 # run the web ui on port 8081
```
Navigate to <http://localhost:8081/> to view the web ui.
To update the web ui, navigate to the frontend directory, make changes and rebuild the ui.
## Capabilities
This demo focuses on the research assistant use case with some generalizations:
- **Skills**: The agent is provided with a list of skills that it can leverage while attempting to address a user's query. Each skill is a python function that may be in any file in a folder made availabe to the agents. We separate the concept of global skills available to all agents `backend/files/global_utlis_dir` and user level skills `backend/files/user/<user_hash>/utils_dir`, relevant in a multi user environment. Agents are aware skills as they are appended to the system message. A list of example skills is available in the `backend/global_utlis_dir` folder. Modify the file or create a new file with a function in the same directory to create new global skills.
- **Conversation Persistence**: Conversation history is persisted in an sqlite database `database.sqlite`.
- **Default Agent Workflow**: The default a sample workflow with two agents - a user proxy agent and an assistant agent.
## Example Usage
Let us use a simple query demonstrating the capabilities of the research assistant.
```
Plot a chart of NVDA and TESLA stock price YTD. Save the result to a file named nvda_tesla.png
```
The agents responds by _writing and executing code_ to create a python program to generate the chart with the stock prices.
> Note than there could be multiple turns between the `AssistantAgent` and the `UserProxyAgent` to produce and execute the code in order to complete the task.
![ARA](./docs/ara_stockprices.png)
> Note: You can also view the debug console that generates useful information to see how the agents are interacting in the background.
<!-- ![ARA](./docs/ara_console.png) -->
## FAQ
- How do I add more skills to the research assistant? This can be done by adding a new file with documented functions to `autogenra/web/skills/global` directory.
- How do I specify the agent configuration (e.g. temperature, model, agent system message, model etc). You can do either from the UI interface or by modifying the default agent configuration in `utils.py` (`get_default_agent_config()` method)
- How do I reset the conversation? You can reset the conversation by deleting the `database.sqlite` file. You can also delete user files by deleting the `autogenra/web/files/user/<user_id_md5hash>` folder.
- How do I view messages generated by agents? You can view the messages generated by the agents in the debug console. You can also view the messages in the `database.sqlite` file.
## Acknowledgements
Based on the [AutoGen](https://microsoft.github.io/autogen) project.
Adapted in October 2023 from a research prototype (original credits: Gagan Bansal, Adam Fourney, Victor Dibia, Piali Choudhury, Saleema Amershi, Ahmed Awadallah, Chi Wang)

View File

@ -0,0 +1,64 @@
import json
import time
from typing import List
from .datamodel import FlowConfig, Message
from .utils import extract_successful_code_blocks, get_default_agent_config, get_modified_files
from .autogenflow import AutoGenFlow
import os
class ChatManager:
def __init__(self) -> None:
pass
def chat(self, message: Message, history: List, flow_config: FlowConfig = None, **kwargs) -> None:
work_dir = kwargs.get("work_dir", None)
scratch_dir = os.path.join(work_dir, "scratch")
skills_suffix = kwargs.get("skills_prompt", "")
# if no flow config is provided, use the default
if flow_config is None:
flow_config = get_default_agent_config(scratch_dir, skills_suffix=skills_suffix)
# print("Flow config: ", flow_config)
flow = AutoGenFlow(config=flow_config, history=history, work_dir=scratch_dir, asst_prompt=skills_suffix)
message_text = message.content.strip()
output = ""
start_time = time.time()
metadata = {}
flow.run(message=f"{message_text}", clear_history=False)
agent_chat_messages = flow.receiver.chat_messages[flow.sender][len(history) :]
metadata["messages"] = agent_chat_messages
successful_code_blocks = extract_successful_code_blocks(agent_chat_messages)
successful_code_blocks = "\n\n".join(successful_code_blocks)
output = (
(
flow.sender.last_message()["content"]
+ "\n The following code snippets were used: \n"
+ successful_code_blocks
)
if successful_code_blocks
else flow.sender.last_message()["content"]
)
metadata["code"] = ""
end_time = time.time()
metadata["time"] = end_time - start_time
modified_files = get_modified_files(start_time, end_time, scratch_dir, dest_dir=work_dir)
metadata["files"] = modified_files
print("Modified files: ", modified_files)
output_message = Message(
user_id=message.user_id,
root_msg_id=message.root_msg_id,
role="assistant",
content=output,
metadata=json.dumps(metadata),
)
return output_message

View File

@ -0,0 +1,134 @@
from typing import List, Optional
from dataclasses import asdict
import autogen
from .datamodel import AgentFlowSpec, FlowConfig, Message
class AutoGenFlow:
"""
AutoGenFlow class to load agents from a provided configuration and run a chat between them
"""
def __init__(
self, config: FlowConfig, history: Optional[List[Message]] = None, work_dir: str = None, asst_prompt: str = None
) -> None:
"""
Initializes the AutoGenFlow with agents specified in the config and optional
message history.
Args:
config: The configuration settings for the sender and receiver agents.
history: An optional list of previous messages to populate the agents' history.
"""
self.work_dir = work_dir
self.asst_prompt = asst_prompt
self.sender = self.load(config.sender)
self.receiver = self.load(config.receiver)
if history:
self.populate_history(history)
def _sanitize_history_message(self, message: str) -> str:
"""
Sanitizes the message e.g. remove references to execution completed
Args:
message: The message to be sanitized.
Returns:
The sanitized message.
"""
to_replace = ["execution succeeded", "exitcode"]
for replace in to_replace:
message = message.replace(replace, "")
return message
def populate_history(self, history: List[Message]) -> None:
"""
Populates the agent message history from the provided list of messages.
Args:
history: A list of messages to populate the agents' history.
"""
for msg in history:
if isinstance(msg, dict):
msg = Message(**msg)
if msg.role == "user":
self.sender.send(
msg.content,
self.receiver,
request_reply=False,
)
elif msg.role == "assistant":
self.receiver.send(
msg.content,
self.sender,
request_reply=False,
)
def sanitize_agent_spec(self, agent_spec: AgentFlowSpec) -> AgentFlowSpec:
"""
Sanitizes the agent spec by setting loading defaults
Args:
config: The agent configuration to be sanitized.
agent_type: The type of the agent.
Returns:
The sanitized agent configuration.
"""
agent_spec.config.is_termination_msg = agent_spec.config.is_termination_msg or (
lambda x: "TERMINATE" in x.get("content", "").rstrip()
)
if agent_spec.type == "userproxy":
code_execution_config = agent_spec.config.code_execution_config or {}
code_execution_config["work_dir"] = self.work_dir
agent_spec.config.code_execution_config = code_execution_config
if agent_spec.type == "assistant":
agent_spec.config.system_message = (
autogen.AssistantAgent.DEFAULT_SYSTEM_MESSAGE
+ "\n\n"
+ agent_spec.config.system_message
+ "\n\n"
+ self.asst_prompt
)
return agent_spec
def load(self, agent_spec: AgentFlowSpec) -> autogen.Agent:
"""
Loads an agent based on the provided agent specification.
Args:
agent_spec: The specification of the agent to be loaded.
Returns:
An instance of the loaded agent.
"""
agent: autogen.Agent
agent_spec = self.sanitize_agent_spec(agent_spec)
if agent_spec.type == "assistant":
agent = autogen.AssistantAgent(**asdict(agent_spec.config))
elif agent_spec.type == "userproxy":
agent = autogen.UserProxyAgent(**asdict(agent_spec.config))
else:
raise ValueError(f"Unknown agent type: {agent_spec.type}")
return agent
def run(self, message: str, clear_history: bool = False) -> None:
"""
Initiates a chat between the sender and receiver agents with an initial message
and an option to clear the history.
Args:
message: The initial message to start the chat.
clear_history: If set to True, clears the chat history before initiating.
"""
self.sender.initiate_chat(
self.receiver,
message=message,
clear_history=clear_history,
)

View File

@ -0,0 +1,48 @@
import os
from typing_extensions import Annotated
import typer
import uvicorn
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 = False,
):
"""
Launch the Autogen RA UI CLI .Pass in parameters host, port, workers, and reload to override the default values.
"""
os.environ["AUTOGENUI_API_DOCS"] = str(docs)
uvicorn.run(
"autogenra.web.app:app",
host=host,
port=port,
workers=workers,
reload=reload,
)
@app.command()
def version():
"""
Print the version of the Autogen RA UI CLI.
"""
typer.echo(f"Autogen RA UI CLI version: {VERSION}")
def run():
app()
if __name__ == "__main__":
app()

View File

@ -0,0 +1,120 @@
import uuid
from datetime import datetime
from typing import Any, Callable, Dict, List, Literal, Optional, Union
from pydantic.dataclasses import dataclass
from dataclasses import field
@dataclass
class Message(object):
user_id: str
role: str
content: str
root_msg_id: Optional[str] = None
msg_id: Optional[str] = None
timestamp: Optional[datetime] = None
personalize: Optional[bool] = False
ra: Optional[str] = None
code: Optional[str] = None
metadata: Optional[Any] = None
def __post_init__(self):
if self.msg_id is None:
self.msg_id = str(uuid.uuid4())
if self.timestamp is None:
self.timestamp = datetime.now()
def dict(self):
return {
"user_id": self.user_id,
"role": self.role,
"content": self.content,
"root_msg_id": self.root_msg_id,
"msg_id": self.msg_id,
"timestamp": self.timestamp,
"personalize": self.personalize,
"ra": self.ra,
"code": self.code,
"metadata": self.metadata,
}
# web api data models
# autogenflow data models
@dataclass
class ModelConfig:
"""Data model for Model Config item in LLMConfig for Autogen"""
model: str
api_key: Optional[str] = None
base_url: Optional[str] = None
api_type: Optional[str] = None
api_version: Optional[str] = None
@dataclass
class LLMConfig:
"""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
@dataclass
class AgentConfig:
"""Data model for Agent Config for Autogen"""
name: str
llm_config: Optional[LLMConfig] = 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: Optional[Union[bool, str, Dict[str, Any]]] = None
@dataclass
class AgentFlowSpec:
"""Data model to help flow load agents from config"""
type: Literal["assistant", "userproxy", "groupchat"]
config: AgentConfig = field(default_factory=AgentConfig)
@dataclass
class FlowConfig:
"""Data model for Flow Config for Autogen"""
name: str
sender: AgentFlowSpec
receiver: Union[AgentFlowSpec, List[AgentFlowSpec]]
type: Literal["default", "groupchat"] = "default"
@dataclass
class ChatWebRequestModel(object):
"""Data model for Chat Web Request for Web End"""
message: Message
flow_config: FlowConfig
@dataclass
class DeleteMessageWebRequestModel(object):
user_id: str
msg_id: str
@dataclass
class ClearDBWebRequestModel(object):
user_id: str
@dataclass
class CreateSkillWebRequestModel(object):
user_id: str
skills: Union[str, List[str]]

View File

@ -0,0 +1,114 @@
import logging
import sqlite3
import threading
import os
from typing import Any, List, Dict, Tuple
lock = threading.Lock()
logger = logging.getLogger()
class DBManager:
"""
A database manager class that handles the creation and interaction with an SQLite database.
"""
def __init__(self, path: str = "database.sqlite", **kwargs: Any) -> None:
"""
Initializes the DBManager object, creates a database if it does not exist, and establishes a connection.
Args:
path (str): The file path to the SQLite database file.
**kwargs: Additional keyword arguments to pass to the sqlite3.connect method.
"""
self.path = path
# check if the database exists, if not create it
if not os.path.exists(self.path):
logger.info("Creating database")
self.init_db(path=self.path, **kwargs)
try:
self.conn = sqlite3.connect(self.path, check_same_thread=False, **kwargs)
self.cursor = self.conn.cursor()
except Exception as e:
logger.error("Error connecting to database: %s", e)
raise e
def init_db(self, path: str = "database.sqlite", **kwargs: Any) -> None:
"""
Initializes the database by creating necessary tables.
Args:
path (str): The file path to the SQLite database file.
**kwargs: Additional keyword arguments to pass to the sqlite3.connect method.
"""
# Connect to the database (or create a new one if it doesn't exist)
self.conn = sqlite3.connect(path, check_same_thread=False, **kwargs)
self.cursor = self.conn.cursor()
# Create the table with the specified columns, appropriate data types, and a UNIQUE constraint on (root_msg_id, msg_id)
self.cursor.execute(
"""
CREATE TABLE IF NOT EXISTS messages (
user_id TEXT NOT NULL,
root_msg_id TEXT NOT NULL,
msg_id TEXT,
role TEXT NOT NULL,
content TEXT NOT NULL,
metadata TEXT,
timestamp DATETIME,
UNIQUE (user_id, root_msg_id, msg_id)
)
"""
)
# Create a table for personalization profiles
self.cursor.execute(
"""
CREATE TABLE IF NOT EXISTS personalization_profiles (
user_id INTEGER NOT NULL,
profile TEXT,
timestamp DATETIME NOT NULL,
UNIQUE (user_id)
)
"""
)
# Commit the changes and close the connection
self.conn.commit()
def query(self, query: str, args: Tuple = (), json: bool = False) -> List[Dict[str, Any]]:
"""
Executes a given SQL query and returns the results.
Args:
query (str): The SQL query to execute.
args (Tuple): The arguments to pass to the SQL query.
json (bool): If True, the results will be returned as a list of dictionaries.
Returns:
List[Dict[str, Any]]: The result of the SQL query.
"""
try:
with lock:
self.cursor.execute(query, args)
result = self.cursor.fetchall()
self.commit()
if json:
result = [dict(zip([key[0] for key in self.cursor.description], row)) for row in result]
return result
except Exception as e:
logger.error("Error running query with query %s and args %s: %s", query, args, e)
raise e
def commit(self) -> None:
"""
Commits the current transaction to the database.
"""
self.conn.commit()
def close(self) -> None:
"""
Closes the database connection.
"""
self.conn.close()

View File

@ -0,0 +1,406 @@
import ast
import hashlib
from typing import List, Dict, Union
import os
import shutil
import re
import uuid
import autogen
from .datamodel import AgentConfig, AgentFlowSpec, FlowConfig, LLMConfig, Message
from .db import DBManager
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 save_message(message: Message, dbmanager: DBManager) -> None:
"""
Save a message in the database using the provided database manager.
:param message: The Message object containing message data
:param dbmanager: The DBManager instance used to interact with the database
"""
query = "INSERT INTO messages (user_id, root_msg_id, msg_id, role, content, metadata, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?)"
args = (
message.user_id,
message.root_msg_id,
message.msg_id,
message.role,
message.content,
message.metadata,
message.timestamp,
)
dbmanager.query(query=query, args=args)
def load_messages(user_id: str, dbmanager: DBManager) -> List[dict]:
"""
Load messages for a specific user from the database, sorted by timestamp.
:param user_id: The ID of the user whose messages are to be loaded
:param dbmanager: The DBManager instance to interact with the database
:return: A list of dictionaries, each representing a message
"""
query = "SELECT * FROM messages WHERE user_id = ?"
args = (user_id,)
result = dbmanager.query(query=query, args=args, json=True)
# Sort by timestamp ascending
result = sorted(result, key=lambda k: k["timestamp"], reverse=False)
return result
def delete_message(user_id: str, msg_id: str, dbmanager: DBManager, delete_all: bool = False) -> List[dict]:
"""
Delete a specific message or all messages for a user from the database.
:param user_id: The ID of the user whose messages are to be deleted
:param msg_id: The ID of the specific message to be deleted (ignored if delete_all is True)
:param dbmanager: The DBManager instance to interact with the database
:param delete_all: If True, all messages for the user will be deleted
:return: A list of the remaining messages if not all were deleted, otherwise an empty list
"""
if delete_all:
query = "DELETE FROM messages WHERE user_id = ?"
args = (user_id,)
dbmanager.query(query=query, args=args)
return []
else:
query = "DELETE FROM messages WHERE user_id = ? AND msg_id = ?"
args = (user_id, msg_id)
dbmanager.query(query=query, args=args)
messages = load_messages(user_id=user_id, dbmanager=dbmanager)
return messages
def get_modified_files(start_timestamp: float, end_timestamp: float, source_dir: str, dest_dir: str) -> List[str]:
"""
Copy files from source_dir that were modified within a specified timestamp range
to dest_dir, renaming files if they already exist there. The function excludes
files with certain file extensions and names.
:param start_timestamp: The start timestamp to filter modified files.
:param end_timestamp: The end timestamp to filter modified files.
:param source_dir: The directory to search for modified files.
:param dest_dir: The destination directory to copy modified files to.
:return: A list of file paths in dest_dir that were modified and copied over.
Files with extensions "__pycache__", "*.pyc", "__init__.py", and "*.cache"
are ignored.
"""
modified_files = []
ignore_extensions = {".pyc", ".cache"}
ignore_files = {"__pycache__", "__init__.py"}
for root, dirs, files in os.walk(source_dir):
# Excluding the directory "__pycache__" if present
dirs[:] = [d for d in dirs if d not in ignore_files]
for file in files:
file_path = os.path.join(root, file)
file_ext = os.path.splitext(file)[1]
file_name = os.path.basename(file)
if file_ext in ignore_extensions or file_name in ignore_files:
continue
file_mtime = os.path.getmtime(file_path)
if start_timestamp < file_mtime < end_timestamp:
dest_file_path = os.path.join(dest_dir, file)
copy_idx = 1
while os.path.exists(dest_file_path):
base, extension = os.path.splitext(file)
# Handling potential name conflicts by appending a number
dest_file_path = os.path.join(dest_dir, f"{base}_{copy_idx}{extension}")
copy_idx += 1
# Copying the modified file to the destination directory
shutil.copy2(file_path, dest_file_path)
uid = dest_dir.split("/")[-1]
print("******", uid)
file_path = f"files/user/{uid}/{dest_file_path.split('/')[-1]}"
modified_files.append(file_path)
return modified_files
def init_webserver_folders(root_file_path: str) -> Dict[str, str]:
"""
Initialize folders needed for a web server, such as static file directories
and user-specific data directories.
:param root_file_path: The root directory where webserver folders will be created
:return: A dictionary with the path of each created folder
"""
files_static_root = os.path.join(root_file_path, "files/")
static_folder_root = os.path.join(root_file_path, "ui")
workdir_root = os.path.join(root_file_path, "workdir")
skills_dir = os.path.join(root_file_path, "skills")
user_skills_dir = os.path.join(skills_dir, "user")
global_skills_dir = os.path.join(skills_dir, "global")
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)
os.makedirs(workdir_root, exist_ok=True)
os.makedirs(skills_dir, exist_ok=True)
os.makedirs(user_skills_dir, exist_ok=True)
os.makedirs(global_skills_dir, exist_ok=True)
folders = {
"files_static_root": files_static_root,
"static_folder_root": static_folder_root,
"workdir_root": workdir_root,
"skills_dir": skills_dir,
"user_skills_dir": user_skills_dir,
"global_skills_dir": global_skills_dir,
}
return folders
def skill_from_folder(folder: str) -> List[Dict[str, str]]:
"""
Given a folder, return a dict of the skill (name, python file content). Only python files are considered.
:param folder: The folder to search for skills
:return: A list of dictionaries, each representing a skill
"""
skills = []
for root, dirs, files in os.walk(folder):
for file in files:
if file.endswith(".py"):
skill_name = file.split(".")[0]
skill_file_path = os.path.join(root, file)
with open(skill_file_path, "r", encoding="utf-8") as f:
skill_content = f.read()
skills.append({"name": skill_name, "content": skill_content, "file_name": file})
return skills
def get_all_skills(user_skills_path: str, global_skills_path: str, dest_dir: str = None) -> List[Dict[str, str]]:
"""
Get all skills from the user and global skills directories. If dest_dir, copy all skills to dest_dir.
:param user_skills_path: The path to the user skills directory
:param global_skills_path: The path to the global skills directory
:param dest_dir: The destination directory to copy all skills to
:return: A dictionary of user and global skills
"""
user_skills = skill_from_folder(user_skills_path)
os.makedirs(user_skills_path, exist_ok=True)
global_skills = skill_from_folder(global_skills_path)
skills = {
"user": user_skills,
"global": global_skills,
}
if dest_dir:
# chcek if dest_dir exists
if not os.path.exists(dest_dir):
os.makedirs(dest_dir)
# copy all skills to dest_dir
for skill in user_skills + global_skills:
skill_file_path = os.path.join(dest_dir, skill["file_name"])
with open(skill_file_path, "w", encoding="utf-8") as f:
f.write(skill["content"])
return skills
def get_skills_prompt(skills: List[Dict[str, str]]) -> str:
"""
Get a prompt with the content of all skills.
:param skills: A dictionary of user and global skills
:return: A string containing the content of all skills
"""
user_skills = skills["user"]
global_skills = skills["global"]
all_skills = user_skills + global_skills
prompt = """
While solving the task you may use functions in the files below.
To use a function from a file in code, import the file and then use the function.
If you need to install python packages, write shell code to
install via pip and use --quiet option.
"""
for skill in all_skills:
prompt += f"""
##### Begin of {skill["file_name"]} #####
{skill["content"]}
#### End of {skill["file_name"]} ####
"""
return prompt
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):
print(f"The folder {folder} does not exist.")
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
print(f"Failed to delete {path}. Reason: {e}")
def get_default_agent_config(work_dir: str, skills_suffix: str = "") -> FlowConfig:
"""
Get a default agent flow config .
"""
llm_config = LLMConfig(
config_list=[{"model": "gpt-4"}],
temperature=0,
)
USER_PROXY_INSTRUCTIONS = """If the request has been addressed sufficiently, summarize the answer and end with the word TERMINATE. Otherwise, ask a follow-up question.
"""
userproxy_spec = AgentFlowSpec(
type="userproxy",
config=AgentConfig(
name="user_proxy",
human_input_mode="NEVER",
system_message=USER_PROXY_INSTRUCTIONS,
code_execution_config={
"work_dir": work_dir,
"use_docker": False,
},
max_consecutive_auto_reply=10,
llm_config=llm_config,
is_termination_msg=lambda x: x.get("content", "").rstrip().endswith("TERMINATE"),
),
)
assistant_spec = AgentFlowSpec(
type="assistant",
config=AgentConfig(
name="primary_assistant",
system_message=autogen.AssistantAgent.DEFAULT_SYSTEM_MESSAGE + skills_suffix,
llm_config=llm_config,
),
)
flow_config = FlowConfig(
name="default",
sender=userproxy_spec,
receiver=assistant_spec,
type="default",
)
return flow_config
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 = []
code_block_regex = r"```[\s\S]*?```" # Regex pattern to capture code blocks enclosed in triple backticks.
for i, message in enumerate(messages):
if message["role"] == "user" and "execution succeeded" in message["content"]:
if i > 0 and messages[i - 1]["role"] == "assistant":
prev_content = messages[i - 1]["content"]
# Find all matches for code blocks
code_blocks = re.findall(code_block_regex, prev_content)
successful_code_blocks.extend(code_blocks) # Add the code blocks with backticks
return successful_code_blocks
def create_skills_from_code(dest_dir: str, skills: Union[str, List[str]]) -> None:
"""
Create skills from a list of code blocks.
Parameters:
dest_dir (str): The destination directory to copy all skills to.
skills (Union[str, List[str]]): A list of strings containing code blocks.
"""
# Ensure skills is a list
if isinstance(skills, str):
skills = [skills]
# Check if dest_dir exists
if not os.path.exists(dest_dir):
os.makedirs(dest_dir)
for skill in skills:
# Attempt to parse the code and extract the top-level function name
try:
parsed = ast.parse(skill)
function_name = None
for node in parsed.body:
if isinstance(node, ast.FunctionDef):
function_name = node.name
break
if function_name is None:
raise ValueError("No top-level function definition found.")
# Sanitize the function name for use as a file name
function_name = "".join(ch for ch in function_name if ch.isalnum() or ch == "_")
skill_file_name = f"{function_name}.py"
except (ValueError, SyntaxError):
skill_file_name = "new_skill.py"
# If the generated/sanitized name already exists, append an index
skill_file_path = os.path.join(dest_dir, skill_file_name)
index = 1
while os.path.exists(skill_file_path):
base, ext = os.path.splitext(skill_file_name)
if base.endswith(f"_{index - 1}"):
base = base.rsplit("_", 1)[0]
skill_file_path = os.path.join(dest_dir, f"{base}_{index}{ext}")
index += 1
# Write the skill to the file
with open(skill_file_path, "w", encoding="utf-8") as f:
f.write(skill)

View File

@ -0,0 +1,2 @@
VERSION = "0.0.05a"
APP_NAME = "autogenra"

View File

@ -0,0 +1,204 @@
import json
import os
import traceback
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi import HTTPException
from ..db import DBManager
from ..datamodel import (
ChatWebRequestModel,
ClearDBWebRequestModel,
CreateSkillWebRequestModel,
DeleteMessageWebRequestModel,
Message,
)
from ..autogenchat import ChatManager
from ..utils import (
create_skills_from_code,
delete_files_in_folder,
get_all_skills,
load_messages,
md5_hash,
save_message,
delete_message,
init_webserver_folders,
get_skills_prompt,
)
app = FastAPI()
# 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=["*"],
)
root_file_path = os.path.dirname(os.path.abspath(__file__))
folders = init_webserver_folders(root_file_path) # init folders skills, workdir, static, files etc
api = FastAPI(root_path="/api")
# mount an api route such that the main route serves the ui and the /api
app.mount("/api", api)
app.mount("/", StaticFiles(directory=folders["static_folder_root"], html=True), name="ui")
api.mount("/files", StaticFiles(directory=folders["files_static_root"], html=True), name="files")
db_path = os.path.join(root_file_path, "database.sqlite")
dbmanager = DBManager(path=db_path) # manage database operations
chatmanager = ChatManager() # manage calls to autogen
@api.post("/messages")
async def add_message(req: ChatWebRequestModel):
message = Message(**req.message.dict())
user_history = load_messages(user_id=message.user_id, dbmanager=dbmanager)
# save incoming message to db
save_message(message=message, dbmanager=dbmanager)
user_dir = os.path.join(folders["files_static_root"], "user", md5_hash(message.user_id))
os.makedirs(user_dir, exist_ok=True)
# load skills, append to chat
skills = get_all_skills(
os.path.join(folders["user_skills_dir"], md5_hash(message.user_id)),
folders["global_skills_dir"],
dest_dir=os.path.join(user_dir, "scratch"),
)
skills_prompt = get_skills_prompt(skills)
try:
response_message: Message = chatmanager.chat(
message=message,
history=user_history,
work_dir=user_dir,
skills_prompt=skills_prompt,
flow_config=req.flow_config,
)
# save assistant response to db
save_message(message=response_message, dbmanager=dbmanager)
response = {
"status": True,
"message": response_message.content,
"metadata": json.loads(response_message.metadata),
}
return response
except Exception as ex_error:
print(traceback.format_exc())
return {
"status": False,
"message": "Error occurred while processing message: " + str(ex_error),
}
@api.get("/messages")
def get_messages(user_id: str = None):
if user_id is None:
raise HTTPException(status_code=400, detail="user_id is required")
user_history = load_messages(user_id=user_id, dbmanager=dbmanager)
return {
"status": True,
"data": user_history,
"message": "Messages retrieved successfully",
}
@api.post("/messages/delete")
async def remove_message(req: DeleteMessageWebRequestModel):
"""Delete a message from the database"""
try:
messages = delete_message(user_id=req.user_id, msg_id=req.msg_id, dbmanager=dbmanager)
return {
"status": True,
"message": "Message deleted successfully",
"data": messages,
}
except Exception as ex_error:
print(ex_error)
return {
"status": False,
"message": "Error occurred while deleting message: " + str(ex_error),
}
@api.post("/cleardb")
async def clear_db(req: ClearDBWebRequestModel):
"""Clear user conversation history database and files"""
user_files_dir = os.path.join(folders["files_static_root"], "user", md5_hash(req.user_id))
# user_skills_dir = os.path.join(folders["user_skills_dir"], md5_hash(req.user_id))
delete_files_in_folder([user_files_dir])
try:
delete_message(user_id=req.user_id, msg_id=None, dbmanager=dbmanager, delete_all=True)
return {
"status": True,
"message": "Messages and files cleared successfully",
}
except Exception as ex_error:
print(ex_error)
return {
"status": False,
"message": "Error occurred while deleting message: " + str(ex_error),
}
@api.get("/skills")
def get_skills(user_id: str):
skills = get_all_skills(os.path.join(folders["user_skills_dir"], md5_hash(user_id)), folders["global_skills_dir"])
return {
"status": True,
"message": "Skills retrieved successfully",
"skills": skills,
}
@api.post("/skills")
def create_user_skills(req: CreateSkillWebRequestModel):
"""_summary_
Args:
user_id (str): the user id
code (str): code that represents the skill to be created
Returns:
_type_: dict
"""
user_skills_dir = os.path.join(folders["user_skills_dir"], md5_hash(req.user_id))
try:
create_skills_from_code(dest_dir=user_skills_dir, skills=req.skills)
skills = get_all_skills(
os.path.join(folders["user_skills_dir"], md5_hash(req.user_id)), folders["global_skills_dir"]
)
return {
"status": True,
"message": "Skills retrieved successfully",
"skills": skills,
}
except Exception as ex_error:
print(ex_error)
return {
"status": False,
"message": "Error occurred while creating skills: " + str(ex_error),
}

View File

@ -0,0 +1,35 @@
from typing import Optional
import requests
from bs4 import BeautifulSoup
def fetch_user_profile(url: str) -> Optional[str]:
"""
Fetches the text content from a personal website.
Given a URL of a person's personal website, this function scrapes
the content of the page and returns the text found within the <body>.
Args:
url (str): The URL of the person's personal website.
Returns:
Optional[str]: The text content of the website's body, or None if any error occurs.
"""
try:
# Send a GET request to the URL
response = requests.get(url)
# Check for successful access to the webpage
if response.status_code == 200:
# Parse the HTML content of the page using BeautifulSoup
soup = BeautifulSoup(response.text, "html.parser")
# Extract the content of the <body> tag
body_content = soup.find("body")
# Return all the text in the body tag, stripping leading/trailing whitespaces
return " ".join(body_content.stripped_strings) if body_content else None
else:
# Return None if the status code isn't 200 (success)
return None
except requests.RequestException:
# Return None if any request-related exception is caught
return None

View File

@ -0,0 +1,73 @@
import os
import re
import json
import hashlib
def search_arxiv(query, max_results=10):
"""
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.
Args:
query (str): The search query.
max_results (int, optional): The maximum number of search results to return. Defaults to 10.
Returns:
jresults (list): A list of dictionaries. Each dictionary contains fields such as 'title', 'authors', 'summary', and 'pdf_url'
Example:
>>> results = search_arxiv("attention is all you need")
>>> print(results)
"""
import arxiv
key = hashlib.md5(("search_arxiv(" + str(max_results) + ")" + query).encode("utf-8")).hexdigest()
# Create the cache if it doesn't exist
cache_dir = ".cache"
if not os.path.isdir(cache_dir):
os.mkdir(cache_dir)
fname = os.path.join(cache_dir, key + ".cache")
# Cache hit
if os.path.isfile(fname):
fh = open(fname, "r", encoding="utf-8")
data = json.loads(fh.read())
fh.close()
return data
# Normalize the query, removing operator keywords
query = re.sub(r"[^\s\w]", " ", query.lower())
query = re.sub(r"\s(and|or|not)\s", " ", " " + query + " ")
query = re.sub(r"[^\s\w]", " ", query.lower())
query = re.sub(r"\s+", " ", query).strip()
search = arxiv.Search(query=query, max_results=max_results, sort_by=arxiv.SortCriterion.Relevance)
jresults = list()
for result in search.results():
r = dict()
r["entry_id"] = result.entry_id
r["updated"] = str(result.updated)
r["published"] = str(result.published)
r["title"] = result.title
r["authors"] = [str(a) for a in result.authors]
r["summary"] = result.summary
r["comment"] = result.comment
r["journal_ref"] = result.journal_ref
r["doi"] = result.doi
r["primary_category"] = result.primary_category
r["categories"] = result.categories
r["links"] = [str(link) for link in result.links]
r["pdf_url"] = result.pdf_url
jresults.append(r)
if len(jresults) > max_results:
jresults = jresults[0:max_results]
# Save to cache
fh = open(fname, "w")
fh.write(json.dumps(jresults))
fh.close()
return jresults

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

View File

@ -0,0 +1,5 @@
# use this for .env.development assuming your backend is running on port 8081
GATSBY_API_URL=http://127.0.0.1:8081/api
# use this .env.production assuming your backend is running on same port as frontend. Remember toremove these comments.
GATSBY_API_URL=/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,30 @@
## 🚀 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

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,52 @@
import type { GatsbyConfig } from "gatsby";
require("dotenv").config({
path: `.env.${process.env.NODE_ENV}`,
});
const config: GatsbyConfig = {
pathPrefix: `${process.env.PREFIX_PATH_VALUE}`,
siteMetadata: {
title: `AutoGen Assistant`,
description: `Build LLM Enabled Agents`,
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,65 @@
{
"name": "AutoGen_Assistant",
"version": "1.0.0",
"private": true,
"description": "AutoGen Assistant - Build LLM Enabled Agents",
"author": "SPIRAL Team",
"keywords": [
"gatsby"
],
"scripts": {
"develop": "gatsby clean && gatsby develop",
"start": "gatsby clean && gatsby develop",
"build": "gatsby clean && rm -rf ../autogenra/web/ui && PREFIX_PATH_VALUE='' gatsby build --prefix-paths && cp -r public/ ../autogenra/web/ui",
"serve": "gatsby serve",
"clean": "gatsby clean",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@ant-design/charts": "^1.3.6",
"@headlessui/react": "^1.7.16",
"@heroicons/react": "^2.0.18",
"@mdx-js/mdx": "^1.6.22",
"@mdx-js/react": "^1.6.22",
"@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",
"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.4",
"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",
"zustand": "^4.4.6"
},
"devDependencies": {
"@types/node": "^18.7.13",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@types/react-inner-image-zoom": "^3.0.0",
"@types/react-resizable": "^3.0.2",
"gh-pages": "^4.0.0",
"typescript": "^4.6.4"
}
}

View File

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

View File

@ -0,0 +1,674 @@
import {
ChevronDownIcon,
ChevronUpIcon,
Cog8ToothIcon,
XMarkIcon,
ClipboardIcon,
PlusIcon,
} from "@heroicons/react/24/outline";
import React, { ReactNode, useRef, useState } from "react";
import Icon from "./icons";
import { Button, Input, Modal, Tooltip, message } from "antd";
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";
import { IModelConfig } from "./types";
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;
}
export const SectionHeader = ({
children,
title,
subtitle,
count,
icon,
}: IProps) => {
return (
<div 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
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
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
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 (
<div
onClick={onClick}
role={"button"}
className={`${border} border-2 bg-secondary group ${className} rounded ${cursor} transition duration-300`}
>
<div className="mt- text-sm text-secondary break-words">
{title && (
<div className="text-accent rounded font-semibold text-xs pb-1">
{title}
</div>
)}
<div>{subtitle}</div>
</div>
</div>
);
};
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
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-light rounded rounded-t-none`}>
{children}
</div>
)}
</div>
);
};
export const HighLight = ({ children }: IProps) => {
return <span className="border-b border-accent">{children}</span>;
};
export const LoadBox = ({
subtitle,
className = "my-2 text-accent ",
}: IProps) => {
return (
<div className={`${className} `}>
{" "}
<span className="mr-2 ">
{" "}
<Icon size={5} icon="loading" />
</span>{" "}
{subtitle}
</div>
);
};
export const LoadingBar = ({ children }: IProps) => {
return (
<>
<div className="rounded bg-secondary mt-4 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
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 = "" }: any) => {
return (
<div className={`rounded mt-4 border-secondary ${className}`}>
<div className="mt-4 p-2 rounded border relative">
<div className="absolute -top-5 bg-primary p-2 inline-block">
{title}
</div>
<div className="mt-2"> {children}</div>
</div>
</div>
);
};
export const ExpandView = ({ children, className = "" }: any) => {
const [isOpen, setIsOpen] = React.useState(false);
return (
<div className={` rounded mb-6 border-secondary ${className}`}>
<div
role="button"
onClick={() => {
setIsOpen(true);
}}
className="text-xs mb-2 break-words"
>
{children}
</div>
{isOpen && (
<Modal
width={800}
open={isOpen}
onCancel={() => setIsOpen(false)}
footer={null}
>
{children}
</Modal>
)}
</div>
);
};
export const MarkdownView = ({
data,
className = "",
showCode = true,
}: {
data: string;
className?: string;
showCode?: boolean;
}) => {
function processString(inputString: string): string {
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);
}}
>
{!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
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 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,
}: {
title: string;
description: string;
value: string | number;
control: any;
className?: string;
}) => {
return (
<div 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 + "", 20)}
</span>
</div>
<div className="text-secondary text-xs"> {description} </div>
{control}
<div className="border-b border-dashed mt-2 mx-2"></div>
</div>
);
};
export const ModelSelector = ({
configs,
setConfigs,
className,
}: {
configs: IModelConfig[];
setConfigs: (configs: IModelConfig[]) => void;
className?: string;
}) => {
// const [configs, setConfigs] = useState<IModelConfig[]>(modelConfigs);
const [isModalVisible, setIsModalVisible] = useState(false);
const [newModelConfig, setNewModelConfig] = useState<IModelConfig | null>(
null
);
const [editIndex, setEditIndex] = useState<number | null>(null);
const sanitizeModelConfig = (config: IModelConfig) => {
const sanitizedConfig: IModelConfig = { model: config.model };
if (config.api_key) sanitizedConfig.api_key = config.api_key;
if (config.base_url) sanitizedConfig.base_url = config.base_url;
if (config.api_type) sanitizedConfig.api_type = config.api_type;
return sanitizedConfig;
};
const handleRemoveConfig = (index: number) => {
const updatedConfigs = configs.filter((_, i) => i !== index);
setConfigs(updatedConfigs);
};
const showModal = (config: IModelConfig | null, index: number | null) => {
setNewModelConfig(config);
setEditIndex(index);
setIsModalVisible(true);
};
const handleOk = () => {
if (newModelConfig?.model.trim()) {
const sanitizedConfig = sanitizeModelConfig(newModelConfig);
if (editIndex !== null) {
// Edit existing model
const updatedConfigs = [...configs];
updatedConfigs[editIndex] = sanitizedConfig;
setConfigs(updatedConfigs);
} else {
// Add new model
setConfigs([...configs, sanitizedConfig]);
}
setIsModalVisible(false);
setNewModelConfig(null);
setEditIndex(null);
} else {
// Handle case where 'model' field is empty
// Could provide user feedback here (e.g., input validation error)
message.error("Model name cannot be empty");
}
};
const handleCancel = () => {
setIsModalVisible(false);
setNewModelConfig(null);
setEditIndex(null);
};
const updateNewModelConfig = (field: keyof IModelConfig, value: string) => {
setNewModelConfig((prevState) =>
prevState ? { ...prevState, [field]: value } : null
);
};
const modelButtons = configs.map((config, i) => {
const tooltipText = `${config.model} \n ${config.base_url || ""} \n ${
config.api_type || ""
}`;
return (
<div
key={"modelrow_" + i}
className="mr-1 mb-1 p-1 px-2 rounded border"
onClick={() => showModal(config, i)}
>
<div className="inline-flex">
{" "}
<Tooltip title={tooltipText}>
<div>{config.model}</div>{" "}
</Tooltip>
<div
role="button"
onClick={(e) => {
e.stopPropagation(); // Prevent opening the modal to edit
handleRemoveConfig(i);
}}
className="ml-1 text-primary hover:text-accent duration-300"
>
<XMarkIcon className="w-4 h-4 inline-block" />
</div>
</div>
</div>
);
});
return (
<div className={`${className}`}>
<div className="flex flex-wrap">
{modelButtons}
<div
className="inline-flex mr-1 mb-1 p-1 px-2 rounded border hover:border-accent duration-300 hover:text-accent"
role="button"
onClick={() =>
showModal(
{ model: "", api_key: "", base_url: "", api_type: "" },
null
)
}
>
add <PlusIcon className="w-4 h-4 inline-block mt-1" />
</div>
</div>
<Modal
title={`${editIndex !== null ? "Edit" : "Add"} Model Configuration`}
open={isModalVisible}
onOk={handleOk}
onCancel={handleCancel}
footer={[
<Button key="back" onClick={handleCancel}>
Cancel
</Button>,
<Button key="submit" type="primary" onClick={handleOk}>
{editIndex !== null ? "Save Changes" : "Add Model"}
</Button>,
]}
>
<div className="text-sm my-2">Enter parameters for your model.</div>
<Input
placeholder="Model Name"
value={newModelConfig?.model}
onChange={(e) => updateNewModelConfig("model", e.target.value)}
/>
<Input.Password
className="mt-2"
placeholder="API Key"
value={newModelConfig?.api_key}
onChange={(e) => updateNewModelConfig("api_key", e.target.value)}
/>
<Input
className="mt-2"
placeholder="Base URL"
value={newModelConfig?.base_url}
onChange={(e) => updateNewModelConfig("base_url", e.target.value)}
/>
<Input
className="mt-2"
placeholder="Model Type (optional)"
value={newModelConfig?.api_type}
onChange={(e) => updateNewModelConfig("api_type", e.target.value)}
/>
</Modal>
</div>
);
};

View File

@ -0,0 +1,26 @@
import * as React from "react";
import Icon from "./icons";
const Footer = () => {
return (
<div className=" mt-4 text-primary p-3 border-t border-secondary ">
<div className="text-xs">
<span className="text-accent hidden inline-block mr-1 ">
{" "}
<Icon icon="app" size={8} />
</span>{" "}
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>
</div>
);
};
export default Footer;

View File

@ -0,0 +1,272 @@
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: "Gallery", href: "/" },
// { 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 hovder:bg-secondary 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 hover:bg-secondary"
>
Sign out
</Disclosure.Button>
</div>
</div>
)}
</Disclosure.Panel>
</>
)}
</Disclosure>
);
};
export default Header;

View File

@ -0,0 +1,101 @@
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 === "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,57 @@
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 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,62 @@
export type NotificationType = "success" | "info" | "warning" | "error";
export interface IMessage {
user_id: string;
root_msg_id: string;
msg_id?: string;
role: string;
content: string;
timestamp?: string;
personalize?: boolean;
ra?: string;
}
export interface IStatus {
message: string;
status: boolean;
data?: any;
}
export interface IChatMessage {
text: string;
sender: "user" | "bot";
metadata?: any;
msg_id: string;
}
export interface ILLMConfig {
config_list: Array<IModelConfig>;
timeout?: number;
cache_seed?: number | null;
temperature: number;
}
export interface IAgentConfig {
name: string;
llm_config?: ILLMConfig;
human_input_mode: string;
max_consecutive_auto_reply: number;
system_message: string | "";
is_termination_msg?: boolean | string;
code_execution_config?: boolean | string | { [key: string]: any } | null;
}
export interface IAgentFlowSpec {
type: "assistant" | "userproxy" | "groupchat";
config: IAgentConfig;
}
export interface IFlowConfig {
name: string;
sender: IAgentFlowSpec;
receiver: IAgentFlowSpec;
type: "default" | "groupchat";
}
export interface IModelConfig {
model: string;
api_key?: string;
api_version?: string;
base_url?: string;
api_type?: string;
}

View File

@ -0,0 +1,374 @@
import {
IAgentConfig,
IAgentFlowSpec,
IFlowConfig,
ILLMConfig,
IModelConfig,
IStatus,
} 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
) {
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}) `,
});
});
}
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
) => {
// exaple 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()}`;
};
/**
* 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 getDefaultConfigFlows = () => {
const llm_model_config: IModelConfig[] = [
{
model: "gpt-4-1106-preview",
},
{
model: "gpt-3.5-turbo-16k",
},
{
model: "TheBloke/zephyr-7B-alpha-AWQ",
base_url: "http://localhost:8000/v1",
},
];
const llm_model_config_35turbo: IModelConfig = {
model: "gpt-3.5-turbo-16k",
};
const llm_config_35turbo: ILLMConfig = {
config_list: [llm_model_config_35turbo],
temperature: 0.1,
timeout: 600,
cache_seed: null,
};
const llm_config: ILLMConfig = {
config_list: llm_model_config,
temperature: 0.1,
timeout: 600,
cache_seed: null,
};
const userProxyConfig: IAgentConfig = {
name: "userproxy",
llm_config: llm_config,
human_input_mode: "NEVER",
max_consecutive_auto_reply: 5,
system_message:
"You are a helpful assistant that can respond with a conscise summary of the previous conversation. The summary must be written as a coherent helpful response to the user request e.g. 'Sure, here is result to your request ' or 'The tallest mountain in Africa is ..' etc. The summary MUST end with the word TERMINATE. If the user request is pleasantry or greeting, you should respond with a pleasantry or greeting and TERMINATE.",
code_execution_config: {
work_dir: null,
use_docker: false,
},
};
const userProxyFlowSpec: IAgentFlowSpec = {
type: "userproxy",
config: userProxyConfig,
};
const assistantConfig: IAgentConfig = {
name: "primary_assistant",
llm_config: llm_config_35turbo,
human_input_mode: "NEVER",
max_consecutive_auto_reply: 8,
system_message: "",
};
const visualizationAssistantConfig: IAgentConfig = {
name: "visualization_assistant",
llm_config: llm_config,
human_input_mode: "NEVER",
max_consecutive_auto_reply: 4,
system_message: `Your task is to ensure you generate a high quality visualization for the user. Your visualizations must follow best practices and you must articulate your reasoning for your choices. The visualization must not have grid or outline box. The visualization should have an APPROPRIATE ASPECT RATIO e..g rectangular for time series data. The title must be bold. Importantly, if THE CHART IS A LINE CHART, you MUST ADD ALINE OF BEST FIT and ADD TEXT ON THE SLOPE OF EACH LINE. Note that today's date is ${new Date().toLocaleDateString()}. YOUR RESPONSE MUST NOT CONTAIN THE WORD TERMINATE.`,
};
const visualizationAssistantFlowSpec: IAgentFlowSpec = {
type: "assistant",
config: visualizationAssistantConfig,
};
const assistantFlowSpec: IAgentFlowSpec = {
type: "assistant",
config: assistantConfig,
};
const GeneralFlowConfig: IFlowConfig = {
name: "General Assistant",
sender: userProxyFlowSpec,
receiver: assistantFlowSpec,
type: "default",
};
const VisualizationChatFlowConfig: IFlowConfig = {
name: "Visualization Assistant",
sender: userProxyFlowSpec,
receiver: visualizationAssistantFlowSpec,
type: "default",
};
return [GeneralFlowConfig, VisualizationChatFlowConfig];
};
export const getModels = () => {
const models = [
{
model: "gpt-4-1106-preview",
},
{
model: "gpt-3.5-turbo-16k",
},
{
model: "TheBloke/zephyr-7B-alpha-AWQ",
base_url: "http://localhost:8000/v1",
},
];
return models;
};
export const getSampleSkill = () => {
const catSkill = `
# this is a sample skill. Replace with your own skill function
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import font_manager as fm
def save_cat_ascii_art_to_png(filename='ascii_cat.png'):
"""
Creates ASCII art of a cat and saves it to a PNG file.
:param filename: str, the name of the PNG file to save the ASCII art.
"""
# ASCII art string
cat_art = [
" /\_/\ ",
" ( o.o ) ",
" > ^ < "
]
# Determine shape of output array
height = len(cat_art)
width = max(len(line) for line in cat_art)
# Create a figure and axis to display ASCII art
fig, ax = plt.subplots(figsize=(width, height))
ax.axis('off') # Hide axes
# Get a monospace font
prop = fm.FontProperties(family='monospace')
# Display ASCII art using text
for y, line in enumerate(cat_art):
ax.text(0, height-y-1, line, fontproperties=prop, fontsize=12)
# Adjust layout
plt.tight_layout()
# Save figure to file
plt.savefig(filename, dpi=120, bbox_inches='tight', pad_inches=0.1)
plt.close(fig)`;
return catSkill;
};

View File

@ -0,0 +1,332 @@
import { AdjustmentsVerticalIcon } from "@heroicons/react/24/outline";
import { Modal, Select, Slider } from "antd";
import * as React from "react";
import { ControlRowView, GroupView, ModelSelector } from "../../atoms";
import { IAgentFlowSpec, IFlowConfig, IModelConfig } from "../../types";
import TextArea from "antd/es/input/TextArea";
import { useConfigStore } from "../../../hooks/store";
import debounce from "lodash.debounce";
import { getModels } from "../../utils";
const FlowView = ({
title,
flowSpec,
setFlowSpec,
}: {
title: string;
flowSpec: IAgentFlowSpec;
setFlowSpec: (newFlowSpec: IAgentFlowSpec) => void;
}) => {
// Local state for the FlowView component
const [localFlowSpec, setLocalFlowSpec] =
React.useState<IAgentFlowSpec>(flowSpec);
// Event handlers for updating local state and propagating changes
const onControlChange = (value: any, key: string) => {
const updatedFlowSpec = {
...localFlowSpec,
config: { ...localFlowSpec.config, [key]: value },
};
setLocalFlowSpec(updatedFlowSpec);
setFlowSpec(updatedFlowSpec);
};
const onDebouncedControlChange = React.useCallback(
debounce((value: any, key: string) => {
onControlChange(value, key);
}, 3000),
[onControlChange]
);
const modelConfigs = getModels();
return (
<>
<div className="text-accent">{title}</div>
<GroupView title={flowSpec.config.name} className="mb-4">
<ControlRowView
title="Max Consecutive Auto Reply"
className="mt-4"
description="Max consecutive auto reply messages before termination."
value={flowSpec.config.max_consecutive_auto_reply}
control={
<Slider
min={2}
max={30}
defaultValue={flowSpec.config.max_consecutive_auto_reply}
step={1}
onAfterChange={(value: any) => {
onControlChange(value, "max_consecutive_auto_reply");
}}
/>
}
/>
<ControlRowView
title="Human Input Mode"
description="Defines when to request human input"
value={flowSpec.config.human_input_mode}
control={
<Select
className="mt-2 w-full"
defaultValue={flowSpec.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={flowSpec.config.system_message}
control={
<TextArea
className="mt-2 w-full"
defaultValue={flowSpec.config.system_message}
rows={3}
onChange={(e) => {
onDebouncedControlChange(e.target.value, "system_message");
}}
/>
}
/>
{flowSpec.config.llm_config && (
<ControlRowView
title="Model"
className="mt-4"
description="Defines which models are used for the agent."
value={flowSpec.config.llm_config?.config_list?.[0]?.model}
control={
<ModelSelector
className="mt-2 w-full"
configs={flowSpec.config.llm_config.config_list || []}
setConfigs={(config_list: IModelConfig[]) => {
const llm_config = {
...flowSpec.config.llm_config,
config_list,
};
onControlChange(llm_config, "llm_config");
}}
/>
}
/>
)}
</GroupView>
</>
);
};
const AgentsControlView = ({
flowConfig,
setFlowConfig,
selectedConfig,
setSelectedConfig,
flowConfigs,
setFlowConfigs,
}: {
flowConfig: IFlowConfig;
setFlowConfig: (newFlowConfig: IFlowConfig) => void;
selectedConfig: number;
setSelectedConfig: (index: number) => void;
flowConfigs: IFlowConfig[];
setFlowConfigs: (newFlowConfigs: IFlowConfig[]) => void;
}) => {
const [isModalVisible, setIsModalVisible] = React.useState(false);
// Function to update a specific flowConfig by index
const updateFlowConfigs = (index: number, newFlowConfig: IFlowConfig) => {
const updatedFlowConfigs = [...flowConfigs];
updatedFlowConfigs[index] = newFlowConfig;
setFlowConfigs(updatedFlowConfigs);
};
React.useEffect(() => {
updateFlowConfigs(selectedConfig, flowConfig);
}, [flowConfig]);
const FlowConfigViewer = ({
flowConfig,
setFlowConfig,
}: {
flowConfig: IFlowConfig;
setFlowConfig: (newFlowConfig: IFlowConfig) => void;
}) => {
// Local state for sender and receiver FlowSpecs
const [senderFlowSpec, setSenderFlowSpec] = React.useState<IAgentFlowSpec>(
flowConfig.sender
);
const [receiverFlowSpec, setReceiverFlowSpec] =
React.useState<IAgentFlowSpec>(flowConfig.receiver);
// Update the local state and propagate changes to the parent component
const updateSenderFlowSpec = (newFlowSpec: IAgentFlowSpec) => {
setSenderFlowSpec(newFlowSpec);
setFlowConfig({ ...flowConfig, sender: newFlowSpec });
};
const updateReceiverFlowSpec = (newFlowSpec: IAgentFlowSpec) => {
setReceiverFlowSpec(newFlowSpec);
setFlowConfig({ ...flowConfig, receiver: newFlowSpec });
};
return (
<>
<div className="mb-2">{flowConfig.name}</div>
<div className="flex gap-3 ">
<div className="w-1/2">
<div className="">
<FlowView
title="Sender"
flowSpec={senderFlowSpec}
setFlowSpec={updateSenderFlowSpec}
/>
</div>
</div>
<div className="w-1/2">
<FlowView
title="Receiver"
flowSpec={receiverFlowSpec}
setFlowSpec={updateReceiverFlowSpec}
/>
</div>
</div>
</>
);
};
return (
<div className="text-secondary rounded p">
<Modal
width={800}
title={
<span>
<AdjustmentsVerticalIcon className="h-4 text-accent inline-block mr-2 -mt-1" />
AutoGen Agent Settings
</span>
}
open={isModalVisible}
onCancel={() => {
setIsModalVisible(false);
}}
onOk={() => {
setIsModalVisible(false);
}}
>
<ControlRowView
title="Agent Flow Specification"
className="mb-4"
description="Select the agent flow specification that will be used for your tasks."
value={flowConfig.name}
control={
<Select
className="mt-2 w-full"
value={flowConfig.name}
onChange={(value: any) => {
setSelectedConfig(value);
setFlowConfig(flowConfigs[value]);
}}
options={
flowConfigs.map((config, index) => {
return { label: config.name, value: index };
}) as any
}
/>
}
/>
<FlowConfigViewer
flowConfig={flowConfig}
setFlowConfig={setFlowConfig}
/>
<p className="mt-4 text-xs text-secondary">
{" "}
Learn more about AutoGen Agent parameters{" "}
<a
className="border-b border-accent hover:text-accent "
target={"_blank"}
rel={"noopener noreferrer"}
href={
"https://microsoft.github.io/autogen/docs/Use-Cases/agent_chat"
}
>
here
</a>
.
</p>
</Modal>
<div
role="button"
onClick={() => {
setIsModalVisible(true);
}}
className="text-right flex-1 -mt-1 text-accent"
>
<span className="inline-block -mt-2">Settings</span>{" "}
<AdjustmentsVerticalIcon className="inline-block w-4 h-6 " />
</div>
</div>
);
};
const AgentsView = () => {
const flowConfigs = useConfigStore((state) => state.flowConfigs);
const setFlowConfigs = useConfigStore((state) => state.setFlowConfigs);
const flowConfig = useConfigStore((state) => state.flowConfig);
const setFlowConfig = useConfigStore((state) => state.setFlowConfig);
const [selectedConfig, setSelectedConfig] = React.useState<number>(0);
// const [flowConfig, setFlowConfig] = React.useState<IFlowConfig>(
// flowConfigs[selectedConfig]
// );
return (
<div className="h-full mb-4 ">
<div className="font-semibold pb-2 border-b">Agents </div>
<div className="text-xs mt-2 mb-2 pb-1 ">
{" "}
Select or create an agent workflow.{" "}
</div>
<div className="text-xs text-secondary mt-2 flex">
<div>Default Agent</div>
<div className="flex-1">
<AgentsControlView
flowConfig={flowConfig}
setFlowConfig={setFlowConfig}
selectedConfig={selectedConfig}
setSelectedConfig={setSelectedConfig}
flowConfigs={flowConfigs}
setFlowConfigs={setFlowConfigs}
/>
</div>
</div>
<Select
className="mt-2 w-full"
value={flowConfigs[selectedConfig].name}
onChange={(value: any) => {
setSelectedConfig(value);
setFlowConfig(flowConfigs[value]);
}}
options={
flowConfigs.map((config, index) => {
return { label: config.name, value: index };
}) as any
}
/>
</div>
);
};
export default AgentsView;

View File

@ -0,0 +1,496 @@
import {
ArrowPathIcon,
Cog6ToothIcon,
ExclamationTriangleIcon,
PaperAirplaneIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import { Button, Dropdown, MenuProps, message } from "antd";
import * as React from "react";
import { IChatMessage, IFlowConfig, IMessage, IStatus } from "../../types";
import { fetchJSON, getServerUrl, guid } from "../../utils";
import { appContext } from "../../../hooks/provider";
import MetaDataView from "./metadata";
import { MarkdownView } from "../../atoms";
import { useConfigStore } from "../../../hooks/store";
const ChatBox = ({
config,
initMessages,
skillup,
}: {
config: any;
initMessages: any[];
skillup: any;
}) => {
const queryInputRef = React.useRef<HTMLInputElement>(null);
const messageBoxInputRef = React.useRef<HTMLDivElement>(null);
const { user } = React.useContext(appContext);
const serverUrl = getServerUrl();
const deleteMsgUrl = `${serverUrl}/messages/delete`;
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
});
const messages = useConfigStore((state) => state.messages);
const setMessages = useConfigStore((state) => state.setMessages);
const flowConfig: IFlowConfig = useConfigStore((state) => state.flowConfig);
let pageHeight, chatMaxHeight;
if (typeof window !== "undefined") {
pageHeight = window.innerHeight;
chatMaxHeight = pageHeight - 300 + "px";
}
const parseMessages = (messages: any) => {
return messages.map((message: any) => {
let meta;
try {
meta = JSON.parse(message.metadata);
} catch (e) {
meta = message.metadata;
}
const msg: IChatMessage = {
text: message.content,
sender: message.role === "user" ? "user" : "bot",
metadata: meta,
msg_id: message.msg_id,
};
return msg;
});
};
React.useEffect(() => {
// console.log("initMessages changed", initMessages);
const initMsgs: IChatMessage[] = parseMessages(initMessages);
setMessages(initMsgs);
}, [initMessages]);
function processString(inputString: string): string {
inputString = inputString.replace(/\n/g, " \n");
const markdownPattern = /```markdown\s+([\s\S]*?)\s+```/g;
return inputString?.replace(markdownPattern, (match, content) => content);
}
const deleteMessage = (messageId: string) => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ user_id: user?.email, msg_id: messageId }),
};
const onSuccess = (data: any) => {
console.log(data);
if (data && data.status) {
message.success(data.message);
setMessages(parseMessages(data.data));
console.log("updated profile", data);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(deleteMsgUrl, payLoad, onSuccess, onError);
};
const examplePrompts = [
{
title: "Stock Price",
prompt:
"Plot a chart of NVDA and TESLA stock price YTD. 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",
},
];
const promptButtons = examplePrompts.map((prompt, i) => {
return (
<Button
key={"prompt" + i}
type="primary"
className=""
onClick={() => {
getCompletion(prompt.prompt);
}}
>
{" "}
{prompt.title}{" "}
</Button>
);
});
const checkIsSkill = (message: string) => {
// check if message contains lowercase of 'Learned a new skill'
if (message.toLowerCase().includes("learned a new skill")) {
skillup.set(new Date().toLocaleTimeString());
console.log("learned a new skill .. updating UI ");
}
};
const messageListView = 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.metadata) {
hasMeta =
message.metadata.code !== null ||
message.metadata.images?.length > 0 ||
message.metadata.files?.length > 0 ||
message.metadata.scripts?.length > 0;
}
let items: MenuProps["items"] = [];
if (isUser) {
items.push({
label: (
<div
onClick={() => {
console.log("retrying");
getCompletion(message.text);
}}
>
<ArrowPathIcon
role={"button"}
title={"Retry"}
className="h-4 w-4 mr-1 inline-block"
/>
Retry
</div>
),
key: "retrymessage",
});
}
if (messages.length - 1 === i) {
// items.push({
// type: "divider",
// });
items.push({
label: (
<div
onClick={() => {
console.log("deleting", message);
deleteMessage(message.msg_id);
}}
>
<TrashIcon
title={"Delete message"}
className="h-4 w-4 mr-1 inline-block"
/>
Delete Message
</div>
),
key: "deletemessage",
});
}
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 "> ...</span>
</div>
</Dropdown>
);
return (
<div
className={`align-right ${isUser ? "text-righpt" : ""} mb-2 border-b`}
key={"message" + i}
>
{" "}
<div className={` ${isUser ? "" : " w-full"} inline-flex gap-2`}>
<div className="">
{" "}
{/* {isUser && <UserIcon className="inline-block h-6 " />}
{!isUser && (
<span className="inline-block text-accent bg-primary pb-2 ml-1">
<Icon icon="app" size={8} />
</span>
)} */}
</div>
<div className="font-semibold text-secondary text-sm w-14">{`${
isUser ? "USER" : "AGENT"
}`}</div>
<div
// style={{ minWidth: "70%" }}
className={`inline-block relative ${
isUser ? "" : " w-full "
} p-2 rounded ${css}`}
>
{" "}
{items.length > 0 && <div className=" ">{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.metadata && (
<div className="">
<MetaDataView metadata={message.metadata} />
</div>
)}
</div>
</div>
</div>
);
});
React.useEffect(() => {
// console.log("messages updated, scrolling");
setTimeout(() => {
scrollChatBox();
}, 200);
}, [messages]);
// clear text box if loading has just changed to false and there is no error
React.useEffect(() => {
if (loading === false && queryInputRef.current) {
if (queryInputRef.current) {
// console.log("loading changed", loading, error);
if (error === null || (error && error.status === false)) {
queryInputRef.current.value = "";
}
}
}
}, [loading]);
// scroll to queryInputRef on load
React.useEffect(() => {
// console.log("scrolling to query input");
// if (queryInputRef.current) {
// queryInputRef.current.scrollIntoView({
// behavior: "smooth",
// block: "center",
// });
// }
}, []);
const chatHistory = (messages: IChatMessage[]) => {
let history = "";
messages.forEach((message) => {
history += message.text + "\n"; // message.sender + ": " + message.text + "\n";
});
return history;
};
const scrollChatBox = () => {
messageBoxInputRef.current?.scroll({
top: messageBoxInputRef.current.scrollHeight,
behavior: "smooth",
});
};
const getCompletion = (query: string) => {
setError(null);
let messageHolder = Object.assign([], messages);
let history = chatHistory(messages);
const userMessage: IChatMessage = {
text: query,
sender: "user",
msg_id: guid(),
};
messageHolder.push(userMessage);
setMessages(messageHolder);
const messagePayload: IMessage = {
role: "user",
content: query,
msg_id: userMessage.msg_id,
user_id: user?.email || "",
root_msg_id: "0",
};
const textUrl = `${serverUrl}/messages`;
const postData = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
message: messagePayload,
flow_config: flowConfig,
}),
};
setLoading(true);
fetch(textUrl, postData)
.then((res) => {
setLoading(false);
if (res.status === 200) {
res.json().then((data) => {
if (data && data.status) {
console.log("******* response received ", data);
const botMesage: IChatMessage = {
text: data.message,
sender: "bot",
metadata: data.metadata,
msg_id: data.msg_id,
};
checkIsSkill(data.message);
// if (data.metadata) {
// setMetadata(data.metadata);
// }
messageHolder.push(botMesage);
messageHolder = Object.assign([], messageHolder);
setMessages(messageHolder);
} else {
console.log("error", data);
// setError(data);
message.error(data.message);
}
});
} else {
res.json().then((data) => {
console.log("error", data);
// setError(data);
message.error(data.message);
});
message.error("Connection error. Ensure server is up and running.");
}
})
.catch((err) => {
setLoading(false);
message.error("Connection error. Ensure server is up and running.");
});
};
return (
<div className="text-primary relative h-full rounded ">
<div
ref={messageBoxInputRef}
className="flex h-full flex-col rounded scroll pr-2 overflow-auto "
style={{ minHeight: "300px", maxHeight: chatMaxHeight }}
>
<div className="flex-1 boder mt-4"></div>
<div className="ml-2"> {messageListView}</div>
<div className="ml-2 h-6 mb-4 mt-2 ">
{loading && (
<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>
)}
</div>
</div>
<div className="mt-2 p-2 absolute bg-primary bottom-0 w-full">
<div
className={`mt-2 rounded p-2 shadow-lg flex mb-1 gap-2 ${
loading ? " opacity-50 pointer-events-none" : ""
}`}
>
{/* <input className="flex-1 p-2 ring-2" /> */}
<form
autoComplete="on"
className="flex-1 "
onSubmit={(e) => {
e.preventDefault();
// if (queryInputRef.current && !loading) {
// getCompletion(queryInputRef.current?.value);
// }
}}
>
<input
id="queryInput"
name="queryInput"
autoComplete="on"
onKeyDown={(e) => {
if (e.key === "Enter" && queryInputRef.current && !loading) {
getCompletion(queryInputRef.current?.value);
}
}}
ref={queryInputRef}
className="w-full text-gray-600 bg-white p-2 ring-2 rounded-sm"
/>
</form>
<div
role={"button"}
onClick={() => {
if (queryInputRef.current && !loading) {
getCompletion(queryInputRef.current?.value);
}
}}
className="bg-accent hover:brightness-75 transition duration-300 rounded pt-2 px-5 "
>
{" "}
{!loading && (
<div className="inline-block ">
<PaperAirplaneIcon className="h-6 text-white inline-block" />{" "}
</div>
)}
{loading && (
<div className="inline-block ">
<Cog6ToothIcon className="relative -pb-2 text-white animate-spin inline-flex rounded-full h-6 w-6" />
</div>
)}
</div>
</div>{" "}
<div>
<div className="mt-2 text-xs text-secondary">
Blank slate? Try one of the example prompts below{" "}
</div>
<div
className={`mt-2 inline-flex gap-2 flex-wrap ${
loading ? "brightness-75 pointer-events-none" : ""
}`}
>
{promptButtons}
</div>
</div>
{error && !error.status && (
<div className="p-2 border 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,94 @@
import { DocumentTextIcon } from "@heroicons/react/24/outline";
import * as React from "react";
import { CollapseBox, ExpandView, MarkdownView } from "../../atoms";
import { formatDuration, getServerUrl } from "../../utils";
const MetaDataView = ({ metadata }: { metadata: any | null }) => {
const serverUrl = getServerUrl();
const renderFile = (file: string, i: number) => {
const file_type = file.split(".").pop() || "";
const image_types = ["png", "jpg", "jpeg", "gif", "svg"];
const is_image = image_types.includes(file_type);
const time_nonce = new Date().getTime().toString();
const file_name = file.split("/").pop() || "";
if (is_image) {
return (
<div key={"metafilesrow" + i} className="text-primary ">
<ExpandView className="lg:w-3/4 xl:w-1/2 2xl:w-1/4 mb-1">
<div className="mb-2">
<DocumentTextIcon className="h-4 mr-1 inline-block" /> {file_name}
</div>
<img
src={`${serverUrl}/${file}?t=${time_nonce}`}
className="w-full rounded"
/>
</ExpandView>
</div>
);
} else {
return (
<div key={"metafilesrow" + i} className="text-primary text-xs">
<DocumentTextIcon className="h-4 mr-1 inline-block" /> {file_name}
</div>
);
}
};
const files = (metadata.files || []).map((file: string, i: number) => {
return (
<div
key={"metafilesrow" + i}
className="text-primary border-b border-dashed py-2"
>
{renderFile(file, i)}
</div>
);
});
const messages = (metadata.messages || []).map((message: any, i: number) => {
return (
<div className="border-b border-dashed" key={"messagerow" + i}>
<MarkdownView data={message?.content} className="text-sm" />
</div>
);
});
const hasContent = files.length > 0;
const hasMessages = messages.length > 0;
return (
<div>
{hasMessages && (
<div className=" rounded bg-primary p-2 ">
<CollapseBox
open={false}
title={`Agent Messages (${messages.length} message${
messages.length > 1 ? "s" : ""
}) | ${formatDuration(metadata?.time)}`}
>
<div
// style={{ maxHeight: "300px" }}
className=" "
>
{messages}
</div>
</CollapseBox>
</div>
)}
{hasContent && (
<div className=" rounded bg-primary p-2 ">
<CollapseBox
open={true}
title={`Results (${files.length} file${
files.length > 1 ? "s" : ""
})`}
>
<div className="mt-2 ">{files}</div>
</CollapseBox>
</div>
)}
</div>
);
};
export default MetaDataView;

View File

@ -0,0 +1,93 @@
import * as React from "react";
import { 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";
const RAView = () => {
const [loading, setLoading] = React.useState(false);
const [messages, setMessages] = React.useState<IMessage[]>([]);
const [skillUpdated, setSkillUpdated] = React.useState("default");
const skillup = {
get: skillUpdated,
set: setSkillUpdated,
};
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}/messages?user_id=${user?.email}`;
const fetchMessages = () => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
// console.log("payload", payLoad);
const onSuccess = (data: any) => {
// console.log(data);
if (data && data.status) {
console.log("******* messages received ", data);
setMessages(data.data);
message.success(data.message);
} 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) {
// console.log("fetching messages", messages);
fetchMessages();
}
}, []);
return (
<div className="h-full ">
<div className="flex h-full ">
<div className=" mr-2 rounded">
<SideBarView
setMessages={setMessages}
skillup={skillup}
config={{ get: config, set: setConfig }}
/>
</div>
<div className=" flex-1 ">
{" "}
<ChatBox
config={{ get: config, set: setConfig }}
initMessages={messages}
skillup={skillup}
/>
</div>
</div>
</div>
);
};
export default RAView;

View File

@ -0,0 +1,45 @@
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
import * as React from "react";
import SkillsView from "./skills";
import AgentsView from "./agents";
const SideBarView = ({ setMessages, notify, skillup, config }: any) => {
const [isOpen, setIsOpen] = React.useState(true);
const minWidth = isOpen ? "270px" : "50px";
return (
<div
style={{ minWidth: minWidth, maxWidth: minWidth }}
className="transition overflow-hidden duration-300 flex flex-col h-full p-2 "
>
<div className="flex-1 ">
<div className={`${isOpen ? "" : "hidden"}`}>
<AgentsView />
<SkillsView
notify={notify}
setMessages={setMessages}
skillup={skillup}
config={config}
/>
</div>
</div>
<div
onClick={() => setIsOpen(!isOpen)}
role="button"
className=" hover:text-accent duration-150 "
>
{isOpen ? (
<>
{" "}
<ChevronLeftIcon className="w-6 h-6 inline-block rounded" />{" "}
<span className="text-xs "> close sidebar</span>
</>
) : (
<ChevronRightIcon className="w-6 h-6 inline-block font-bold rounded " />
)}
</div>
</div>
);
};
export default SideBarView;

View File

@ -0,0 +1,337 @@
import { PlusIcon, TrashIcon } from "@heroicons/react/24/outline";
import { Modal, message } from "antd";
import * as React from "react";
import { IStatus } from "../../types";
import { appContext } from "../../../hooks/provider";
import {
fetchJSON,
getSampleSkill,
getServerUrl,
truncateText,
} from "../../utils";
import {
CodeBlock,
CollapseBox,
LaunchButton,
LoadBox,
MarkdownView,
} from "../../atoms";
import { useConfigStore } from "../../../hooks/store";
import TextArea from "antd/es/input/TextArea";
const SkillsView = ({ setMessages, skillup }: 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 clearDbUrl = `${serverUrl}/cleardb`;
const listSkillsUrl = `${serverUrl}/skills?user_id=${user?.email}`;
const saveSkillsUrl = `${serverUrl}/skills/`;
const clearSkillsUrl = `${serverUrl}/skills/clear?user_id=${user?.email}`;
const [skills, setSkills] = React.useState<any>({});
const [skillsLoading, setSkillsLoading] = React.useState(false);
const [selectedSkill, setSelectedSkill] = React.useState<any>(null);
const [showSkillModal, setShowSkillModal] = React.useState(false);
const [showNewSkillModal, setShowNewSkillModal] = React.useState(false);
const sampleSkill = getSampleSkill();
const [skillCode, setSkillCode] = React.useState(sampleSkill);
// console.log("skukkup", skillup);
const clearDb = () => {
setError(null);
setLoading(true);
// const fetch;
const payLoad = {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
}),
};
console.log("payload", payLoad);
const onSuccess = (data: any) => {
console.log(data);
if (data && data.status) {
message.success(data.message);
setMessages([]);
} else {
message.error(data.message);
}
setLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setLoading(false);
};
fetchJSON(clearDbUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
console.log("fetching messages", skillup.get);
//
if (skillup.get !== "default") {
fetchSkills();
}
}
}, [skillup.get]);
const fetchSkills = () => {
setError(null);
setSkillsLoading(true);
// const fetch;
const payLoad = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const onSuccess = (data: any) => {
console.log(data);
if (data && data.status) {
message.success(data.message);
console.log("skills", data.skills);
setSkills(data.skills);
} else {
message.error(data.message);
}
setSkillsLoading(false);
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setSkillsLoading(false);
};
fetchJSON(listSkillsUrl, payLoad, onSuccess, onError);
};
const saveSkill = () => {
// check if skillTextAreaRef.current is not null or ""
if (!skillCode || skillCode == "" || skillCode == sampleSkill) {
message.error("Please provide code for the skill");
return;
}
setError(null);
setSkillsLoading(true);
// const fetch;
const payLoad = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: user?.email,
skills: skillCode,
}),
};
const onSuccess = (data: any) => {
console.log(data);
if (data && data.status) {
message.success(data.message);
console.log("skills", data.skills);
setSkills(data.skills);
} else {
message.error(data.message);
}
setSkillsLoading(false);
setSkillCode("");
};
const onError = (err: any) => {
setError(err);
message.error(err.message);
setSkillsLoading(false);
};
fetchJSON(saveSkillsUrl, payLoad, onSuccess, onError);
};
React.useEffect(() => {
if (user) {
// console.log("fetching messages", messages);
fetchSkills();
}
}, []);
let userSkills: any[] = [];
let globalSkills: any[] = [];
if (skills) {
userSkills = skills.user;
globalSkills = skills.global;
}
const showSkillRows = (
skills: any[],
title: string,
open: boolean = false
) => {
const skillrows = (skills || []).map((skill: any, i: number) => {
return (
<div
role="button"
onClick={() => {
setSelectedSkill(skill);
setShowSkillModal(true);
}}
key={"skillrow" + i}
className="hover:bg-primary rounded p-2 rounded-b-none duration-300 text-primary text-sm border-b border-dashed py-1 break-all gap-2 "
title={skill?.docstring}
>
{" "}
<span className="font-semibold">{skill?.name}</span>
<div className="text-secondary">
{truncateText(skill.content, 50)}
</div>
</div>
);
});
return (
<div className="mb-2">
<CollapseBox
open={open}
title={
<div className="font-semibold ">
{" "}
{title} ({skills.length})
</div>
}
>
<>
{skillrows}
{(!skills || skills.length == 0) && (
<div className=" rounded p-2 px-3 text-xs my-1">
{" "}
No {title} created yet.
</div>
)}
</>
</CollapseBox>
</div>
);
};
let windowHeight, skillsMaxHeight;
if (typeof window !== "undefined") {
windowHeight = window.innerHeight;
skillsMaxHeight = windowHeight - 400 + "px";
}
return (
<div className=" ">
<Modal
title={selectedSkill?.name}
width={800}
open={showSkillModal}
onOk={() => {
setShowSkillModal(false);
}}
onCancel={() => {
setShowSkillModal(false);
}}
>
{selectedSkill && (
<div>
<div className="mb-2">{selectedSkill.file_name}</div>
<CodeBlock code={selectedSkill?.content} language="python" />
</div>
)}
</Modal>
<Modal
title={
<div>
<PlusIcon className="w-5 h-5 inline-block mr-1" /> Create New Skill
</div>
}
width={800}
open={showNewSkillModal}
onOk={() => {
saveSkill();
setShowNewSkillModal(false);
}}
onCancel={() => {
setShowNewSkillModal(false);
}}
>
<>
<div className="mb-2">
Provide code for a new skill or create from current conversation.
</div>
<TextArea
value={skillCode}
onChange={(e) => {
setSkillCode(e.target.value);
}}
rows={10}
/>
</>
</Modal>
<div className="mb-2">
<div
style={{
maxHeight: skillsMaxHeight,
}}
className="overflow-x-hidden scroll rounded "
>
<div className="font-semibold mb-2 pb-1 border-b"> Skills </div>
<div className="text-xs mb-2 pb-1 ">
{" "}
Skills are python functions that agents can use to solve tasks.{" "}
</div>
{userSkills && <>{showSkillRows(userSkills, "User Skills")}</>}
{globalSkills && globalSkills.length > 0 && (
<>{showSkillRows(globalSkills, "Global Skills")}</>
)}
</div>
<div className="flex">
<div className="flex-1"></div>
<LaunchButton
className="text-sm p-2 px-3"
onClick={() => {
setShowNewSkillModal(true);
}}
>
{" "}
<PlusIcon className="w-5 h-5 inline-block mr-1" />
New Skill
</LaunchButton>
</div>
</div>
<hr className="mb-2" />
<div
role="button"
className="inline-block text-xs hover:text-accent"
onClick={clearDb}
>
{!loading && (
<>
<TrashIcon className="w-5, h-5 inline-block mr-1" />
Clear Conversation
</>
)}
{loading && <LoadBox subtitle={"clearing db .."} />}
</div>
</div>
);
};
export default SkillsView;

View File

@ -0,0 +1,76 @@
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 [darkMode, setDarkMode] = useState(
getLocalStorage("darkmode", false) === null
? "dark"
: getLocalStorage("darkmode", false) !== ("dark" || "light")
? getLocalStorage("darkmode", false)
: "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,21 @@
import { create } from "zustand";
import { getDefaultConfigFlows } from "../components/utils";
import { IChatMessage, IFlowConfig } from "../components/types";
interface ConfigState {
flowConfigs: IFlowConfig[];
setFlowConfigs: (flowConfigs: IFlowConfig[]) => void;
flowConfig: IFlowConfig;
setFlowConfig: (flowConfig: IFlowConfig) => void;
messages: IChatMessage[];
setMessages: (messages: IChatMessage[]) => void;
}
export const useConfigStore = create<ConfigState>()((set) => ({
flowConfigs: getDefaultConfigFlows(),
setFlowConfigs: (flowConfigs) => set({ flowConfigs }),
flowConfig: getDefaultConfigFlows()[0],
setFlowConfig: (flowConfig) => set({ flowConfig }),
messages: [],
setMessages: (messages) => set({ messages }),
}));

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

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 RAView from "../components/views/ra/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,318 @@
.dark {
--color-bg-primary: #111827;
--color-bg-secondary: #1e293b;
--color-bg-light: #27354c;
--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-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; /* width of the entire scrollbar */
height: 8px;
}
.scroll::-webkit-scrollbar-track {
@apply bg-secondary;
border-radius: 20px;
}
.scroll::-webkit-scrollbar-thumb {
@apply bg-accent;
border-radius: 20px; /* roundness of the scroll thumb */
border: 3px solid rgb(214, 214, 214); /* creates padding around scroll thumb */
}
.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 {
@apply text-primary !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-radio-input::before {
@apply bg-primary !important;
} */
.prose > pre {
padding: 0px !important;
margin: 0px !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; */
}

View File

@ -0,0 +1,39 @@
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)",
},
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/line-clamp"),
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,61 @@
[build-system]
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"
[project]
name = "autogenra"
authors = [
{ name="Autogen Team", email="autogen@microsoft.com" },
]
description = "Autogen Assistant UI"
readme = "README.md"
license = { file="LICENSE" }
requires-python = ">=3.9"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
dependencies = [
"pydantic",
"fastapi",
"typer",
"uvicorn",
"arxiv",
"pyautogen==0.2.0b5"
]
optional-dependencies = {web = ["fastapi", "uvicorn"]}
dynamic = ["version"]
[tool.setuptools]
include-package-data = true
[tool.setuptools.dynamic]
version = {attr = "autogenra.version.VERSION"}
readme = {file = ["README.md"]}
[tool.setuptools.packages.find]
include = ["autogenra*"]
exclude = ["*.tests*"]
namespaces = false
[tool.setuptools.package-data]
"autogenra" = ["*.*"]
[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]
autogenra = "autogenra.cli:run"

View File

@ -0,0 +1 @@
.

View File

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

View File

@ -34,6 +34,30 @@ rickyloynd-microsoft:
url: https://github.com/rickyloynd-microsoft
image_url: https://github.com/rickyloynd-microsoft.png
samershi:
name: Saleema Amershi
title: Senior Principal Research Manager at Microsoft Research
url: https://github.com/samershi
image_url: https://github.com/samershi.png
pcdeadeasy:
name: Piali Choudhury
title: Principal RSDE at Microsoft Research
url: https://github.com/pcdeadeasy
image_url: https://github.com/pcdeadeasy.png
victordibia:
name: Victor Dibia
title: Principal RSDE at Microsoft Research
url: https://github.com/victordibia
image_url: https://github.com/victordibia.png
afourney:
name: Adam Fourney
title: Principal Researcher Microsoft Research
url: https://github.com/afourney
image_url: https://github.com/afourney.png
beibinli:
name: Beibin Li
title: Senior Research Engineer at Microsoft