mirror of https://github.com/microsoft/autogen.git
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:
parent
a4d9ce8c5b
commit
143e49c6e8
|
@ -3,3 +3,4 @@ branch = True
|
|||
source = autogen
|
||||
omit =
|
||||
*test*
|
||||
*samples*
|
||||
|
|
|
@ -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/
|
|
@ -0,0 +1,5 @@
|
|||
recursive-include autogenra/web/ui *
|
||||
recursive-exclude notebooks *
|
||||
recursive-exclude frontend *
|
||||
recursive-exclude docs *
|
||||
recursive-exclude tests *
|
|
@ -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)
|
|
@ -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
|
|
@ -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,
|
||||
)
|
|
@ -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()
|
|
@ -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]]
|
|
@ -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()
|
|
@ -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)
|
|
@ -0,0 +1,2 @@
|
|||
VERSION = "0.0.05a"
|
||||
APP_NAME = "autogenra"
|
|
@ -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),
|
||||
}
|
|
@ -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
|
|
@ -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 |
|
@ -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
|
|
@ -0,0 +1,8 @@
|
|||
node_modules/
|
||||
.cache/
|
||||
public/
|
||||
|
||||
.env.development
|
||||
.env.production
|
||||
|
||||
yarn.lock
|
|
@ -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.
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
import "antd/dist/reset.css";
|
||||
import "./src/styles/global.css";
|
||||
|
||||
import AuthProvider from "./src/hooks/provider";
|
||||
|
||||
export const wrapRootElement = AuthProvider;
|
|
@ -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;
|
|
@ -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 }}
|
||||
/>,
|
||||
]);
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
module.exports = () => ({
|
||||
plugins: [require("tailwindcss")],
|
||||
})
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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>;
|
|
@ -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 |
|
@ -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 couldn’t 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
|
|
@ -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;
|
|
@ -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; */
|
||||
}
|
|
@ -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"),
|
||||
],
|
||||
};
|
|
@ -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/**/*"
|
||||
]
|
||||
}
|
|
@ -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"
|
|
@ -0,0 +1 @@
|
|||
.
|
|
@ -0,0 +1,3 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup()
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue