Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { Button, Input, Label, ScrollArea, Select, cn } from "@geolibre/ui";
import {
ChevronDown,
ChevronUp,
Copy,
Download,
GripVertical,
LayoutGrid,
Expand Down Expand Up @@ -64,6 +65,7 @@ import { useTranslation } from "react-i18next";
import type { TFunction } from "i18next";
import { clamp } from "../../../lib/clamp";
import { createDuckDbCapability } from "../../../lib/duckdb-processing";
import { modelGraphToPython } from "../../../lib/model-python-script";
import {
buildModelToolCatalog,
groupModelTools,
Expand Down Expand Up @@ -296,6 +298,8 @@ export function ModelBuilderPanel({
const [search, setSearch] = useState("");
const [log, setLog] = useState<string[]>([]);
const [running, setRunning] = useState(false);
/** Script for the last graph that completed successfully, not later canvas edits. */
const [pythonScript, setPythonScript] = useState<string | null>(null);
const [nodeStatus, setNodeStatus] = useState<Record<string, "running" | "done" | "error">>({});
/**
* The output port a keyboard/click user has armed, waiting for an input port
Expand Down Expand Up @@ -442,6 +446,7 @@ export function ModelBuilderPanel({
// abortRun() clears the ref, so the in-flight run's `finally` no longer
// matches its own controller and will not clear this itself.
setRunning(false);
setPythonScript(null);
setNodeStatus({});
setLog([]);
}, [abortRun]);
Expand Down Expand Up @@ -886,6 +891,7 @@ export function ModelBuilderPanel({
const controller = new AbortController();
abortRef.current = controller;
setRunning(true);
setPythonScript(null);
setNodeStatus({});
const duckdb = createDuckDbCapability();
// Outputs the host refused after the graph itself finished, so the summary
Expand Down Expand Up @@ -946,6 +952,7 @@ export function ModelBuilderPanel({
// runModelGraph returns, so counting failures first would always read 0.
await Promise.allSettled(pendingAdds);
if (!controller.signal.aborted) {
if (!result.error) setPythonScript(modelGraphToPython(graph, resolveDescriptor));
appendLog(
result.error
? `${t("processing.modelBuilder.runFailed")}: ${result.error.message}`
Expand All @@ -964,6 +971,20 @@ export function ModelBuilderPanel({
}
}, [issues.length, graph, resolveDescriptor, layers, addGeoJsonLayer, onAddRaster, appendLog, t]);

const handleCopyPython = useCallback(async () => {
if (!pythonScript) return;
try {
await navigator.clipboard.writeText(pythonScript);
appendLog(t("processing.modelBuilder.pythonCopied"));
} catch (error) {
appendLog(
`${t("processing.modelBuilder.pythonCopyFailed")}: ${
error instanceof Error ? error.message : String(error)
}`,
);
}
}, [pythonScript, appendLog, t]);

// --- Panel chrome -------------------------------------------------------

const handleDragStart = (event: ReactPointerEvent<HTMLDivElement>) => {
Expand Down Expand Up @@ -1314,6 +1335,18 @@ export function ModelBuilderPanel({
<Download className="h-3.5 w-3.5" />{" "}
{!compact && t("processing.modelBuilder.exportModel")}
</Button>
<Button
size="sm"
variant="ghost"
className="h-7 shrink-0 gap-1 px-2"
onClick={() => void handleCopyPython()}
disabled={!pythonScript || running}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pythonScript is cleared on run-start/reset/new-model/load-model, but not on the many in-place setGraph calls that follow a successful run (adding/removing nodes, editing a parameter, rewiring an edge, dragging a node — e.g. handleCanvasDrop, addToolAtDefault, the node-field editor around line 1631, removeNode, removeEdge). The comment at line 301 documents this as intentional ("not later canvas edits"), but the "Copy Python" button gives no visual indication that the script may now be stale relative to the edited canvas — a user who tweaks a parameter after a successful run and then clicks Copy Python silently gets Python for the pre-edit graph. Given the component already tracks a dirty flag for a similar purpose (save prompts), consider clearing pythonScript on graph-mutating setGraph calls too, or otherwise surfacing that the copied script may not match the current canvas.

Confidence: medium — this is flagged as deliberate in the code comment, so it may be an accepted tradeoff rather than an oversight.

aria-label={t("processing.modelBuilder.copyPython")}
title={t("processing.modelBuilder.copyPython")}
>
<Copy className="h-3.5 w-3.5" />
{!compact && t("processing.modelBuilder.copyPython")}
</Button>
{running ? (
<Button
size="sm"
Expand Down
23 changes: 13 additions & 10 deletions apps/geolibre-desktop/src/i18n/locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -1796,6 +1796,16 @@
"exportCsv": "تصدير CSV",
"exportGeoParquet": "تصدير GeoParquet"
},
"auth": {
"unavailableTitle": "تسجيل الدخول غير متاح",
"unavailableDescription": "تعذّر على GeoLibre الوصول إلى خدمة تسجيل الدخول، لذا لا يمكنه معرفة ما إذا كنت قد سجّلت الدخول. عادةً ما يكون هذا مؤقتًا؛ وإذا استمر، فقد تحتاج إعدادات المصادقة في هذا النشر إلى المراجعة.",
"retry": "إعادة المحاولة",
"signInTitle": "تسجيل الدخول إلى GeoLibre",
"signInDescription": "يتطلب هذا النشر حسابًا. سيتم نقلك إلى صفحة تسجيل دخول آمنة ثم إعادتك إلى هنا.",
"signIn": "تسجيل الدخول",
"signOut": "تسجيل الخروج",
"account": "الحساب"
},
"basemapExtract": {
"title": "استخراج خريطة أساس دون اتصال",
"url": "عنوان URL لخريطة الأساس",
Expand Down Expand Up @@ -4341,6 +4351,9 @@
"importTooLarge": "هذا النموذج أكبر من أن يُفتح.",
"importModel": "استيراد",
"exportModel": "تصدير",
"copyPython": "نسخ برنامج Python النصي",
"pythonCopied": "تم نسخ برنامج Python النصي إلى الحافظة.",
"pythonCopyFailed": "تعذر نسخ برنامج Python النصي",
"savedModels": "النماذج المحفوظة",
"loadModelPlaceholder": "تحميل نموذج محفوظ...",
"deleteModel": "حذف",
Expand Down Expand Up @@ -5982,15 +5995,5 @@
"mapPoint": "نقطة على الخريطة",
"reply": "رد",
"replyPlaceholder": "اكتب ردًا..."
},
"auth": {
"unavailableTitle": "تسجيل الدخول غير متاح",
"unavailableDescription": "تعذّر على GeoLibre الوصول إلى خدمة تسجيل الدخول، لذا لا يمكنه معرفة ما إذا كنت قد سجّلت الدخول. عادةً ما يكون هذا مؤقتًا؛ وإذا استمر، فقد تحتاج إعدادات المصادقة في هذا النشر إلى المراجعة.",
"retry": "إعادة المحاولة",
"signInTitle": "تسجيل الدخول إلى GeoLibre",
"signInDescription": "يتطلب هذا النشر حسابًا. سيتم نقلك إلى صفحة تسجيل دخول آمنة ثم إعادتك إلى هنا.",
"signIn": "تسجيل الدخول",
"signOut": "تسجيل الخروج",
"account": "الحساب"
}
}
23 changes: 13 additions & 10 deletions apps/geolibre-desktop/src/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1625,6 +1625,16 @@
"exportCsv": "CSV exportieren",
"exportGeoParquet": "GeoParquet exportieren"
},
"auth": {
"unavailableTitle": "Anmeldung ist nicht verfügbar",
"unavailableDescription": "GeoLibre konnte den Anmeldedienst nicht erreichen und kann daher nicht feststellen, ob Sie angemeldet sind. Das ist meist vorübergehend; falls es weiterhin auftritt, müssen möglicherweise die Authentifizierungseinstellungen dieser Bereitstellung geprüft werden.",
"retry": "Erneut versuchen",
"signInTitle": "Bei GeoLibre anmelden",
"signInDescription": "Diese Bereitstellung erfordert ein Konto. Sie werden zu einer sicheren Anmeldeseite weitergeleitet und danach hierher zurückgebracht.",
"signIn": "Anmelden",
"signOut": "Abmelden",
"account": "Konto"
},
"basemapExtract": {
"title": "Offline-Basiskarte extrahieren",
"url": "Basiskarten-URL",
Expand Down Expand Up @@ -4074,6 +4084,9 @@
"importTooLarge": "Dieses Modell ist zu groß zum Öffnen.",
"importModel": "Importieren",
"exportModel": "Exportieren",
"copyPython": "Python-Skript kopieren",
"pythonCopied": "Python-Skript wurde in die Zwischenablage kopiert.",
"pythonCopyFailed": "Python-Skript konnte nicht kopiert werden",
"savedModels": "Gespeicherte Modelle",
"loadModelPlaceholder": "Gespeichertes Modell laden ...",
"deleteModel": "Löschen",
Expand Down Expand Up @@ -5663,15 +5676,5 @@
"mapPoint": "Kartenpunkt",
"reply": "Antworten",
"replyPlaceholder": "Antwort schreiben..."
},
"auth": {
"unavailableTitle": "Anmeldung ist nicht verfügbar",
"unavailableDescription": "GeoLibre konnte den Anmeldedienst nicht erreichen und kann daher nicht feststellen, ob Sie angemeldet sind. Das ist meist vorübergehend; falls es weiterhin auftritt, müssen möglicherweise die Authentifizierungseinstellungen dieser Bereitstellung geprüft werden.",
"retry": "Erneut versuchen",
"signInTitle": "Bei GeoLibre anmelden",
"signInDescription": "Diese Bereitstellung erfordert ein Konto. Sie werden zu einer sicheren Anmeldeseite weitergeleitet und danach hierher zurückgebracht.",
"signIn": "Anmelden",
"signOut": "Abmelden",
"account": "Konto"
}
}
3 changes: 3 additions & 0 deletions apps/geolibre-desktop/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4084,6 +4084,9 @@
"importTooLarge": "That model is too large to open.",
"importModel": "Import",
"exportModel": "Export",
"copyPython": "Copy Python script",
"pythonCopied": "Python script copied to the clipboard.",
"pythonCopyFailed": "Could not copy the Python script",
"savedModels": "Saved models",
"loadModelPlaceholder": "Load a saved model...",
"deleteModel": "Delete",
Expand Down
23 changes: 13 additions & 10 deletions apps/geolibre-desktop/src/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1625,6 +1625,16 @@
"exportCsv": "Exportar CSV",
"exportGeoParquet": "Exportar GeoParquet"
},
"auth": {
"unavailableTitle": "El inicio de sesión no está disponible",
"unavailableDescription": "GeoLibre no pudo conectar con el servicio de inicio de sesión, por lo que no puede saber si ha iniciado sesión. Esto suele ser temporal; si persiste, es posible que haya que revisar la configuración de autenticación de esta implementación.",
"retry": "Intentar de nuevo",
"signInTitle": "Inicie sesión en GeoLibre",
"signInDescription": "Esta implementación requiere una cuenta. Se le llevará a una página de inicio de sesión segura y volverá aquí.",
"signIn": "Iniciar sesión",
"signOut": "Cerrar sesión",
"account": "Cuenta"
},
"basemapExtract": {
"title": "Extraer mapa base sin conexión",
"url": "URL del mapa base",
Expand Down Expand Up @@ -4074,6 +4084,9 @@
"importTooLarge": "Ese modelo es demasiado grande para abrirlo.",
"importModel": "Importar",
"exportModel": "Exportar",
"copyPython": "Copiar script de Python",
"pythonCopied": "El script de Python se copió al portapapeles.",
"pythonCopyFailed": "No se pudo copiar el script de Python",
"savedModels": "Modelos guardados",
"loadModelPlaceholder": "Cargar un modelo guardado...",
"deleteModel": "Eliminar",
Expand Down Expand Up @@ -5663,15 +5676,5 @@
"mapPoint": "Punto del mapa",
"reply": "Responder",
"replyPlaceholder": "Escriba una respuesta..."
},
"auth": {
"unavailableTitle": "El inicio de sesión no está disponible",
"unavailableDescription": "GeoLibre no pudo conectar con el servicio de inicio de sesión, por lo que no puede saber si ha iniciado sesión. Esto suele ser temporal; si persiste, es posible que haya que revisar la configuración de autenticación de esta implementación.",
"retry": "Intentar de nuevo",
"signInTitle": "Inicie sesión en GeoLibre",
"signInDescription": "Esta implementación requiere una cuenta. Se le llevará a una página de inicio de sesión segura y volverá aquí.",
"signIn": "Iniciar sesión",
"signOut": "Cerrar sesión",
"account": "Cuenta"
}
}
23 changes: 13 additions & 10 deletions apps/geolibre-desktop/src/i18n/locales/fa.json
Original file line number Diff line number Diff line change
Expand Up @@ -1625,6 +1625,16 @@
"exportCsv": "خروجی CSV",
"exportGeoParquet": "خروجی GeoParquet"
},
"auth": {
"unavailableTitle": "ورود در دسترس نیست",
"unavailableDescription": "GeoLibre نتوانست به سرویس ورود دسترسی پیدا کند، بنابراین نمی‌داند که آیا وارد شده‌اید یا نه. این وضعیت معمولاً موقتی است؛ اگر ادامه یافت، شاید تنظیمات احراز هویت این استقرار نیاز به بررسی داشته باشد.",
"retry": "تلاش دوباره",
"signInTitle": "ورود به GeoLibre",
"signInDescription": "این استقرار به یک حساب نیاز دارد. به یک صفحهٔ ورود امن هدایت می‌شوید و سپس به اینجا بازمی‌گردید.",
"signIn": "ورود",
"signOut": "خروج",
"account": "حساب"
},
"basemapExtract": {
"title": "استخراج نقشهٔ پایهٔ برون‌خط",
"url": "نشانی نقشهٔ پایه",
Expand Down Expand Up @@ -4074,6 +4084,9 @@
"importTooLarge": "این مدل بزرگ‌تر از آن است که باز شود.",
"importModel": "درون‌ریزی",
"exportModel": "برون‌ریزی",
"copyPython": "کپی اسکریپت پایتون",
"pythonCopied": "اسکریپت پایتون در کلیپ‌بورد کپی شد.",
"pythonCopyFailed": "اسکریپت پایتون کپی نشد",
"savedModels": "مدل‌های ذخیره‌شده",
"loadModelPlaceholder": "بارگذاری یک مدل ذخیره‌شده...",
"deleteModel": "حذف",
Expand Down Expand Up @@ -5663,15 +5676,5 @@
"mapPoint": "نقطهٔ نقشه",
"reply": "پاسخ",
"replyPlaceholder": "پاسخی بنویسید..."
},
"auth": {
"unavailableTitle": "ورود در دسترس نیست",
"unavailableDescription": "GeoLibre نتوانست به سرویس ورود دسترسی پیدا کند، بنابراین نمی‌داند که آیا وارد شده‌اید یا نه. این وضعیت معمولاً موقتی است؛ اگر ادامه یافت، شاید تنظیمات احراز هویت این استقرار نیاز به بررسی داشته باشد.",
"retry": "تلاش دوباره",
"signInTitle": "ورود به GeoLibre",
"signInDescription": "این استقرار به یک حساب نیاز دارد. به یک صفحهٔ ورود امن هدایت می‌شوید و سپس به اینجا بازمی‌گردید.",
"signIn": "ورود",
"signOut": "خروج",
"account": "حساب"
}
}
23 changes: 13 additions & 10 deletions apps/geolibre-desktop/src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1625,6 +1625,16 @@
"exportCsv": "Exporter en CSV",
"exportGeoParquet": "Exporter en GeoParquet"
},
"auth": {
"unavailableTitle": "La connexion est indisponible",
"unavailableDescription": "GeoLibre n'a pas pu joindre le service de connexion et ne peut donc pas savoir si vous êtes connecté. Ce problème est généralement temporaire ; s'il persiste, les paramètres d'authentification de ce déploiement méritent peut-être une vérification.",
"retry": "Réessayer",
"signInTitle": "Connectez-vous à GeoLibre",
"signInDescription": "Ce déploiement nécessite un compte. Vous serez redirigé vers une page de connexion sécurisée, puis ramené ici.",
"signIn": "Se connecter",
"signOut": "Se déconnecter",
"account": "Compte"
},
"basemapExtract": {
"title": "Extraire un fond de carte hors ligne",
"url": "URL du fond de carte",
Expand Down Expand Up @@ -4074,6 +4084,9 @@
"importTooLarge": "Ce modèle est trop volumineux pour être ouvert.",
"importModel": "Importer",
"exportModel": "Exporter",
"copyPython": "Copier le script Python",
"pythonCopied": "Le script Python a été copié dans le presse-papiers.",
"pythonCopyFailed": "Impossible de copier le script Python",
"savedModels": "Modèles enregistrés",
"loadModelPlaceholder": "Charger un modèle enregistré...",
"deleteModel": "Supprimer",
Expand Down Expand Up @@ -5663,15 +5676,5 @@
"mapPoint": "Point de la carte",
"reply": "Répondre",
"replyPlaceholder": "Écrire une réponse..."
},
"auth": {
"unavailableTitle": "La connexion est indisponible",
"unavailableDescription": "GeoLibre n'a pas pu joindre le service de connexion et ne peut donc pas savoir si vous êtes connecté. Ce problème est généralement temporaire ; s'il persiste, les paramètres d'authentification de ce déploiement méritent peut-être une vérification.",
"retry": "Réessayer",
"signInTitle": "Connectez-vous à GeoLibre",
"signInDescription": "Ce déploiement nécessite un compte. Vous serez redirigé vers une page de connexion sécurisée, puis ramené ici.",
"signIn": "Se connecter",
"signOut": "Se déconnecter",
"account": "Compte"
}
}
23 changes: 13 additions & 10 deletions apps/geolibre-desktop/src/i18n/locales/hi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1625,6 +1625,16 @@
"exportCsv": "CSV निर्यात करें",
"exportGeoParquet": "GeoParquet निर्यात करें"
},
"auth": {
"unavailableTitle": "साइन इन उपलब्ध नहीं है",
"unavailableDescription": "GeoLibre साइन-इन सेवा तक नहीं पहुँच सका, इसलिए यह नहीं बता सकता कि आपने साइन इन किया है या नहीं। यह आमतौर पर अस्थायी होता है; यदि यह बना रहता है, तो इस परिनियोजन की प्रमाणीकरण सेटिंग्स पर ध्यान देने की आवश्यकता हो सकती है।",
"retry": "पुनः प्रयास करें",
"signInTitle": "GeoLibre में साइन इन करें",
"signInDescription": "इस परिनियोजन के लिए एक खाता आवश्यक है। आपको एक सुरक्षित साइन-इन पृष्ठ पर ले जाया जाएगा और फिर यहाँ वापस लाया जाएगा।",
"signIn": "साइन इन करें",
"signOut": "साइन आउट करें",
"account": "खाता"
},
"basemapExtract": {
"title": "ऑफ़लाइन बेसमैप एक्सट्रैक्ट करें",
"url": "बेसमैप URL",
Expand Down Expand Up @@ -4074,6 +4084,9 @@
"importTooLarge": "वह मॉडल खोलने के लिए बहुत बड़ा है।",
"importModel": "आयात",
"exportModel": "निर्यात",
"copyPython": "Python स्क्रिप्ट कॉपी करें",
"pythonCopied": "Python स्क्रिप्ट क्लिपबोर्ड पर कॉपी की गई।",
"pythonCopyFailed": "Python स्क्रिप्ट कॉपी नहीं की जा सकी",
"savedModels": "सहेजे गए मॉडल",
"loadModelPlaceholder": "सहेजा गया मॉडल लोड करें...",
"deleteModel": "हटाएँ",
Expand Down Expand Up @@ -5663,15 +5676,5 @@
"mapPoint": "मानचित्र बिंदु",
"reply": "उत्तर दें",
"replyPlaceholder": "उत्तर लिखें..."
},
"auth": {
"unavailableTitle": "साइन इन उपलब्ध नहीं है",
"unavailableDescription": "GeoLibre साइन-इन सेवा तक नहीं पहुँच सका, इसलिए यह नहीं बता सकता कि आपने साइन इन किया है या नहीं। यह आमतौर पर अस्थायी होता है; यदि यह बना रहता है, तो इस परिनियोजन की प्रमाणीकरण सेटिंग्स पर ध्यान देने की आवश्यकता हो सकती है।",
"retry": "पुनः प्रयास करें",
"signInTitle": "GeoLibre में साइन इन करें",
"signInDescription": "इस परिनियोजन के लिए एक खाता आवश्यक है। आपको एक सुरक्षित साइन-इन पृष्ठ पर ले जाया जाएगा और फिर यहाँ वापस लाया जाएगा।",
"signIn": "साइन इन करें",
"signOut": "साइन आउट करें",
"account": "खाता"
}
}
Loading
Loading