diff --git a/.env b/.env new file mode 100644 index 0000000..a595fb7 --- /dev/null +++ b/.env @@ -0,0 +1,6 @@ +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/.gitignore b/.gitignore deleted file mode 100644 index 0949605..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.venv/ -.idea/ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/Strona AI.iml b/.idea/Strona AI.iml new file mode 100644 index 0000000..a55366f --- /dev/null +++ b/.idea/Strona AI.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..539dad3 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..3769195 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..34f09f4 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index e69de29..ecefae0 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,3 @@ +#Pathl.Ai cześc projektu Cafe + +Ceo: Maro \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..72eb3fb --- /dev/null +++ b/app.py @@ -0,0 +1,144 @@ +from flask import Flask, request, render_template, jsonify, send_from_directory +import os, json, importlib.util +from werkzeug.utils import secure_filename +import markdown +app = Flask(__name__) +UPLOAD_FOLDER = "models" +os.makedirs(UPLOAD_FOLDER, exist_ok=True) +ADMIN_KEY = "supersecretadminkey" + +MODELS = {} + +def load_models(): + for folder in os.listdir(UPLOAD_FOLDER): + path = os.path.join(UPLOAD_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() + +# ---- 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_text = f.read() + # konwersja Markdown -> HTML + md_content = markdown.markdown(md_text) + except Exception: + md_content = "

Brak opisu.

" + + models_with_md[name] = {**model, "md_content": md_content} + + return render_template("user.html", models=models_with_md) +# ---- 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 = {} + for inp in inputs: + kwargs[inp] = request.form.get(inp) + + try: + for k in kwargs: + try: + kwargs[k] = float(kwargs[k]) + except: + pass + output = func(**kwargs) + except Exception as e: + output = str(e) + + return jsonify({"output": str(output)}) + +# ---- ADD MODEL ---- +@app.route("/add_model", methods=["POST"]) +def add_model(): + admin_key = request.form.get("admin_key") + if admin_key != ADMIN_KEY: + return "Brak dostępu", 403 + + name = request.form["name"] + function_name = request.form["function_name"] + inputs = request.form.getlist("inputs") + model_py = request.files["model_py"] + description_file = request.files["description"] + + folder_name = secure_filename(name) + folder_path = os.path.join(UPLOAD_FOLDER, folder_name) + os.makedirs(folder_path, exist_ok=True) + + # zapis plików + model_py.save(os.path.join(folder_path, "model.py")) + description_file.save(os.path.join(folder_path, "description.md")) + + # meta.json + meta = { + "name": name, + "function_name": function_name, + "inputs": inputs, + "description_file": "description.md", + "downloadable": True, # można pobrać pliki + "type": request.form.get("type", "algorithmic") + } + with open(os.path.join(folder_path, "meta.json"), "w") as f: + json.dump(meta, f) + + # załaduj od razu + spec = importlib.util.spec_from_file_location("model_module", os.path.join(folder_path, "model.py")) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + func = getattr(module, function_name) + + MODELS[folder_name] = { + "func": func, + "inputs": inputs, + "meta": meta, + "path": folder_path + } + + return "Dodano model!" + +# ---- DOWNLOAD FILE ---- +@app.route("/download//") +def download_file(model_name, file_name): + model_entry = MODELS.get(model_name) + if not model_entry: + return "Model nie istnieje", 404 + folder_path = model_entry["path"] + if file_name not in ["model.py", "description.md", "meta.json"]: + return "Nieprawidłowy plik", 400 + return send_from_directory(folder_path, file_name, as_attachment=True) + +if __name__ == "__main__": + app.run(debug=True) diff --git a/main.py b/main.py deleted file mode 100644 index 25c9c74..0000000 --- a/main.py +++ /dev/null @@ -1,22 +0,0 @@ -from flask import Flask, request, jsonify - -app = Flask(__name__) - -# Strona główna -@app.route("/") -def home(): - return "AI Pathl Test działa!" - -# Endpoint testowy np. dla promptów AI -@app.route("/test", methods=["POST"]) -def test_ai(): - data = request.json - prompt = data.get("prompt", "") - # Na razie tylko echo promptu - response = {"response": f"Otrzymałem Twój prompt: {prompt}"} - return jsonify(response) - -if __name__ == "__main__": - print("server") - print("dodanie") - app.run(host="0.0.0.0", port=6000) diff --git a/model.py b/model.py new file mode 100644 index 0000000..e69de29 diff --git a/models/Power/Readme.md b/models/Power/Readme.md new file mode 100644 index 0000000..6b8d77d --- /dev/null +++ b/models/Power/Readme.md @@ -0,0 +1 @@ +#model Power diff --git a/models/Power/__pycache__/model.cpython-313.pyc b/models/Power/__pycache__/model.cpython-313.pyc new file mode 100644 index 0000000..ecf8832 Binary files /dev/null and b/models/Power/__pycache__/model.cpython-313.pyc differ diff --git a/models/Power/description.md b/models/Power/description.md new file mode 100644 index 0000000..c1c04e9 --- /dev/null +++ b/models/Power/description.md @@ -0,0 +1,18 @@ +# 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 new file mode 100644 index 0000000..56e5e8c --- /dev/null +++ b/models/Power/meta.json @@ -0,0 +1,6 @@ +{ + "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 new file mode 100644 index 0000000..80559c0 --- /dev/null +++ b/models/Power/model.py @@ -0,0 +1,16 @@ +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/static/style.css b/static/style.css new file mode 100644 index 0000000..32ef883 --- /dev/null +++ b/static/style.css @@ -0,0 +1,150 @@ +/* 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 { + 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; +} + +.model-card:hover { + transform: translateY(-5px); + box-shadow: 0 12px 24px rgba(0,0,0,0.12); +} + +/* Nazwa modelu */ +.model-card h3 { + margin-bottom: 15px; + color: #1a1a1a; + font-size: 1.4em; + border-bottom: 1px solid #e0e0e0; + padding-bottom: 5px; +} + +/* Opis modelu (.md) */ +.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 linki */ +.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 oddzielające karty */ +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; + } +} diff --git a/templates/user.html b/templates/user.html new file mode 100644 index 0000000..e22d990 --- /dev/null +++ b/templates/user.html @@ -0,0 +1,59 @@ + + + + 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 %} +
+ + + +