diazla wersja

This commit is contained in:
ALicja 2026-01-30 21:41:10 +01:00
parent 7530c2aa45
commit b025000018
36 changed files with 3246 additions and 800 deletions

6
.env
View File

@ -1,6 +0,0 @@
SECRET_KEY=MOKO00koko
PASSWORD_KEY=tZnq1IyFSKeBwaLa0Bx5Ge722GgrJztHHNv3jqXMswo=
ADMIN_PASSWORD=admin
MODEL_REPO_URL=https:/git.pathl.pl
UPLOAD_LIMIT_MB=500
DEBUG=True

View File

@ -2,18 +2,33 @@
<project version="4">
<component name="ChangeListManager">
<list default="true" id="12dec3ca-9052-46eb-9015-fb58a43ed712" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/.idea/Pathl.Stona.iml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/apks/mk.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/interpreter.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/templates/editor.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.gitignore" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/Strona AI.iml" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/modules.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/modules.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/libs/string.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/models.json" afterDir="false" />
<change afterPath="$PROJECT_DIR$/models/ai.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ql_interpreter.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/ql_stdlib.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/static/css/console.css" afterDir="false" />
<change afterPath="$PROJECT_DIR$/static/css/editor.css" afterDir="false" />
<change afterPath="$PROJECT_DIR$/static/js/editor.js" afterDir="false" />
<change afterPath="$PROJECT_DIR$/templates/dashboard.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/templates/login.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/templates/models.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/templates/register.html" afterDir="false" />
<change afterPath="$PROJECT_DIR$/templates/user_projects.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.env" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/apks/mk.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/app.py" beforeDir="false" afterPath="$PROJECT_DIR$/app.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/style.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/style.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/user.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/user.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/interpreter.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/model.py" beforeDir="false" afterPath="$PROJECT_DIR$/libs/lib.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/models/Power/Readme.md" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/models/Power/description.md" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/models/Power/meta.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/models/Power/model.py" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/style.css" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/templates/editor.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/editor.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/user.html" beforeDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -23,6 +38,8 @@
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="HTML File" />
<option value="JavaScript File" />
<option value="Python Script" />
</list>
</option>
@ -40,6 +57,7 @@
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"DefaultHtmlFileTemplate": "HTML File",
"ModuleVcsDetector.initialDetectionPerformed": "true",
"Python.Pathl.Stona.executor": "Run",
"RunOnceActivity.ShowReadmeOnStart": "true",
@ -53,6 +71,12 @@
"vue.rearranger.settings.migration": "true"
}
}]]></component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/static/js" />
<recent name="$PROJECT_DIR$" />
</key>
</component>
<component name="RunManager">
<configuration name="Pathl.Stona" type="PythonConfigurationType" factoryName="Python">
<module name="Pathl.Stona" />
@ -94,7 +118,7 @@
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1769791641577</updated>
<workItem from="1769791642745" duration="1260000" />
<workItem from="1769791642745" duration="13856000" />
</task>
<servers />
</component>
@ -102,6 +126,6 @@
<option name="version" value="3" />
</component>
<component name="com.intellij.coverage.CoverageDataManagerImpl">
<SUITE FILE_PATH="coverage/Pathl_Stona$Pathl_Stona.coverage" NAME="Pathl.Stona Coverage Results" MODIFIED="1769792843549" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
<SUITE FILE_PATH="coverage/Pathl_Stona$Pathl_Stona.coverage" NAME="Pathl.Stona Coverage Results" MODIFIED="1769805559894" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
</component>
</project>

View File

@ -1,3 +0,0 @@
#Pathl.Ai cześc projektu Cafe
Ceo: Maro

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +0,0 @@
def add(a, b):
return a + b
def sub(a, b):
return a - b

807
app.py
View File

