diff --git a/.env b/.env deleted file mode 100644 index a595fb7..0000000 --- a/.env +++ /dev/null @@ -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 \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index aadd831..e9cc865 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,18 +2,33 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + - - + + + + + + + + + - + \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index ecefae0..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -#Pathl.Ai cześc projektu Cafe - -Ceo: Maro \ No newline at end of file diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc deleted file mode 100644 index 1af1a6c..0000000 Binary files a/__pycache__/app.cpython-313.pyc and /dev/null differ diff --git a/__pycache__/ql_interpreter.cpython-313.pyc b/__pycache__/ql_interpreter.cpython-313.pyc new file mode 100644 index 0000000..2542a40 Binary files /dev/null and b/__pycache__/ql_interpreter.cpython-313.pyc differ diff --git a/apks/__pycache__/mk.cpython-313.pyc b/apks/__pycache__/mk.cpython-313.pyc deleted file mode 100644 index c341966..0000000 Binary files a/apks/__pycache__/mk.cpython-313.pyc and /dev/null differ diff --git a/apks/mk.py b/apks/mk.py deleted file mode 100644 index 6cddd28..0000000 --- a/apks/mk.py +++ /dev/null @@ -1,5 +0,0 @@ -def add(a, b): - return a + b - -def sub(a, b): - return a - b diff --git a/app.py b/app.py index 9a658a4..518018e 100644 --- a/app.py +++ b/app.py @@ -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__) +USERS_FILE = "users.json" +BASE_DIR = "projects" +os.makedirs(BASE_DIR, exist_ok=True) -# 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 = {} +# ------------------------------ +# Funkcje pomocnicze dla templatki +# ------------------------------ +@app.context_processor +def utility_processor(): + def file_size(filename): + """Zwraca rozmiar pliku w czytelnej formie""" + try: + username = session.get('username', '') + project = 'Projekt1' # Domyślny projekt + if 'project' in request.view_args: + project = request.view_args['project'] -# ---- Load models at startup ---- -def load_models(): - global MODELS - MODELS = {} + 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 + return "0 B" - 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 + 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' - 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) + def format_date(timestamp): + """Formatuje datę""" + return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M') - func = getattr(module, func_name) - MODELS[folder] = { - "func": func, - "inputs": meta.get("inputs", []), - "meta": meta, - "path": path + 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//", 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///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///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///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: + return jsonify({"error": f"Błąd usuwania: {str(e)}"}), 500 + +@app.route("/editor///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///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}) + + +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 - zmień katalog +pwd - pokaż aktualny katalog +mkdir - utwórz katalog +touch - utwórz plik +cat - pokaż zawartość pliku +rm - 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 " + + 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 " + + 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 " + + 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 " + + 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 " + + 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)}" + + +# ------------------------------ +# Projekty użytkowników +# ------------------------------ +@app.route("/user_projects/") +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': [] } -load_models() + # 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) - -# ---------------- DASHBOARD ---------------- -@app.route("/") -def dashboard(): - models_with_md = {} - for name, model in MODELS.items(): - md_content = "" - 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 = "

Brak opisu.

" - models_with_md[name] = {**model, "md_content": md_content} - - return render_template("user.html", models=models_with_md) - - -# ---------------- EDITOR ---------------- -@app.route("/editor/") -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/", 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: + # README + md_path = os.path.join(user_dir, project, "README.md") + if os.path.exists(md_path): try: - kwargs[k] = float(kwargs[k]) + with open(md_path, 'r', encoding='utf-8') as f: + html_content = markdown.markdown(f.read()) + info['readme'] = Markup(html_content) except: - pass - output = func(**kwargs) - except Exception as e: - output = str(e) + info['readme'] = Markup("

Błąd odczytu README

