enhanced tools

This commit is contained in:
Saifeddine ALOUI 2024-08-11 20:11:14 +02:00
parent 3f677b0158
commit a2582a650b
16 changed files with 403 additions and 97 deletions

92
app.py
View File

@ -9,46 +9,78 @@ from lollms.utilities import PackageManager
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
print("Checking ParisNeo libraries installation")
expected_ascii_colors_version = "0.4.0"
import threading
import time
import sys
from typing import List, Tuple
expected_ascii_colors_version = "0.4.2"
print(f"Checking ascii_colors ({expected_ascii_colors_version}) ...", end="", flush=True)
if not PackageManager.check_package_installed_with_version("ascii_colors", ):
if not PackageManager.check_package_installed_with_version("ascii_colors", expected_ascii_colors_version):
PackageManager.install_or_update("ascii_colors")
from ascii_colors import ASCIIColors
ASCIIColors.success("OK")
expected_pipmaster_version = "0.2.4"
ASCIIColors.yellow(f"Checking pipmaster ({expected_pipmaster_version}) ...", end="", flush=True)
if not PackageManager.check_package_installed_with_version("pipmaster", expected_pipmaster_version):
ASCIIColors.yellow(f"Checking pipmaster ({expected_ascii_colors_version}) ...", end="", flush=True)
if not PackageManager.check_package_installed_with_version("pipmaster", "0.2.4"):
PackageManager.install_or_update("pipmaster")
ASCIIColors.success("OK")
expected_lollmsvectordb_version = "0.7.9"
ASCIIColors.yellow(f"Checking lollmsvectordb ({expected_lollmsvectordb_version}) ...", end="", flush=True)
if not PackageManager.check_package_installed_with_version("lollmsvectordb", expected_lollmsvectordb_version):
PackageManager.install_or_update("lollmsvectordb")
ASCIIColors.success("OK")
expected_freedom_search_version = "0.1.7"
ASCIIColors.yellow(f"Checking freedom_search ({expected_freedom_search_version}) ...", end="", flush=True)
if not PackageManager.check_package_installed_with_version("freedom_search", expected_freedom_search_version):
PackageManager.install_or_update("freedom-search")
ASCIIColors.success("OK")
expected_scrapemaster_version = "0.1.6"
ASCIIColors.yellow(f"Checking scrapemaster ({expected_scrapemaster_version}) ...", end="", flush=True)
if not PackageManager.check_package_installed_with_version("scrapemaster", expected_scrapemaster_version):
PackageManager.install_or_update("scrapemaster")
ASCIIColors.success("OK")
expected_lollms_client_version = "0.6.2"
ASCIIColors.yellow(f"Checking lollms_client ({expected_lollms_client_version}) ...", end="", flush=True)
if not PackageManager.check_package_installed_with_version("lollms_client", expected_lollms_client_version):
PackageManager.install_or_update("lollms-client")
import pipmaster as pm
ASCIIColors.success("OK")
def animate(text: str, stop_event: threading.Event):
animation = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
idx = 0
while not stop_event.is_set():
ASCIIColors.yellow(f"\r{text} {animation[idx % len(animation)]}", end="", flush=True)
idx += 1
time.sleep(0.1)
print("\r" + " " * 50, end="\r") # Clear the line
def check_and_install_package(package: str, version: str):
stop_event = threading.Event()
animation_thread = threading.Thread(target=animate, args=(f"Checking {package} ({version})", stop_event))
animation_thread.start()
try:
installed = PackageManager.check_package_installed_with_version(package, version)
if not installed:
stop_event.set()
animation_thread.join()
print("\r" + " " * 50, end="\r") # Clear the line
PackageManager.install_or_update(package)
stop_event.set()
animation_thread.join()
print("\r" + " " * 50, end="\r") # Clear the line
ASCIIColors.yellow(f"Checking {package} ({version}) ...", end="")
ASCIIColors.success("OK")
except Exception as e:
stop_event.set()
animation_thread.join()
print("\r" + " " * 50, end="\r") # Clear the line
ASCIIColors.red(f"Error checking/installing {package}: {str(e)}")
packages: List[Tuple[str, str]] = [
("lollmsvectordb", "0.7.9"),
("freedom_search", "0.1.7"),
("scrapemaster", "0.1.6"),
("lollms_client", "0.6.2")
]
def main():
ASCIIColors.cyan("Checking ParisNeo libraries installation")
print()
for package, version in packages:
check_and_install_package(package, version)
print() # Add a newline for better readability between package checks
ASCIIColors.green("All packages have been checked and are up to date!")
main()
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from starlette.responses import FileResponse

