Spaces:
Running
Running
deepakpant
commited on
Commit
·
a532053
0
Parent(s):
Initial Project structure
Browse files- .devcontainer/devcontainer.json +40 -0
- .devcontainer/postCreateCommand.sh +11 -0
- .dockerignore +68 -0
- .env.example +2 -0
- .gitattributes +35 -0
- .github/workflows/build-container.yml +33 -0
- .github/workflows/hugging_face-deploy.yml +28 -0
- .gitignore +2 -0
- .vscode/launch.json +15 -0
- .vscode/settings.json +16 -0
- Dockerfile +49 -0
- README.md +68 -0
- knowledge/content_style_mapping.json +48 -0
- knowledge/format.json +46 -0
- knowledge/target_audience.json +59 -0
- knowledge/tone.json +36 -0
- pyproject.toml +21 -0
- src/expressly_server/__init__.py +0 -0
- src/expressly_server/app.py +69 -0
- src/expressly_server/config/agents.yaml +10 -0
- src/expressly_server/config/tasks.yaml +40 -0
- src/expressly_server/crew.py +164 -0
- src/expressly_server/main.py +64 -0
- src/expressly_server/routers/route.py +28 -0
- src/expressly_server/schemas/schema.py +22 -0
- src/expressly_server/tools/__init__.py +0 -0
- src/expressly_server/utils/utils.py +33 -0
- src/expressly_server/web_app.py +150 -0
- uv.lock +0 -0
.devcontainer/devcontainer.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Expressly AI Agent",
|
| 3 |
+
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
| 4 |
+
"image": "python:3.12-slim",
|
| 5 |
+
"features": {
|
| 6 |
+
"ghcr.io/devcontainers/features/python:1": {
|
| 7 |
+
"version": "3.12"
|
| 8 |
+
},
|
| 9 |
+
"ghcr.io/devcontainers/features/git:1": {},
|
| 10 |
+
"ghcr.io/devcontainers/features/docker-in-docker:2.12.0": {
|
| 11 |
+
"version": "latest",
|
| 12 |
+
"moby": true
|
| 13 |
+
}
|
| 14 |
+
},
|
| 15 |
+
// Use 'postCreateCommand' to run commands after the container is created.
|
| 16 |
+
"postCreateCommand": "./.devcontainer/postCreateCommand.sh",
|
| 17 |
+
"forwardPorts": [
|
| 18 |
+
7860,
|
| 19 |
+
80
|
| 20 |
+
],
|
| 21 |
+
// Configure tool-specific properties.
|
| 22 |
+
"customizations": {
|
| 23 |
+
"vscode": {
|
| 24 |
+
"extensions": [
|
| 25 |
+
"ms-python.python",
|
| 26 |
+
"editorconfig.editorconfig",
|
| 27 |
+
"ms-azuretools.vscode-docker", // Docker
|
| 28 |
+
"ms-python.isort", // isort
|
| 29 |
+
"visualstudioexptteam.vscodeintellicode", // IntelliCode
|
| 30 |
+
"codeium.codeium", // Codeium AI
|
| 31 |
+
"ms-vscode.makefile-tools", // Makefile tool
|
| 32 |
+
"ms-python.python", // Python
|
| 33 |
+
"ms-python.black-formatter", // Black
|
| 34 |
+
"ms-python.debugpy", // Debugger for Python
|
| 35 |
+
"redhat.vscode-yaml", // YAML
|
| 36 |
+
"tamasfe.even-better-toml" // TOML
|
| 37 |
+
]
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
}
|
.devcontainer/postCreateCommand.sh
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#! /usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
# Install fish terminal
|
| 4 |
+
sudo apt update -y
|
| 5 |
+
sudo apt-get install fish -y
|
| 6 |
+
|
| 7 |
+
# Repo Initialization
|
| 8 |
+
git config --global --add safe.directory /workspaces/expressly
|
| 9 |
+
|
| 10 |
+
# Install Dependencies
|
| 11 |
+
pip install crewai
|
.dockerignore
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled Python files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*.pyo
|
| 5 |
+
*.pyd
|
| 6 |
+
.pytest_cache/
|
| 7 |
+
.mypy_cache/
|
| 8 |
+
|
| 9 |
+
# Environment variables and config files
|
| 10 |
+
.env
|
| 11 |
+
.env.*
|
| 12 |
+
|
| 13 |
+
# Virtual environment directories
|
| 14 |
+
venv/
|
| 15 |
+
env/
|
| 16 |
+
.virtualenv/
|
| 17 |
+
.venv/
|
| 18 |
+
|
| 19 |
+
# System files
|
| 20 |
+
.DS_Store
|
| 21 |
+
Thumbs.db
|
| 22 |
+
|
| 23 |
+
# Logs and debug files
|
| 24 |
+
*.log
|
| 25 |
+
*.out
|
| 26 |
+
*.err
|
| 27 |
+
|
| 28 |
+
# Cache and coverage files
|
| 29 |
+
*.egg-info/
|
| 30 |
+
.eggs/
|
| 31 |
+
*.cache
|
| 32 |
+
*.coverage
|
| 33 |
+
.coverage.*
|
| 34 |
+
.cache/
|
| 35 |
+
.pytest_cache/
|
| 36 |
+
nosetests.xml
|
| 37 |
+
coverage.xml
|
| 38 |
+
.ruff_cache/
|
| 39 |
+
|
| 40 |
+
# Build files and directories
|
| 41 |
+
build/
|
| 42 |
+
dist/
|
| 43 |
+
*.egg
|
| 44 |
+
*.whl
|
| 45 |
+
|
| 46 |
+
# Temporary files
|
| 47 |
+
*.swp
|
| 48 |
+
*.swo
|
| 49 |
+
*.bak
|
| 50 |
+
*.tmp
|
| 51 |
+
|
| 52 |
+
# Docker-specific files
|
| 53 |
+
docker-compose.override.yml
|
| 54 |
+
|
| 55 |
+
# Other files to exclude
|
| 56 |
+
.idea/
|
| 57 |
+
.vscode/
|
| 58 |
+
*.iml
|
| 59 |
+
.DS_Store
|
| 60 |
+
|
| 61 |
+
# Git files
|
| 62 |
+
.git
|
| 63 |
+
.github
|
| 64 |
+
.gitignore
|
| 65 |
+
.gitattributes
|
| 66 |
+
|
| 67 |
+
# Other files
|
| 68 |
+
uv.lock
|
.env.example
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MODEL=gemini/gemini-1.5-flash
|
| 2 |
+
GEMINI_API_KEY=<gemini_api_key> # Your API key here
|
.gitattributes
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/build-container.yml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Build and Deploy Docker Image
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
workflow_dispatch: {}
|
| 5 |
+
push:
|
| 6 |
+
branches:
|
| 7 |
+
- main # or master, depending on your default branch
|
| 8 |
+
|
| 9 |
+
env:
|
| 10 |
+
DOCKER_IMAGE: deepak93p/expressly-app # Replace with your DockerHub image
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
jobs:
|
| 14 |
+
build-push-docker-image:
|
| 15 |
+
runs-on: ubuntu-latest
|
| 16 |
+
|
| 17 |
+
steps:
|
| 18 |
+
- name: Checkout repository
|
| 19 |
+
uses: actions/checkout@v4
|
| 20 |
+
|
| 21 |
+
- name: Login to Docker Hub
|
| 22 |
+
env:
|
| 23 |
+
DOCKER_USER: deepak93p
|
| 24 |
+
DOCKER_PWD: ${{ secrets.DOCKERHUB_PUSH_TOKEN }}
|
| 25 |
+
run: echo $DOCKER_PWD | docker login -u $DOCKER_USER --password-stdin
|
| 26 |
+
|
| 27 |
+
- name: Build and Push Docker Image
|
| 28 |
+
run: |
|
| 29 |
+
docker build -t ${{ env.DOCKER_IMAGE }}:latest -f Dockerfile .
|
| 30 |
+
docker push ${{ env.DOCKER_IMAGE }}:latest
|
| 31 |
+
|
| 32 |
+
- name: Clean up Docker system
|
| 33 |
+
run: docker system prune -f
|
.github/workflows/hugging_face-deploy.yml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Deploy to Hugging Face Spaces
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
workflow_dispatch: {}
|
| 5 |
+
push:
|
| 6 |
+
branches:
|
| 7 |
+
- main # or master, depending on your default branch
|
| 8 |
+
|
| 9 |
+
jobs:
|
| 10 |
+
deploy:
|
| 11 |
+
runs-on: ubuntu-latest
|
| 12 |
+
steps:
|
| 13 |
+
- name: Checkout repository
|
| 14 |
+
uses: actions/checkout@v4
|
| 15 |
+
with:
|
| 16 |
+
fetch-depth: 0
|
| 17 |
+
|
| 18 |
+
- name: Configure Git
|
| 19 |
+
run: |
|
| 20 |
+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
| 21 |
+
git config --global user.name "github-actions[bot]"
|
| 22 |
+
|
| 23 |
+
- name: Push to Hugging Face Space
|
| 24 |
+
env:
|
| 25 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 26 |
+
run: |
|
| 27 |
+
git remote add space https://USER:[email protected]/spaces/deepakpant/expressly-app
|
| 28 |
+
git push --force space main
|
.gitignore
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.env
|
| 2 |
+
__pycache__/
|
.vscode/launch.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"version": "0.2.0",
|
| 3 |
+
"configurations": [
|
| 4 |
+
{
|
| 5 |
+
"name": "Python Debugger: expressly",
|
| 6 |
+
"type": "debugpy",
|
| 7 |
+
"request": "launch",
|
| 8 |
+
"program": "${workspaceFolder}/src/expressly_server/web_app.py",
|
| 9 |
+
"args": ["run"],
|
| 10 |
+
"console": "integratedTerminal",
|
| 11 |
+
"justMyCode": true,
|
| 12 |
+
"jinja": true
|
| 13 |
+
}
|
| 14 |
+
]
|
| 15 |
+
}
|
.vscode/settings.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"files.exclude": {
|
| 3 |
+
"**/.mypy_cache": true,
|
| 4 |
+
"**/__pycache__": true,
|
| 5 |
+
"**/.pytest_cache": true,
|
| 6 |
+
"**/.ruff_cache": true,
|
| 7 |
+
"**/.venv": true,
|
| 8 |
+
"**/venv": true,
|
| 9 |
+
"**/node_modules": true,
|
| 10 |
+
"**/site": true,
|
| 11 |
+
"**/.coverage": true,
|
| 12 |
+
"**/coverage.xml": true,
|
| 13 |
+
"**/build": true,
|
| 14 |
+
"**/dist": true
|
| 15 |
+
}
|
| 16 |
+
}
|
Dockerfile
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use an official Python image as a base
|
| 2 |
+
FROM python:3.12-slim
|
| 3 |
+
|
| 4 |
+
RUN useradd -m -u 1000 user
|
| 5 |
+
|
| 6 |
+
# Install system dependencies
|
| 7 |
+
RUN apt-get update && apt-get install -y \
|
| 8 |
+
build-essential \
|
| 9 |
+
curl \
|
| 10 |
+
gcc \
|
| 11 |
+
libssl-dev \
|
| 12 |
+
pkg-config \
|
| 13 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 14 |
+
|
| 15 |
+
# Install Rust (required for orjson and other Rust-based Python packages)
|
| 16 |
+
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
|
| 17 |
+
&& export PATH="$HOME/.cargo/bin:$PATH" \
|
| 18 |
+
&& rustc --version
|
| 19 |
+
|
| 20 |
+
# Ensure Rust is added to PATH for all subsequent RUN commands
|
| 21 |
+
ENV PATH="/root/.cargo/bin:$PATH"
|
| 22 |
+
|
| 23 |
+
# Set the working directory inside the container
|
| 24 |
+
WORKDIR /app
|
| 25 |
+
|
| 26 |
+
# Copy the pyproject.toml and any other build-related files
|
| 27 |
+
COPY --chown=user pyproject.toml .
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# Install dependencies
|
| 31 |
+
RUN pip install --upgrade pip \
|
| 32 |
+
&& pip install uv crewai \
|
| 33 |
+
&& crewai install
|
| 34 |
+
|
| 35 |
+
# Ensure the /app directory is owned by the non-root user
|
| 36 |
+
RUN chown -R user:user /app
|
| 37 |
+
|
| 38 |
+
# Switch to the non-root user
|
| 39 |
+
USER user
|
| 40 |
+
|
| 41 |
+
# Copy the application code into the container
|
| 42 |
+
COPY --chown=user . .
|
| 43 |
+
|
| 44 |
+
EXPOSE 7860
|
| 45 |
+
|
| 46 |
+
ENV GRADIO_SERVER_NAME="0.0.0.0"
|
| 47 |
+
|
| 48 |
+
# Define the command to run your application
|
| 49 |
+
CMD ["uv", "run", "expressly"]
|
README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Expressly
|
| 3 |
+
emoji: 🛠️
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
license: mit
|
| 9 |
+
short_description: Expressly - Text Transformation App
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# Expressly - Text Transformation App Backend
|
| 13 |
+
|
| 14 |
+
This document provides an overview of the backend server for the Expressly - Text Transformation App. The app is designed to transform text based on user preferences and descriptions, leveraging a CrewAI-based multi-agent AI application.
|
| 15 |
+
|
| 16 |
+
## Key Features
|
| 17 |
+
|
| 18 |
+
### Text Transformation Options
|
| 19 |
+
The backend supports the following transformation capabilities:
|
| 20 |
+
1. **Formats**
|
| 21 |
+
- For details on supported formats, please refer to the [Expressly Wiki](https://github.com/DeepakPant93/expressly/wiki).
|
| 22 |
+
|
| 23 |
+
2. **Tones**
|
| 24 |
+
- For details on supported tones, please refer to the [Expressly Wiki](https://github.com/DeepakPant93/expressly/wiki).
|
| 25 |
+
|
| 26 |
+
3. **Target Audience**
|
| 27 |
+
- For details on supported target audiences, please refer to the [Expressly Wiki](https://github.com/DeepakPant93/expressly/wiki).
|
| 28 |
+
|
| 29 |
+
### Output
|
| 30 |
+
The backend processes user inputs and generates formatted text tailored to the specified preferences. Users can easily copy and utilize the output.
|
| 31 |
+
|
| 32 |
+
## Backend Configuration
|
| 33 |
+
|
| 34 |
+
### Prerequisites
|
| 35 |
+
1. Install `uv` if not already installed:
|
| 36 |
+
```bash
|
| 37 |
+
pip install uv
|
| 38 |
+
```
|
| 39 |
+
2. Navigate to your project directory and install dependencies:
|
| 40 |
+
```bash
|
| 41 |
+
crewai install
|
| 42 |
+
```
|
| 43 |
+
(Optional: Lock dependencies using the CLI command.)
|
| 44 |
+
|
| 45 |
+
### Customization
|
| 46 |
+
1. Add environment variables to the `.env` file:
|
| 47 |
+
```plaintext
|
| 48 |
+
MODEL=gemini/gemini-1.5-flash
|
| 49 |
+
GEMINI_API_KEY=<gemini_api_key> # Your API key here
|
| 50 |
+
```
|
| 51 |
+
2. Modify configuration files as needed:
|
| 52 |
+
- `src/expressly_server/config/agents.yaml`: Define your agents.
|
| 53 |
+
- `src/expressly_server/config/tasks.yaml`: Define your tasks.
|
| 54 |
+
- `src/expressly_server/crew.py`: Add custom logic, tools, and arguments.
|
| 55 |
+
- `src/expressly_server/main.py`: Customize inputs for agents and tasks.
|
| 56 |
+
|
| 57 |
+
### Running the Backend
|
| 58 |
+
To start the backend server and execute tasks:
|
| 59 |
+
```bash
|
| 60 |
+
crewai run
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
## Additional Notes
|
| 64 |
+
- Ensure all environment variables are correctly set in the `.env` file.
|
| 65 |
+
- Regularly update your agents and tasks configuration to enhance functionality.
|
| 66 |
+
- Refer to the CrewAI documentation for advanced customizations.
|
| 67 |
+
|
| 68 |
+
This backend server powers the text transformation capabilities of Expressly, making it adaptable to a wide range of use cases.
|
knowledge/content_style_mapping.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"target_audience": {
|
| 3 |
+
"linkedin_post": {
|
| 4 |
+
"format": "post",
|
| 5 |
+
"tone": "professional"
|
| 6 |
+
},
|
| 7 |
+
"whatsapp_message": {
|
| 8 |
+
"format": "chat",
|
| 9 |
+
"tone": "casual"
|
| 10 |
+
},
|
| 11 |
+
"tweet": {
|
| 12 |
+
"format": "tweet",
|
| 13 |
+
"tone": "straightforward"
|
| 14 |
+
},
|
| 15 |
+
"news_article": {
|
| 16 |
+
"format": "article",
|
| 17 |
+
"tone": "neutral"
|
| 18 |
+
},
|
| 19 |
+
"technical_blog": {
|
| 20 |
+
"format": "blog",
|
| 21 |
+
"tone": "professional"
|
| 22 |
+
},
|
| 23 |
+
"formal_email": {
|
| 24 |
+
"format": "email",
|
| 25 |
+
"tone": "professional"
|
| 26 |
+
},
|
| 27 |
+
"instagram_post": {
|
| 28 |
+
"format": "post",
|
| 29 |
+
"tone": "friendly"
|
| 30 |
+
},
|
| 31 |
+
"website_content": {
|
| 32 |
+
"format": "report",
|
| 33 |
+
"tone": "neutral"
|
| 34 |
+
},
|
| 35 |
+
"marketing_email": {
|
| 36 |
+
"format": "email",
|
| 37 |
+
"tone": "confident"
|
| 38 |
+
},
|
| 39 |
+
"job_application": {
|
| 40 |
+
"format": "email",
|
| 41 |
+
"tone": "professional"
|
| 42 |
+
},
|
| 43 |
+
"customer_support_response": {
|
| 44 |
+
"format": "chat",
|
| 45 |
+
"tone": "friendly"
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
}
|
knowledge/format.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"format": {
|
| 3 |
+
"post": {
|
| 4 |
+
"name": "Post",
|
| 5 |
+
"description": "A short and engaging content piece, often used for platforms like LinkedIn or Facebook. It highlights key points and encourages interaction, such as likes, shares, or comments.",
|
| 6 |
+
"max_length": 300,
|
| 7 |
+
"negative": "Avoid long paragraphs, excessive technical jargon, or overly formal language. Do not make the content too detailed or complex."
|
| 8 |
+
},
|
| 9 |
+
"chat": {
|
| 10 |
+
"name": "Chat",
|
| 11 |
+
"description": "A conversational and interactive response that mimics real-time messaging. It focuses on direct, clear, and natural communication with a friendly tone.",
|
| 12 |
+
"max_length": 200,
|
| 13 |
+
"negative": "Do not use overly formal language, complex sentence structures, or impersonal tones. Avoid too much technical information and hashtags."
|
| 14 |
+
},
|
| 15 |
+
"tweet": {
|
| 16 |
+
"name": "Tweet",
|
| 17 |
+
"description": "A concise and impactful message designed for Twitter or similar platforms. It often uses hashtags, mentions, or trending topics to increase visibility.",
|
| 18 |
+
"max_length": 280,
|
| 19 |
+
"negative": "Avoid lengthy explanations, irrelevant details, or complex sentences. Do not exceed the character limit or use a formal tone."
|
| 20 |
+
},
|
| 21 |
+
"email": {
|
| 22 |
+
"name": "Email",
|
| 23 |
+
"description": "A formal or semi-formal written message for direct communication. It includes a clear subject line, opening, body, and closing, often addressing specific recipients.",
|
| 24 |
+
"max_length": 1500,
|
| 25 |
+
"negative": "Avoid casual or overly brief content. Do not make the email too long or include excessive details that deviate from the purpose."
|
| 26 |
+
},
|
| 27 |
+
"blog": {
|
| 28 |
+
"name": "Blog",
|
| 29 |
+
"description": "A detailed and informative piece written for online readers. It includes a compelling introduction, multiple sections with headings, and a conclusion to engage and educate the audience.",
|
| 30 |
+
"max_length": 2000,
|
| 31 |
+
"negative": "Avoid overly technical language, jargon, or too formal language. Do not make the blog too short or lacking in detail."
|
| 32 |
+
},
|
| 33 |
+
"article": {
|
| 34 |
+
"name": "Article",
|
| 35 |
+
"description": "A comprehensive, well-researched write-up, often featuring in-depth analysis or expert perspectives. Suitable for magazines, websites, or journals to inform or persuade a broad audience.",
|
| 36 |
+
"max_length": 3000,
|
| 37 |
+
"negative": "Do not oversimplify complex topics, avoid using unverified sources or vague statements. Avoid making the article too conversational or informal."
|
| 38 |
+
},
|
| 39 |
+
"report": {
|
| 40 |
+
"name": "Report",
|
| 41 |
+
"description": "A structured document that presents facts, data, and analysis for a specific audience or purpose. It often includes charts, tables, and a clear summary of findings.",
|
| 42 |
+
"max_length": 5000,
|
| 43 |
+
"negative": "Avoid informal language, speculative content, or a lack of data. Do not make the report subjective or unorganized."
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
}
|
knowledge/target_audience.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"target_audience": {
|
| 3 |
+
"linkedin_post": {
|
| 4 |
+
"name": "LinkedIn Post",
|
| 5 |
+
"description": "Aimed at professionals, thought leaders, and businesses. Content is typically career-focused or industry-related, designed to spark engagement among people seeking professional growth, networking, or knowledge.",
|
| 6 |
+
"ideal_audience": "Business professionals, marketers, entrepreneurs, recruiters, job seekers, industry experts"
|
| 7 |
+
},
|
| 8 |
+
"whatsapp_message": {
|
| 9 |
+
"name": "WhatsApp Message",
|
| 10 |
+
"description": "Casual or semi-formal communication meant for direct, personal messaging. Used for sending quick updates, reminders, or messages to small groups, often informal in tone.",
|
| 11 |
+
"ideal_audience": "Friends, family, small teams, personal contacts, colleagues in a more relaxed setting"
|
| 12 |
+
},
|
| 13 |
+
"tweet": {
|
| 14 |
+
"name": "Tweet",
|
| 15 |
+
"description": "Aimed at a broad, diverse audience on Twitter, from the general public to niche communities. Tweets are brief and designed to provoke quick engagement, whether through likes, retweets, or comments.",
|
| 16 |
+
"ideal_audience": "General public, influencers, content creators, tech enthusiasts, trend followers, entertainment fans"
|
| 17 |
+
},
|
| 18 |
+
"news_article": {
|
| 19 |
+
"name": "News Article",
|
| 20 |
+
"description": "Targeted at a wide audience seeking timely, relevant information. News articles are often written for readers who are looking for the latest updates, in-depth analysis, or expert opinions on current events or trending topics.",
|
| 21 |
+
"ideal_audience": "General public, journalists, news enthusiasts, academics, policy makers, professionals interested in current affairs"
|
| 22 |
+
},
|
| 23 |
+
"technical_blog": {
|
| 24 |
+
"name": "Technical Blog",
|
| 25 |
+
"description": "Aimed at professionals, experts, or hobbyists in specialized fields like technology, software development, engineering, or data science. The content is typically in-depth, offering insights, tutorials, and technical knowledge.",
|
| 26 |
+
"ideal_audience": "Software developers, engineers, data scientists, tech entrepreneurs, IT professionals, learners seeking advanced technical skills"
|
| 27 |
+
},
|
| 28 |
+
"formal_email": {
|
| 29 |
+
"name": "Formal Email",
|
| 30 |
+
"description": "Designed for business or professional communication. The email is meant for communicating with colleagues, clients, business partners, or supervisors in a polite, respectful, and formal tone.",
|
| 31 |
+
"ideal_audience": "Business professionals, corporate partners, clients, managers, senior leadership, job candidates"
|
| 32 |
+
},
|
| 33 |
+
"instagram_post": {
|
| 34 |
+
"name": "Instagram Post",
|
| 35 |
+
"description": "Aimed at a visually-driven, creative audience. Instagram posts are often used for branding, lifestyle content, promotions, or personal expression, with a focus on imagery and concise captions.",
|
| 36 |
+
"ideal_audience": "Millennials, Gen Z, influencers, lifestyle bloggers, fashion enthusiasts, foodies, beauty influencers, brand followers"
|
| 37 |
+
},
|
| 38 |
+
"website_content": {
|
| 39 |
+
"name": "Website Content",
|
| 40 |
+
"description": "Targeted at a broad range of users, from potential customers to casual visitors. Website content needs to be informative, engaging, and easily navigable, with a focus on conversion and user experience.",
|
| 41 |
+
"ideal_audience": "Consumers, visitors, potential customers, search engine users, anyone looking for information or services"
|
| 42 |
+
},
|
| 43 |
+
"marketing_email": {
|
| 44 |
+
"name": "Marketing Email",
|
| 45 |
+
"description": "Aimed at potential customers or existing leads. Marketing emails are designed to drive action, whether it’s to purchase a product, sign up for a service, or engage with a special offer.",
|
| 46 |
+
"ideal_audience": "Prospective customers, leads, subscribers, clients, consumers interested in sales promotions"
|
| 47 |
+
},
|
| 48 |
+
"job_application": {
|
| 49 |
+
"name": "Job Application",
|
| 50 |
+
"description": "Directed towards hiring managers, recruiters, or HR professionals. The job application content should clearly convey qualifications, skills, and the applicant's interest in a specific role.",
|
| 51 |
+
"ideal_audience": "Recruiters, hiring managers, HR professionals, employers, staffing agencies"
|
| 52 |
+
},
|
| 53 |
+
"customer_support_response": {
|
| 54 |
+
"name": "Customer Support Response",
|
| 55 |
+
"description": "Meant for existing or potential customers seeking assistance with a product or service. The tone should be empathetic, understanding, and solution-oriented, addressing the customer’s issue in a clear, helpful manner.",
|
| 56 |
+
"ideal_audience": "Customers with inquiries, complaints, technical issues, or service requests"
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
}
|
knowledge/tone.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"tone": {
|
| 3 |
+
"professional": {
|
| 4 |
+
"name": "Professional",
|
| 5 |
+
"description": "Formal and respectful, suited for workplace communication."
|
| 6 |
+
},
|
| 7 |
+
"casual": {
|
| 8 |
+
"name": "Casual",
|
| 9 |
+
"description": "Relaxed and conversational, suitable for informal interactions."
|
| 10 |
+
},
|
| 11 |
+
"straightforward": {
|
| 12 |
+
"name": "Straightforward",
|
| 13 |
+
"description": "Direct and concise, avoiding unnecessary details."
|
| 14 |
+
},
|
| 15 |
+
"confident": {
|
| 16 |
+
"name": "Confident",
|
| 17 |
+
"description": "Assertive and positive, showing conviction in the message."
|
| 18 |
+
},
|
| 19 |
+
"friendly": {
|
| 20 |
+
"name": "Friendly",
|
| 21 |
+
"description": "Warm and approachable, making the user feel comfortable."
|
| 22 |
+
},
|
| 23 |
+
"neutral": {
|
| 24 |
+
"name": "Neutral",
|
| 25 |
+
"description": "Objective and balanced, without bias or emotion."
|
| 26 |
+
},
|
| 27 |
+
"storytelling": {
|
| 28 |
+
"name": "Storytelling",
|
| 29 |
+
"description": "Narrative style, weaving details into a compelling story."
|
| 30 |
+
},
|
| 31 |
+
"inspirational": {
|
| 32 |
+
"name": "Inspirational",
|
| 33 |
+
"description": "Uplifting and encouraging, motivating the user."
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
}
|
pyproject.toml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "expressly_server"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "expressly-server using crewAI"
|
| 5 |
+
authors = [{ name = "Deepak Pant", email = "[email protected]" }]
|
| 6 |
+
requires-python = ">=3.10,<=3.13"
|
| 7 |
+
dependencies = [
|
| 8 |
+
"crewai[tools]>=0.86.0,<1.0.0",
|
| 9 |
+
"gradio>=5.12.0,<5.13.0"
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
[project.scripts]
|
| 13 |
+
expressly = "expressly_server.web_app:launch"
|
| 14 |
+
run_crew = "expressly_server.main:run"
|
| 15 |
+
train = "expressly_server.main:train"
|
| 16 |
+
replay = "expressly_server.main:replay"
|
| 17 |
+
test = "expressly_server.main:test"
|
| 18 |
+
|
| 19 |
+
[build-system]
|
| 20 |
+
requires = ["hatchling"]
|
| 21 |
+
build-backend = "hatchling.build"
|
src/expressly_server/__init__.py
ADDED
|
File without changes
|
src/expressly_server/app.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, responses
|
| 2 |
+
from fastapi.openapi.utils import get_openapi
|
| 3 |
+
from expressly_server.routers.route import router
|
| 4 |
+
|
| 5 |
+
__version__ = "0.0.1"
|
| 6 |
+
|
| 7 |
+
app = FastAPI(
|
| 8 |
+
title="Expressly AI Server",
|
| 9 |
+
description="Expressly is your ultimate tool for transforming text effortlessly.",
|
| 10 |
+
version=__version__,
|
| 11 |
+
docs_url="/docs",
|
| 12 |
+
redoc_url="/redoc",
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
app = FastAPI()
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@app.get("/", include_in_schema=False)
|
| 20 |
+
async def root() -> responses.RedirectResponse:
|
| 21 |
+
"""
|
| 22 |
+
Redirects the root URL to the API documentation page.
|
| 23 |
+
|
| 24 |
+
Returns:
|
| 25 |
+
RedirectResponse: A response object that redirects the client to the "/docs" URL.
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
return responses.RedirectResponse("/docs")
|
| 29 |
+
|
| 30 |
+
# Include routers
|
| 31 |
+
app.include_router(router, prefix="/app/v1", tags=["Operations"])
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _custom_openapi() -> dict:
|
| 35 |
+
if app.openapi_schema:
|
| 36 |
+
return app.openapi_schema
|
| 37 |
+
openapi_schema = get_openapi(
|
| 38 |
+
title="Expressly AI Server",
|
| 39 |
+
description="Expressly is your ultimate tool for transforming text effortlessly.",
|
| 40 |
+
version=__version__,
|
| 41 |
+
routes=app.routes,
|
| 42 |
+
)
|
| 43 |
+
app.openapi_schema = openapi_schema
|
| 44 |
+
return app.openapi_schema
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
app.openapi = _custom_openapi
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def main() -> None:
|
| 51 |
+
"""
|
| 52 |
+
The main entry point of the application.
|
| 53 |
+
|
| 54 |
+
This function starts the FastAPI server using Uvicorn. It serves the API
|
| 55 |
+
on the specified host and port. The function is intended to be run
|
| 56 |
+
directly when the script is executed.
|
| 57 |
+
|
| 58 |
+
Notes:
|
| 59 |
+
- The 'nosec B104' comment is used to suppress a security warning
|
| 60 |
+
related to binding to all network interfaces.
|
| 61 |
+
"""
|
| 62 |
+
|
| 63 |
+
import uvicorn
|
| 64 |
+
|
| 65 |
+
uvicorn.run(app, host="0.0.0.0", port=10000)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
if __name__ == "__main__":
|
| 69 |
+
main()
|
src/expressly_server/config/agents.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
content_creator:
|
| 2 |
+
role: >
|
| 3 |
+
Senior Content Creator
|
| 4 |
+
goal: >
|
| 5 |
+
Created engaging and informative content
|
| 6 |
+
backstory: >
|
| 7 |
+
You're a seasoned content creator with a knack for producing high-quality
|
| 8 |
+
content that captivates and educates your audience. You're known for your
|
| 9 |
+
ability to translate complex concepts into clear and engaging narratives,
|
| 10 |
+
making it easy for others to learn and understand.
|
src/expressly_server/config/tasks.yaml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
content_creator_task:
|
| 2 |
+
description: >
|
| 3 |
+
Understand the context carefully
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
Context:
|
| 7 |
+
({context})
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
Describe the context in a way that would follow the tone, format, and guidelines provided.
|
| 11 |
+
|
| 12 |
+
---
|
| 13 |
+
Tone:(
|
| 14 |
+
[Tone: {tone[name]}] ###
|
| 15 |
+
[Tone Description: {tone[description]}] ###
|
| 16 |
+
)
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
---
|
| 20 |
+
Format:(
|
| 21 |
+
[Format Type: {format[name]}] ###
|
| 22 |
+
[Format Description:{format[description]}] ###
|
| 23 |
+
[Max Content Length: {format[max_length]}] ###
|
| 24 |
+
[Negetive: {format[negative]}])
|
| 25 |
+
)
|
| 26 |
+
---
|
| 27 |
+
|
| 28 |
+
---
|
| 29 |
+
Target Audience: (
|
| 30 |
+
[Target Audience: {target_audience[name]}] ###
|
| 31 |
+
[Target Audience Description: {target_audience[description]}] ###
|
| 32 |
+
[Ideal Audience Description: {target_audience[ideal_audience]}] ###
|
| 33 |
+
)
|
| 34 |
+
---
|
| 35 |
+
|
| 36 |
+
Make sure you find the latest information about the topic in the internet if needed and provide a well-researched content.
|
| 37 |
+
expected_output: >
|
| 38 |
+
A fully fledge output with the desised format & tone and the the Guidelines provided in the description.
|
| 39 |
+
Formatted as markdown without '```'
|
| 40 |
+
agent: content_creator
|
src/expressly_server/crew.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from crewai import Agent, Crew, Process, Task
|
| 2 |
+
from crewai.project import CrewBase, agent, crew, task, before_kickoff
|
| 3 |
+
from crewai.llm import LLM
|
| 4 |
+
from crewai.knowledge.source.json_knowledge_source import JSONKnowledgeSource
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
+
import os
|
| 7 |
+
from typing import Dict, Any, Optional
|
| 8 |
+
import json
|
| 9 |
+
from expressly_server.utils.utils import load_json_data, sanitize_input
|
| 10 |
+
|
| 11 |
+
load_dotenv()
|
| 12 |
+
|
| 13 |
+
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
| 14 |
+
MODEL = os.getenv("MODEL")
|
| 15 |
+
|
| 16 |
+
FORMAT_JSON_FILE = "format.json"
|
| 17 |
+
TONE_JSON_FILE = "tone.json"
|
| 18 |
+
TARGET_AUDIENCE_JSON_FILE = "target_audience.json"
|
| 19 |
+
CONTENT_STYLE_MAPPING_JSON_FILE = "content_style_mapping.json"
|
| 20 |
+
KNOWLEDGE_SOURCE_PATH = "knowledge"
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
@CrewBase
|
| 24 |
+
class ExpresslyServer:
|
| 25 |
+
"""ExpresslyServer crew"""
|
| 26 |
+
|
| 27 |
+
# Create a knowledge source
|
| 28 |
+
json_knowledge_source = JSONKnowledgeSource(
|
| 29 |
+
file_paths=["format.json", "tone.json", "target_audience.json"],
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
agents_config = "config/agents.yaml"
|
| 33 |
+
tasks_config = "config/tasks.yaml"
|
| 34 |
+
|
| 35 |
+
llm = LLM(model=MODEL, api_key=GEMINI_API_KEY, temperature=0.7)
|
| 36 |
+
|
| 37 |
+
@before_kickoff
|
| 38 |
+
def validate_inputs(
|
| 39 |
+
self, inputs: Optional[Dict[str, Any]]
|
| 40 |
+
) -> Optional[Dict[str, Any]]:
|
| 41 |
+
"""
|
| 42 |
+
Validate and process user inputs based on the active tab selection.
|
| 43 |
+
|
| 44 |
+
This method checks the integrity and presence of required inputs, loads
|
| 45 |
+
necessary JSON data, and validates the active_tab value to ensure the
|
| 46 |
+
appropriate fields are populated. It formats the inputs for further processing.
|
| 47 |
+
|
| 48 |
+
Parameters:
|
| 49 |
+
inputs (Optional[Dict[str, Any]]): The dictionary containing user inputs,
|
| 50 |
+
including 'target_audience', 'format', 'tone', 'active_tab', and 'prompt'.
|
| 51 |
+
|
| 52 |
+
Returns:
|
| 53 |
+
Optional[Dict[str, Any]]: A dictionary formatted with context, format, tone,
|
| 54 |
+
and target_audience details based on the inputs provided.
|
| 55 |
+
|
| 56 |
+
Raises:
|
| 57 |
+
ValueError: If inputs are missing, not a dictionary, or required fields
|
| 58 |
+
('active_tab', 'prompt', 'format', 'tone', 'target_audience') are not provided
|
| 59 |
+
or invalid.
|
| 60 |
+
"""
|
| 61 |
+
|
| 62 |
+
if inputs is None or len(inputs) == 0 or not isinstance(inputs, dict):
|
| 63 |
+
raise ValueError("Inputs is required and must be a dictionary")
|
| 64 |
+
|
| 65 |
+
## Get the first element from the list of inputs and get the value of target, format, active_tab and prompt
|
| 66 |
+
query = inputs
|
| 67 |
+
target_audience: str = sanitize_input(query.get("target_audience"))
|
| 68 |
+
content_format: str = sanitize_input(query.get("format"))
|
| 69 |
+
content_tone: str = sanitize_input(query.get("tone"))
|
| 70 |
+
prompt: str = query.get("prompt")
|
| 71 |
+
|
| 72 |
+
# Check if prompt are not None
|
| 73 |
+
if prompt is None:
|
| 74 |
+
raise ValueError("Prompt is required")
|
| 75 |
+
|
| 76 |
+
# Load JSON data from content_style_mapping.json
|
| 77 |
+
content_style_mapping_json = load_json_data(
|
| 78 |
+
CONTENT_STYLE_MAPPING_JSON_FILE, KNOWLEDGE_SOURCE_PATH
|
| 79 |
+
)
|
| 80 |
+
tone_json = load_json_data(TONE_JSON_FILE, KNOWLEDGE_SOURCE_PATH)
|
| 81 |
+
format_json = load_json_data(FORMAT_JSON_FILE, KNOWLEDGE_SOURCE_PATH)
|
| 82 |
+
target_audience_json = load_json_data(
|
| 83 |
+
TARGET_AUDIENCE_JSON_FILE, KNOWLEDGE_SOURCE_PATH
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
if target_audience != "":
|
| 87 |
+
## Resetting the format and tone as per the target audience
|
| 88 |
+
mappings: dict = content_style_mapping_json.get("target_audience").get(
|
| 89 |
+
target_audience
|
| 90 |
+
)
|
| 91 |
+
format_dict = format_json.get("format").get(mappings.get("format"))
|
| 92 |
+
tone_dict = tone_json.get("tone").get(mappings.get("tone"))
|
| 93 |
+
target_audience_dict = target_audience_json.get("target_audience").get(
|
| 94 |
+
target_audience
|
| 95 |
+
)
|
| 96 |
+
elif content_format != "" and content_tone != "":
|
| 97 |
+
## Constructing a target_audience_dict with empty values and populating the format and tone as per the input
|
| 98 |
+
target_audience_dict = {
|
| 99 |
+
"name": "",
|
| 100 |
+
"description": "",
|
| 101 |
+
"ideal_audience": "",
|
| 102 |
+
}
|
| 103 |
+
format_dict = format_json.get("format").get(content_format)
|
| 104 |
+
tone_dict = tone_json.get("tone").get(content_tone)
|
| 105 |
+
else:
|
| 106 |
+
raise ValueError("Provide either target audience or format and tone")
|
| 107 |
+
|
| 108 |
+
## Format the inputs
|
| 109 |
+
inputs = {
|
| 110 |
+
"context": prompt,
|
| 111 |
+
"format": format_dict,
|
| 112 |
+
"tone": tone_dict,
|
| 113 |
+
"target_audience": target_audience_dict,
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
return inputs
|
| 117 |
+
|
| 118 |
+
@agent
|
| 119 |
+
def content_creator(self) -> Agent:
|
| 120 |
+
"""
|
| 121 |
+
Initializes and returns an Agent for content creation.
|
| 122 |
+
|
| 123 |
+
This agent is configured using predefined settings for the content creator
|
| 124 |
+
and utilizes a language model (LLM) for generating content. The agent
|
| 125 |
+
accesses a JSON knowledge source to enhance its capabilities and operates
|
| 126 |
+
in verbose mode for detailed output logging.
|
| 127 |
+
|
| 128 |
+
Returns:
|
| 129 |
+
Agent: An initialized agent configured for content creation.
|
| 130 |
+
"""
|
| 131 |
+
|
| 132 |
+
return Agent(
|
| 133 |
+
config=self.agents_config["content_creator"],
|
| 134 |
+
llm=self.llm,
|
| 135 |
+
knowledge_source=[self.json_knowledge_source],
|
| 136 |
+
verbose=True,
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
@task
|
| 140 |
+
def content_creator_task(self) -> Task:
|
| 141 |
+
"""
|
| 142 |
+
Initializes and returns a Task for content creation.
|
| 143 |
+
|
| 144 |
+
This task is configured using predefined settings for content creation
|
| 145 |
+
and is used by the content creator agent to generate content.
|
| 146 |
+
|
| 147 |
+
Returns:
|
| 148 |
+
Task: An initialized task configured for content creation.
|
| 149 |
+
"""
|
| 150 |
+
return Task(
|
| 151 |
+
config=self.tasks_config["content_creator_task"],
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
@crew
|
| 155 |
+
def crew(self) -> Crew:
|
| 156 |
+
"""Creates the ExpresslyServer crew"""
|
| 157 |
+
|
| 158 |
+
return Crew(
|
| 159 |
+
agents=self.agents,
|
| 160 |
+
tasks=self.tasks,
|
| 161 |
+
process=Process.sequential,
|
| 162 |
+
knowledge_source=[self.json_knowledge_source],
|
| 163 |
+
verbose=True,
|
| 164 |
+
)
|
src/expressly_server/main.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
import sys
|
| 3 |
+
import warnings
|
| 4 |
+
|
| 5 |
+
from expressly_server.crew import ExpresslyServer
|
| 6 |
+
import dotenv
|
| 7 |
+
|
| 8 |
+
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
|
| 9 |
+
|
| 10 |
+
dotenv.load_dotenv()
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def run():
|
| 14 |
+
"""
|
| 15 |
+
Run the crew.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
inputs = {
|
| 19 |
+
"prompt": "I want to thanks DeepLearning and John from the crewAI for this amazing course..",
|
| 20 |
+
"format": "Email",
|
| 21 |
+
"tone": "Friendly",
|
| 22 |
+
"target_audience": "",
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
ExpresslyServer().crew().kickoff(inputs=inputs)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def train():
|
| 29 |
+
"""
|
| 30 |
+
Train the crew for a given number of iterations.
|
| 31 |
+
"""
|
| 32 |
+
inputs = {"topic": "AI LLMs"}
|
| 33 |
+
try:
|
| 34 |
+
ExpresslyServer().crew().train(
|
| 35 |
+
n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
except Exception as e:
|
| 39 |
+
raise Exception(f"An error occurred while training the crew: {e}")
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def replay():
|
| 43 |
+
"""
|
| 44 |
+
Replay the crew execution from a specific task.
|
| 45 |
+
"""
|
| 46 |
+
try:
|
| 47 |
+
ExpresslyServer().crew().replay(task_id=sys.argv[1])
|
| 48 |
+
|
| 49 |
+
except Exception as e:
|
| 50 |
+
raise Exception(f"An error occurred while replaying the crew: {e}")
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def test():
|
| 54 |
+
"""
|
| 55 |
+
Test the crew execution and returns the results.
|
| 56 |
+
"""
|
| 57 |
+
inputs = {"topic": "AI LLMs"}
|
| 58 |
+
try:
|
| 59 |
+
ExpresslyServer().crew().test(
|
| 60 |
+
n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
except Exception as e:
|
| 64 |
+
raise Exception(f"An error occurred while replaying the crew: {e}")
|
src/expressly_server/routers/route.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter
|
| 2 |
+
from expressly_server.crew import ExpresslyServer
|
| 3 |
+
from expressly_server.schemas.schema import ChatInput, ChatOutput
|
| 4 |
+
from fastapi import HTTPException
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
router = APIRouter()
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@router.post("/chat")
|
| 11 |
+
async def chat(inputs: ChatInput) -> ChatOutput:
|
| 12 |
+
|
| 13 |
+
result = None
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
outputs = ExpresslyServer().crew().kickoff(inputs=inputs.model_dump())
|
| 17 |
+
|
| 18 |
+
if outputs is None:
|
| 19 |
+
result = "Please check the inputs and try again. If the issue persists, contact support."
|
| 20 |
+
else:
|
| 21 |
+
result = outputs.raw
|
| 22 |
+
except ValueError as ve:
|
| 23 |
+
raise ValueError(f"Value error occurred: {ve}")
|
| 24 |
+
except Exception as e:
|
| 25 |
+
print(f"An Internal server error occurred: {e}")
|
| 26 |
+
raise HTTPException(status_code=500, detail="Internal server error: Please try again later.")
|
| 27 |
+
|
| 28 |
+
return ChatOutput(result=result)
|
src/expressly_server/schemas/schema.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
from pydantic.fields import Field
|
| 3 |
+
from typing import Optional
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class ChatInput(BaseModel):
|
| 7 |
+
prompt: str = Field(
|
| 8 |
+
..., min_length=5, max_length=1000, description="The text prompt for the chat"
|
| 9 |
+
)
|
| 10 |
+
format: Optional[str] = Field(
|
| 11 |
+
None, description="The format of the response, e.g., text, markdown"
|
| 12 |
+
)
|
| 13 |
+
tone: Optional[str] = Field(
|
| 14 |
+
None, description="The tone of the response, e.g., formal, informal"
|
| 15 |
+
)
|
| 16 |
+
target_audience: Optional[str] = Field(
|
| 17 |
+
None, description="The target audience for the response"
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class ChatOutput(BaseModel):
|
| 22 |
+
result: str = Field(..., description="The transformed text response")
|
src/expressly_server/tools/__init__.py
ADDED
|
File without changes
|
src/expressly_server/utils/utils.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def load_json_data(file_name: str, file_path: str) -> dict:
|
| 5 |
+
"""
|
| 6 |
+
Loads JSON data from a file.
|
| 7 |
+
|
| 8 |
+
Args:
|
| 9 |
+
file_name (str): The name of the JSON file.
|
| 10 |
+
file_path (str): The path to the directory containing the file.
|
| 11 |
+
|
| 12 |
+
Returns:
|
| 13 |
+
dict: The content of the JSON file as a dictionary.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
file_path = f"{file_path}/{file_name}"
|
| 17 |
+
with open(file_path, "r") as file:
|
| 18 |
+
return json.load(file)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def sanitize_input(input: str) -> str:
|
| 22 |
+
"""
|
| 23 |
+
Sanitizes a string by stripping, lower casing, and replacing whitespace and hyphens with underscores.
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
input (str): The string to be sanitized.
|
| 27 |
+
|
| 28 |
+
Returns:
|
| 29 |
+
str: The sanitized string.
|
| 30 |
+
"""
|
| 31 |
+
if input is None or input == "":
|
| 32 |
+
return ""
|
| 33 |
+
return input.strip().lower().replace(" ", "_").replace("-", "_")
|
src/expressly_server/web_app.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import os
|
| 3 |
+
from expressly_server.crew import ExpresslyServer
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def call(prompt, target_audience, format, tone, active_tab):
|
| 7 |
+
"""
|
| 8 |
+
Calls the Expressly Server API to generate content based on the given inputs.
|
| 9 |
+
|
| 10 |
+
Args:
|
| 11 |
+
prompt (str): The text prompt for the chat.
|
| 12 |
+
target_audience (str): The target audience for the response.
|
| 13 |
+
format (str): The format of the response, e.g., text, markdown.
|
| 14 |
+
tone (str): The tone of the response, e.g., formal, informal.
|
| 15 |
+
active_tab (str): The active tab on the UI, either "target_audience" or "format_tone".
|
| 16 |
+
|
| 17 |
+
Returns:
|
| 18 |
+
str: The generated text response.
|
| 19 |
+
|
| 20 |
+
Raises:
|
| 21 |
+
ValueError: If prompt is empty, or if the active_tab value is invalid.
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
# Validating and constructing the inputs
|
| 25 |
+
if prompt is None or prompt == "":
|
| 26 |
+
raise ValueError("Prompt is required")
|
| 27 |
+
|
| 28 |
+
if active_tab == "target_audience":
|
| 29 |
+
format = ""
|
| 30 |
+
tone = ""
|
| 31 |
+
elif active_tab == "format_tone":
|
| 32 |
+
target_audience = ""
|
| 33 |
+
else:
|
| 34 |
+
raise ValueError("Invalid active_tab value")
|
| 35 |
+
|
| 36 |
+
inputs = {
|
| 37 |
+
"prompt": prompt,
|
| 38 |
+
"target_audience": target_audience,
|
| 39 |
+
"format": format,
|
| 40 |
+
"tone": tone,
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
outputs = ExpresslyServer().crew().kickoff(inputs=inputs)
|
| 44 |
+
|
| 45 |
+
if outputs is None:
|
| 46 |
+
result = "Please check the inputs and try again. If the issue persists, contact support."
|
| 47 |
+
else:
|
| 48 |
+
result = outputs.raw
|
| 49 |
+
|
| 50 |
+
return result
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
with gr.Blocks() as app:
|
| 54 |
+
gr.Markdown("# Expressly - Text Transformation App")
|
| 55 |
+
|
| 56 |
+
with gr.Row():
|
| 57 |
+
# Left Column for Inputs
|
| 58 |
+
with gr.Column(scale=1):
|
| 59 |
+
prompt = gr.Textbox(label="Message Expressly", max_length=1024, lines=3)
|
| 60 |
+
|
| 61 |
+
# Create a state variable to store active tab
|
| 62 |
+
active_tab = gr.State("target_audience")
|
| 63 |
+
|
| 64 |
+
with gr.Tab("Target Audience", id="tab_audience") as tab1:
|
| 65 |
+
target_audience = gr.Dropdown(
|
| 66 |
+
[
|
| 67 |
+
"LinkedIn Post",
|
| 68 |
+
"WhatsApp Message",
|
| 69 |
+
"Tweet",
|
| 70 |
+
"News Article",
|
| 71 |
+
"Technical Blog",
|
| 72 |
+
"Formal Email",
|
| 73 |
+
"Instagram Post",
|
| 74 |
+
"Website Content",
|
| 75 |
+
"Marketing Email",
|
| 76 |
+
"Job Application",
|
| 77 |
+
"Customer Support Response",
|
| 78 |
+
],
|
| 79 |
+
label="Target Audience",
|
| 80 |
+
info="Pick a Target Audience to specify the purpose or platform.",
|
| 81 |
+
)
|
| 82 |
+
# Update state when this tab is selected
|
| 83 |
+
tab1.select(lambda: "target_audience", None, active_tab)
|
| 84 |
+
|
| 85 |
+
with gr.Tab("Format & Tone", id="tab_format") as tab2:
|
| 86 |
+
format = gr.Dropdown(
|
| 87 |
+
[
|
| 88 |
+
"Post",
|
| 89 |
+
"Chat",
|
| 90 |
+
"Tweet",
|
| 91 |
+
"Email",
|
| 92 |
+
"Blog",
|
| 93 |
+
"Article",
|
| 94 |
+
"Report",
|
| 95 |
+
"Product Description",
|
| 96 |
+
],
|
| 97 |
+
label="Format",
|
| 98 |
+
info="Choose a Format to define the type of content.",
|
| 99 |
+
)
|
| 100 |
+
tone = gr.Dropdown(
|
| 101 |
+
[
|
| 102 |
+
"Professional",
|
| 103 |
+
"Casual",
|
| 104 |
+
"Straightforward",
|
| 105 |
+
"Confident",
|
| 106 |
+
"Friendly",
|
| 107 |
+
"Neutral",
|
| 108 |
+
"Storytelling",
|
| 109 |
+
"Inspirational",
|
| 110 |
+
],
|
| 111 |
+
label="Tone",
|
| 112 |
+
info="Select a Tone to set the communication style.",
|
| 113 |
+
)
|
| 114 |
+
# Update state when this tab is selected
|
| 115 |
+
tab2.select(lambda: "format_tone", None, active_tab)
|
| 116 |
+
|
| 117 |
+
btn_submit = gr.Button("Submit")
|
| 118 |
+
|
| 119 |
+
# Right Column for Output
|
| 120 |
+
with gr.Column(scale=1):
|
| 121 |
+
results = gr.Markdown(label="Result")
|
| 122 |
+
|
| 123 |
+
btn_submit.click(
|
| 124 |
+
fn=call,
|
| 125 |
+
inputs=[prompt, target_audience, format, tone, active_tab],
|
| 126 |
+
outputs=[results],
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def launch():
|
| 131 |
+
"""
|
| 132 |
+
Launch the Expressly web app.
|
| 133 |
+
|
| 134 |
+
This function starts the Expressly web app in a threaded mode, allowing it
|
| 135 |
+
to process multiple requests concurrently. The app is configured to
|
| 136 |
+
accept up to 10 requests in its queue at any given time.
|
| 137 |
+
|
| 138 |
+
The app is launched with strict CORS checks disabled, which allows it
|
| 139 |
+
to be accessed from any origin.
|
| 140 |
+
|
| 141 |
+
To launch the app, call this function with no arguments.
|
| 142 |
+
|
| 143 |
+
Example:
|
| 144 |
+
launch()
|
| 145 |
+
"""
|
| 146 |
+
app.queue(max_size=10).launch(strict_cors=False)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
if __name__ == "__main__":
|
| 150 |
+
launch()
|
uv.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|