import os import io import base64 import json import logging import threading import time import math import re import urllib.parse from datetime import datetime, timedelta, timezone from uuid import uuid4 import random import string from flask import Flask, render_template_string, request, redirect, url_for, flash, jsonify, session, make_response from PIL import Image import google.generativeai as genai from huggingface_hub import HfApi, hf_hub_download from huggingface_hub.utils import RepositoryNotFoundError, HfHubHTTPError from werkzeug.utils import secure_filename from dotenv import load_dotenv import requests load_dotenv() app = Flask(__name__) app.secret_key = 'your_unique_secret_key_gippo_312_shop_54321_no_login' DATA_FILE = 'data.json' SYNC_FILES = [DATA_FILE] REPO_ID = "Kgshop/qrex" HF_TOKEN_WRITE = os.getenv("HF_TOKEN") HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY") DOWNLOAD_RETRIES = 3 DOWNLOAD_DELAY = 5 ALMATY_TZ = timezone(timedelta(hours=6)) db_lock = threading.RLock() TRANSLATIONS = { 'Zaloguj się do Panelu': {'en': 'Login to Admin Panel', 'de': 'Anmeldung zum Admin-Panel'}, 'Zaloguj': {'en': 'Login', 'de': 'Anmelden'}, 'Wprowadź hasło': {'en': 'Enter password', 'de': 'Passwort eingeben'}, 'Główny Panel Administracyjny': {'en': 'Main Admin Panel', 'de': 'Haupt-Admin-Panel'}, 'Zarządzanie Wizytówkami': {'en': 'vCard Management', 'de': 'Visitenkartenverwaltung'}, 'Utwórz nową wizytówkę': {'en': 'Create new vCard', 'de': 'Neue Visitenkarte erstellen'}, 'Szukaj po ID lub Nazwie...': {'en': 'Search by ID or Name...', 'de': 'Suchen nach ID oder Name...'}, 'Istniejące wizytówki': {'en': 'Existing vCards', 'de': 'Vorhandene Visitenkarten'}, 'Ustawienia': {'en': 'Settings', 'de': 'Einstellungen'}, 'Zobacz': {'en': 'View', 'de': 'Ansehen'}, 'Włącz hasło admina': {'en': 'Enable admin pwd', 'de': 'Admin-Passwort aktivieren'}, 'Hasło': {'en': 'Password', 'de': 'Passwort'}, 'Zapisz': {'en': 'Save', 'de': 'Speichern'}, 'Nie utworzono jeszcze żadnych wizytówek.': {'en': 'No vCards created yet.', 'de': 'Noch keine Visitenkarten erstellt.'}, 'Zapisz w kontaktach': {'en': 'Save to contacts', 'de': 'In Kontakten speichern'}, 'Katalog / Usługi': {'en': 'Catalog / Services', 'de': 'Katalog / Dienstleistungen'}, 'Szukaj...': {'en': 'Search...', 'de': 'Suchen...'}, 'Wszystkie': {'en': 'All', 'de': 'Alle'}, 'Do koszyka': {'en': 'Add to cart', 'de': 'In den Warenkorb'}, 'Mój kod QR': {'en': 'My QR code', 'de': 'Mein QR-Code'}, 'Zeskanuj, aby otworzyć wizytówkę': {'en': 'Scan to open vCard', 'de': 'Scannen, um Visitenkarte zu öffnen'}, 'Koszyk': {'en': 'Cart', 'de': 'Warenkorb'}, 'Twoje Imię': {'en': 'Your Name', 'de': 'Ihr Name'}, 'Twój Telefon': {'en': 'Your Phone', 'de': 'Ihre Telefonnummer'}, 'Złóż zamówienie': {'en': 'Checkout', 'de': 'Kasse'}, 'Razem:': {'en': 'Total:', 'de': 'Gesamt:'}, 'Koszyk jest pusty': {'en': 'Cart is empty', 'de': 'Warenkorb ist leer'}, 'Brak wyników.': {'en': 'Nothing found.', 'de': 'Nichts gefunden.'}, 'Przetwarzanie...': {'en': 'Processing...', 'de': 'Verarbeitung...'}, 'Błąd składania zamówienia': {'en': 'Checkout error', 'de': 'Bestellfehler'}, 'Błąd sieci': {'en': 'Network error', 'de': 'Netzwerkfehler'}, 'Wypełnij imię i telefon': {'en': 'Fill in name and phone', 'de': 'Bitte Name und Telefon ausfüllen'}, 'Otwórz wizytówkę': {'en': 'Open vCard', 'de': 'Visitenkarte öffnen'}, 'Historia zamówień': {'en': 'Order History', 'de': 'Bestellhistorie'}, 'Kod QR': {'en': 'QR Code', 'de': 'QR-Code'}, 'Wyloguj': {'en': 'Logout', 'de': 'Abmelden'}, 'Profil i wygląd': {'en': 'Profile & Appearance', 'de': 'Profil & Aussehen'}, 'Imię:': {'en': 'First Name:', 'de': 'Vorname:'}, 'Nazwisko:': {'en': 'Last Name:', 'de': 'Nachname:'}, 'Stanowisko / Specjalizacja:': {'en': 'Job Title:', 'de': 'Beruf / Spezialisierung:'}, 'Nazwa organizacji:': {'en': 'Organization:', 'de': 'Organisation:'}, 'O mnie / Status:': {'en': 'About / Status:', 'de': 'Über mich / Status:'}, 'Awatar (Zdjęcie profilowe):': {'en': 'Avatar (Profile Photo):', 'de': 'Avatar (Profilbild):'}, 'Schemat kolorów:': {'en': 'Color Scheme:', 'de': 'Farbschema:'}, 'Waluta (dla produktów/usług):': {'en': 'Currency (for products):', 'de': 'Währung (für Produkte):'}, 'Ustawienia koszyka i zamówień': {'en': 'Cart & Orders Settings', 'de': 'Warenkorb & Bestellungen'}, 'Włącz koszyk (zakupy)': {'en': 'Enable cart (purchases)', 'de': 'Warenkorb aktivieren (Käufe)'}, 'Komunikator dla zamówień:': {'en': 'Order messenger:', 'de': 'Bestell-Messenger:'}, 'Nie przyjmuj zamówień': {'en': 'Do not accept orders', 'de': 'Keine Bestellungen annehmen'}, 'Numer (WhatsApp) lub Username (Telegram):': {'en': 'Number (WhatsApp) / Username (Telegram):', 'de': 'Nummer (WhatsApp) / Benutzername (Telegram):'}, 'Hasło dostępu (dla admina)': {'en': 'Access Password (for admin)', 'de': 'Zugangspasswort (für Admin)'}, 'Wymagaj hasła': {'en': 'Require password', 'de': 'Passwort erforderlich'}, 'Zapisz ustawienia': {'en': 'Save settings', 'de': 'Einstellungen speichern'}, 'Przyciski i Linki': {'en': 'Buttons & Links', 'de': 'Schaltflächen & Links'}, 'Dodaj przycisk': {'en': 'Add button', 'de': 'Schaltfläche hinzufügen'}, 'Typ bloku:': {'en': 'Block type:', 'de': 'Blocktyp:'}, 'Przycisk-link': {'en': 'Link button', 'de': 'Link-Schaltfläche'}, 'Blok tekstowy': {'en': 'Text block', 'de': 'Textblock'}, 'Katalog PDF': {'en': 'PDF Catalog', 'de': 'PDF-Katalog'}, 'Tytuł / Tekst przycisku:': {'en': 'Title / Button text:', 'de': 'Titel / Schaltflächentext:'}, 'Ikona:': {'en': 'Icon:', 'de': 'Symbol:'}, 'Bez ikony': {'en': 'No icon', 'de': 'Kein Symbol'}, 'URL / Numer / Email...': {'en': 'URL / Number / Email...', 'de': 'URL / Nummer / E-Mail...'}, 'Prześlij PDF:': {'en': 'Upload PDF:', 'de': 'PDF hochladen:'}, 'Zmień PDF (zastąpi obecny):': {'en': 'Upload PDF (replaces current):', 'de': 'PDF hochladen (ersetzt aktuelles):'}, 'Tekst bloku:': {'en': 'Block text:', 'de': 'Blocktext:'}, 'Dodaj': {'en': 'Add', 'de': 'Hinzufügen'}, 'Czy na pewno usunąć?': {'en': 'Are you sure to delete?', 'de': 'Wirklich löschen?'}, 'Edytuj': {'en': 'Edit', 'de': 'Bearbeiten'}, 'Kategorie produktów/usług': {'en': 'Product Categories', 'de': 'Produktkategorien'}, 'Dodaj nową kategorię': {'en': 'Add new category', 'de': 'Neue Kategorie hinzufügen'}, 'Nazwa nowej kategorii:': {'en': 'New category name:', 'de': 'Name der neuen Kategorie:'}, 'Produkty i Usługi': {'en': 'Products & Services', 'de': 'Produkte & Dienstleistungen'}, 'Dodaj produkt/usługę': {'en': 'Add product/service', 'de': 'Produkt/Dienstleistung hinzufügen'}, 'Nazwa *:': {'en': 'Name *:', 'de': 'Name *:'}, 'Cena (0 aby ukryć):': {'en': 'Price (0 to hide):', 'de': 'Preis (0 zum Ausblenden):'}, 'Zdjęcia (max 10):': {'en': 'Photos (max 10):', 'de': 'Fotos (max. 10):'}, 'Opis:': {'en': 'Description:', 'de': 'Beschreibung:'}, 'Kategoria:': {'en': 'Category:', 'de': 'Kategorie:'}, 'Bez kategorii': {'en': 'Uncategorized', 'de': 'Nicht kategorisiert'}, 'Szukaj': {'en': 'Search', 'de': 'Suchen'}, 'Resetuj': {'en': 'Reset', 'de': 'Zurücksetzen'}, 'Usuń': {'en': 'Delete', 'de': 'Löschen'}, 'Brak rekordów.': {'en': 'No records found.', 'de': 'Keine Einträge gefunden.'}, 'Zamówienie #': {'en': 'Order #', 'de': 'Bestellung #'}, 'Faktura zamówienia': {'en': 'Order Invoice', 'de': 'Bestellrechnung'}, 'Kupujący:': {'en': 'Buyer:', 'de': 'Käufer:'}, 'Telefon:': {'en': 'Phone:', 'de': 'Telefon:'}, 'Sprzedawca:': {'en': 'Seller:', 'de': 'Verkäufer:'}, 'szt.': {'en': 'pcs', 'de': 'Stk.'}, 'Wyślij do sprzedawcy': {'en': 'Send to seller', 'de': 'An Verkäufer senden'}, 'Wróć do katalogu': {'en': 'Back to catalog', 'de': 'Zurück zum Katalog'}, 'Wróć do panelu': {'en': 'Back to panel', 'de': 'Zurück zum Panel'}, 'Generuj opis AI z pierwszego zdjęcia': {'en': 'Generate AI description from 1st photo', 'de': 'KI-Beschreibung aus 1. Foto generieren'}, 'Zapisywanie...': {'en': 'Saving...', 'de': 'Speichern...'}, 'Faktura': {'en': 'Invoice', 'de': 'Rechnung'}, 'Cena:': {'en': 'Price:', 'de': 'Preis:'}, 'Obecny awatar:': {'en': 'Current avatar:', 'de': 'Aktueller Avatar:'}, 'Obecny PDF:': {'en': 'Current PDF:', 'de': 'Aktuelles PDF:'}, 'Brak': {'en': 'None', 'de': 'Keine'}, 'Ustawienia wizytówki': {'en': 'vCard Settings', 'de': 'Visitenkarten-Einstellungen'}, 'Link': {'en': 'Link', 'de': 'Link'}, 'Tekst': {'en': 'Text', 'de': 'Text'}, 'Zmień zdjęcia (stare zostaną usunięte):': {'en': 'Replace photos (old will be removed):', 'de': 'Fotos ersetzen (alte werden entfernt):'}, 'Utworzono wizytówkę ID:': {'en': 'Created vCard ID:', 'de': 'Visitenkarte erstellt ID:'}, 'Zaktualizowano hasło dla:': {'en': 'Password updated for:', 'de': 'Passwort aktualisiert für:'}, 'Nie znaleziono wizytówki:': {'en': 'vCard not found:', 'de': 'Visitenkarte nicht gefunden:'}, 'Usunięto wizytówkę:': {'en': 'Deleted vCard:', 'de': 'Visitenkarte gelöscht:'}, 'Nieprawidłowe hasło': {'en': 'Invalid password', 'de': 'Ungültiges Passwort'}, 'Dodano blok.': {'en': 'Block added.', 'de': 'Block hinzugefügt.'}, 'Zaktualizowano blok.': {'en': 'Block updated.', 'de': 'Block aktualisiert.'}, 'Usunięto blok.': {'en': 'Block deleted.', 'de': 'Block gelöscht.'}, 'Przeniesiono blok wyżej.': {'en': 'Block moved up.', 'de': 'Block nach oben verschoben.'}, 'Przeniesiono blok niżej.': {'en': 'Block moved down.', 'de': 'Block nach unten verschoben.'}, 'Dodano kategorię:': {'en': 'Category added:', 'de': 'Kategorie hinzugefügt:'}, 'Nazwa kategorii nie może być pusta.': {'en': 'Category name cannot be empty.', 'de': 'Kategoriename darf nicht leer sein.'}, 'Kategoria już istnieje:': {'en': 'Category already exists:', 'de': 'Kategorie existiert bereits:'}, 'Usunięto kategorię:': {'en': 'Category deleted:', 'de': 'Kategorie gelöscht:'}, 'Zaktualizowano pomyślnie.': {'en': 'Updated successfully.', 'de': 'Erfolgreich aktualisiert.'}, 'Błąd podczas aktualizacji:': {'en': 'Error during update:', 'de': 'Fehler bei der Aktualisierung:'}, 'Nazwa jest wymagana.': {'en': 'Name is required.', 'de': 'Name ist erforderlich.'}, 'Zapisano:': {'en': 'Saved:', 'de': 'Gespeichert:'}, 'Usunięto rekord:': {'en': 'Record deleted:', 'de': 'Eintrag gelöscht:'}, 'Usunięto zamówienie z historii.': {'en': 'Order deleted from history.', 'de': 'Bestellung aus der Historie gelöscht.'}, 'Błąd:': {'en': 'Error:', 'de': 'Fehler:'}, 'Błąd ładowania PDF:': {'en': 'Error uploading PDF:', 'de': 'Fehler beim Hochladen von PDF:'}, 'Awatar pomyślnie zaktualizowany.': {'en': 'Avatar successfully updated.', 'de': 'Avatar erfolgreich aktualisiert.'}, 'Błąd podczas ładowania awatara:': {'en': 'Error uploading avatar:', 'de': 'Fehler beim Hochladen des Avatars:'}, 'Zapisano ustawienia.': {'en': 'Settings saved.', 'de': 'Einstellungen gespeichert.'}, 'Błąd usuwania:': {'en': 'Delete error:', 'de': 'Löschfehler:'}, 'Nieznana akcja:': {'en': 'Unknown action:', 'de': 'Unbekannte Aktion:'}, 'Błąd wykonania akcji:': {'en': 'Action execution error:', 'de': 'Fehler bei der Aktionsausführung:'}, 'Czy na pewno usunąć tę wizytówkę? To działanie jest nieodwracalne.': {'en': 'Are you sure you want to delete this vCard? This action is irreversible.', 'de': 'Möchten Sie diese Visitenkarte wirklich löschen? Diese Aktion ist unumkehrbar.'}, 'Czy na pewno? Produkty w tej kategorii zostaną oznaczone jako "Bez kategorii".': {'en': 'Are you sure? Products in this category will be marked as "Uncategorized".', 'de': 'Sind Sie sicher? Produkte in dieser Kategorie werden als "Nicht kategorisiert" markiert.'} } CURRENCIES = { 'PLN': 'Polski Złoty', 'EUR': 'Euro', 'USD': 'Dolar Amerykański', 'GBP': 'Funt Szterling', 'UAH': 'Hrywna ukraińska', 'CHF': 'Frank Szwajcarski' } COLOR_SCHEMES = { 'default': 'Turkusowy (domyślny)', 'forest': 'Leśna zieleń', 'ocean': 'Głęboki błękit', 'sunset': 'Zachód słońca', 'lavender': 'Lawenda', 'vintage': 'Vintage', 'dark': 'Ciemny (Północ)', 'cosmic': 'Kosmiczna noc', 'minty': 'Świeża mięta', 'mocha': 'Mokka', 'crimson': 'Karmazynowy świt', 'solar': 'Słoneczny blask', 'cyberpunk': 'Cyberpunk neon', 'neon': 'Neonowy błysk', 'pastel': 'Pastelowy (jasny)', 'emerald': 'Szmaragdowe miasto', 'gold': 'Luksusowe złoto', 'sakura': 'Kwiat wiśni', 'arctic': 'Arktyczny lód', 'volcano': 'Magma', 'monochrome_light': 'Klasyczny (Jasny)', 'monochrome_dark': 'Klasyczny (Ciemny)', 'nord': 'Skandynawski Nord', 'dracula': 'Dracula (Ciemny)', 'ruby': 'Głęboki Rubin', 'sapphire': 'Królewski Szafir', 'amethyst': 'Ametystowy blask' } ICONS = { 'fa-link': 'Link (strona www)', 'fa-phone': 'Telefon', 'fa-whatsapp': 'WhatsApp', 'fa-telegram': 'Telegram', 'fa-instagram': 'Instagram', 'fa-envelope': 'Email', 'fa-map-marker-alt': 'Lokalizacja/Adres', 'fa-youtube': 'YouTube', 'fa-tiktok': 'TikTok' } logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') def get_lang(): try: from flask import session, request return session.get('lang') or request.cookies.get('lang') or 'pl' except: return 'pl' def t_text(pl_string): lang = get_lang() if lang == 'pl': return pl_string return TRANSLATIONS.get(pl_string, {}).get(lang, pl_string) @app.context_processor def inject_t(): return dict(t=t_text, current_lang=get_lang()) def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWNLOAD_DELAY): if not HF_TOKEN_READ and not HF_TOKEN_WRITE: pass token_to_use = HF_TOKEN_READ if HF_TOKEN_READ else HF_TOKEN_WRITE files_to_download = [specific_file] if specific_file else SYNC_FILES all_successful = True for file_name in files_to_download: success = False for attempt in range(retries + 1): try: local_path = hf_hub_download( repo_id=REPO_ID, filename=file_name, repo_type="dataset", token=token_to_use, local_dir=".", local_dir_use_symlinks=False, force_download=True, resume_download=False ) success = True break except RepositoryNotFoundError: return False except HfHubHTTPError as e: if e.response.status_code == 404: if attempt == 0 and not os.path.exists(file_name): try: if file_name == DATA_FILE: with open(file_name, 'w', encoding='utf-8') as f: json.dump({}, f) except Exception: pass success = False break else: pass except requests.exceptions.RequestException: pass except Exception: pass if attempt < retries: time.sleep(delay) if not success: all_successful = False return all_successful def upload_db_to_hf(specific_file=None): if not HF_TOKEN_WRITE: return try: api = HfApi() files_to_upload = [specific_file] if specific_file else SYNC_FILES for file_name in files_to_upload: if os.path.exists(file_name): try: api.upload_file( path_or_fileobj=file_name, path_in_repo=file_name, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE, commit_message=f"Sync {file_name} {datetime.now(ALMATY_TZ).strftime('%Y-%m-%d %H:%M:%S')}" ) except Exception: pass except Exception: pass def periodic_backup(): backup_interval = 1800 while True: time.sleep(backup_interval) upload_db_to_hf() def load_data(): with db_lock: try: with open(DATA_FILE, 'r', encoding='utf-8') as f: data = json.load(f) if not isinstance(data, dict): data = {} except (FileNotFoundError, json.JSONDecodeError): if download_db_from_hf(specific_file=DATA_FILE): try: with open(DATA_FILE, 'r', encoding='utf-8') as f: data = json.load(f) if not isinstance(data, dict): data = {} except (FileNotFoundError, json.JSONDecodeError): data = {} else: data = {} return data def save_data(data): with db_lock: try: with open(DATA_FILE, 'w', encoding='utf-8') as file: json.dump(data, file, ensure_ascii=False, indent=4) except Exception: pass upload_db_to_hf(specific_file=DATA_FILE) def get_env_data(env_id): with db_lock: all_data = load_data() default_settings = { "vcard_firstname": "Imię", "vcard_lastname": "Nazwisko", "vcard_job": "Specjalista", "organization_name": "Moja Firma", "currency_code": "PLN", "chat_avatar": None, "color_scheme": "default", "admin_password_enabled": False, "admin_password": "", "categories_as_lines": False, "about_text": "Cześć! To moja wirtualna wizytówka.", "enable_cart": False, "order_messenger": "whatsapp", "order_contact": "" } env_data = all_data.get(env_id, {}) if not env_data: env_data = { 'products': [], 'categories':[], 'blocks':[], 'orders': [], 'settings': default_settings } if 'products' not in env_data: env_data['products'] = [] if 'categories' not in env_data: env_data['categories'] = [] if 'settings' not in env_data: env_data['settings'] = default_settings if 'blocks' not in env_data: env_data['blocks'] = [] if 'orders' not in env_data: env_data['orders'] = [] settings_changed = False for key, value in default_settings.items(): if key not in env_data['settings']: env_data['settings'][key] = value settings_changed = True products_changed = False for product in env_data['products']: if 'product_id' not in product: product['product_id'] = uuid4().hex products_changed = True if 'views' not in product: product['views'] = 0 products_changed = True if 'price' not in product: product['price'] = 0.0 products_changed = True if products_changed or settings_changed: save_env_data(env_id, env_data) return env_data def save_env_data(env_id, env_data): with db_lock: all_data = load_data() all_data[env_id] = env_data save_data(all_data) def configure_gemini(): if not GOOGLE_API_KEY: return False try: genai.configure(api_key=GOOGLE_API_KEY) return True except Exception: return False def generate_ai_description_from_image(image_data, language): if not configure_gemini(): raise ValueError(t_text("Błąd: Google AI API nie jest skonfigurowane.")) try: if not image_data: raise ValueError(t_text("Błąd: Nie znaleziono pliku obrazu.")) image_stream = io.BytesIO(image_data) image = Image.open(image_stream).convert('RGB') except Exception: raise ValueError(t_text("Błąd: Nie udało się przetworzyć obrazu.")) base_prompt = "Napisz atrakcyjny opis dla tego produktu/usługi. Tekst powinien być zwięzły, sprzedażowy, z użyciem emoji. Nie pisz cen, adresów ani telefonów." if language == "English": base_prompt = "Write an attractive description for this product/service. The text should be concise, sales-oriented, using emojis. Do not write prices, addresses, or phone numbers." elif language == "Deutsch": base_prompt = "Schreiben Sie eine attraktive Beschreibung für dieses Produkt/Dienstleistung. Der Text sollte prägnant, verkaufsorientiert und mit Emojis versehen sein. Schreiben Sie keine Preise, Adressen oder Telefonnummern." try: model = genai.GenerativeModel('gemma-4-31b-it') response = model.generate_content([base_prompt, image]) if hasattr(response, 'text'): return response.text else: if response.parts: return "".join(part.text for part in response.parts if hasattr(part, 'text')) else: response.resolve() return response.text except Exception as e: raise ValueError(f"{t_text('Błąd wykonania akcji:')} {e}") LANDING_PAGE_TEMPLATE = ''' Platforma Wizytówek Online ''' LOGIN_TEMPLATE = ''' {{ t('Zaloguj się do Panelu') }}
PL EN DE

