This commit is contained in:
Saifeddine ALOUI 2024-07-28 02:49:10 +02:00
parent 813b74e94c
commit ba771d54af
10 changed files with 823 additions and 69 deletions

View File

@ -0,0 +1,5 @@
description: This is a 2 players chess game
version: 1.0
author: ParisNeo
model: gpt-4o-mini
disclaimer: None

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -0,0 +1,639 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chess Game with Check and Checkmate</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.game-container {
display: flex;
gap: 20px;
}
#chessboard {
display: grid;
grid-template-columns: repeat(8, 60px);
grid-template-rows: repeat(8, 60px);
border: 2px solid #333;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
}
.square {
width: 60px;
height: 60px;
display: flex;
justify-content: center;
align-items: center;
font-size: 40px;
cursor: pointer;
transition: background-color 0.3s;
}
.white {
background-color: #f0d9b5;
}
.black {
background-color: #b58863;
}
.selected {
background-color: #7fc97f;
}
.possible-move {
position: relative;
}
.possible-move::before {
content: '';
position: absolute;
width: 20px;
height: 20px;
background-color: rgba(0, 0, 0, 0.2);
border-radius: 50%;
}
.in-check {
background-color: #ff6b6b;
}
.turn-indicator {
position: absolute;
top: 20px;
left: 20px;
font-size: 24px;
font-weight: bold;
}
.game-status {
position: absolute;
top: 60px;
left: 20px;
font-size: 24px;
font-weight: bold;
color: #ff6b6b;
}
.scoreboard {
background-color: #fff;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.scoreboard h2 {
margin-top: 0;
}
.score {
font-size: 18px;
margin-bottom: 10px;
}
.leaderboard {
margin-top: 20px;
}
.leaderboard ol {
padding-left: 20px;
}
.winner-input {
display: none;
margin-top: 20px;
}
.credits {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
background-color: rgba(0, 0, 0, 0.8);
color: white;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.credits h1 {
margin: 0;
font-size: 48px;
}
.credits p {
font-size: 24px;
}
.credits button {
margin-top: 20px;
padding: 10px 20px;
font-size: 18px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="turn-indicator" id="turnIndicator"></div>
<div class="game-status" id="gameStatus"></div>
<div class="game-container">
<div id="chessboard"></div>
<div class="scoreboard">
<h2>Scoreboard</h2>
<div class="score" id="whiteScore">White: 0</div>
<div class="score" id="blackScore">Black: 0</div>
<div class="leaderboard">
<h3>Leaderboard</h3>
<ol id="leaderboardList"></ol>
</div>
<div class="winner-input" id="winnerInput">
<input type="text" id="winnerName" placeholder="Enter your name">
<button onclick="saveWinner()">Save</button>
</div>
</div>
</div>
<div class="credits" id="credits">
<h1>Congratulations!</h1>
<p>Game created by WebCraft Maestro, prompted by ParisNeo on lollms system.</p>
<button onclick="playAgain()">Play Again</button>
<audio id="creditMusic" src="https://www.bensound.com/bensound-music/bensound-sunny.mp3" loop></audio>
</div>
<script>
const board = document.getElementById('chessboard');
const turnIndicator = document.getElementById('turnIndicator');
const gameStatus = document.getElementById('gameStatus');
const whiteScoreElement = document.getElementById('whiteScore');
const blackScoreElement = document.getElementById('blackScore');
const leaderboardList = document.getElementById('leaderboardList');
const winnerInput = document.getElementById('winnerInput');
const winnerNameInput = document.getElementById('winnerName');
const credits = document.getElementById('credits');
const creditMusic = document.getElementById('creditMusic');
let selectedPiece = null;
let currentPlayer = 'white';
let whiteScore = 0;
let blackScore = 0;
let leaderboard = [];
let gameBoard = [];
const initialBoard = [
['♜', '♞', '♝', '♛', '♚', '♝', '♞', '♜'],
['♟', '♟', '♟', '♟', '♟', '♟', '♟', '♟'],
['', '', '', '', '', '', '', ''],
['', '', '', '', '', '', '', ''],
['', '', '', '', '', '', '', ''],
['', '', '', '', '', '', '', ''],
['♙', '♙', '♙', '♙', '♙', '♙', '♙', '♙'],
['♖', '♘', '♗', '♕', '♔', '♗', '♘', '♖']
];
const pieceValues = {
'♙': 1, '♟': 1, // Pawns
'♘': 3, '♞': 3, // Knights
'♗': 3, '♝': 3, // Bishops
'♖': 5, '♜': 5, // Rooks
'♕': 9, '♛': 9, // Queens
'♔': 0, '♚': 0 // Kings (no point value)
};
function createBoard() {
for (let row = 0; row < 8; row++) {
gameBoard[row] = [];
for (let col = 0; col < 8; col++) {
const square = document.createElement('div');
square.className = `square ${(row + col) % 2 === 0 ? 'white' : 'black'}`;
square.dataset.row = row;
square.dataset.col = col;
square.textContent = initialBoard[row][col];
square.addEventListener('click', handleClick);
board.appendChild(square);
gameBoard[row][col] = initialBoard[row][col];
}
}
updateTurnIndicator();
}
function handleClick(event) {
const square = event.target;
const row = parseInt(square.dataset.row);
const col = parseInt(square.dataset.col);
if (selectedPiece) {
if (square.classList.contains('possible-move')) {
const capturedPiece = square.textContent;
const fromRow = parseInt(selectedPiece.dataset.row);
const fromCol = parseInt(selectedPiece.dataset.col);
// Make the move
movePiece(selectedPiece, square);
updateGameBoard(fromRow, fromCol, row, col);
// Check for check and checkmate
const oppositeColor = currentPlayer === 'white' ? 'black' : 'white';
if (isInCheck(oppositeColor)) {
if (isCheckmate(oppositeColor)) {
gameStatus.textContent = `Checkmate! ${currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1)} wins!`;
winnerInput.style.display = 'block';
showCredits();
} else {
gameStatus.textContent = `${oppositeColor.charAt(0).toUpperCase() + oppositeColor.slice(1)} is in check!`;
}
} else {
gameStatus.textContent = '';
}
updateScore(capturedPiece);
clearSelection();
currentPlayer = oppositeColor;
updateTurnIndicator();
} else {
clearSelection();
selectPiece(square);
}
} else {
selectPiece(square);
}
}
function selectPiece(square) {
const piece = square.textContent;
if (piece && ((currentPlayer === 'white' && piece.charCodeAt(0) >= 9812 && piece.charCodeAt(0) <= 9817) ||
(currentPlayer === 'black' && piece.charCodeAt(0) >= 9818 && piece.charCodeAt(0) <= 9823))) {
selectedPiece = square;
square.classList.add('selected');
showPossibleMoves(square);
}
}
function showPossibleMoves(square) {
const piece = square.textContent;
const row = parseInt(square.dataset.row);
const col = parseInt(square.dataset.col);
clearPossibleMoves();
switch (piece) {
case '♔': case '♚': // King
showKingMoves(row, col);
break;
case '♕': case '♛': // Queen
showQueenMoves(row, col);
break;
case '♖': case '♜': // Rook
showRookMoves(row, col);
break;
case '♗': case '♝': // Bishop
showBishopMoves(row, col);
break;
case '♘': case '♞': // Knight
showKnightMoves(row, col);
break;
case '♙': case '♟': // Pawn
showPawnMoves(row, col, piece === '♙');
break;
}
}
function showKingMoves(row, col) {
const directions = [
[-1, -1], [-1, 0], [-1, 1],
[0, -1], [0, 1],
[1, -1], [1, 0], [1, 1]
];
for (const [dx, dy] of directions) {
const newRow = row + dx;
const newCol = col + dy;
if (isValidMove(newRow, newCol)) {
const targetSquare = document.querySelector(`.square[data-row="${newRow}"][data-col="${newCol}"]`);
if (isValidTarget(targetSquare) && !wouldBeInCheck(row, col, newRow, newCol)) {
targetSquare.classList.add('possible-move');
}
}
}
}
function showQueenMoves(row, col) {
showRookMoves(row, col);
showBishopMoves(row, col);
}
function showRookMoves(row, col) {
const directions = [[-1, 0], [1, 0], [0, -1], [0, 1]];
for (const [dx, dy] of directions) {
let newRow = row + dx;
let newCol = col + dy;
while (isValidMove(newRow, newCol)) {
const targetSquare = document.querySelector(`.square[data-row="${newRow}"][data-col="${newCol}"]`);
if (isValidTarget(targetSquare)) {
if (!wouldBeInCheck(row, col, newRow, newCol)) {
targetSquare.classList.add('possible-move');
}
if (targetSquare.textContent !== '') break;
} else {
break;
}
newRow += dx;
newCol += dy;
}
}
}
function showBishopMoves(row, col) {
const directions = [[-1, -1], [-1, 1], [1, -1], [1, 1]];
for (const [dx, dy] of directions) {
let newRow = row + dx;
let newCol = col + dy;
while (isValidMove(newRow, newCol)) {
const targetSquare = document.querySelector(`.square[data-row="${newRow}"][data-col="${newCol}"]`);
if (isValidTarget(targetSquare)) {
if (!wouldBeInCheck(row, col, newRow, newCol)) {
targetSquare.classList.add('possible-move');
}
if (targetSquare.textContent !== '') break;
} else {
break;
}
newRow += dx;
newCol += dy;
}
}
}
function showKnightMoves(row, col) {
const moves = [
[-2, -1], [-2, 1], [-1, -2], [-1, 2],
[1, -2], [1, 2], [2, -1], [2, 1]
];
for (const [dx, dy] of moves) {
const newRow = row + dx;
const newCol = col + dy;
if (isValidMove(newRow, newCol)) {
const targetSquare = document.querySelector(`.square[data-row="${newRow}"][data-col="${newCol}"]`);
if (isValidTarget(targetSquare) && !wouldBeInCheck(row, col, newRow, newCol)) {
targetSquare.classList.add('possible-move');
}
}
}
}
function showPawnMoves(row, col, isWhite) {
const direction = isWhite ? -1 : 1;
const startRow = isWhite ? 6 : 1;
// Move forward
let newRow = row + direction;
if (isValidMove(newRow, col)) {
const targetSquare = document.querySelector(`.square[data-row="${newRow}"][data-col="${col}"]`);
if (targetSquare.textContent === '' && !wouldBeInCheck(row, col, newRow, col)) {
targetSquare.classList.add('possible-move');
// Double move from starting position
if (row === startRow) {
newRow = row + 2 * direction;
const doubleSquare = document.querySelector(`.square[data-row="${newRow}"][data-col="${col}"]`);
if (doubleSquare.textContent === '' && !wouldBeInCheck(row, col, newRow, col)) {
doubleSquare.classList.add('possible-move');
}
}
}
}
// Capture diagonally
for (const dcol of [-1, 1]) {
newRow = row + direction;
const newCol = col + dcol;
if (isValidMove(newRow, newCol)) {
const targetSquare = document.querySelector(`.square[data-row="${newRow}"][data-col="${newCol}"]`);
if (targetSquare.textContent !== '' && isValidTarget(targetSquare) && !wouldBeInCheck(row, col, newRow, newCol)) {
targetSquare.classList.add('possible-move');
}
}
}
}
function isValidMove(row, col) {
return row >= 0 && row < 8 && col >= 0 && col < 8;
}
function isValidTarget(square) {
const piece = square.textContent;
if (!piece) return true;
if (currentPlayer === 'white') {
return piece.charCodeAt(0) >= 9818;
} else {
return piece.charCodeAt(0) <= 9817;
}
}
function clearPossibleMoves() {
document.querySelectorAll('.possible-move').forEach(square => square.classList.remove('possible-move'));
}
function clearSelection() {
if (selectedPiece) {
selectedPiece.classList.remove('selected');
selectedPiece = null;
clearPossibleMoves();
}
}
function movePiece(fromSquare, toSquare) {
toSquare.textContent = fromSquare.textContent;
fromSquare.textContent = '';
}
function updateGameBoard(fromRow, fromCol, toRow, toCol) {
gameBoard[toRow][toCol] = gameBoard[fromRow][fromCol];
gameBoard[fromRow][fromCol] = '';
}
function updateTurnIndicator() {
turnIndicator.textContent = `${currentPlayer.charAt(0).toUpperCase() + currentPlayer.slice(1)}'s turn`;
turnIndicator.style.color = currentPlayer;
}
function updateScore(capturedPiece) {
if (capturedPiece) {
const points = pieceValues[capturedPiece] || 0;
if (currentPlayer === 'white') {
whiteScore += points;
} else {
blackScore += points;
}
whiteScoreElement.textContent = `White: ${whiteScore}`;
blackScoreElement.textContent = `Black: ${blackScore}`;
updateLeaderboard();
}
}
function updateLeaderboard() {
leaderboard = [
{ name: 'White', score: whiteScore },
{ name: 'Black', score: blackScore },
...leaderboard.filter(entry => entry.name !== 'White' && entry.name !== 'Black')
];
leaderboard.sort((a, b) => b.score - a.score);
leaderboard = leaderboard.slice(0, 5); // Keep only top 5
leaderboardList.innerHTML = '';
leaderboard.forEach(entry => {
const li = document.createElement('li');
li.textContent = `${entry.name}: ${entry.score}`;
leaderboardList.appendChild(li);
});
}
function isInCheck(color) {
const king = color === 'white' ? '♔' : '♚';
let kingPosition;
// Find the king's position
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
if (gameBoard[row][col] === king) {
kingPosition = { row, col };
break;
}
}
if (kingPosition) break;
}
// Check if any opponent's piece can attack the king
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
const piece = gameBoard[row][col];
if (piece && ((color === 'white' && piece.charCodeAt(0) >= 9818) || (color === 'black' && piece.charCodeAt(0) <= 9817))) {
if (canAttack(row, col, kingPosition.row, kingPosition.col)) {
return true;
}
}
}
}
return false;
}
function canAttack(fromRow, fromCol, toRow, toCol) {
const piece = gameBoard[fromRow][fromCol];
switch (piece) {
case '♔': case '♚': // King
return Math.abs(fromRow - toRow) <= 1 && Math.abs(fromCol - toCol) <= 1;
case '♕': case '♛': // Queen
return canAttackStraight(fromRow, fromCol, toRow, toCol) || canAttackDiagonal(fromRow, fromCol, toRow, toCol);
case '♖': case '♜': // Rook
return canAttackStraight(fromRow, fromCol, toRow, toCol);
case '♗': case '♝': // Bishop
return canAttackDiagonal(fromRow, fromCol, toRow, toCol);
case '♘': case '♞': // Knight
const dx = Math.abs(fromRow - toRow);
const dy = Math.abs(fromCol - toCol);
return (dx === 2 && dy === 1) || (dx === 1 && dy === 2);
case '♙': case '♟': // Pawn
const direction = piece === '♙' ? -1 : 1;
return (fromCol === toCol && fromRow + direction === toRow) ||
(Math.abs(fromCol - toCol) === 1 && fromRow + direction === toRow);
}
return false;
}
function canAttackStraight(fromRow, fromCol, toRow, toCol) {
if (fromRow === toRow) {
const step = fromCol < toCol ? 1 : -1;
for (let col = fromCol + step; col !== toCol; col += step) {
if (gameBoard[fromRow][col] !== '') return false;
}
return true;
}
if (fromCol === toCol) {
const step = fromRow < toRow ? 1 : -1;
for (let row = fromRow + step; row !== toRow; row += step) {
if (gameBoard[row][fromCol] !== '') return false;
}
return true;
}
return false;
}
function canAttackDiagonal(fromRow, fromCol, toRow, toCol) {
if (Math.abs(fromRow - toRow) === Math.abs(fromCol - toCol)) {
const rowStep = fromRow < toRow ? 1 : -1;
const colStep = fromCol < toCol ? 1 : -1;
let row = fromRow + rowStep;
let col = fromCol + colStep;
while (row !== toRow && col !== toCol) {
if (gameBoard[row][col] !== '') return false;
row += rowStep;
col += colStep;
}
return true;
}
return false;
}
function wouldBeInCheck(fromRow, fromCol, toRow, toCol) {
const tempPiece = gameBoard[toRow][toCol];
gameBoard[toRow][toCol] = gameBoard[fromRow][fromCol];
gameBoard[fromRow][fromCol] = '';
const inCheck = isInCheck(currentPlayer);
gameBoard[fromRow][fromCol] = gameBoard[toRow][toCol];
gameBoard[toRow][toCol] = tempPiece;
return inCheck;
}
function isCheckmate(color) {
for (let fromRow = 0; fromRow < 8; fromRow++) {
for (let fromCol = 0; fromCol < 8; fromCol++) {
const piece = gameBoard[fromRow][fromCol];
if (piece && ((color === 'white' && piece.charCodeAt(0) <= 9817) || (color === 'black' && piece.charCodeAt(0) >= 9818))) {
for (let toRow = 0; toRow < 8; toRow++) {
for (let toCol = 0; toCol < 8; toCol++) {
if (canAttack(fromRow, fromCol, toRow, toCol) && !wouldBeInCheck(fromRow, fromCol, toRow, toCol)) {
return false;
}
}
}
}
}
}
return true;
}
function saveWinner() {
const winnerName = winnerNameInput.value.trim();
if (winnerName) {
const winner = leaderboard.find(entry => entry.name === winnerName);
if (winner) {
winner.score += 1;
} else {
leaderboard.push({ name: winnerName, score: 1 });
}
updateLeaderboard();
winnerInput.style.display = 'none';
winnerNameInput.value = '';
}
}
function showCredits() {
credits.style.display = 'flex';
creditMusic.play();
}
function playAgain() {
credits.style.display = 'none';
creditMusic.pause();
creditMusic.currentTime = 0;
resetGame();
}
function resetGame() {
board.innerHTML = '';
gameStatus.textContent = '';
winnerInput.style.display = 'none';
selectedPiece = null;
currentPlayer = 'white';
whiteScore = 0;
blackScore = 0;
gameBoard = [];
createBoard();
updateTurnIndicator();
updateScore();
}
createBoard();
updateLeaderboard();
</script>
</body>
</html>