2
endpoints/libraries/axios.min.js vendored Normal file

File diff suppressed because one or more lines are too long

13
endpoints/libraries/jszip.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,7 @@
from fastapi import APIRouter, HTTPException
from fastapi.responses import PlainTextResponse
from pathlib import Path
from fastapi import APIRouter, HTTPException, Query
from fastapi.responses import PlainTextResponse
from pydantic import BaseModel, Field
@ -21,6 +25,21 @@ import pipmaster as pm
if not pm.is_installed("httpx"):
pm.install("httpx")
import httpx
from lollms.utilities import PackageManager
# Pull the repository if it already exists
def check_lollms_models_zoo():
if not PackageManager.check_package_installed("zipfile"):
PackageManager.install_or_update("zipfile36")
ASCIIColors.execute_with_animation("Checking zip library.", check_lollms_models_zoo)
from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from pathlib import Path
import zipfile
import io
router = APIRouter()
lollmsElfServer: LOLLMSWebUI = LOLLMSWebUI.get_instance()
@ -136,13 +155,6 @@ async def open_folder_in_vscode(request: OpenFolderRequest):
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to open folder: {str(e)}")
@router.post("/apps/{app_name}/code")
async def get_app_code(app_name: str, auth: AuthRequest):
check_access(lollmsElfServer, auth.client_id)
app_path = lollmsElfServer.lollms_paths.apps_zoo_path / app_name / "index.html"
if not app_path.exists():
raise HTTPException(status_code=404, detail="App not found")
return FileResponse(app_path)
@router.get("/apps/{app_name}/{file}")
async def get_app_file(app_name: str, file: str):
@ -152,6 +164,100 @@ async def get_app_file(app_name: str, file: str):
raise HTTPException(status_code=404, detail="App file not found")
return FileResponse(app_path)
class AppNameInput(BaseModel):
client_id: str
app_name: str
@router.post("/download_app")
async def download_app(input_data: AppNameInput):
check_access(lollmsElfServer, input_data.client_id)
app_name = sanitize_path(input_data.app_name)
app_path = lollmsElfServer.lollms_paths.apps_zoo_path / app_name
if not app_path.exists():
raise HTTPException(status_code=404, detail="App not found")
# Create a BytesIO object to store the zip file
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
for file in app_path.rglob('*'):
if file.is_file() and '.git' not in file.parts:
relative_path = file.relative_to(app_path)
zip_file.write(file, arcname=relative_path)
# Seek to the beginning of the BytesIO object
zip_buffer.seek(0)
# Create a StreamingResponse to return the zip file
return StreamingResponse(
zip_buffer,
media_type="application/zip",
headers={"Content-Disposition": f"attachment; filename={app_name}.zip"}
)
import os
import yaml
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import JSONResponse
import zipfile
import shutil
@router.post("/upload_app")
async def upload_app(client_id: str, file: UploadFile = File(...)):
check_access(lollmsElfServer, client_id)
# Create a temporary directory to extract the zip file
temp_dir = lollmsElfServer.lollms_paths.personal_path / "temp"
os.makedirs(temp_dir, exist_ok=True)
try:
# Save the uploaded file temporarily
temp_file = temp_dir / file.filename
with open(temp_file, "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
# Extract the zip file
with zipfile.ZipFile(temp_file, 'r') as zip_ref:
zip_ref.extractall(temp_dir)
# Check for required files
required_files = ['index.html', 'description.yaml', 'icon.png']
for required_file in required_files:
if not os.path.exists(os.path.join(temp_dir, required_file)):
raise HTTPException(status_code=400, detail=f"Missing required file: {required_file}")
# Read the description.yaml file
with open(os.path.join(temp_dir, 'description.yaml'), 'r') as yaml_file:
description = yaml.safe_load(yaml_file)
# Get the app name from the description
app_name = description.get('name')
if not app_name:
raise HTTPException(status_code=400, detail="App name not found in description.yaml")
# Create the app directory
app_dir = lollmsElfServer.lollms_paths.apps_zoo_path / app_name
if os.path.exists(app_dir):
raise HTTPException(status_code=400, detail="An app with this name already exists")
# Move the extracted files to the app directory
shutil.move(temp_dir, app_dir)
return JSONResponse(content={"message": f"App '{app_name}' uploaded successfully"}, status_code=200)
except zipfile.BadZipFile:
raise HTTPException(status_code=400, detail="Invalid zip file")
except yaml.YAMLError:
raise HTTPException(status_code=400, detail="Invalid YAML in description.yaml")
finally:
# Clean up temporary files
if os.path.exists(temp_file):
os.remove(temp_file)
if os.path.exists(temp_dir):
shutil.rmtree(temp_dir)
@router.post("/install/{app_name}")
async def install_app(app_name: str, auth: AuthRequest):
@ -164,9 +270,9 @@ async def install_app(app_name: str, auth: AuthRequest):
# Define the local paths for the files to copy
files_to_copy = {
"icon.png": REPO_DIR/app_name/"icon.png",
"icon.png": REPO_DIR/app_name/"icon.png",
"description.yaml": REPO_DIR/app_name/"description.yaml",
"index.html": REPO_DIR/app_name/"index.html"
"index.html": REPO_DIR/app_name/"index.html"
}
# Copy each file from the local repo
@ -250,33 +356,38 @@ def load_apps_data():
))
return apps
@router.get("/lollms_js", response_class=PlainTextResponse)
async def lollms_js():
# Define the path to the JSON file using pathlib
file_path = Path(__file__).parent / "lollms_client_js.js"
# Read the JSON file
with file_path.open('r') as file:
data = file.read()
return data
@router.get("/lollms_assets/{asset_type}/{file_name}", response_class=PlainTextResponse)
async def lollms_assets(asset_type: str, file_name: str):
# Define the base path
base_path = Path(__file__).parent
@router.get("/lollms_markdown_renderer", response_class=PlainTextResponse)
async def lollms_markdown_renderer():
# Define the path to the JSON file using pathlib
file_path = Path(__file__).parent / "lollms_markdown_renderer.js"
# Read the JSON file
with file_path.open('r') as file:
data = file.read()
return data
# Determine the correct directory based on asset_type
if asset_type == "js":
directory = base_path / "libraries"
file_extension = ".js"
elif asset_type == "css":
directory = base_path / "styles"
file_extension = ".css"
else:
raise HTTPException(status_code=400, detail="Invalid asset type")
@router.get("/lollms_markdown_renderer_css")
async def lollms_markdown_renderer_css():
# Define the path to the CSS file using pathlib
file_path = Path(__file__).parent / "lollms_markdown_renderer.css"
# Sanitize the file name to prevent path traversal
safe_file_name = sanitize_path(file_name)
# Use FileResponse to serve the CSS file
return FileResponse(file_path, media_type="text/css")
# Construct the full file path
file_path = directory / f"{safe_file_name}{file_extension}"
# Check if the file exists and is within the allowed directory
if not file_path.is_file() or not file_path.is_relative_to(directory):
raise HTTPException(status_code=404, detail="File not found")
# Read and return the file content
try:
with file_path.open('r') as file:
content = file.read()
return content
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error reading file: {str(e)}")
@router.get("/template")