@ -1,182 +1,657 @@
from flask import Flask, request, render_template, jsonify, send_file
import os, json, importlib, importlib.util, sys, re
from markdown import markdown
from flask import Flask, render_template, request, redirect, url_for, session, flash, jsonify, send_from_directory
from markupsafe import Markup
import os, json, time, re
import markdown
from datetime import datetime
from ql_interpreter import run_ql_code # Upewnij się że to jest
app = Flask(__name__, static_folder='static')
app.secret_key = "super_secret_key_12345"
app = Flask(__name__)
# FOLDERS
UPLOAD_FOLDER = "scripts"
MODELS_FOLDER = "models"
APKS_FOLDER = "apks"
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(MODELS_FOLDER, exist_ok=True)
sys.path.insert(0, os.path.join(os.getcwd(), APKS_FOLDER))
# GLOBAL MODELS
MODELS = {}
# ---- Load models at startup ----
def load_models():
global MODELS
MODELS = {}
for folder in os.listdir(MODELS_FOLDER):
path = os.path.join(MODELS_FOLDER, folder)
if not os.path.isdir(path):
continue
meta_path = os.path.join(path, "meta.json")
if not os.path.exists(meta_path):
continue
with open(meta_path) as f:
meta = json.load(f)
func_name = meta.get("function_name")
if not func_name:
continue
py_file = os.path.join(path, "model.py")
spec = importlib.util.spec_from_file_location("model_module", py_file)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
func = getattr(module, func_name)
MODELS[folder] = {
"func": func,
"inputs": meta.get("inputs", []),
"meta": meta,
"path": path
}
load_models()
USERS_FILE = "users.json"
BASE_DIR = "projects"
os.makedirs(BASE_DIR, exist_ok=True)
# ---------------- DASHBOARD ----------------
@app.route("/")
def dashboard():
models_with_md = {}
for name, model in MODELS.items():
md_content = ""
# ------------------------------
# Funkcje pomocnicze dla templatki
# ------------------------------
@app.context_processor
def utility_processor():
def file_size(filename):
"""Zwraca rozmiar pliku w czytelnej formie"""
try:
md_path = os.path.join(model["path"], model["meta"]["description_file"])
with open(md_path, "r", encoding="utf-8") as f:
md_content = markdown(f.read())
except:
md_content = "<p>Brak opisu.</p>"
models_with_md[name] = {**model, "md_content": md_content}
username = session.get('username', '')
project = 'Projekt1' # Domyślny projekt
if 'project' in request.view_args:
project = request.view_args['project']
return render_template("user.html", models=models_with_md)
# ---------------- EDITOR ----------------
@app.route("/editor/<model_name>")
def editor(model_name):
return render_template("editor.html", model_name=model_name)
# ---------------- RUN SCRIPT (MKScript) ----------------
@app.route("/run_script", methods=["POST"])
def run_script():
code = request.json.get("code", "")
output = []
variables = {}
modules = {}
try:
lines = code.split(";")
for line in lines:
line = line.strip()
if not line:
continue
# use mk
if line.startswith("use "):
name = line.split()[1]
mod = importlib.import_module(name)
modules[name] = mod
variables[name] = mod
continue
# print(...)
if line.startswith("print"):
inside = line[line.find("(")+1:line.rfind(")")]
output.append(str(eval_expr(inside, variables)))
continue
# typy int/float/bool
if any(line.startswith(t) for t in ["int ", "float ", "bool "]):
_, rest = line.split(" ", 1)
var, expr = rest.split("=")
variables[var.strip()] = eval_expr(expr.strip(), variables)
continue
# wywołanie funkcji np mk.add(1,2)
if "." in line and "(" in line:
obj, rest = line.split(".", 1)
fname = rest[:rest.find("(")]
args = rest[rest.find("(")+1:rest.find(")")]
args = [eval_expr(a.strip(), variables) for a in args.split(",")]
result = getattr(modules[obj], fname)(*args)
variables["_"] = result
continue
except Exception as e:
return jsonify({"error": str(e)})
return jsonify({
"output": output,
"variables": {k: str(v) for k, v in variables.items()}
})
def eval_expr(expr, variables):
expr = expr.replace("true", "True").replace("false", "False")
return eval(expr, {}, variables)
# ---------------- PREDICT ----------------
@app.route("/predict/<model_name>", methods=["POST"])
def predict(model_name):
model_entry = MODELS.get(model_name)
if not model_entry:
return jsonify({"output": "Model nie istnieje"}), 404
func = model_entry["func"]
inputs = model_entry["inputs"]
kwargs = {inp: request.form.get(inp) for inp in inputs}
try:
for k in kwargs:
try:
kwargs[k] = float(kwargs[k])
file_path = os.path.join(BASE_DIR, username, project, filename)
if os.path.exists(file_path):
size = os.path.getsize(file_path)
if size < 1024:
return f"{size} B"
elif size < 1024 * 1024:
return f"{size / 1024:.1f} KB"
else:
return f"{size / (1024 * 1024):.1f} MB"
except:
pass
output = func(**kwargs)
return "0 B"
def get_file_icon(filename):
"""Zwraca ikonę dla typu pliku"""
if filename.endswith('.ql'):
return 'bi-file-earmark-code'
elif filename.endswith('.md'):
return 'bi-file-earmark-text'
elif filename.endswith('.py'):
return 'bi-file-earmark-code'
elif filename.endswith('.js'):
return 'bi-file-earmark-code'
elif filename.endswith('.html'):
return 'bi-file-earmark-code'
elif filename.endswith('.css'):
return 'bi-file-earmark-code'
elif filename.endswith('.json'):
return 'bi-file-earmark-data'
else:
return 'bi-file-earmark'
def format_date(timestamp):
"""Formatuje datę"""
return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M')
return dict(
file_size=file_size,
get_file_icon=get_file_icon,
format_date=format_date
)
# ------------------------------
# Pomocnicze funkcje
# ------------------------------
def load_users():
if not os.path.exists(USERS_FILE):
return {}
try:
with open(USERS_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
return {}
def save_users(users):
with open(USERS_FILE, "w", encoding='utf-8') as f:
json.dump(users, f, indent=4, ensure_ascii=False)
def create_user_dir(username):
user_path = os.path.join(BASE_DIR, username)
os.makedirs(user_path, exist_ok=True)
return user_path
# ------------------------------
# Routes
# ------------------------------
@app.route("/")
def index():
return redirect(url_for('login'))
@app.route("/login", methods=["GET", "POST"])
def login():
if 'username' in session:
return redirect(url_for('dashboard'))
if request.method == "POST":
username = request.form.get('username', '').strip()
password = request.form.get('password', '').strip()
if not username or not password:
flash("Wypełnij wszystkie pola!")
return render_template("login.html")
users = load_users()
if username in users and users[username]['password'] == password:
session['username'] = username
session['cwd'] = os.path.join(BASE_DIR, username)
return redirect(url_for('dashboard'))
flash("Nieprawidłowy login lub hasło")
return render_template("login.html")
@app.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
username = request.form.get('username', '').strip()
password = request.form.get('password', '').strip()
if not username or not password:
flash("Wypełnij wszystkie pola!")
return render_template("register.html")
if len(username) < 3:
flash("Nazwa użytkownika musi mieć co najmniej 3 znaki")
return render_template("register.html")
if len(password) < 4:
flash("Hasło musi mieć co najmniej 4 znaki")
return render_template("register.html")
users = load_users()
if username in users:
flash("Użytkownik już istnieje!")
else:
users[username] = {"password": password}
save_users(users)
user_path = create_user_dir(username)
default_project = os.path.join(user_path, "Projekt1")
os.makedirs(default_project, exist_ok=True)
with open(os.path.join(default_project, "main.ql"), "w", encoding='utf-8') as f:
f.write("# Mój pierwszy program QL\nprint('Witaj w QL!');")
with open(os.path.join(default_project, "README.md"), "w", encoding='utf-8') as f:
f.write(
"# Projekt1\n\nTo jest domyślny projekt.\n\n## Funkcje:\n- Edycja kodu QL\n- Konsola systemowa\n- Zarządzanie plikami")
flash("Rejestracja udana! Możesz się teraz zalogować.")
return redirect(url_for('login'))
return render_template("register.html")
@app.route("/logout")
def logout():
session.clear()
return redirect(url_for("login"))
@app.route("/dashboard")
def dashboard():
if 'username' not in session:
return redirect(url_for('login'))
username = session['username']
user_dir = os.path.join(BASE_DIR, username)
if not os.path.exists(user_dir):
os.makedirs(user_dir, exist_ok=True)
projects = []
if os.path.exists(user_dir):
for item in os.listdir(user_dir):
item_path = os.path.join(user_dir, item)
if os.path.isdir(item_path):
projects.append(item)
if not projects:
default_project = os.path.join(user_dir, "Projekt1")
os.makedirs(default_project, exist_ok=True)
with open(os.path.join(default_project, "main.ql"), "w", encoding='utf-8') as f:
f.write("# Mój pierwszy program QL\nprint('Witaj w QL!');")
projects.append("Projekt1")
last_project = projects[-1] if projects else "Projekt1"
# Zaktualizowane kafelki z kolorami i ikonami
tiles = [
{
"title": "🔄 Modele",
"desc": "Wybierz model i funkcję",
"link": url_for('models_page'),
"icon": "bi-diagram-3",
"bg_color": "#007bff",
"bg_color2": "#6610f2"
},
{
"title": "📁 Projekty",
"desc": "Zarządzaj swoimi projektami",
"link": url_for('user_projects', username=username),
"icon": "bi-folder",
"bg_color": "#28a745",
"bg_color2": "#20c997"
},
{
"title": "✏️ Edytor",
"desc": "Otwórz edytor kodu",
"link": url_for('ql_editor', username=username, project=last_project),
"icon": "bi-code-slash",
"bg_color": "#343a40",
"bg_color2": "#6c757d"
},
{
"title": "🛒 Marketplace",
"desc": "Przeglądaj moduły",
"link": "#",
"icon": "bi-shop",
"bg_color": "#ffc107",
"bg_color2": "#fd7e14"
},
{
"title": "💬 Chat",
"desc": "Czat z użytkownikami",
"link": "#",
"icon": "bi-chat",
"bg_color": "#dc3545",
"bg_color2": "#e83e8c"
},
{
"title": "⚙️ Ustawienia",
"desc": "Konfiguracja platformy",
"link": "#",
"icon": "bi-gear",
"bg_color": "#17a2b8",
"bg_color2": "#20c997"
},
{
"title": "📚 Dokumentacja",
"desc": "Przeczytaj dokumentację",
"link": "#",
"icon": "bi-book",
"bg_color": "#6f42c1",
"bg_color2": "#e83e8c"
},
{
"title": "🚪 Wyloguj",
"desc": "Zakończ sesję",
"link": url_for('logout'),
"icon": "bi-box-arrow-right",
"bg_color": "#6c757d",
"bg_color2": "#495057"
},
]
return render_template("dashboard.html",
username=username,
tiles=tiles,
projects=projects)
@app.route("/models")
def models_page():
if 'username' not in session:
return redirect(url_for('login'))
return render_template("models.html", username=session['username'])
# ------------------------------
# Edytor QL
# ------------------------------
@app.route("/editor/<username>/<project>", methods=["GET"])
def ql_editor(username, project):
if 'username' not in session or session['username'] != username:
return redirect(url_for('login'))
project_path = os.path.join(BASE_DIR, username, project)
if not os.path.exists(project_path):
os.makedirs(project_path, exist_ok=True)
with open(os.path.join(project_path, "main.ql"), "w", encoding='utf-8') as f:
f.write("# Mój program QL\nprint('Witaj!');")
# Pobierz wszystkie pliki (nie tylko .ql)
files = []
if os.path.exists(project_path):
for f in os.listdir(project_path):
if os.path.isfile(os.path.join(project_path, f)):
files.append(f)
selected_file = request.args.get("file")
if not selected_file:
if files:
selected_file = files[0]
else:
selected_file = "main.ql"
with open(os.path.join(project_path, selected_file), "w", encoding='utf-8') as f:
f.write("# Nowy program QL\nprint('Hello World!');")
files.append(selected_file)
content = ""
file_path = os.path.join(project_path, selected_file)
if os.path.exists(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
except:
content = f"# Błąd odczytu pliku: {selected_file}"
return render_template("editor.html",
username=username,
project=project,
files=files,
selected_file=selected_file,
content=content)
@app.route("/editor/<username>/<project>/save", methods=["POST"])
def save_editor_file(username, project):
if 'username' not in session or session['username'] != username:
return jsonify({"error": "Nie zalogowano"}), 403
data = request.get_json()
if not data:
return jsonify({"error": "Brak danych"}), 400
filename = data.get("filename", "").strip()
content = data.get("content", "")
if not filename:
return jsonify({"error": "Brak nazwy pliku"}), 400
project_path = os.path.join(BASE_DIR, username, project)
os.makedirs(project_path, exist_ok=True)
file_path = os.path.join(project_path, filename)
with open(file_path, "w", encoding='utf-8') as f:
f.write(content)
return jsonify({"status": "ok", "filename": filename})
# ------------------------------
# Tworzenie pliku
# ------------------------------
@app.route("/editor/<username>/<project>/create", methods=["POST"])
def create_file(username, project):
if 'username' not in session or session['username'] != username:
return jsonify({"error": "Nie zalogowano"}), 403
data = request.get_json()
if not data:
return jsonify({"error": "Brak danych"}), 400
filename = data.get("filename", "").strip()
content = data.get("content", "")
if not filename:
return jsonify({"error": "Brak nazwy pliku"}), 400
project_path = os.path.join(BASE_DIR, username, project)
os.makedirs(project_path, exist_ok=True)
file_path = os.path.join(project_path, filename)
# Sprawdź czy plik już istnieje
if os.path.exists(file_path):
return jsonify({"error": "Plik już istnieje"}), 400
# Utwórz plik
with open(file_path, "w", encoding='utf-8') as f:
f.write(content if content else "")
return jsonify({"status": "ok", "filename": filename})
# ------------------------------
# Usuwanie pliku
# ------------------------------
@app.route("/editor/<username>/<project>/delete", methods=["POST"])
def delete_file(username, project):
if 'username' not in session or session['username'] != username:
return jsonify({"error": "Nie zalogowano"}), 403
data = request.get_json()
filename = data.get("filename", "").strip()
if not filename:
return jsonify({"error": "Brak nazwy pliku"}), 400
file_path = os.path.join(BASE_DIR, username, project, filename)
if not os.path.exists(file_path):
return jsonify({"error": "Plik nie istnieje"}), 404
try:
os.remove(file_path)
return jsonify({"status": "ok"})
except Exception as e:
output = str(e)
return jsonify({"error": f"Błąd usuwania: {str(e)}"}), 500
return jsonify({"output": str(output)})
@app.route("/editor/<username>/<project>/execute", methods=["POST"])
def execute_ql_code(username, project):
if 'username' not in session or session['username'] != username:
return jsonify({"error": "Nie zalogowano"}), 403
data = request.get_json()
if not data:
return jsonify({"error": "Brak kodu"}), 400
code = data.get("code", "")
try:
# Użyj NOWEGO interpretera QL (bez verbose)
result = run_ql_code(code)
# Zwróć TYLKO output - bez dodatkowych komunikatów
return jsonify({
"status": "success",
"output": result['output'] # TYLKO to co w print()
})
except Exception as e:
return jsonify({
"status": "error",
"output": [f"{str(e)}"] # TYLKO błąd
})
# ------------------------------
# Konsola systemowa
# ------------------------------
@app.route("/editor/<username>/<project>/console", methods=["POST"])
def console_command(username, project):
if 'username' not in session or session['username'] != username:
return jsonify({"output": "Nie zalogowano"}), 403
data = request.get_json()
if not data:
return jsonify({"output": "Brak komendy"}), 400
cmd = data.get("command", "").strip()
if 'cwd' not in session:
session['cwd'] = os.path.join(BASE_DIR, username, project)
cwd = session.get('cwd', os.path.join(BASE_DIR, username, project))
user_root = os.path.join(BASE_DIR, username)
if not os.path.exists(cwd):
cwd = user_root
session['cwd'] = cwd
output = execute_console_command(cmd, cwd, user_root)
if 'cwd' in locals():
session['cwd'] = cwd
return jsonify({"output": output})
# ---------------- DOWNLOAD SCRIPT ----------------
@app.route("/download_script/<filename>")
def download_script(filename):
path = os.path.join(UPLOAD_FOLDER, filename)
if os.path.exists(path):
return send_file(path, as_attachment=True)
return "Plik nie istnieje", 404
def execute_console_command(cmd, cwd, user_root):
parts = cmd.strip().split()
if not parts:
return ""
command = parts[0].lower()
try:
if command == "help":
return """Dostępne komendy:
help - wyświetla pomoc
ls - lista plików
cd <dir> - zmień katalog
pwd - pokaż aktualny katalog
mkdir <dir> - utwórz katalog
touch <file> - utwórz plik
cat <file> - pokaż zawartość pliku
rm <file> - usuń plik
clear - wyczyść konsolę"""
elif command == "ls":
items = os.listdir(cwd)
if not items:
return "Katalog jest pusty"
result = []
for item in items:
item_path = os.path.join(cwd, item)
if os.path.isdir(item_path):
result.append(f"[DIR] {item}/")
else:
size = os.path.getsize(item_path)
result.append(f"[FILE] {item} ({size} bytes)")
return "\n".join(result)
elif command == "pwd":
rel_path = os.path.relpath(cwd, user_root)
return f"{rel_path}\nAbsolutna ścieżka: {cwd}"
elif command == "cd":
if len(parts) < 2:
return "Użycie: cd <katalog>"
target = os.path.normpath(os.path.join(cwd, parts[1]))
if not os.path.exists(target):
return f"Katalog nie istnieje: {parts[1]}"
if not target.startswith(user_root):
return "Błąd: Próba dostępu poza dozwolony obszar"
if not os.path.isdir(target):
return f"To nie jest katalog: {parts[1]}"
# Aktualizacja cwd
cwd = target
return f"Zmieniono katalog na: {os.path.relpath(target, user_root)}"
elif command == "mkdir":
if len(parts) < 2:
return "Użycie: mkdir <nazwa_katalogu>"
new_dir = os.path.join(cwd, parts[1])
os.makedirs(new_dir, exist_ok=True)
return f"Utworzono katalog: {parts[1]}"
elif command == "touch":
if len(parts) < 2:
return "Użycie: touch <nazwa_pliku>"
file_path = os.path.join(cwd, parts[1])
with open(file_path, 'a', encoding='utf-8'):
pass
return f"Utworzono plik: {parts[1]}"
elif command == "cat":
if len(parts) < 2:
return "Użycie: cat <nazwa_pliku>"
file_path = os.path.join(cwd, parts[1])
if not os.path.exists(file_path):
return f"Plik nie istnieje: {parts[1]}"
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
return f"Zawartość pliku {parts[1]}:\n{'-' * 40}\n{content}"
elif command == "rm":
if len(parts) < 2:
return "Użycie: rm <nazwa_pliku>"
file_path = os.path.join(cwd, parts[1])
if not os.path.exists(file_path):
return f"Plik nie istnieje: {parts[1]}"
if os.path.isdir(file_path):
return "Użyj 'rmdir' do usuwania katalogów"
os.remove(file_path)
return f"Usunięto plik: {parts[1]}"
elif command == "clear":
return "CLEAR_CONSOLE"
else:
return f"Nieznana komenda: {command}\nWpisz 'help' aby zobaczyć dostępne komendy."
except PermissionError:
return "Błąd: Brak uprawnień"
except Exception as e:
return f"Błąd wykonania: {str(e)}"
# ---------------- UPLOAD SCRIPT ----------------
@app.route("/upload_script", methods=["POST"])
def upload_script():
file = request.files.get("file")
if not file:
return "Nie przesłano pliku", 400
filename = file.filename
path = os.path.join(UPLOAD_FOLDER, filename)
file.save(path)
return "OK"
# ------------------------------
# Projekty użytkowników
# ------------------------------
@app.route("/user_projects/<username>")
def user_projects(username):
if 'username' not in session or session['username'] != username:
return redirect(url_for('login'))
user_dir = os.path.join(BASE_DIR, username)
if not os.path.exists(user_dir):
projects = []
else:
projects = [d for d in os.listdir(user_dir)
if os.path.isdir(os.path.join(user_dir, d))]
project_info = []
for project in projects:
info = {
'name': project,
'path': os.path.join(user_dir, project),
'created': os.path.getctime(os.path.join(user_dir, project)),
'files': []
}
# Liczba plików
project_path = os.path.join(user_dir, project)
for root, dirs, files in os.walk(project_path):
for file in files:
if file.endswith('.ql'):
info['files'].append(file)
# README
md_path = os.path.join(user_dir, project, "README.md")
if os.path.exists(md_path):
try:
with open(md_path, 'r', encoding='utf-8') as f:
html_content = markdown.markdown(f.read())
info['readme'] = Markup(html_content)
except:
info['readme'] = Markup("<p>Błąd odczytu README</p>")
else:
info['readme'] = None
project_info.append(info)
return render_template("user_projects.html",
username=username,
projects=project_info)
# ---------------- MAIN ----------------
# ------------------------------
# Static files
# ------------------------------
@app.route('/static/<path:filename>')
def serve_static(filename):
return send_from_directory(app.static_folder, filename)
# ------------------------------
# Uruchomienie
# ------------------------------
if __name__ == "__main__":
app.run(port=4500)
# Utwórz niezbędne katalogi
os.makedirs('static/css', exist_ok=True)
os.makedirs('static/js', exist_ok=True)
os.makedirs('templates', exist_ok=True)
os.makedirs('libs', exist_ok=True) # Dodaj katalog libs
os.makedirs(BASE_DIR, exist_ok=True)
print("Serwer uruchamia się na http://localhost:5000")
print("Katalog libs:", os.path.abspath('libs'))
app.run(debug=True, host='0.0.0.0', port=5000)

View File

@ -1,73 +0,0 @@
import re, importlib
variables = {}
modules = {}
functions = {}
# Rejestracja “handlerów” dla linii
handlers = []
def eval_expr(expr):
"""Bezpieczne obliczanie wyrażeń z użyciem zmiennych"""
expr = expr.replace("true", "True").replace("false", "False")
return eval(expr, {}, variables)
def handle_use(line):
if line.startswith("use "):
name = line.split()[1]
modules[name] = importlib.import_module(f"apks.{name}")
variables[name] = modules[name]
return True
return False
def handle_print(line):
if line.startswith("print"):
inside = re.search(r"\((.*)\)", line).group(1)
print(eval_expr(inside))
return True
return False
def handle_var(line):
for t in ["int ", "float ", "bool ", "string "]:
if line.startswith(t):
_, rest = line.split(" ", 1)
var, expr = rest.split("=")
val = expr.strip()
if t.strip() == "string":
val = val.strip('"').strip("'")
else:
val = eval_expr(val)
variables[var.strip()] = val
return True
return False
def handle_func_call(line):
if "." in line and "(" in line:
obj, rest = line.split(".", 1)
fname = rest[:rest.find("(")]
args_str = rest[rest.find("(")+1:rest.rfind(")")]
args = [eval_expr(a.strip()) for a in args_str.split(",") if a.strip()]
result = getattr(modules[obj], fname)(*args)
variables["_"] = result # ostatni wynik
return True
return False
# Dodajemy wszystkie handlery do listy
handlers.extend([handle_use, handle_print, handle_var, handle_func_call])
def run(code):
"""Interpreter MKScript"""
lines = code.split(";")
for line in lines:
line = line.strip()
if not line or line.startswith("//"): # komentarze
continue
# uruchamiamy wszystkie handlery aż któryś obsłuży linię
handled = False
for h in handlers:
if h(line):
handled = True
break
if not handled:
print(f"Nieznana linia: {line}")

22
libs/lib.py Normal file
View File

@ -0,0 +1,22 @@
"""
Biblioteka przykładowa dla QL
"""
def lib(x):
"""Przykładowa funkcja"""
return f"Wynik z biblioteki: {x * 2}"
def add(a, b):
"""Dodawanie"""
return a + b
def multiply(a, b):
"""Mnożenie"""
return a * b
def greet(name):
"""Powitanie"""
return f"Witaj, {name}!"
__all__ = ['add', 'multiply', 'greet']

125
libs/string.py Normal file
View File

@ -0,0 +1,125 @@
"""
Biblioteka operacji na stringach dla QL
"""
import re
import base64
def length(s):
"""Długość stringa"""
return len(str(s))
def upper(s):
"""Zamienia na wielkie litery"""
return str(s).upper()
def lower(s):
"""Zamienia na małe litery"""
return str(s).lower()
def capitalize(s):
"""Pierwsza litera wielka"""
return str(s).capitalize()
def title(s):
"""Każde słowo z wielkiej litery"""
return str(s).title()
def trim(s):
"""Usuwa białe znaki z początku i końca"""
return str(s).strip()
def ltrim(s):
"""Usuwa białe znaki z początku"""
return str(s).lstrip()
def rtrim(s):
"""Usuwa białe znaki z końca"""
return str(s).rstrip()
def replace(s, old, new):
"""Zamienia fragmenty stringa"""
return str(s).replace(old, new)
def split(s, delimiter=' '):
"""Dzieli string na listę"""
return str(s).split(delimiter)
def join(arr, delimiter=''):
"""Łączy listę w string"""
return delimiter.join(str(x) for x in arr)
def substring(s, start, end=None):
"""Wyciąga podciąg"""
if end is None:
return str(s)[start:]
return str(s)[start:end]
def char_at(s, index):
"""Znak na pozycji"""
return str(s)[index]
def index_of(s, search):
"""Pozycja pierwszego wystąpienia"""
return str(s).find(search)
def last_index_of(s, search):
"""Pozycja ostatniego wystąpienia"""
return str(s).rfind(search)
def contains(s, search):
"""Czy zawiera podciąg"""
return search in str(s)
def starts_with(s, prefix):
"""Czy zaczyna się od"""
return str(s).startswith(prefix)
def ends_with(s, suffix):
"""Czy kończy się na"""
return str(s).endswith(suffix)
def repeat(s, count):
"""Powtarza string"""
return str(s) * count
def reverse(s):
"""Odwraca string"""
return str(s)[::-1]
def is_alpha(s):
"""Czy tylko litery"""
return str(s).isalpha()
def is_digit(s):
"""Czy tylko cyfry"""
return str(s).isdigit()
def is_alnum(s):
"""Czy litery i cyfry"""
return str(s).isalnum()
def is_space(s):
"""Czy tylko białe znaki"""
return str(s).isspace()
def match(s, pattern):
"""Dopasowanie wyrażenia regularnego"""
return bool(re.match(pattern, str(s)))
def search(s, pattern):
"""Wyszukiwanie wyrażenia regularnego"""
match = re.search(pattern, str(s))
return match.group() if match else None
def format(template, *args):
"""Formatowanie stringa"""
return template.format(*args)
def encode_base64(s):
"""Kodowanie Base64"""
return base64.b64encode(str(s).encode()).decode()
def decode_base64(s):
"""Dekodowanie Base64"""
return base64.b64decode(str(s).encode()).decode()

8
models.json Normal file
View File

@ -0,0 +1,8 @@
{
"Calculator": {
"source": "models/ai.py",
"funcs": {
"add": "a b"
}
}
}

View File

@ -1 +0,0 @@
#model Power

View File

@ -1,18 +0,0 @@
# Model Power
Ten model oblicza potęgę liczby.
## Funkcje:
- `oblicz_potega(x, typ)`
### Parametry:
- `x` (int/float): liczba do podniesienia do potęgi
- `typ` (str): `"kwadrat"` lub `"sześcian"`
### Przykłady użycia:
```python
oblicz_potega(3, "kwadrat") # wynik: 9
oblicz_potega(2, "sześcian") # wynik: 8

View File

@ -1,6 +0,0 @@
{
"name": "Power",
"function_name": "oblicz_potega",
"inputs": ["x","typ"],
"description_file": "description.md"
}

View File

@ -1,16 +0,0 @@
def oblicz_potega(x, typ):
"""
Funkcja oblicza kwadrat lub sześcian liczby x w zależności od parametru typ.
Parametry:
x (int/float): liczba, którą chcemy podnieść do potęgi
typ (str): "kwadrat" lub "sześcian"
Zwraca:
int/float: wynik działania
"""
if typ == "kwadrat":
return x ** 2
elif typ == "sześcian":
return x ** 3
else:
return "Niepoprawny typ. Wpisz 'kwadrat' lub 'sześcian'."

Binary file not shown.

2
models/ai.py Normal file
View File

@ -0,0 +1,2 @@
def add(a,b):
return a+b

View File

@ -0,0 +1,2 @@
# kod.py
# Utworzono: 30.01.2026, 20:45:03

View File

@ -0,0 +1,3 @@
use lib;
print(lib.lib(1))
print("elo")

101
ql_interpreter.py Normal file
View File

@ -0,0 +1,101 @@
import re
class QLInterpreter:
def __init__(self):
self.output = []
self.functions = self._register_functions()
def _register_functions(self):
"""TUTAJ DODAJESZ NOWE FUNKCJE"""
return {
'print': self._func_print,
# DODAJ TU SWOJE FUNKCJE:
# 'powitanie': self._func_powitanie,
}
# ========== FUNKCJE WBUDOWANE ==========
def _func_print(self, *args):
"""Tylko dodaje do outputu - bez printowania"""
text = ' '.join(str(arg) for arg in args)
self.output.append(text)
def execute(self, code: str):
"""Wykonuje kod QL - NIE RUSZAJ TEJ FUNKCJI"""
self.output = []
for line in code.strip().split('\n'):
line = line.strip()
if not line or line.startswith(('//', '#')):
continue
# use lib; - ignoruj
if line.startswith('use '):
continue
# Wywołanie funkcji: nazwa(arg1, arg2, ...)
match = re.match(r'(\w+)\s*\((.*)\)', line.rstrip(';'))
if match:
func_name = match.group(1)
args_str = match.group(2)
if func_name in self.functions:
try:
args = self._parse_args(args_str)
self.functions[func_name](*args)
except Exception:
pass # Ignoruj błędy
return self.output
def _parse_args(self, args_str: str):
"""Parsuje argumenty - NIE RUSZAJ"""
args = []
current = ''
in_string = False
quote_char = None
for char in args_str:
if char in '"\'' and (quote_char is None or quote_char == char):
in_string = not in_string
quote_char = char if in_string else None
current += char
elif char == ',' and not in_string:
args.append(self._parse_arg(current.strip()))
current = ''
else:
current += char
if current.strip():
args.append(self._parse_arg(current.strip()))
return args
def _parse_arg(self, arg: str):
"""Parsuje pojedynczy argument - NIE RUSZAJ"""
arg = arg.strip()
# String "tekst" lub 'tekst'
if (arg.startswith('"') and arg.endswith('"')) or \
(arg.startswith("'") and arg.endswith("'")):
return arg[1:-1]
# Liczba
if arg.replace('.', '', 1).isdigit():
return float(arg) if '.' in arg else int(arg)
return arg
# ========== FUNKCJA URUCHAMIAJĄCA ==========
def run_ql_code(code: str):
"""Uruchamia kod QL - NIE RUSZAJ"""
interpreter = QLInterpreter()
output = interpreter.execute(code)
return {
'output': output, # TYLKO output z print()
'variables': {},
'functions': list(interpreter.functions.keys()),
'libraries': []
}

68
static/css/console.css Normal file
View File

@ -0,0 +1,68 @@
.console-wrapper {
background: #1e1e1e;
color: #d4d4d4;
border-radius: 5px;
overflow: hidden;
}
.console-header {
background: #252526;
padding: 8px 15px;
border-bottom: 1px solid #3e3e42;
font-weight: bold;
}
.console-body {
padding: 15px;
min-height: 200px;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 13px;
}
.console-line {
margin-bottom: 5px;
word-wrap: break-word;
}
.console-prompt {
color: #4ec9b0;
font-weight: bold;
}
.console-command {
color: #dcdcdc;
}
.console-response {
color: #ce9178;
}
.console-error {
color: #f44747;
}
.console-success {
color: #6a9955;
}
.console-input-line {
display: flex;
align-items: center;
padding: 5px 15px;
background: #252526;
border-top: 1px solid #3e3e42;
}
.console-input-line input {
flex: 1;
background: transparent;
border: none;
color: #dcdcdc;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
padding: 5px;
outline: none;
}
.console-input-line input::placeholder {
color: #858585;
}

73
static/css/editor.css Normal file
View File

@ -0,0 +1,73 @@
/* Podstawowe style dla edytora */
.code-editor-container {
border: 1px solid #dee2e6;
border-radius: 5px;
overflow: hidden;
}
.code-editor-toolbar {
background: #f8f9fa;
padding: 10px;
border-bottom: 1px solid #dee2e6;
}
.code-editor-textarea {
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 14px;
line-height: 1.5;
border: none;
padding: 15px;
width: 100%;
min-height: 400px;
resize: vertical;
}
.code-editor-textarea:focus {
outline: none;
}
.file-explorer {
background: #f8f9fa;
min-height: 500px;
border-right: 1px solid #dee2e6;
}
.file-item {
padding: 8px 12px;
border-bottom: 1px solid #eee;
cursor: pointer;
}
.file-item:hover {
background: #e9ecef;
}
.file-item.active {
background: #007bff;
color: white;
}
.console-output {
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 12px;
background: #1e1e1e;
color: #d4d4d4;
padding: 10px;
border-radius: 3px;
min-height: 200px;
max-height: 300px;
overflow-y: auto;
}
.console-input {
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
background: #252526;
color: #d4d4d4;
border: 1px solid #3e3e42;
}
.console-input:focus {
background: #252526;
color: #d4d4d4;
border-color: #007bff;
}

314
static/js/console.js Normal file
View File

@ -0,0 +1,314 @@
class QLConsole {
constructor(username, project) {
this.username = username;
this.project = project;
this.history = [];
this.historyIndex = -1;
this.currentCommand = '';
this.isProcessing = false;
this.init();
}
init() {
this.bindEvents();
this.printWelcome();
this.setupHistory();
}
bindEvents() {
const input = document.getElementById('consoleInput');
const output = document.getElementById('consoleOutput');
if (!input || !output) return;
// Obsługa klawisza Enter
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.executeCommand(input.value.trim());
input.value = '';
this.historyIndex = -1;
}
// Strzałki dla historii
else if (e.key === 'ArrowUp') {
e.preventDefault();
this.navigateHistory(-1);
}
else if (e.key === 'ArrowDown') {
e.preventDefault();
this.navigateHistory(1);
}
// Tab dla autouzupełniania
else if (e.key === 'Tab') {
e.preventDefault();
this.autoComplete(input);
}
});
// Obsługa przycisków konsoli
document.getElementById('clearConsoleBtn')?.addEventListener('click', () => this.clearConsole());
document.getElementById('helpConsoleBtn')?.addEventListener('click', () => this.showHelp());
// Fokus na input przy kliknięciu w konsolę
output.addEventListener('click', () => {
input.focus();
});
// Auto-focus przy załadowaniu
setTimeout(() => input.focus(), 100);
}
async executeCommand(command) {
if (!command) return;
// Dodaj do historii
if (command !== this.history[this.history.length - 1]) {
this.history.push(command);
if (this.history.length > 50) {
this.history.shift();
}
}
// Wyświetl komendę
this.printCommand(command);
// Ustaw flagę przetwarzania
this.isProcessing = true;
this.showLoading();
try {
const response = await fetch(`/editor/${this.username}/${this.project}/console`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
command: command
})
});
const data = await response.json();
if (response.ok) {
if (data.output === 'CLEAR_CONSOLE') {
this.clearConsole();
this.printWelcome();
} else {
this.printResponse(data.output);
}
} else {
this.printError(data.output || 'Błąd wykonania komendy');
}
} catch (error) {
this.printError(`Błąd sieciowy: ${error.message}`);
} finally {
this.isProcessing = false;
this.hideLoading();
this.scrollToBottom();
}
}
printWelcome() {
const welcome = `
========================================
QL Console - Terminal systemowy
Projekt: ${this.project}
Użytkownik: ${this.username}
Wpisz 'help' aby zobaczyć dostępne komendy
========================================
`.trim();
this.printInfo(welcome);
}
printCommand(command) {
const output = document.getElementById('consoleOutput');
const line = document.createElement('div');
line.className = 'console-line';
line.innerHTML = `
<span class="console-prompt">$</span>
<span class="console-command">${this.escapeHtml(command)}</span>
`;
output.appendChild(line);
}
printResponse(response) {
const output = document.getElementById('consoleOutput');
const lines = response.split('\n');
lines.forEach(line => {
const div = document.createElement('div');
div.className = 'console-line';
if (line.includes('Błąd:') || line.includes('Nie można')) {
div.className += ' console-error';
} else if (line.includes('Utworzono') || line.includes('Zmieniono') || line.includes('Usunięto')) {
div.className += ' console-success';
} else if (line.includes('Ścieżka:') || line.includes('Katalog:')) {
div.className += ' console-path';
} else {
div.className += ' console-info';
}
div.textContent = line;
output.appendChild(div);
});
this.scrollToBottom();
}
printError(error) {
const output = document.getElementById('consoleOutput');
const line = document.createElement('div');
line.className = 'console-line console-error';
line.textContent = `${error}`;
output.appendChild(line);
this.scrollToBottom();
}
printInfo(info) {
const output = document.getElementById('consoleOutput');
const line = document.createElement('div');
line.className = 'console-line console-info';
line.textContent = info;
output.appendChild(line);
this.scrollToBottom();
}
clearConsole() {
const output = document.getElementById('consoleOutput');
output.innerHTML = '';
}
showHelp() {
const help = `Dostępne komendy:
help - wyświetla pomoc
ls - lista plików i folderów
cd [dir] - zmień katalog
pwd - pokaż aktualny katalog
mkdir [dir] - utwórz nowy katalog
touch [file] - utwórz nowy plik
cat [file] - wyświetl zawartość pliku
rm [file] - usuń plik
clear - wyczyść konsolę`;
this.printResponse(help);
}
navigateHistory(direction) {
if (this.history.length === 0) return;
const input = document.getElementById('consoleInput');
if (direction === -1) { // Strzałka w górę
if (this.historyIndex < this.history.length - 1) {
if (this.historyIndex === -1) {
this.currentCommand = input.value;
}
this.historyIndex++;
}
} else { // Strzałka w dół
if (this.historyIndex > 0) {
this.historyIndex--;
} else if (this.historyIndex === 0) {
this.historyIndex = -1;
input.value = this.currentCommand;
return;
}
}
if (this.historyIndex >= 0) {
input.value = this.history[this.history.length - 1 - this.historyIndex];
}
// Przesuń kursor na koniec
input.selectionStart = input.selectionEnd = input.value.length;
}
autoComplete(input) {
const current = input.value;
// Proste autouzupełnianie - można rozbudować
const commands = ['help', 'ls', 'cd', 'pwd', 'mkdir', 'touch', 'cat', 'rm', 'clear'];
const matching = commands.filter(cmd => cmd.startsWith(current));
if (matching.length === 1) {
input.value = matching[0];
} else if (matching.length > 1) {
this.printInfo(`Możliwe komendy: ${matching.join(', ')}`);
}
}
showLoading() {
const output = document.getElementById('consoleOutput');
const loader = document.createElement('div');
loader.id = 'consoleLoading';
loader.className = 'console-loading';
output.appendChild(loader);
}
hideLoading() {
const loader = document.getElementById('consoleLoading');
if (loader) {
loader.remove();
}
}
scrollToBottom() {
const output = document.getElementById('consoleOutput');
output.scrollTop = output.scrollHeight;
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
setupHistory() {
// Załaduj historię z localStorage
const savedHistory = localStorage.getItem(`console_history_${this.username}_${this.project}`);
if (savedHistory) {
this.history = JSON.parse(savedHistory);
}
// Zapisz historię przy wyjściu
window.addEventListener('beforeunload', () => {
localStorage.setItem(`console_history_${this.username}_${this.project}`,
JSON.stringify(this.history.slice(-20)));
});
}
}
// Inicjalizacja konsoli
document.addEventListener('DOMContentLoaded', () => {
const username = document.body.dataset.username;
const project = document.body.dataset.project;
if (username && project) {
window.qlconsole = new QLConsole(username, project);
}
});
// Globalne funkcje dostępne z konsoli
window.Console = {
clear: function() {
if (window.qlconsole) {
window.qlconsole.clearConsole();
window.qlconsole.printWelcome();
}
},
run: function(command) {
if (window.qlconsole) {
window.qlconsole.executeCommand(command);
}
},
help: function() {
if (window.qlconsole) {
window.qlconsole.showHelp();
}
}
};

681
static/js/editor.js Normal file
View File

@ -0,0 +1,681 @@
class QLEditor {
constructor(username, project, selectedFile) {
this.username = username;
this.project = project;
this.selectedFile = selectedFile;
this.editor = null;
this.autoSaveTimer = null;
this.isDirty = false;
this.consoleHistory = [];
this.consoleHistoryIndex = -1;
this.init();
}
init() {
this.bindEvents();
this.setupAutoSave();
this.updateStatus();
this.initConsole();
console.log(`QL Editor initialized for ${this.username}/${this.project}/${this.selectedFile}`);
}
bindEvents() {
// Zmiana pliku
document.querySelectorAll('.file-item').forEach(item => {
item.addEventListener('click', (e) => {
const filename = e.currentTarget.dataset.filename;
if (filename !== this.selectedFile) {
this.switchFile(filename);
}
});
});
// Zapisz przycisk
document.getElementById('saveBtn')?.addEventListener('click', () => this.saveFile());
// Nowy plik
document.getElementById('newFileBtn')?.addEventListener('click', () => this.showNewFileModal());
// Uruchom kod
document.getElementById('runBtn')?.addEventListener('click', () => this.runCode());
// Formatuj kod
document.getElementById('formatBtn')?.addEventListener('click', () => this.formatCode());
// Śledź zmiany w edytorze
const textarea = document.getElementById('codeEditor');
if (textarea) {
textarea.addEventListener('input', () => {
this.isDirty = true;
this.updateStatus();
});
// Skróty klawiaturowe w edytorze
textarea.addEventListener('keydown', (e) => {
// Ctrl+S - zapisz
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
this.saveFile();
}
// Ctrl+Enter - uruchom
if (e.ctrlKey && e.key === 'Enter') {
e.preventDefault();
this.runCode();
}
// Ctrl+F - formatuj
if (e.ctrlKey && e.key === 'f') {
e.preventDefault();
this.formatCode();
}
});
}
// Globalne skróty klawiaturowe
document.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
this.saveFile();
}
});
// Ostrzeżenie przed opuszczeniem strony
window.addEventListener('beforeunload', (e) => {
if (this.isDirty) {
e.preventDefault();
e.returnValue = 'Masz niezapisane zmiany. Czy na pewno chcesz opuścić stronę?';
return e.returnValue;
}
});
}
initConsole() {
const consoleInput = document.getElementById('consoleInput');
const consoleSend = document.getElementById('consoleSend');
const clearConsoleBtn = document.getElementById('clearConsoleBtn');
if (!consoleInput) return;
// Enter w konsoli
consoleInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.executeConsoleCommand();
}
// Strzałki dla historii
if (e.key === 'ArrowUp') {
e.preventDefault();
this.navigateConsoleHistory(-1);
}
if (e.key === 'ArrowDown') {
e.preventDefault();
this.navigateConsoleHistory(1);
}
});
// Przycisk wysyłania
if (consoleSend) {
consoleSend.addEventListener('click', () => this.executeConsoleCommand());
}
// Przycisk czyszczenia konsoli
if (clearConsoleBtn) {
clearConsoleBtn.addEventListener('click', () => this.clearConsole());
}
// Auto-focus na konsoli
setTimeout(() => {
consoleInput?.focus();
}, 100);
}
switchFile(filename) {
if (this.isDirty) {
if (!confirm('Masz niezapisane zmiany. Czy na pewno chcesz zmienić plik?')) {
return;
}
}
window.location.href = `/editor/${this.username}/${this.project}?file=${filename}`;
}
async saveFile() {
const content = document.getElementById('codeEditor').value;
const filename = this.selectedFile;
if (!content.trim()) {
this.showNotification('Plik jest pusty!', 'warning');
return;
}
try {
this.showNotification('Zapisywanie...', 'info');
const response = await fetch(`/editor/${this.username}/${this.project}/save`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
filename: filename,
content: content
})
});
const data = await response.json();
if (response.ok) {
this.showNotification('✓ Plik zapisany pomyślnie', 'success');
this.isDirty = false;
this.updateStatus();
this.addConsoleMessage('✓ Plik zapisany', 'success');
// Zaktualizuj czas modyfikacji w liście plików
const fileItem = document.querySelector(`[data-filename="${filename}"] .file-modified`);
if (fileItem) {
const now = new Date();
fileItem.textContent = now.toLocaleTimeString();
}
} else {
throw new Error(data.error || 'Błąd zapisu');
}
} catch (error) {
this.showNotification(`✗ Błąd: ${error.message}`, 'error');
this.addConsoleMessage(`✗ Błąd zapisu: ${error.message}`, 'error');
}
}
async createFile(filename) {
if (!filename) {
this.showNotification('Podaj nazwę pliku', 'warning');
return;
}
try {
const response = await fetch(`/editor/${this.username}/${this.project}/create`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
filename: filename
})
});
const data = await response.json();
if (response.ok) {
this.showNotification('✓ Plik utworzony pomyślnie', 'success');
this.addConsoleMessage(`✓ Utworzono plik: ${filename}`, 'success');
// Przełącz na nowy plik
setTimeout(() => {
window.location.href = `/editor/${this.username}/${this.project}?file=${filename}`;
}, 500);
} else {
throw new Error(data.error || 'Błąd tworzenia pliku');
}
} catch (error) {
this.showNotification(`✗ Błąd: ${error.message}`, 'error');
}
}
async deleteFile() {
if (!this.selectedFile || this.selectedFile === 'README.md') {
this.showNotification('Nie można usunąć tego pliku', 'warning');
return;
}
if (!confirm(`Czy na pewno chcesz usunąć plik "${this.selectedFile}"?`)) {
return;
}
try {
const response = await fetch(`/editor/${this.username}/${this.project}/delete`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
filename: this.selectedFile
})
});
const data = await response.json();
if (response.ok) {
this.showNotification('✓ Plik usunięty pomyślnie', 'success');
this.addConsoleMessage(`✓ Usunięto plik: ${this.selectedFile}`, 'success');
// Wróć do głównego pliku
setTimeout(() => {
window.location.href = `/editor/${this.username}/${this.project}`;
}, 500);
} else {
throw new Error(data.error || 'Błąd usuwania pliku');
}
} catch (error) {
this.showNotification(`✗ Błąd: ${error.message}`, 'error');
}
}
showNewFileModal() {
const filename = prompt('Podaj nazwę nowego pliku (np. program.ql):', 'nowy.ql');
if (filename) {
this.createFile(filename);
}
}
setupAutoSave() {
// Auto-zapis co 30 sekund jeśli są zmiany
this.autoSaveTimer = setInterval(() => {
if (this.isDirty) {
this.autoSave();
}
}, 30000);
}
async autoSave() {
const content = document.getElementById('codeEditor').value;
const filename = this.selectedFile;
try {
const response = await fetch(`/editor/${this.username}/${this.project}/save`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
filename: filename,
content: content
})
});
if (response.ok) {
this.addConsoleMessage('✓ Auto-zapisano plik', 'success');
}
} catch (error) {
console.error('Auto-save error:', error);
}
}
updateStatus() {
const status = document.getElementById('editorStatus');
if (status) {
const editor = document.getElementById('codeEditor');
if (!editor) return;
const lines = editor.value.split('\n').length;
const chars = editor.value.length;
const dirtyIndicator = this.isDirty ? ' ●' : '';
status.textContent = `${this.selectedFile} | Linie: ${lines} | Znaki: ${chars}${dirtyIndicator}`;
}
}
async runCode() {
const code = document.getElementById('codeEditor').value;
if (!code.trim()) {
return;
}
// WYCZYŚĆ KONSOLĘ PRZED NOWYM WYKONANIEM
this.clearConsoleForRun();
try {
const response = await fetch(`/editor/${this.username}/${this.project}/execute`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ code: code })
});
const data = await response.json();
if (data.status === 'success') {
// WYŚWIETL TYLKO OUTPUT Z PRINT()
if (data.output && data.output.length > 0) {
data.output.forEach(line => {
if (line && line.trim()) {
this.addConsoleMessage(line, 'response');
}
});
}
} else {
// Tylko błędy
if (data.output && data.output.length > 0) {
this.addConsoleMessage(data.output[0], 'error');
}
}
} catch (error) {
this.addConsoleMessage(`Błąd: ${error.message}`, 'error');
}
}
// Dodaj tę nową funkcję do klasy:
clearConsoleForRun() {
const consoleOutput = document.getElementById('consoleOutput');
if (!consoleOutput) return;
// WYCZYŚĆ KONSOLĘ, ale NIE dodawaj komunikatów "QL Console"
consoleOutput.innerHTML = '';
// Możesz dodać tylko nazwę polecenia jak w terminalu:
// this.addConsoleMessage(`$ ql ${this.selectedFile}`, 'command');
}
formatCode() {
const editor = document.getElementById('codeEditor');
if (!editor) return;
let code = editor.value;
// Proste formatowanie kodu QL
// 1. Zamień taby na 4 spacje
code = code.replace(/\t/g, ' ');
// 2. Usuń nadmiarowe puste linie
code = code.replace(/\n\s*\n\s*\n/g, '\n\n');
// 3. Usuń białe znaki na końcu linii
code = code.replace(/\s+$/gm, '');
// 4. Dodaj wcięcia dla bloków
let indent = 0;
const lines = code.split('\n');
const formattedLines = [];
for (let line of lines) {
line = line.trim();
// Zmniejsz wcięcie przed zamknięciem bloku
if (line.includes('}')) {
indent = Math.max(0, indent - 1);
}
// Dodaj wcięcie
const indentedLine = ' '.repeat(indent) + line;
formattedLines.push(indentedLine);
// Zwiększ wcięcie po otwarciu bloku
if (line.includes('{')) {
indent++;
}
}
editor.value = formattedLines.join('\n');
this.isDirty = true;
this.updateStatus();
this.addConsoleMessage('✓ Kod sformatowany', 'success');
this.showNotification('Kod sformatowany!', 'info');
}
executeConsoleCommand() {
const consoleInput = document.getElementById('consoleInput');
if (!consoleInput) return;
const command = consoleInput.value.trim();
if (!command) return;
// Dodaj do historii
this.consoleHistory.push(command);
this.consoleHistoryIndex = -1;
// Wyświetl komendę
this.addConsoleLine(`> ${command}`, 'command');
// Wykonaj komendę
const result = this.processConsoleCommand(command);
if (result) {
this.addConsoleLine(result);
}
// Wyczyść input
consoleInput.value = '';
consoleInput.focus();
}
processConsoleCommand(command) {
const parts = command.trim().split(' ');
const cmd = parts[0].toLowerCase();
const args = parts.slice(1);
const commands = {
'help': () => {
return `Dostępne komendy:
help - wyświetla pomoc
ls - lista plików w projekcie
clear - czyści konsolę
run - uruchamia aktualny plik
save - zapisuje aktualny plik
format - formatuje kod
status - pokazuje status projektu
time - pokazuje aktualny czas
echo [text] - wyświetla tekst`;
},
'ls': () => {
const fileList = document.getElementById('fileList');
if (!fileList) return 'Brak listy plików';
const files = Array.from(fileList.querySelectorAll('.list-group-item'))
.map(item => item.dataset.filename)
.filter(Boolean);
if (files.length === 0) return 'Brak plików w projekcie';
return files.map(f => `📄 ${f}`).join('\n');
},
'clear': () => {
this.clearConsole();
return 'Konsola wyczyszczona';
},
'run': () => {
this.runCode();
return 'Uruchamianie kodu...';
},
'save': () => {
this.saveFile();
return 'Zapisywanie pliku...';
},
'format': () => {
this.formatCode();
return 'Formatowanie kodu...';
},
'status': () => {
const editor = document.getElementById('codeEditor');
if (!editor) return 'Brak edytora';
const lines = editor.value.split('\n').length;
const chars = editor.value.length;
return `Projekt: ${this.project}
Plik: ${this.selectedFile}
Linie: ${lines}
Znaki: ${chars}
Zmiany: ${this.isDirty ? 'TAK' : 'NIE'}`;
},
'time': () => new Date().toLocaleString(),
'echo': () => args.join(' ')
};
if (commands[cmd]) {
try {
const result = commands[cmd]();
return typeof result === 'string' ? result : '';
} catch (error) {
return `Błąd: ${error.message}`;
}
}
return `Nieznana komenda: ${cmd}\nWpisz 'help' aby zobaczyć dostępne komendy`;
}
navigateConsoleHistory(direction) {
const consoleInput = document.getElementById('consoleInput');
if (!consoleInput || this.consoleHistory.length === 0) return;
if (direction === -1) { // Strzałka w górę
if (this.consoleHistoryIndex < this.consoleHistory.length - 1) {
if (this.consoleHistoryIndex === -1) {
this.currentCommand = consoleInput.value;
}
this.consoleHistoryIndex++;
}
} else { // Strzałka w dół
if (this.consoleHistoryIndex > 0) {
this.consoleHistoryIndex--;
} else if (this.consoleHistoryIndex === 0) {
this.consoleHistoryIndex = -1;
consoleInput.value = this.currentCommand || '';
return;
}
}
if (this.consoleHistoryIndex >= 0) {
consoleInput.value = this.consoleHistory[this.consoleHistory.length - 1 - this.consoleHistoryIndex];
}
// Przesuń kursor na koniec
consoleInput.selectionStart = consoleInput.selectionEnd = consoleInput.value.length;
}
addConsoleLine(text, type = 'response') {
const consoleOutput = document.getElementById('consoleOutput');
if (!consoleOutput) return;
const line = document.createElement('div');
line.className = `console-${type}`;
line.textContent = text;
consoleOutput.appendChild(line);
consoleOutput.scrollTop = consoleOutput.scrollHeight;
}
addConsoleMessage(message, type = 'info') {
this.addConsoleLine(message, type);
}
clearConsole() {
const consoleOutput = document.getElementById('consoleOutput');
if (!consoleOutput) return;
consoleOutput.innerHTML = '';
this.addConsoleMessage('=== QL Console ===', 'success');
this.addConsoleMessage('Wpisz \'help\' aby uzyskać pomoc', 'info');
}
showNotification(message, type = 'info') {
// Utwórz powiadomienie Bootstrap
const alert = document.createElement('div');
alert.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
alert.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
min-width: 300px;
max-width: 400px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
`;
// Ikona w zależności od typu
let icon = 'bi-info-circle';
if (type === 'success') icon = 'bi-check-circle';
if (type === 'warning') icon = 'bi-exclamation-triangle';
if (type === 'danger' || type === 'error') icon = 'bi-x-circle';
alert.innerHTML = `
<div class="d-flex align-items-center">
<i class="bi ${icon} me-2"></i>
<span>${message}</span>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(alert);
// Auto-ukrywanie
setTimeout(() => {
if (alert.parentNode) {
// Użyj Bootstrapowego zamykania
const bsAlert = new bootstrap.Alert(alert);
bsAlert.close();
}
}, 3000);
}
// Czyszczenie interwału przy zamknięciu
destroy() {
if (this.autoSaveTimer) {
clearInterval(this.autoSaveTimer);
}
}
}
// Inicjalizacja edytora po załadowaniu strony
document.addEventListener('DOMContentLoaded', () => {
const username = document.body.dataset.username;
const project = document.body.dataset.project;
const selectedFile = document.body.dataset.selectedFile;
if (username && project && selectedFile) {
window.qleditor = new QLEditor(username, project, selectedFile);
}
// Podświetlanie składni dla QL (podstawowe)
const editor = document.getElementById('codeEditor');
if (editor) {
// Zapisz oryginalny tekst dla podświetlania
let originalText = editor.value;
editor.addEventListener('input', function() {
// Możesz dodać podświetlanie składni tutaj
// Przykład prostego podświetlania:
const keywords = ['func', 'use', 'if', 'else', 'while', 'for', 'return', 'print', 'true', 'false', 'null'];
let text = this.value;
// To tylko przykład - w prawdziwym edytorze użyj biblioteki jak CodeMirror lub Monaco
keywords.forEach(keyword => {
const regex = new RegExp(`\\b${keyword}\\b`, 'g');
text = text.replace(regex, `<span class="text-primary fw-bold">${keyword}</span>`);
});
// Zapisz oryginalny tekst
originalText = this.value;
});
}
// Czyszczenie przy opuszczeniu strony
window.addEventListener('unload', () => {
if (window.qleditor) {
window.qleditor.destroy();
}
});
});
// Globalne funkcje dostępne z konsoli (opcjonalnie)
window.Console = {
run: function() {
if (window.qleditor) {
window.qleditor.runCode();
}
},
save: function() {
if (window.qleditor) {
window.qleditor.saveFile();
}
},
format: function() {
if (window.qleditor) {
window.qleditor.formatCode();
}
},
clear: function() {
if (window.qleditor) {
window.qleditor.clearConsole();
}
}
};

View File

@ -1,256 +0,0 @@
/* =========================
RESET PODSTAWOWY
========================= */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
/* =========================
BODY
========================= */
body {
background: #f4f7fc;
color: #333;
padding: 20px;
}
/* =========================
NAGŁÓWEK
========================= */
h2 {
text-align: center;
margin-bottom: 30px;
color: #4a4a4a;
font-size: 2em;
}
/* =========================
KONTENER MODELI
========================= */
#models-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 20px;
}
/* =========================
KARTA MODELU
========================= */
.model-card {
position: relative;
background: #ffffff;
border-radius: 15px;
padding: 20px;
box-shadow: 0 6px 18px rgba(0,0,0,0.08);
transition: transform 0.2s, box-shadow 0.2s;
overflow: hidden;
}
.model-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 24px rgba(0,0,0,0.12);
}
/* =========================
3 KROPKI (MENU)
========================= */
.menu-btn {
position: absolute;
top: 12px;
right: 14px;
cursor: pointer;
font-size: 22px;
color: #777;
user-select: none;
}
.menu-btn:hover {
color: #000;
}
/* =========================
BOCZNY PANEL (SLIDE)
========================= */
.side-menu {
position: absolute;
top: 0;
right: -260px;
width: 250px;
height: 100%;
background: #111;
color: #fff;
padding: 20px;
transition: right 0.3s ease;
z-index: 20;
display: flex;
flex-direction: column;
gap: 10px;
}
.side-menu.open {
right: 0;
}
.side-menu h4 {
margin-bottom: 10px;
border-bottom: 1px solid #333;
padding-bottom: 5px;
}
.side-menu input {
padding: 8px;
border-radius: 6px;
border: none;
font-size: 0.9em;
}
.side-menu button {
padding: 8px;
border-radius: 8px;
border: none;
cursor: pointer;
font-weight: bold;
background: #6c63ff;
color: white;
}
.side-menu button:hover {
background: #574fd6;
}
/* =========================
NAZWA MODELU
========================= */
.model-card h3 {
margin-bottom: 15px;
color: #1a1a1a;
font-size: 1.4em;
border-bottom: 1px solid #e0e0e0;
padding-bottom: 5px;
}
/* =========================
OPIS MODELU (Markdown)
========================= */
.description {
font-style: italic;
color: #555;
margin-bottom: 15px;
line-height: 1.5;
}
/* =========================
FORMULARZ
========================= */
form.predict-form {
display: flex;
flex-direction: column;
gap: 10px;
}
form.predict-form label {
font-weight: 600;
}
form.predict-form input {
padding: 8px 12px;
border-radius: 8px;
border: 1px solid #ccc;
font-size: 0.95em;
transition: border 0.2s, box-shadow 0.2s;
}
form.predict-form input:focus {
border-color: #6c63ff;
box-shadow: 0 0 5px rgba(108, 99, 255, 0.4);
outline: none;
}
/* =========================
PRZYCISK
========================= */
form.predict-form button {
padding: 10px 15px;
background: #6c63ff;
color: #fff;
border: none;
border-radius: 10px;
cursor: pointer;
font-weight: bold;
transition: background 0.2s, transform 0.2s;
}
form.predict-form button:hover {
background: #574fd6;
transform: translateY(-2px);
}
/* =========================
OUTPUT
========================= */
.output {
margin-top: 10px;
font-weight: bold;
color: #1a73e8;
}
/* =========================
DOWNLOAD
========================= */
.download {
margin-top: 15px;
font-size: 0.9em;
}
.download a {
text-decoration: none;
color: #6c63ff;
margin-right: 10px;
transition: color 0.2s;
}
.download a:hover {
color: #574fd6;
text-decoration: underline;
}
/* =========================
LINIE
========================= */
hr {
border: none;
height: 1px;
background: #e0e0e0;
margin: 20px 0;
}
/* =========================
RESPONSYWNOŚĆ
========================= */
@media (max-width: 600px) {
h2 {
font-size: 1.5em;
}
.model-card {
padding: 15px;
}
form.predict-form input,
form.predict-form button {
font-size: 0.9em;
}
}
.side-menu {
pointer-events: none;
}
.side-menu.open {
pointer-events: auto;
}
.side-menu {
position: fixed;
}

163
templates/dashboard.html Normal file
View File

@ -0,0 +1,163 @@
<!doctype html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>Dashboard - QL Platform</title>
<!-- Bootstrap 5 z ikonkami -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding-top: 20px;
}
.dashboard-card {
height: 200px;
border-radius: 15px;
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
transition: all 0.3s ease;
border: none;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
color: white;
position: relative;
overflow: hidden;
}
.dashboard-card:hover {
transform: translateY(-10px);
box-shadow: 0 15px 30px rgba(0,0,0,0.3);
}
.dashboard-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.1);
z-index: 1;
}
.dashboard-card-content {
position: relative;
z-index: 2;
padding: 20px;
}
.card-icon {
font-size: 2.5rem;
margin-bottom: 15px;
opacity: 0.9;
}
.card-title {
font-size: 1.3rem;
font-weight: 600;
margin-bottom: 10px;
}
.card-desc {
font-size: 0.9rem;
opacity: 0.9;
margin-bottom: 15px;
}
.welcome-card {
background: linear-gradient(135deg, #2c3e50 0%, #4ca1af 100%);
color: white;
padding: 30px;
border-radius: 15px;
margin-bottom: 30px;
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
}
.user-avatar {
width: 80px;
height: 80px;
background: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
color: #667eea;
margin: 0 auto 20px;
}
</style>
</head>
<body>
<div class="container">
<!-- Karta powitalna -->
<div class="welcome-card">
<div class="row align-items-center">
<div class="col-md-8">
<h1>Witaj, {{ username }}! 👋</h1>
<p class="lead">Twoja platforma programistyczna QL jest gotowa do działania.</p>
<p>Wybierz jedną z poniższych opcji, aby rozpocząć pracę.</p>
</div>
<div class="col-md-4 text-center">
<div class="user-avatar">
<i class="bi bi-person-circle"></i>
</div>
<h5>{{ username }}</h5>
<a href="{{ url_for('logout') }}" class="btn btn-outline-light btn-sm mt-2">
<i class="bi bi-box-arrow-right"></i> Wyloguj
</a>
</div>
</div>
</div>
<!-- Kafelki -->
<div class="row g-4">
{% for tile in tiles %}
<div class="col-md-4 col-lg-3">
<a href="{{ tile.link }}" class="text-decoration-none">
<div class="dashboard-card" style="background: linear-gradient(135deg, {{ tile.bg_color }} 0%, {{ tile.bg_color2 }} 100%);">
<div class="dashboard-card-content">
<div class="card-icon">
<i class="bi {{ tile.icon }}"></i>
</div>
<h5 class="card-title">{{ tile.title }}</h5>
<p class="card-desc">{{ tile.desc }}</p>
<span class="btn btn-outline-light btn-sm">
Otwórz <i class="bi bi-arrow-right"></i>
</span>
</div>
</div>
</a>
</div>
{% endfor %}
</div>
<!-- Projekt list -->
{% if projects %}
<div class="mt-5">
<div class="card">
<div class="card-header bg-dark text-white">
<h5 class="mb-0"><i class="bi bi-folder"></i> Twoje projekty</h5>
</div>
<div class="card-body">
<div class="row">
{% for project in projects %}
<div class="col-md-3 mb-3">
<div class="card h-100">
<div class="card-body text-center">
<i class="bi bi-folder-fill text-warning" style="font-size: 2rem;"></i>
<h6 class="mt-2">{{ project }}</h6>
<a href="{{ url_for('ql_editor', username=username, project=project) }}"
class="btn btn-primary btn-sm mt-2">
<i class="bi bi-pencil"></i> Edytuj
</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endif %}
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

34
templates/login.html Normal file
View File

@ -0,0 +1,34 @@
<!doctype html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>Login</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<h2>Logowanie</h2>
<form method="POST">
<div class="mb-3">
<label>Username</label>
<input type="text" name="username" class="form-control" required>
</div>
<div class="mb-3">
<label>Hasło</label>
<input type="password" name="password" class="form-control" required>
</div>
<button class="btn btn-primary">Zaloguj</button>
</form>
<p class="mt-3">Nie masz konta? <a href="{{ url_for('register') }}">Zarejestruj się</a></p>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-warning mt-3">
{% for message in messages %}
<div>{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
</div>
</body>
</html>

22
templates/models.html Normal file
View File

@ -0,0 +1,22 @@
<!doctype html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>Modele QL</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{{ url_for('dashboard') }}">QL Platform</a>
<span class="text-light">{{ username }}</span>
</div>
</nav>
<div class="container mt-4">
<h2>Modele i Funkcje</h2>
<p>Ta sekcja jest w budowie...</p>
<a href="{{ url_for('dashboard') }}" class="btn btn-primary">Powrót</a>
</div>
</body>
</html>

34
templates/register.html Normal file
View File

@ -0,0 +1,34 @@
<!doctype html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>Rejestracja</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
<h2>Rejestracja</h2>
<form method="POST">
<div class="mb-3">
<label>Username</label>
<input type="text" name="username" class="form-control" required>
</div>
<div class="mb-3">
<label>Hasło</label>
<input type="password" name="password" class="form-control" required>
</div>
<button class="btn btn-success">Zarejestruj się</button>
</form>
<p class="mt-3">Masz już konto? <a href="{{ url_for('login') }}">Zaloguj się</a></p>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="alert alert-warning mt-3">
{% for message in messages %}
<div>{{ message }}</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
</div>
</body>
</html>

View File

@ -1,100 +0,0 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>AI Models</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<h2>Lista modeli AI</h2>
<div id="models-container">
{% for name, model in models.items() %}
<div class="model-card" id="model-{{name}}">
<!-- 3 KROPKI -->
<div class="menu-btn" onclick="toggleMenu('{{name}}')"></div>
<!-- WYSUWANY PANEL -->
<div class="side-menu" id="menu-{{name}}">
<h4>Akcje modelu</h4>
<input type="text" value="{{name}}" readonly>
<button onclick="goToEditor('{{name}}')">
Edytor kodu
</button>
<button onclick="toggleMenu('{{name}}')">
Zamknij
</button>
</div>
<!-- NAZWA -->
<h3>{{name}}</h3>
<!-- OPIS (Markdown -> HTML) -->
<div class="description">
{{ model.md_content | safe }}
</div>
<!-- FORMULARZ -->
<form class="predict-form">
{% for inp in model.inputs %}
<label>{{inp}}:</label>
<input name="{{inp}}">
{% endfor %}
<button type="submit">Wyślij</button>
</form>
<!-- OUTPUT -->
<div class="output" id="output-{{name}}"></div>
<!-- DOWNLOAD -->
{% if model.meta.downloadable %}
<div class="download">
<a href="/download/{{name}}/model.py" target="_blank">model.py</a> |
<a href="/download/{{name}}/description.md" target="_blank">description.md</a> |
<a href="/download/{{name}}/meta.json" target="_blank">meta.json</a>
</div>
{% endif %}
</div>
{% endfor %}
</div>
<script>
/* ---- PREDICT ---- */
document.querySelectorAll(".predict-form").forEach(form => {
form.addEventListener("submit", async (e) => {
e.preventDefault();
const parent = e.target.closest(".model-card");
const modelName = parent.id.replace("model-", "");
const outputDiv = parent.querySelector(".output");
const formData = new FormData(form);
const response = await fetch(`/predict/${modelName}`, {
method: "POST",
body: formData
});
const data = await response.json();
outputDiv.textContent = data.output;
});
});
/* ---- MENU ---- */
function toggleMenu(name) {
const menu = document.getElementById("menu-" + name);
menu.classList.toggle("open");
}
/* ---- EDITOR ---- */
function goToEditor(name) {
window.location.href = `/editor/${name}`;
}
</script>
</body>
</html>

View File

@ -0,0 +1,45 @@
<!doctype html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<title>Moje Projekty</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{{ url_for('dashboard') }}">QL Platform</a>
<span class="text-light">Projekty: {{ username }}</span>
</div>
</nav>
<div class="container mt-4">
<h2>Moje Projekty</h2>
{% for project in projects %}
<div class="card mb-3">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="bi bi-folder"></i> {{ project.name }}
</h5>
<a href="{{ url_for('ql_editor', username=username, project=project.name) }}"
class="btn btn-primary btn-sm">
Otwórz w edytorze
</a>
</div>
<div class="card-body">
{% if project.readme %}
<div class="mb-3">{{ project.readme }}</div>
{% endif %}
<small class="text-muted">
<i class="bi bi-file-earmark-code"></i>
{{ project.files|length }} plików QL
</small>
</div>
</div>
{% endfor %}
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Powrót</a>
</div>
</body>
</html>

5
users.json Normal file
View File

@ -0,0 +1,5 @@
{
"Maro": {
"password": "770528"
}
}