View File

@ -8,6 +8,12 @@ import uuid
import os
import requests
import yaml
from lollms.security import check_access
import os
import subprocess
import yaml
import uuid
router = APIRouter()
lollmsElfServer: LOLLMSWebUI = LOLLMSWebUI.get_instance()
@ -76,25 +82,28 @@ async def get_app_code(app_name: str, auth: AuthRequest):
@router.post("/install/{app_name}")
async def install_app(app_name: str, auth: AuthRequest):
check_access(lollmsElfServer, auth.client_id)
REPO_DIR = lollmsElfServer.lollms_paths.personal_path/"apps_zoo_repo"
# Create the app directory
app_path = lollmsElfServer.lollms_paths.apps_zoo_path / app_name
app_path = lollmsElfServer.lollms_paths.apps_zoo_path/app_name # Adjust the path as needed
os.makedirs(app_path, exist_ok=True)
# Define the URLs for the files to download
files_to_download = {
"icon.png": f"https://github.com/ParisNeo/lollms_apps_zoo/raw/main/{app_name}/icon.png",
"description.yaml": f"https://raw.githubusercontent.com/ParisNeo/lollms_apps_zoo/main/{app_name}/description.yaml",
"index.html": f"https://raw.githubusercontent.com/ParisNeo/lollms_apps_zoo/main/{app_name}/index.html"
# Define the local paths for the files to copy
files_to_copy = {
"icon.png": REPO_DIR/app_name/"icon.png",
"description.yaml": REPO_DIR/app_name/"description.yaml",
"index.html": REPO_DIR/app_name/"index.html"
}
# Download each file
for file_name, url in files_to_download.items():
response = requests.get(url)
if response.status_code == 200:
with open(app_path / file_name, 'wb') as f:
f.write(response.content)
# Copy each file from the local repo
for file_name, local_path in files_to_copy.items():
if local_path.exists():
with open(local_path, 'rb') as src_file:
with open(app_path/file_name, 'wb') as dest_file:
dest_file.write(src_file.read())
else:
raise HTTPException(status_code=404, detail=f"{file_name} not found on GitHub")
raise HTTPException(status_code=404, detail=f"{file_name} not found in the local repository")
return {"message": f"App {app_name} installed successfully."}
@ -108,28 +117,43 @@ async def uninstall_app(app_name: str, auth: AuthRequest):
raise HTTPException(status_code=404, detail="App not found")
@router.get("/github/apps")
async def fetch_github_apps():
github_repo_url = "https://api.github.com/repos/ParisNeo/lollms_apps_zoo/contents"
response = requests.get(github_repo_url)
REPO_URL = "https://github.com/ParisNeo/lollms_apps_zoo.git"
if response.status_code == 200:
def clone_repo():
REPO_DIR = Path(lollmsElfServer.lollms_paths.personal_path) / "apps_zoo_repo"
# Check if the directory exists and if it is empty
if REPO_DIR.exists():
if any(REPO_DIR.iterdir()): # Check if the directory is not empty
print(f"Directory {REPO_DIR} is not empty. Aborting clone.")
return
else:
REPO_DIR.mkdir(parents=True, exist_ok=True) # Create the directory if it doesn't exist
# Clone the repository
subprocess.run(["git", "clone", REPO_URL, str(REPO_DIR)], check=True)
print(f"Repository cloned into {REPO_DIR}")
def pull_repo():
REPO_DIR = lollmsElfServer.lollms_paths.personal_path/"apps_zoo_repo"
subprocess.run(["git", "-C", str(REPO_DIR), "pull"], check=True)
def load_apps_data():
apps = []
for item in response.json():
if item['type'] == 'dir':
app_name = item['name']
description_url = f"https://api.github.com/repos/ParisNeo/lollms_apps_zoo/contents/{app_name}/description.yaml"
icon_url = f"https://github.com/ParisNeo/lollms_apps_zoo/blob/main/{app_name}/icon.png?raw=true"
# Fetch description.yaml
description_response = requests.get(description_url)
description_data = {}
if description_response.status_code == 200:
description_data = yaml.safe_load(requests.get(description_url).text)
REPO_DIR = lollmsElfServer.lollms_paths.personal_path/"apps_zoo_repo"
for item in os.listdir(REPO_DIR):
item_path = os.path.join(REPO_DIR, item)
if os.path.isdir(item_path):
description_path = os.path.join(item_path, "description.yaml")
icon_url = f"https://github.com/ParisNeo/lollms_apps_zoo/blob/main/{item}/icon.png?raw=true"
if os.path.exists(description_path):
with open(description_path, 'r') as file:
description_data = yaml.safe_load(file)
apps.append(AppInfo(
uid=str(uuid.uuid4()),
name=app_name,
name=item,
icon=icon_url,
description=description_data.get('description', ''),
author=description_data.get('author', ''),
@ -137,9 +161,19 @@ async def fetch_github_apps():
model_name=description_data.get('model_name', ''),
disclaimer=description_data.get('disclaimer', 'No disclaimer provided.')
))
return apps
@router.get("/github/apps")
async def fetch_github_apps():
try:
clone_repo()
pull_repo()
apps = load_apps_data()
return {"apps": apps}
else:
raise HTTPException(status_code=response.status_code, detail="Failed to fetch apps from GitHub")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/apps/{app_name}/icon")
async def get_app_icon(app_name: str):

1
lollms_apps_zoo Submodule

@ -0,0 +1 @@
Subproject commit 10ea733d4354e9cb3b293d7c6393c60e7dfb3504

8
web/dist/assets/index-0db1108c.css 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

File diff suppressed because one or more lines are too long

4
web/dist/index.html vendored
View File

@ -6,8 +6,8 @@
<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-b768925e.js"></script>
<link rel="stylesheet" href="/assets/index-4578000a.css">
<script type="module" crossorigin src="/assets/index-11efc659.js"></script>
<link rel="stylesheet" href="/assets/index-0db1108c.css">
</head>
<body>
<div id="app"></div>

View File

@ -1,6 +1,7 @@
<template>
<div class="app-zoo w-full">
<button @click="fetchGithubApps" class="bg-green-500 text-white px-4 py-2 rounded mb-4">Refresh apps from github</button>
<button @click="fetchGithubApps" class="bg-green-500 text-white px-4 py-2 rounded mb-4">Refresh apps from GitHub</button>
<div v-if="loading" class="loading-animation">Loading...</div>
<div class="app-list grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 w-full">
<div
v-for="app in combinedApps"
@ -8,7 +9,7 @@
class="app-card border rounded-lg shadow-lg p-4 cursor-pointer hover:shadow-xl transition"
@click="handleAppClick(app)"
>
<img :src="app.icon" alt="App Icon" class="w-16 h-16 mx-auto mb-2" />
<img :src="app.icon" alt="App Icon" class="app-icon w-16 h-16 mx-auto mb-2 rounded-full border border-gray-300" />
<p class="text-center font-semibold">{{ app.name }}</p>
<p class="text-center text-sm text-gray-600">Author: {{ app.author }}</p>
<p class="text-center text-sm text-gray-600">Version: {{ app.version }}</p>
@ -28,6 +29,7 @@
<iframe v-if="appCode" :srcdoc="appCode" class="app-frame w-full h-full border-none"></iframe>
<p v-else class="text-center text-red-500">Please install this app to view its code.</p>
</div>
<div v-if="message" class="message" :class="{ success: successMessage, error: !successMessage }">{{ message }}</div>
</div>
</template>
@ -41,6 +43,9 @@ export default {
githubApps: [],
selectedApp: null,
appCode: '',
loading: false,
message: '',
successMessage: true,
};
},
computed: {
@ -59,13 +64,28 @@ export default {
},
methods: {
async fetchApps() {
this.loading = true;
try {
const response = await axios.get('/apps');
this.apps = response.data;
this.showMessage('Refresh successful!', true);
} catch (error) {
this.showMessage('Failed to refresh apps.', false);
} finally {
this.loading = false;
}
},
async fetchGithubApps() {
this.loading = true;
try {
const response = await axios.get('/github/apps');
this.githubApps = response.data.apps;
this.fetchApps(); // Refresh the app list after fetching GitHub apps
await this.fetchApps(); // Refresh the app list after fetching GitHub apps
} catch (error) {
this.showMessage('Failed to refresh GitHub apps.', false);
} finally {
this.loading = false;
}
},
async handleAppClick(app) {
if (app.installed) {
@ -83,18 +103,41 @@ export default {
this.appCode = '';
},
async installApp(appName) {
this.loading = true;
try {
await axios.post(`/install/${appName}`, {
client_id: this.$store.state.client_id,
});
this.showMessage('Installation succeeded!', true);
} catch (error) {
this.showMessage('Installation failed.', false);
} finally {
this.loading = false;
this.fetchApps(); // Refresh the app list
this.fetchGithubApps(); // Refresh GitHub apps
}
},
async uninstallApp(appName) {
this.loading = true;
try {
await axios.post(`/uninstall/${appName}`, {
client_id: this.$store.state.client_id,
});
this.showMessage('Uninstallation succeeded!', true);
} catch (error) {
this.showMessage('Uninstallation failed.', false);
} finally {
this.loading = false;
this.fetchApps(); // Refresh the app list
}
},
showMessage(msg, success) {
this.message = msg;
this.successMessage = success;
setTimeout(() => {
this.message = '';
}, 3000); // Clear message after 3 seconds
}
},
mounted() {
this.fetchGithubApps(); // Fetch GitHub apps when the component is mounted
@ -115,9 +158,41 @@ export default {
gap: 1rem;
width: 100%;
}
.app-icon {
border-radius: 50%; /* Rounded edges */
border: 2px solid #d1d5db; /* Light gray border */
}
.app-frame {
width: 100%;
height: 100%;
border: none;
}
.loading-animation {
animation: fadeIn 1s ease-in-out;
margin: 20px;
font-size: 1.5rem;
color: #4a5568; /* Gray color */
}
.message {
margin-top: 20px;
padding: 10px;
border-radius: 5px;
transition: opacity 0.5s ease;
}
.message.success {
background-color: #c6f6d5; /* Green background */
color: #2f855a; /* Dark green text */
}
.message.error {
background-color: #fed7d7; /* Red background */
color: #c53030; /* Dark red text */
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>