") + else: + info['readme'] = None - return jsonify({"output": str(output)}) + project_info.append(info) + + return render_template("user_projects.html", + username=username, + projects=project_info) -# ---------------- DOWNLOAD SCRIPT ---------------- -@app.route("/download_script/") -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 +# ------------------------------ +# Static files +# ------------------------------ +@app.route('/static/') +def serve_static(filename): + return send_from_directory(app.static_folder, filename) -# ---------------- 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" - - -# ---------------- MAIN ---------------- +# ------------------------------ +# 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) \ No newline at end of file diff --git a/interpreter.py b/interpreter.py deleted file mode 100644 index 57c2a0d..0000000 --- a/interpreter.py +++ /dev/null @@ -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}") diff --git a/libs/lib.py b/libs/lib.py new file mode 100644 index 0000000..1aa7437 --- /dev/null +++ b/libs/lib.py @@ -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'] \ No newline at end of file diff --git a/libs/string.py b/libs/string.py new file mode 100644 index 0000000..fe17653 --- /dev/null +++ b/libs/string.py @@ -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() \ No newline at end of file diff --git a/models.json b/models.json new file mode 100644 index 0000000..fdef829 --- /dev/null +++ b/models.json @@ -0,0 +1,8 @@ +{ + "Calculator": { + "source": "models/ai.py", + "funcs": { + "add": "a b" + } + } +} \ No newline at end of file diff --git a/models/Power/Readme.md b/models/Power/Readme.md deleted file mode 100644 index 6b8d77d..0000000 --- a/models/Power/Readme.md +++ /dev/null @@ -1 +0,0 @@ -#model Power diff --git a/models/Power/__pycache__/model.cpython-313.pyc b/models/Power/__pycache__/model.cpython-313.pyc deleted file mode 100644 index 00addeb..0000000 Binary files a/models/Power/__pycache__/model.cpython-313.pyc and /dev/null differ diff --git a/models/Power/description.md b/models/Power/description.md deleted file mode 100644 index c1c04e9..0000000 --- a/models/Power/description.md +++ /dev/null @@ -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 diff --git a/models/Power/meta.json b/models/Power/meta.json deleted file mode 100644 index 56e5e8c..0000000 --- a/models/Power/meta.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Power", - "function_name": "oblicz_potega", - "inputs": ["x","typ"], - "description_file": "description.md" -} diff --git a/models/Power/model.py b/models/Power/model.py deleted file mode 100644 index 80559c0..0000000 --- a/models/Power/model.py +++ /dev/null @@ -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'." diff --git a/models/__pycache__/ai.cpython-313.pyc b/models/__pycache__/ai.cpython-313.pyc new file mode 100644 index 0000000..7a79a71 Binary files /dev/null and b/models/__pycache__/ai.cpython-313.pyc differ diff --git a/models/ai.py b/models/ai.py new file mode 100644 index 0000000..f083797 --- /dev/null +++ b/models/ai.py @@ -0,0 +1,2 @@ +def add(a,b): + return a+b diff --git a/projects/Maro/Projekt1/kod.py b/projects/Maro/Projekt1/kod.py new file mode 100644 index 0000000..96dff94 --- /dev/null +++ b/projects/Maro/Projekt1/kod.py @@ -0,0 +1,2 @@ +# kod.py +# Utworzono: 30.01.2026, 20:45:03 diff --git a/projects/Maro/Projekt1/main.ql b/projects/Maro/Projekt1/main.ql new file mode 100644 index 0000000..cb61e12 --- /dev/null +++ b/projects/Maro/Projekt1/main.ql @@ -0,0 +1,3 @@ +use lib; +print(lib.lib(1)) +print("elo") diff --git a/ql_interpreter.py b/ql_interpreter.py new file mode 100644 index 0000000..95ea16a --- /dev/null +++ b/ql_interpreter.py @@ -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': [] + } \ No newline at end of file diff --git a/model.py b/ql_stdlib.py similarity index 100% rename from model.py rename to ql_stdlib.py diff --git a/static/css/console.css b/static/css/console.css new file mode 100644 index 0000000..cdc0ba1 --- /dev/null +++ b/static/css/console.css @@ -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; +} \ No newline at end of file diff --git a/static/css/editor.css b/static/css/editor.css new file mode 100644 index 0000000..bbe6078 --- /dev/null +++ b/static/css/editor.css @@ -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; +} \ No newline at end of file diff --git a/static/js/console.js b/static/js/console.js new file mode 100644 index 0000000..7e969d5 --- /dev/null +++ b/static/js/console.js @@ -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 = ` + $ + ${this.escapeHtml(command)} + `; + 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 tę 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(); + } + } +}; \ No newline at end of file diff --git a/static/js/editor.js b/static/js/editor.js new file mode 100644 index 0000000..f8580c0 --- /dev/null +++ b/static/js/editor.js @@ -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 tę 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 = ` +
+ + ${message} +
+ + `; + + 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, `${keyword}`); + }); + + // 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(); + } + } +}; \ No newline at end of file diff --git a/static/style.css b/static/style.css deleted file mode 100644 index 087bb67..0000000 --- a/static/style.css +++ /dev/null @@ -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; -} diff --git a/templates/dashboard.html b/templates/dashboard.html new file mode 100644 index 0000000..6a249ac --- /dev/null +++ b/templates/dashboard.html @@ -0,0 +1,163 @@ + + + + + Dashboard - QL Platform + + + + + + +
+ +
+
+
+

Witaj, {{ username }}! 👋

+

Twoja platforma programistyczna QL jest gotowa do działania.

+

Wybierz jedną z poniższych opcji, aby rozpocząć pracę.

+
+
+
+ +
+
{{ username }}
+ + Wyloguj + +
+
+
+ + +
+ {% for tile in tiles %} + + {% endfor %} +
+ + + {% if projects %} +
+
+
+
Twoje projekty
+
+
+
+ {% for project in projects %} +
+
+
+ +
{{ project }}
+ + Edytuj + +
+
+
+ {% endfor %} +
+
+
+
+ {% endif %} +
+ + + + + \ No newline at end of file diff --git a/templates/editor.html b/templates/editor.html index 0df8cb8..a5527bd 100644 --- a/templates/editor.html +++ b/templates/editor.html @@ -1,169 +1,898 @@ - + - -MKScript Editor – {{model_name}} + + QL Editor - {{ project }} - {{ selected_file }} - - - + + + - + #codeEditor { + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 14px; + line-height: 1.6; + border: none; + padding: 20px; + width: 100%; + min-height: 400px; + resize: vertical; + background: #f8f9fa; + } + + #codeEditor:focus { + outline: none; + background: white; + box-shadow: inset 0 0 0 1px #007bff; + } + + .console-output { + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 13px; + background: #1e1e1e; + color: #d4d4d4; + padding: 15px; + border-radius: 5px; + min-height: 200px; + max-height: 300px; + overflow-y: auto; + white-space: pre-wrap; + word-break: break-word; + border: 1px solid #333; + } + + .console-command { + color: #4ec9b0; + font-weight: bold; + } + + .console-response { + color: #ce9178; + } + + .console-error { + color: #f44747; + } + + .console-success { + color: #6a9955; + } + + .console-info { + color: #9cdcfe; + } + + .status-badge { + font-size: 0.75rem; + padding: 2px 8px; + } + + .editor-toolbar { + background: linear-gradient(to right, #f8f9fa, #e9ecef); + border-bottom: 1px solid #dee2e6; + padding: 10px 15px; + } + + .btn-editor { + padding: 5px 12px; + font-size: 0.875rem; + border-radius: 4px; + transition: all 0.2s; + } + + .btn-editor:hover { + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + + -

MKScript Editor – {{model_name}}

+
+
+ +
+
+
+
+ Pliki projektu +
+ {{ files|length }} +
+
+ {% for file in files %} + +
+ + + {{ file }} +
+ + {{ file_size(file) }} + +
+ {% endfor %} +
+ +
-
- - - - - + +
+
+
Statystyki
+
+
+ Pliki: + {{ files|length }} +
+
+ Rozmiar projektu: + - +
+
+ Ostatnia zmiana: + teraz +
+
+
+
+
- ← Wróć + +
+
+ +
+
+
+ + {{ selected_file }} + Nie zapisano +
+
+
+ + + + +
+
+ + +
+ +
+ + +
+
+ + Linie: {{ content.split('\n')|length }} | + Znaki: {{ content|length }} + +
+
+ Gotowy +
+
+
+ + +
+
+
+ Konsola +
+
+ + +
+
+
+
+
=== QL Console v1.0 ===
+
Projekt: {{ project }}
+
Wpisz 'help' aby uzyskać pomoc
+
Wpisz 'ls' aby zobaczyć pliki
+
+
+ + > + + + +
+
+
+
+
-
- + + -
-

Console Output

-

-
- -
-

Debugger (zmienne)

-

-
- - - - + + - - + \ No newline at end of file diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..1b08860 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,34 @@ + + + + +Login + + + +
+

Logowanie

+
+
+ + +
+
+ + +
+ +
+

Nie masz konta? Zarejestruj się

+ {% with messages = get_flashed_messages() %} + {% if messages %} +
+ {% for message in messages %} +
{{ message }}
+ {% endfor %} +
+ {% endif %} + {% endwith %} +
+ + diff --git a/templates/models.html b/templates/models.html new file mode 100644 index 0000000..cdf28bb --- /dev/null +++ b/templates/models.html @@ -0,0 +1,22 @@ + + + + + Modele QL + + + + + +
+

Modele i Funkcje

+

Ta sekcja jest w budowie...

+ Powrót +
+ + \ No newline at end of file diff --git a/templates/register.html b/templates/register.html new file mode 100644 index 0000000..1c9c1fd --- /dev/null +++ b/templates/register.html @@ -0,0 +1,34 @@ + + + + +Rejestracja + + + +
+

Rejestracja

+
+
+ + +
+
+ + +
+ +
+

Masz już konto? Zaloguj się

+ {% with messages = get_flashed_messages() %} + {% if messages %} +
+ {% for message in messages %} +
{{ message }}
+ {% endfor %} +
+ {% endif %} + {% endwith %} +
+ + diff --git a/templates/user.html b/templates/user.html deleted file mode 100644 index 73f1fed..0000000 --- a/templates/user.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - AI Models - - - - -

Lista modeli AI

- -
-{% for name, model in models.items() %} -
- - - - - - - - -

{{name}}

- - -
- {{ model.md_content | safe }} -
- - -
- {% for inp in model.inputs %} - - - {% endfor %} - -
- - -
- - - {% if model.meta.downloadable %} - - {% endif %} - -
-{% endfor %} -
- - - - - diff --git a/templates/user_projects.html b/templates/user_projects.html new file mode 100644 index 0000000..d48abe6 --- /dev/null +++ b/templates/user_projects.html @@ -0,0 +1,45 @@ + + + + + Moje Projekty + + + + + +
+

Moje Projekty

+ + {% for project in projects %} +
+
+
+ {{ project.name }} +
+ + Otwórz w edytorze + +
+
+ {% if project.readme %} +
{{ project.readme }}
+ {% endif %} + + + {{ project.files|length }} plików QL + +
+
+ {% endfor %} + + Powrót +
+ + \ No newline at end of file diff --git a/users.json b/users.json new file mode 100644 index 0000000..0953a41 --- /dev/null +++ b/users.json @@ -0,0 +1,5 @@ +{ + "Maro": { + "password": "770528" + } +} \ No newline at end of file