{{ t('Zaloguj') }}

{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %}
{{ message }}
{% endfor %} {% endif %} {% endwith %}
''' ADMHOSTO_TEMPLATE = ''' {{ t('Główny Panel Administracyjny') }}
PL EN DE

{{ t('Zarządzanie Wizytówkami') }}

{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %}
{{ message }}
{% endfor %} {% endif %} {% endwith %}

{{ t('Istniejące wizytówki') }}

{% if environments %} {% else %}

{{ t('Nie utworzono jeszcze żadnych wizytówek.') }}

{% endif %}
''' CATALOG_TEMPLATE = ''' {{ settings.vcard_firstname }} {{ settings.vcard_lastname }} - {{ t('Wizytówka') }}
PL EN DE
Avatar
{{ settings.vcard_firstname }} {{ settings.vcard_lastname }}
{% if settings.vcard_job %}
{{ settings.vcard_job }}
{% endif %} {% if settings.organization_name %}
{{ settings.organization_name }}
{% endif %} {% if settings.about_text %}
{{ settings.about_text|replace('\\n', '
')|safe }}
{% endif %}
{{ t('Zapisz w kontaktach') }} {% if blocks %}
{% for block in blocks %} {% if block.type == 'link' %} {% if block.icon %} {% set prefix = 'fab' if block.icon in ['fa-whatsapp', 'fa-telegram', 'fa-instagram', 'fa-youtube', 'fa-tiktok'] else 'fas' %} {% endif %} {{ block.title }} {% elif block.type == 'text' %}
{% if block.title %}

{{ block.title }}

{% endif %}

{{ block.content|replace('\\n', '
')|safe }}

{% elif block.type == 'pdf' %} {% endif %} {% endfor %}
{% endif %} {% if products_json != '[]' %}

{{ t('Katalog / Usługi') }}

{% endif %}
0
''' ADMIN_TEMPLATE = ''' {{ t('Ustawienia wizytówki') }} - {{ settings.organization_name }}
PL EN DE

{{ t('Zapisywanie...') }}

Logo

{{ t('Ustawienia wizytówki') }}

{{ t('Otwórz wizytówkę') }} {{ t('Historia zamówień') }} {% if new_orders_count > 0 %} {{ new_orders_count }} {% endif %} {% if settings.admin_password_enabled %} {{ t('Wyloguj') }} {% endif %}
{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %}
{{ message }}
{% endfor %} {% endif %} {% endwith %}
{{ t('Profil i wygląd') }}
{% if settings.chat_avatar %} {{ t('Obecny awatar:') }} {% endif %}

{{ t('Ustawienia koszyka i zamówień') }}

{{ t('Hasło dostępu (dla admina)') }}

{{ t('Przyciski i Linki') }}

{{ t('Dodaj przycisk') }}
{% if blocks %} {% for block in blocks %}
{% if block.icon %} {% set prefix = 'fab' if block.icon in ['fa-whatsapp', 'fa-telegram', 'fa-instagram', 'fa-youtube', 'fa-tiktok'] else 'fas' %} {% endif %} {{ block.title }} ({% if block.type == 'link' %}{{ t('Link') }}{% elif block.type == 'pdf' %}PDF{% else %}{{ t('Tekst') }}{% endif %}) {% if block.type == 'link' %}
{{ block.url }}{% endif %}
{% endfor %} {% else %}

{{ t('Brak rekordów.') }}

{% endif %}

{{ t('Kategorie produktów/usług') }}

{{ t('Dodaj nową kategorię') }}
{% if categories %}
{% for category in categories %}
{{ category }}
{% endfor %}
{% else %}

{{ t('Brak rekordów.') }}

{% endif %}

{{ t('Produkty i Usługi') }}

{{ t('Dodaj produkt/usługę') }}

{{ t('Produkty i Usługi') }}:

{% if search_q %} {{ t('Resetuj') }} {% endif %}
{% if paginated_products %}
{% for product in paginated_products %}
{% if product.get('photos') %} Photo {% else %} No photo {% endif %}
{{ product['name'] }}

{{ t('Kategoria:') }} {{ product.get('category', 'Bez kategorii') }}

{{ t('Cena:') }} {% if product.get('price', 0) > 0 %}{{ "%.2f"|format(product.get('price', 0)) }} {{ currency_code }}{% else %}{{ t('Brak') }}{% endif %}

{{ t('Edytuj') }}

{% endfor %}
{% if total_pages > 1 %} {% endif %} {% else %}

{{ t('Brak rekordów.') }}

{% endif %}
''' ORDER_TEMPLATE = ''' {{ t('Zamówienie #') }}{{ order['id'] }}
PL EN DE

{{ t('Faktura zamówienia') }}

#{{ order['id'] }} / {{ order['date'] }}

{{ t('Kupujący:') }} {{ order['customer_name'] }}

{{ t('Telefon:') }} {{ order['customer_phone'] }}

{{ t('Sprzedawca:') }} {{ settings['organization_name'] }}

{% for pid, item in order['items'].items() %}
{% if item['photo'] %} {{ item['name'] }} {% else %} No photo {% endif %}
{{ item['name'] }}
{{ item['qty'] }} {{ t('szt.') }} × {{ "%.2f"|format(item['price']|float) }} {{ currency }}
{{ "%.2f"|format(item['qty']|int * item['price']|float) }} {{ currency }}
{% endfor %}
{{ t('Razem:') }} {{ "%.2f"|format(order['total']|float) }} {{ currency }}
{% if send_link != "#" %} {{ t('Wyślij do sprzedawcy') }} ({{ messenger_name }}) {% endif %} {{ t('Wróć do katalogu') }}
''' ADMIN_ORDERS_TEMPLATE = ''' {{ t('Historia zamówień') }} - {{ settings.organization_name }}
PL EN DE

{{ t('Historia zamówień') }}

{{ t('Wróć do panelu') }}
{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %} {% for category, message in messages %}
{{ message }}
{% endfor %} {% endif %} {% endwith %} {% if orders %} {% for order in orders %}
{{ t('Zamówienie #') }}{{ order['id'] }} {{ order['date'] }}
{{ t('Faktura') }}

{{ t('Kupujący:') }} {{ order['customer_name'] }}

{{ t('Telefon:') }} {{ order['customer_phone'] }}

{% for pid, item in order['items'].items() %}
{{ item['name'] }} ({{ item['qty'] }} {{ t('szt.') }}) {{ "%.2f"|format(item['price']|float * item['qty']|int) }} {{ currency }}
{% endfor %}
{{ t('Razem:') }} {{ "%.2f"|format(order['total']|float) }} {{ currency }}
{% endfor %} {% else %}

{{ t('Brak rekordów.') }}

{% endif %}
''' @app.route('/') def index(): return render_template_string(LANDING_PAGE_TEMPLATE) @app.route('/set_lang', methods=['POST']) def set_lang(): lang = request.json.get('lang', 'pl') session['lang'] = lang resp = jsonify(success=True) resp.set_cookie('lang', lang, max_age=60*60*24*365) return resp @app.route('/admhosto', methods=['GET']) def admhosto(): data = load_data() environments_data = [] for env_id, env_data in data.items(): settings = env_data.get('settings', {}) org_name = settings.get("organization_name", f"Wizytówka {env_id}") environments_data.append({ "id": env_id, "org_name": org_name, "pwd_enabled": settings.get("admin_password_enabled", False), "password": settings.get("admin_password", "") }) environments_data.sort(key=lambda x: x['id']) return render_template_string(ADMHOSTO_TEMPLATE, environments=environments_data) @app.route('/admhosto/create', methods=['POST']) def create_environment(): all_data = load_data() while True: new_id = ''.join(random.choices(string.digits, k=6)) if new_id not in all_data: break all_data[new_id] = { 'products': [], 'categories':[], 'blocks':[], 'orders': [], 'settings': { "vcard_firstname": "Imię", "vcard_lastname": "Nazwisko", "vcard_job": "Specjalista", "organization_name": "Moja Firma", "currency_code": "PLN", "chat_avatar": None, "color_scheme": "default", "admin_password_enabled": False, "admin_password": "", "categories_as_lines": False, "about_text": "Cześć! To moja wirtualna wizytówka.", "enable_cart": False, "order_messenger": "whatsapp", "order_contact": "" } } save_data(all_data) flash(f"{t_text('Utworzono wizytówkę ID:')} {new_id}", 'success') return redirect(url_for('admhosto')) @app.route('/admhosto/update_pwd/', methods=['POST']) def update_env_pwd(env_id): all_data = load_data() if env_id in all_data: pwd_enabled = 'pwd_enabled' in request.form password = request.form.get('password', '').strip() all_data[env_id]['settings']['admin_password_enabled'] = pwd_enabled all_data[env_id]['settings']['admin_password'] = password save_data(all_data) flash(f"{t_text('Zaktualizowano hasło dla:')} {env_id}", 'success') else: flash(f"{t_text('Nie znaleziono wizytówki:')} {env_id}", 'error') return redirect(url_for('admhosto')) @app.route('/admhosto/delete/', methods=['POST']) def delete_environment(env_id): all_data = load_data() if env_id in all_data: del all_data[env_id] save_data(all_data) flash(f"{t_text('Usunięto wizytówkę:')} {env_id}", 'success') else: flash(f"{t_text('Nie znaleziono wizytówki:')} {env_id}", 'error') return redirect(url_for('admhosto')) @app.route('//login', methods=['GET', 'POST']) def admin_login(env_id): data = get_env_data(env_id) settings = data.get('settings', {}) if not settings.get('admin_password_enabled'): return redirect(url_for('admin', env_id=env_id)) if request.method == 'POST': pwd = request.form.get('password', '') if pwd == settings.get('admin_password', ''): session[f'admin_auth_{env_id}'] = True return redirect(url_for('admin', env_id=env_id)) else: flash(t_text('Nieprawidłowe hasło'), 'error') return render_template_string(LOGIN_TEMPLATE, env_id=env_id) @app.route('//logout') def admin_logout(env_id): session.pop(f'admin_auth_{env_id}', None) return redirect(url_for('admin_login', env_id=env_id)) @app.route('//vcard') def download_vcard(env_id): data = get_env_data(env_id) settings = data.get('settings', {}) blocks = data.get('blocks', []) first_name = settings.get("vcard_firstname", "") last_name = settings.get("vcard_lastname", "") org = settings.get("organization_name", "") title = settings.get("vcard_job", "") note = settings.get("about_text", "").replace('\n', '\\n') card_url = url_for('catalog', env_id=env_id, _external=True) vcard = [ "BEGIN:VCARD", "VERSION:3.0", f"N:{last_name};{first_name};;;", f"FN:{first_name} {last_name}".strip(), ] if org: vcard.append(f"ORG:{org}") if title: vcard.append(f"TITLE:{title}") if note: vcard.append(f"NOTE:{note}") vcard.append(f"URL;type=pref:{card_url}") for b in blocks: if b.get('type') == 'link' and b.get('url'): url = b.get('url', '').strip() if url.startswith('+') or url.replace('-', '').replace(' ', '').isdigit(): vcard.append(f"TEL;TYPE=CELL:{url}") elif '@' in url and not url.startswith('http'): vcard.append(f"EMAIL;TYPE=WORK:{url.replace('mailto:', '')}") else: vcard.append(f"URL:{url}") vcard.append("END:VCARD") vcard_str = "\r\n".join(vcard) response = make_response(vcard_str) response.headers["Content-Disposition"] = f"attachment; filename=contact_{env_id}.vcf" response.headers["Content-Type"] = "text/vcard; charset=utf-8" return response @app.route('//catalog') def catalog(env_id): data = get_env_data(env_id) all_products = data.get('products',[]) settings = data.get('settings', {}) blocks = data.get('blocks',[]) product_categories = set(p.get('category', t_text('Bez kategorii')) for p in all_products) admin_categories = set(data.get('categories',[])) all_cat_names = sorted(list(product_categories.union(admin_categories))) products_sorted_for_js = sorted(all_products, key=lambda p: p.get('name', '').lower()) products_by_category = {cat:[] for cat in all_cat_names} for product in all_products: products_by_category[product.get('category', t_text('Bez kategorii'))].append(product) ordered_categories = [cat for cat in all_cat_names if products_by_category.get(cat)] chat_avatar_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/avatars/{settings['chat_avatar']}" if settings.get('chat_avatar') else "https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png" return render_template_string( CATALOG_TEMPLATE, ordered_categories=ordered_categories, products_json=json.dumps(products_sorted_for_js), repo_id=REPO_ID, currency_code=settings.get('currency_code', 'PLN'), settings=settings, chat_avatar_url=chat_avatar_url, env_id=env_id, blocks=blocks ) @app.route('//checkout', methods=['POST']) def checkout(env_id): req_data = request.get_json() env_data = get_env_data(env_id) items = req_data.get('items', {}) if not items: return jsonify({"success": False, "error": t_text('Koszyk jest pusty')}) total = sum(item.get('qty', 0) * item.get('price', 0) for item in items.values()) order = { "id": ''.join(random.choices(string.digits, k=8)), "date": datetime.now(ALMATY_TZ).strftime('%Y-%m-%d %H:%M:%S'), "customer_name": req_data.get('customer_name', ''), "customer_phone": req_data.get('customer_phone', ''), "items": items, "total": total, "viewed": False } if 'orders' not in env_data: env_data['orders'] = [] env_data['orders'].append(order) save_env_data(env_id, env_data) return jsonify({"success": True, "url": url_for('order_invoice', env_id=env_id, order_id=order['id'])}) @app.route('//order/') def order_invoice(env_id, order_id): env_data = get_env_data(env_id) settings = env_data.get('settings', {}) orders = env_data.get('orders', []) order = next((o for o in orders if o.get('id') == order_id), None) if not order: return t_text("Brak wyników."), 404 currency = settings.get('currency_code', 'PLN') lang = get_lang() if lang == 'en': text = f"Hello! I placed an order #{order['id']} for {order['total']} {currency}.\nInvoice link: {request.url}" elif lang == 'de': text = f"Hallo! Ich habe eine Bestellung #{order['id']} über {order['total']} {currency} aufgegeben.\nRechnungslink: {request.url}" else: text = f"Dzień dobry! Złożyłem zamówienie #{order['id']} na kwotę {order['total']} {currency}.\nLink do faktury: {request.url}" encoded_text = urllib.parse.quote(text) messenger = settings.get('order_messenger', 'whatsapp') contact = settings.get('order_contact', '') if messenger == 'whatsapp': num = re.sub(r'[^0-9+]', '', contact) send_link = f"https://wa.me/{num}?text={encoded_text}" messenger_name = "WhatsApp" elif messenger == 'telegram': username = contact.replace('@', '').strip() send_link = f"tg://resolve?domain={username}&text={encoded_text}" messenger_name = "Telegram" else: send_link = "#" messenger_name = t_text("Brak") return render_template_string( ORDER_TEMPLATE, order=order, currency=currency, send_link=send_link, messenger_name=messenger_name, repo_id=REPO_ID, settings=settings, env_id=env_id ) @app.route('//admin', methods=['GET', 'POST']) def admin(env_id): data = get_env_data(env_id) settings = data.get('settings', {}) if settings.get('admin_password_enabled') and not session.get(f'admin_auth_{env_id}'): return redirect(url_for('admin_login', env_id=env_id)) products = data.get('products',[]) categories = data.get('categories',[]) blocks = data.get('blocks',[]) page = request.args.get('p', 1, type=int) search_q = request.args.get('q', '').strip() if request.method == 'POST': action = request.form.get('action') try: if action == 'add_block': b_type = request.form.get('block_type') b_title = request.form.get('block_title', '').strip() b_icon = request.form.get('block_icon', '') b_url = request.form.get('block_url', '').strip() b_content = request.form.get('block_content', '').strip() if b_type == 'link' and b_url: if not b_url.startswith(('http://', 'https://', 'mailto:', 'tel:')) and not b_url.startswith('+'): if '@' in b_url: b_url = 'mailto:' + b_url else: b_url = 'https://' + b_url elif b_url.startswith('+'): b_url = 'tel:' + b_url.replace(' ', '').replace('-', '') elif b_type == 'pdf': b_url = '' pdf_file = request.files.get('block_pdf') if pdf_file and pdf_file.filename and HF_TOKEN_WRITE: try: ext = os.path.splitext(pdf_file.filename)[1].lower() pdf_filename = f"pdf_{uuid4().hex[:8]}{ext}" uploads_dir = 'uploads_temp' os.makedirs(uploads_dir, exist_ok=True) temp_path = os.path.join(uploads_dir, pdf_filename) pdf_file.save(temp_path) HfApi().upload_file(path_or_fileobj=temp_path, path_in_repo=f"pdfs/{pdf_filename}", repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE) os.remove(temp_path) b_url = pdf_filename except Exception as e: flash(f"{t_text('Błąd ładowania PDF:')} {e}", 'error') blocks.append({ 'id': uuid4().hex[:8], 'type': b_type, 'title': b_title, 'icon': b_icon if b_type in ['link', 'pdf'] else '', 'url': b_url, 'content': b_content }) data['blocks'] = blocks save_env_data(env_id, data) flash(t_text('Dodano blok.'), "success") elif action == 'edit_block': b_id = request.form.get('block_id') b_type = request.form.get('block_type') b_title = request.form.get('block_title', '').strip() b_icon = request.form.get('block_icon', '') b_url = request.form.get('block_url', '').strip() b_content = request.form.get('block_content', '').strip() old_url = request.form.get('old_block_url', '') for b in blocks: if b.get('id') == b_id: b['type'] = b_type b['title'] = b_title b['icon'] = b_icon if b_type in ['link', 'pdf'] else '' b['content'] = b_content if b_type == 'link': if b_url: if not b_url.startswith(('http://', 'https://', 'mailto:', 'tel:')) and not b_url.startswith('+'): if '@' in b_url: b_url = 'mailto:' + b_url else: b_url = 'https://' + b_url elif b_url.startswith('+'): b_url = 'tel:' + b_url.replace(' ', '').replace('-', '') b['url'] = b_url elif b_type == 'pdf': pdf_file = request.files.get('block_pdf') if pdf_file and pdf_file.filename and HF_TOKEN_WRITE: if old_url and old_url.startswith('pdf_'): try: HfApi().delete_files(repo_id=REPO_ID, paths_in_repo=[f"pdfs/{old_url}"], repo_type="dataset", token=HF_TOKEN_WRITE) except Exception: pass try: ext = os.path.splitext(pdf_file.filename)[1].lower() pdf_filename = f"pdf_{uuid4().hex[:8]}{ext}" uploads_dir = 'uploads_temp' os.makedirs(uploads_dir, exist_ok=True) temp_path = os.path.join(uploads_dir, pdf_filename) pdf_file.save(temp_path) HfApi().upload_file(path_or_fileobj=temp_path, path_in_repo=f"pdfs/{pdf_filename}", repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE) os.remove(temp_path) b['url'] = pdf_filename except Exception as e: flash(f"{t_text('Błąd ładowania PDF:')} {e}", 'error') b['url'] = old_url else: b['url'] = old_url else: b['url'] = '' break data['blocks'] = blocks save_env_data(env_id, data) flash(t_text('Zaktualizowano blok.'), "success") elif action == 'delete_block': b_id = request.form.get('block_id') block_to_del = next((b for b in blocks if b.get('id') == b_id), None) if block_to_del: if block_to_del.get('type') == 'pdf' and block_to_del.get('url') and block_to_del.get('url').startswith('pdf_') and HF_TOKEN_WRITE: try: HfApi().delete_files(repo_id=REPO_ID, paths_in_repo=[f"pdfs/{block_to_del.get('url')}"], repo_type="dataset", token=HF_TOKEN_WRITE) except Exception: pass blocks = [b for b in blocks if b.get('id') != b_id] data['blocks'] = blocks save_env_data(env_id, data) flash(t_text('Usunięto blok.'), "success") elif action == 'move_block_up': b_id = request.form.get('block_id') idx = next((i for i, b in enumerate(blocks) if b.get('id') == b_id), -1) if idx > 0: blocks[idx], blocks[idx-1] = blocks[idx-1], blocks[idx] data['blocks'] = blocks save_env_data(env_id, data) flash(t_text('Przeniesiono blok wyżej.'), "success") elif action == 'move_block_down': b_id = request.form.get('block_id') idx = next((i for i, b in enumerate(blocks) if b.get('id') == b_id), -1) if idx != -1 and idx < len(blocks) - 1: blocks[idx], blocks[idx+1] = blocks[idx+1], blocks[idx] data['blocks'] = blocks save_env_data(env_id, data) flash(t_text('Przeniesiono blok niżej.'), "success") elif action == 'add_category': category_name = request.form.get('category_name', '').strip() if category_name and category_name not in categories: categories.append(category_name) data['categories'] = categories save_env_data(env_id, data) flash(f"{t_text('Dodano kategorię:')} {category_name}", 'success') elif not category_name: flash(t_text('Nazwa kategorii nie może być pusta.'), 'error') else: flash(f"{t_text('Kategoria już istnieje:')} {category_name}", 'error') elif action == 'delete_category': category_to_delete = request.form.get('category_name') if category_to_delete and category_to_delete in categories: categories.remove(category_to_delete) for product in products: if product.get('category') == category_to_delete: product['category'] = t_text('Bez kategorii') data['categories'] = categories data['products'] = products save_env_data(env_id, data) flash(f"{t_text('Usunięto kategorię:')} {category_to_delete}", 'success') else: flash(f"{t_text('Błąd usuwania:')} {category_to_delete}", 'error') elif action == 'update_settings': settings['admin_password_enabled'] = 'admin_password_enabled' in request.form settings['admin_password'] = request.form.get('admin_password', '').strip() settings['vcard_firstname'] = request.form.get('vcard_firstname', '').strip() settings['vcard_lastname'] = request.form.get('vcard_lastname', '').strip() settings['vcard_job'] = request.form.get('vcard_job', '').strip() settings['organization_name'] = request.form.get('organization_name', '').strip() settings['about_text'] = request.form.get('about_text', '').strip() settings['currency_code'] = request.form.get('currency_code', 'PLN') settings['color_scheme'] = request.form.get('color_scheme', 'default') settings['enable_cart'] = 'enable_cart' in request.form settings['order_messenger'] = request.form.get('order_messenger', 'whatsapp') settings['order_contact'] = request.form.get('order_contact', '').strip() avatar_file = request.files.get('chat_avatar') if avatar_file and avatar_file.filename: if HF_TOKEN_WRITE: try: api = HfApi() old_avatar = settings.get('chat_avatar') if old_avatar: try: api.delete_files(repo_id=REPO_ID, paths_in_repo=[f"avatars/{old_avatar}"], repo_type="dataset", token=HF_TOKEN_WRITE) except Exception: pass ext = os.path.splitext(avatar_file.filename)[1].lower() avatar_filename = f"avatar_{env_id}_{int(time.time())}{ext}" uploads_dir = 'uploads_temp' os.makedirs(uploads_dir, exist_ok=True) temp_path = os.path.join(uploads_dir, avatar_filename) avatar_file.save(temp_path) api.upload_file(path_or_fileobj=temp_path, path_in_repo=f"avatars/{avatar_filename}", repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE) settings['chat_avatar'] = avatar_filename os.remove(temp_path) flash(t_text("Awatar pomyślnie zaktualizowany."), 'success') except Exception as e: flash(f"{t_text('Błąd podczas ładowania awatara:')} {e}", 'error') else: pass data['settings'] = settings save_env_data(env_id, data) flash(t_text("Zapisano ustawienia."), 'success') elif action == 'add_product' or action == 'edit_product': product_id = request.form.get('product_id') product_data = {} is_edit = action == 'edit_product' if is_edit: product_data = next((p for p in products if p.get('product_id') == product_id), None) if not product_data: flash(t_text("Błąd: nie znaleziono rekordu."), 'error') return redirect(url_for('admin', env_id=env_id, p=page, q=search_q)) else: product_data['views'] = 0 product_data['name'] = request.form.get('name', '').strip() product_data['description'] = request.form.get('description', '').strip() try: product_data['price'] = float(request.form.get('price', 0)) except ValueError: product_data['price'] = 0.0 category = request.form.get('category') product_data['category'] = category if category in categories else t_text('Bez kategorii') if not product_data['name']: flash(t_text("Nazwa jest wymagana."), 'error') return redirect(url_for('admin', env_id=env_id, p=page, q=search_q)) photos_files = request.files.getlist('photos') if photos_files and any(f.filename for f in photos_files): if HF_TOKEN_WRITE: uploads_dir = 'uploads_temp' os.makedirs(uploads_dir, exist_ok=True) api = HfApi() new_photos_list = [] photo_limit = 10 uploaded_count = 0 for photo in photos_files: if uploaded_count >= photo_limit: break if photo and photo.filename: try: ext = os.path.splitext(photo.filename)[1].lower() if ext not in ['.jpg', '.jpeg', '.png', '.gif', '.webp']: continue safe_name = secure_filename(product_data['name'].replace(' ', '_'))[:50] photo_filename = f"{safe_name}_{uuid4().hex[:8]}{ext}" temp_path = os.path.join(uploads_dir, photo_filename) photo.save(temp_path) api.upload_file(path_or_fileobj=temp_path, path_in_repo=f"photos/{photo_filename}", repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE) new_photos_list.append(photo_filename) os.remove(temp_path) uploaded_count += 1 except Exception as e: flash(f"{t_text('Błąd:')} {e}", 'error') if new_photos_list and is_edit and product_data.get('photos'): try: api.delete_files(repo_id=REPO_ID, paths_in_repo=[f"photos/{p}" for p in product_data['photos']], repo_type="dataset", token=HF_TOKEN_WRITE) except Exception: pass if new_photos_list: product_data['photos'] = new_photos_list else: pass if is_edit: product_index = next((i for i, p in enumerate(products) if p.get('product_id') == product_id), -1) if product_index != -1: products[product_index] = product_data flash(f"{t_text('Zaktualizowano pomyślnie.')} '{product_data['name']}'", 'success') else: product_data['product_id'] = uuid4().hex products.append(product_data) flash(f"{t_text('Zapisano:')} '{product_data['name']}'", 'success') data['products'] = products save_env_data(env_id, data) elif action == 'delete_product': product_id = request.form.get('product_id') product_index = next((i for i, p in enumerate(products) if p.get('product_id') == product_id), -1) if product_index == -1: flash(t_text("Błąd usuwania: nie znaleziono rekordu."), 'error') return redirect(url_for('admin', env_id=env_id, p=page, q=search_q)) deleted_product = products.pop(product_index) product_name = deleted_product.get('name', 'N/A') photos_to_delete = deleted_product.get('photos', []) if photos_to_delete and HF_TOKEN_WRITE: try: api = HfApi() api.delete_files(repo_id=REPO_ID, paths_in_repo=[f"photos/{p}" for p in photos_to_delete], repo_type="dataset", token=HF_TOKEN_WRITE) except Exception: pass data['products'] = products save_env_data(env_id, data) flash(f"{t_text('Usunięto rekord:')} '{product_name}'", 'success') else: flash(f"{t_text('Nieznana akcja:')} {action}", 'warning') return redirect(url_for('admin', env_id=env_id, p=page, q=search_q)) except Exception as e: flash(f"{t_text('Błąd wykonania akcji:')} {e}", 'error') return redirect(url_for('admin', env_id=env_id, p=page, q=search_q)) filtered_products = products if search_q: q_lower = search_q.lower() filtered_products = [p for p in products if q_lower in p.get('name', '').lower() or q_lower in p.get('description', '').lower()] filtered_products = sorted(filtered_products, key=lambda p: p.get('name', '').lower()) PER_PAGE = 20 total_items = len(filtered_products) total_pages = math.ceil(total_items / PER_PAGE) if total_items > 0 else 1 if page < 1: page = 1 if page > total_pages: page = total_pages start_idx = (page - 1) * PER_PAGE end_idx = start_idx + PER_PAGE paginated_products = filtered_products[start_idx:end_idx] display_categories = sorted(categories) display_settings = settings chat_avatar_url = f"https://huggingface.co/datasets/{REPO_ID}/resolve/main/avatars/{display_settings['chat_avatar']}" if display_settings.get('chat_avatar') else "https://huggingface.co/spaces/gippo312/admin/resolve/main/Picsart_25-11-04_12-02-21-390.png" new_orders_count = sum(1 for o in data.get('orders', []) if not o.get('viewed', False)) return render_template_string( ADMIN_TEMPLATE, paginated_products=paginated_products, total_pages=total_pages, page=page, search_q=search_q, categories=display_categories, settings=display_settings, blocks=blocks, repo_id=REPO_ID, currency_code=display_settings.get('currency_code', 'PLN'), chat_avatar_url=chat_avatar_url, currencies=CURRENCIES, color_schemes=COLOR_SCHEMES, icons=ICONS, env_id=env_id, new_orders_count=new_orders_count, current_lang=get_lang() ) @app.route('//admin/orders') def admin_orders(env_id): data = get_env_data(env_id) settings = data.get('settings', {}) if settings.get('admin_password_enabled') and not session.get(f'admin_auth_{env_id}'): return redirect(url_for('admin_login', env_id=env_id)) orders = data.get('orders', []) changed = False for o in orders: if not o.get('viewed', False): o['viewed'] = True changed = True if changed: save_env_data(env_id, data) orders_sorted = sorted(orders, key=lambda x: x.get('date', ''), reverse=True) return render_template_string(ADMIN_ORDERS_TEMPLATE, orders=orders_sorted, env_id=env_id, settings=settings, currency=settings.get('currency_code', 'PLN'), current_lang=get_lang()) @app.route('//admin/orders/delete/', methods=['POST']) def delete_order(env_id, order_id): data = get_env_data(env_id) settings = data.get('settings', {}) if settings.get('admin_password_enabled') and not session.get(f'admin_auth_{env_id}'): return redirect(url_for('admin_login', env_id=env_id)) orders = data.get('orders', []) data['orders'] = [o for o in orders if o.get('id') != order_id] save_env_data(env_id, data) flash(t_text("Usunięto zamówienie z historii."), "success") return redirect(url_for('admin_orders', env_id=env_id)) @app.route('/generate_description_ai', methods=['POST']) def handle_generate_description_ai(): request_data = request.get_json() base64_image = request_data.get('image') language = request_data.get('language', 'Polski') if not base64_image: return jsonify({"error": t_text("Błąd: Nie znaleziono pliku obrazu.")}), 400 try: image_data = base64.b64decode(base64_image) result_text = generate_ai_description_from_image(image_data, language) return jsonify({"text": result_text}) except ValueError as ve: return jsonify({"error": str(ve)}), 400 except Exception as e: return jsonify({"error": f"{t_text('Błąd:')} {e}"}), 500 if __name__ == '__main__': configure_gemini() download_db_from_hf() load_data() if HF_TOKEN_WRITE: backup_thread = threading.Thread(target=periodic_backup, daemon=True) backup_thread.start() port = int(os.environ.get('PORT', 7860)) app.run(debug=False, host='0.0.0.0', port=port)