View File

@ -0,0 +1,9 @@
/*!
Theme: Default
Description: Original highlight.js style
Author: (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
Maintainer: @highlightjs/core-team
Website: https://highlightjs.org/
License: see project LICENSE
Touched: 2021
*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}

1
endpoints/styles/tailwind.min.css vendored Normal file

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit fa70a95766bbda5860af375b0d2c046414066f4a
Subproject commit 1b4c8ca2930731f1012380e00538d8ccdf9fd816

View File

@ -32,4 +32,7 @@ pipmaster>=0.1.7
lollmsvectordb>=0.3.0
freedom-search
scrapemaster
poetry
aiofiles
python-multipart
zipfile36

File diff suppressed because one or more lines are too long

2
web/dist/index.html vendored
View File

@ -6,7 +6,7 @@
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LoLLMS WebUI</title>
<script type="module" crossorigin src="/assets/index-36498a73.js"></script>
<script type="module" crossorigin src="/assets/index-c846d59c.js"></script>
<link rel="stylesheet" href="/assets/index-22501ef4.css">
</head>
<body>

View File

@ -24,7 +24,13 @@
</svg>
Open Folder
</button>
<input type="file" @change="onFileSelected" accept=".zip" ref="fileInput" style="display: none;">
<button @click="triggerFileInput" :disabled="isUploading" class="btn-secondary text-green-500 hover:text-green-600 transition duration-300 ease-in-out" title="Upload App">
{{ isUploading ? 'Uploading...' : 'Upload App' }}
</button>
</div>
<p v-if="message">{{ message }}</p>
<p v-if="error" class="error">{{ error }}</p>
<div class="relative flex-grow max-w-md">
<input
@ -96,7 +102,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</button>
<button @click="downloadApp(app.name)" class="text-green-500 hover:text-green-600 transition duration-300 ease-in-out" title="Download">
<button @click="downloadApp(app.folder_name)" class="text-green-500 hover:text-green-600 transition duration-300 ease-in-out" title="Download">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
@ -149,6 +155,10 @@ export default {
message: '',
successMessage: true,
searchQuery: '',
selectedFile: null,
isUploading: false,
message: '',
error: ''
};
},
computed: {
@ -175,6 +185,46 @@ export default {
}
},
methods: {
triggerFileInput() {
this.$refs.fileInput.click();
},
onFileSelected(event) {
this.selectedFile = event.target.files[0];
this.message = '';
this.error = '';
this.uploadApp();
},
async uploadApp() {
if (!this.selectedFile) {
this.error = 'Please select a file to upload.';
return;
}
this.isUploading = true;
this.message = '';
this.error = '';
const formData = new FormData();
formData.append('file', this.selectedFile);
formData.append('client_id', this.$store.state.client_id);
try {
const response = await axios.post('/upload_app', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
});
this.message = response.data.message;
this.$refs.fileInput.value = ''; // Reset file input
this.selectedFile = null;
} catch (error) {
console.error('Error uploading app:', error);
this.error = error.response?.data?.detail || 'Failed to upload the app. Please try again.';
} finally {
this.isUploading = false;
}
},
async fetchApps() {
this.loading = true;
try {
@ -283,20 +333,43 @@ export default {
}
},
async downloadApp(appName) {
this.isLoading = true;
this.error = null;
try {
const response = await axios.get(`/download/${appName}`, {
responseType: 'blob',
const response = await axios.post('/download_app', {
client_id: this.$store.state.client_id,
app_name: appName
});
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${appName}.zip`);
document.body.appendChild(link);
link.click();
link.remove();
this.showMessage('Download started!', true);
// Get the filename from the Content-Disposition header
const contentDisposition = response.headers['content-disposition'];
const filenameMatch = contentDisposition && contentDisposition.match(/filename="?(.+)"?/i);
const filename = filenameMatch ? filenameMatch[1] : 'app.zip';
// Create a Blob from the response data
const blob = new Blob([response.data], { type: 'application/zip' });
// Create a temporary URL for the Blob
const url = window.URL.createObjectURL(blob);
// Create a temporary anchor element and trigger the download
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
// Clean up
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (error) {
this.showMessage('Download failed.', false);
console.error('Error downloading app:', error);
this.error = 'Failed to download the app. Please try again.';
} finally {
this.isLoading = false;
}
},
openApp(app) {

@ -1 +1 @@
Subproject commit 70cc5e9c506d73c392bb123402106b5df07eac90
Subproject commit dfaad7d9e9c973b41f99d2faeb938bfd7d8bf9e6