feat: replace initial-data with full *.lafabriquedesgenies.be HTTPS services & direct URL editor
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
91a57285b9
commit
205337f5c8
8 files changed
+844
-296
No files matched your search
+401
-2
@@ -1,4 +1,403 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.0",
|
"version": "3.0.0",
|
||||||
"note": "Fichier de persistance locale pour le portail Home. Synchronisé avec src/data/initial-data.ts."
|
"dockTileIds": [
|
||||||
|
"nextcloud",
|
||||||
|
"dockge",
|
||||||
|
"gitea",
|
||||||
|
"jellyfin",
|
||||||
|
"open-webui",
|
||||||
|
"vaultwarden"
|
||||||
|
],
|
||||||
|
"quickNote": "Bienvenue sur Home OS (*.lafabriquedesgenies.be). Cliquez sur « Éditer » pour ajuster n'importe quelle URL ou sous-domaine à la volée.",
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"id": "infrastructure",
|
||||||
|
"name": "Infrastructure & Serveur",
|
||||||
|
"subtitle": "Administration système, conteneurs Docker, sauvegardes & réseau",
|
||||||
|
"icon": "Server",
|
||||||
|
"tabs": [
|
||||||
|
{
|
||||||
|
"id": "infra-main",
|
||||||
|
"label": "Services Serveur",
|
||||||
|
"tiles": [
|
||||||
|
{
|
||||||
|
"id": "dockge",
|
||||||
|
"title": "Dockge",
|
||||||
|
"description": "Gestionnaire Docker Compose",
|
||||||
|
"url": "https://dockge.lafabriquedesgenies.be",
|
||||||
|
"icon": "Terminal",
|
||||||
|
"size": "2x1",
|
||||||
|
"badge": "Docker",
|
||||||
|
"accentColor": "emerald",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nginx-proxy-manager",
|
||||||
|
"title": "Nginx Proxy Manager",
|
||||||
|
"description": "Reverse Proxy & Certificats SSL",
|
||||||
|
"url": "https://proxy.lafabriquedesgenies.be",
|
||||||
|
"icon": "Shield",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Proxy SSL",
|
||||||
|
"accentColor": "blue",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "duplicati",
|
||||||
|
"title": "Duplicati",
|
||||||
|
"description": "Sauvegardes du serveur",
|
||||||
|
"url": "https://backup.lafabriquedesgenies.be",
|
||||||
|
"icon": "Archive",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Backup",
|
||||||
|
"accentColor": "sky",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "speedtest",
|
||||||
|
"title": "Speedtest",
|
||||||
|
"description": "Surveillance débit réseau",
|
||||||
|
"url": "https://speedtest.lafabriquedesgenies.be",
|
||||||
|
"icon": "Activity",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Réseau",
|
||||||
|
"accentColor": "cyan",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "adminer",
|
||||||
|
"title": "Adminer",
|
||||||
|
"description": "Gestionnaire bases SQL",
|
||||||
|
"url": "https://db.lafabriquedesgenies.be",
|
||||||
|
"icon": "Database",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "SQL",
|
||||||
|
"accentColor": "amber",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "adguard-home",
|
||||||
|
"title": "AdGuard Home",
|
||||||
|
"description": "Filtrage DNS & Antipub",
|
||||||
|
"url": "https://adguard.lafabriquedesgenies.be",
|
||||||
|
"icon": "ShieldCheck",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "DNS",
|
||||||
|
"accentColor": "teal",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "watchtower",
|
||||||
|
"title": "Watchtower",
|
||||||
|
"description": "Mises à jour automatiques",
|
||||||
|
"url": "https://dockge.lafabriquedesgenies.be",
|
||||||
|
"icon": "RefreshCw",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Daemon Auto",
|
||||||
|
"accentColor": "violet",
|
||||||
|
"openBehavior": "external"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "private-security",
|
||||||
|
"name": "Privé & Sécurité",
|
||||||
|
"subtitle": "Cloud personnel, mots de passe, documents, notes & domotique",
|
||||||
|
"icon": "ShieldCheck",
|
||||||
|
"tabs": [
|
||||||
|
{
|
||||||
|
"id": "private-main",
|
||||||
|
"label": "Espace Privé",
|
||||||
|
"tiles": [
|
||||||
|
{
|
||||||
|
"id": "nextcloud",
|
||||||
|
"title": "Nextcloud",
|
||||||
|
"description": "Fichiers & Drive Cloud",
|
||||||
|
"url": "https://nc.lafabriquedesgenies.be",
|
||||||
|
"icon": "Cloud",
|
||||||
|
"size": "2x1",
|
||||||
|
"badge": "Cloud",
|
||||||
|
"accentColor": "sky",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vaultwarden",
|
||||||
|
"title": "Vaultwarden",
|
||||||
|
"description": "Gestionnaire de mots de passe",
|
||||||
|
"url": "https://vault.lafabriquedesgenies.be",
|
||||||
|
"icon": "Key",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Coffre-fort",
|
||||||
|
"accentColor": "amber",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "paperless-ngx",
|
||||||
|
"title": "Paperless-ngx",
|
||||||
|
"description": "GED & Archivage intelligent",
|
||||||
|
"url": "https://paperless.lafabriquedesgenies.be",
|
||||||
|
"icon": "FileText",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "GED",
|
||||||
|
"accentColor": "emerald",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wikijs",
|
||||||
|
"title": "Wiki.js",
|
||||||
|
"description": "Base de connaissances & notes",
|
||||||
|
"url": "https://wiki.lafabriquedesgenies.be",
|
||||||
|
"icon": "BookOpen",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Wiki",
|
||||||
|
"accentColor": "indigo",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "stirling-pdf",
|
||||||
|
"title": "Stirling PDF",
|
||||||
|
"description": "Boîte à outils PDF complète",
|
||||||
|
"url": "https://pdf.lafabriquedesgenies.be",
|
||||||
|
"icon": "FileCode",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "PDF",
|
||||||
|
"accentColor": "rose",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "home-assistant",
|
||||||
|
"title": "Home Assistant",
|
||||||
|
"description": "Domotique de la maison",
|
||||||
|
"url": "https://hass.lafabriquedesgenies.be",
|
||||||
|
"icon": "Home",
|
||||||
|
"size": "2x1",
|
||||||
|
"badge": "Domotique",
|
||||||
|
"accentColor": "cyan",
|
||||||
|
"openBehavior": "external"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multimedia",
|
||||||
|
"name": "Multimédia & Médias",
|
||||||
|
"subtitle": "Streaming vidéo, livres audio, musique & téléchargements",
|
||||||
|
"icon": "Film",
|
||||||
|
"tabs": [
|
||||||
|
{
|
||||||
|
"id": "media-main",
|
||||||
|
"label": "Streaming & Téléchargements",
|
||||||
|
"tiles": [
|
||||||
|
{
|
||||||
|
"id": "jellyfin",
|
||||||
|
"title": "Jellyfin",
|
||||||
|
"description": "Streaming Films & Séries",
|
||||||
|
"url": "https://jellyfin.lafabriquedesgenies.be",
|
||||||
|
"icon": "Film",
|
||||||
|
"size": "2x1",
|
||||||
|
"badge": "Streaming",
|
||||||
|
"accentColor": "purple",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "audiobookshelf",
|
||||||
|
"title": "Audiobookshelf",
|
||||||
|
"description": "Livres audio & Podcasts",
|
||||||
|
"url": "https://audiobookshelf.lafabriquedesgenies.be",
|
||||||
|
"icon": "Headphones",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Audio",
|
||||||
|
"accentColor": "amber",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sonarr",
|
||||||
|
"title": "Sonarr",
|
||||||
|
"description": "Suivi & gestion des séries",
|
||||||
|
"url": "https://sonarr.lafabriquedesgenies.be",
|
||||||
|
"icon": "Tv",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Séries",
|
||||||
|
"accentColor": "sky",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "radarr",
|
||||||
|
"title": "Radarr",
|
||||||
|
"description": "Suivi & gestion des films",
|
||||||
|
"url": "https://radarr.lafabriquedesgenies.be",
|
||||||
|
"icon": "Video",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Films",
|
||||||
|
"accentColor": "amber",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "qbittorrent",
|
||||||
|
"title": "qBittorrent",
|
||||||
|
"description": "Téléchargements VPN (Gluetun)",
|
||||||
|
"url": "https://torrent.lafabriquedesgenies.be",
|
||||||
|
"icon": "DownloadCloud",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "VPN Gluetun",
|
||||||
|
"accentColor": "blue",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "deemix",
|
||||||
|
"title": "Deemix",
|
||||||
|
"description": "Téléchargement Musique",
|
||||||
|
"url": "https://deemix.lafabriquedesgenies.be",
|
||||||
|
"icon": "Music",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Musique",
|
||||||
|
"accentColor": "pink",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sabnzbd",
|
||||||
|
"title": "SABnzbd",
|
||||||
|
"description": "Client Usenet",
|
||||||
|
"url": "https://sabnzbd.lafabriquedesgenies.be",
|
||||||
|
"icon": "HardDriveDownload",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Usenet",
|
||||||
|
"accentColor": "emerald",
|
||||||
|
"openBehavior": "external"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "projects-work",
|
||||||
|
"name": "Projets & Travail",
|
||||||
|
"subtitle": "Forge Git, applications La Fabrique des Génies & outils métiers",
|
||||||
|
"icon": "Briefcase",
|
||||||
|
"tabs": [
|
||||||
|
{
|
||||||
|
"id": "work-main",
|
||||||
|
"label": "Applications & Projets",
|
||||||
|
"tiles": [
|
||||||
|
{
|
||||||
|
"id": "gitea",
|
||||||
|
"title": "Gitea",
|
||||||
|
"description": "Dépôts de code Git",
|
||||||
|
"url": "https://git.lafabriquedesgenies.be",
|
||||||
|
"icon": "GitBranch",
|
||||||
|
"size": "2x1",
|
||||||
|
"badge": "Git",
|
||||||
|
"accentColor": "emerald",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wordpress-lafabrique",
|
||||||
|
"title": "WordPress La Fabrique",
|
||||||
|
"description": "Site officiel La Fabrique",
|
||||||
|
"url": "https://lafabriquedesgenies.be",
|
||||||
|
"icon": "Globe",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Site Web",
|
||||||
|
"accentColor": "blue",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "teacher-app",
|
||||||
|
"title": "Teacher App",
|
||||||
|
"description": "Plateforme pédagogique",
|
||||||
|
"url": "https://teacher.lafabriquedesgenies.be",
|
||||||
|
"icon": "GraduationCap",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Pédagogie",
|
||||||
|
"accentColor": "indigo",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "creaprof",
|
||||||
|
"title": "CreaProf",
|
||||||
|
"description": "Outil création enseignant",
|
||||||
|
"url": "https://creaprof.lafabriquedesgenies.be",
|
||||||
|
"icon": "PenTool",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Création",
|
||||||
|
"accentColor": "violet",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ubicraft",
|
||||||
|
"title": "Ubicraft",
|
||||||
|
"description": "Application métier",
|
||||||
|
"url": "https://ubicraft.lafabriquedesgenies.be",
|
||||||
|
"icon": "LayoutGrid",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Métier",
|
||||||
|
"accentColor": "cyan",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "lma-v2",
|
||||||
|
"title": "LMA V2",
|
||||||
|
"description": "Plateforme Last Minute Activities",
|
||||||
|
"url": "https://lma.lafabriquedesgenies.be",
|
||||||
|
"icon": "Briefcase",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "LMA",
|
||||||
|
"accentColor": "amber",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "coo-app",
|
||||||
|
"title": "Coo App",
|
||||||
|
"description": "Application de gestion",
|
||||||
|
"url": "https://coo.lafabriquedesgenies.be",
|
||||||
|
"icon": "Layers",
|
||||||
|
"size": "1x1",
|
||||||
|
"badge": "Gestion",
|
||||||
|
"accentColor": "teal",
|
||||||
|
"openBehavior": "external"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ai",
|
||||||
|
"name": "Intelligence Artificielle",
|
||||||
|
"subtitle": "Modèles LLM auto-hébergés & Assistant Google Gemini intégré",
|
||||||
|
"icon": "Sparkles",
|
||||||
|
"tabs": [
|
||||||
|
{
|
||||||
|
"id": "ai-main",
|
||||||
|
"label": "Assistants IA",
|
||||||
|
"tiles": [
|
||||||
|
{
|
||||||
|
"id": "open-webui",
|
||||||
|
"title": "Open WebUI",
|
||||||
|
"description": "Interface modèles LLM",
|
||||||
|
"url": "https://ai.lafabriquedesgenies.be",
|
||||||
|
"icon": "Bot",
|
||||||
|
"size": "2x1",
|
||||||
|
"badge": "LLM",
|
||||||
|
"accentColor": "emerald",
|
||||||
|
"openBehavior": "external"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "google-gemini",
|
||||||
|
"title": "Google Gemini",
|
||||||
|
"description": "Assistant IA intégré",
|
||||||
|
"url": "#gemini",
|
||||||
|
"icon": "Sparkles",
|
||||||
|
"size": "2x1",
|
||||||
|
"badge": "Omnibox IA",
|
||||||
|
"accentColor": "blue",
|
||||||
|
"openBehavior": "external"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,11 @@ async function readOrInitData(): Promise<DashboardData> {
|
|||||||
await fs.mkdir(DATA_DIR, { recursive: true });
|
await fs.mkdir(DATA_DIR, { recursive: true });
|
||||||
const raw = await fs.readFile(DATA_FILE, "utf-8");
|
const raw = await fs.readFile(DATA_FILE, "utf-8");
|
||||||
const parsed = JSON.parse(raw);
|
const parsed = JSON.parse(raw);
|
||||||
if (parsed && Array.isArray(parsed.categories)) {
|
if (
|
||||||
|
parsed &&
|
||||||
|
Array.isArray(parsed.categories) &&
|
||||||
|
parsed.version === initialDashboardData.version
|
||||||
|
) {
|
||||||
return parsed as DashboardData;
|
return parsed as DashboardData;
|
||||||
}
|
}
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
@@ -46,9 +50,13 @@ export async function POST(request: Request) {
|
|||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const payload: DashboardData = {
|
||||||
|
...body,
|
||||||
|
version: initialDashboardData.version,
|
||||||
|
};
|
||||||
await fs.mkdir(DATA_DIR, { recursive: true });
|
await fs.mkdir(DATA_DIR, { recursive: true });
|
||||||
await fs.writeFile(DATA_FILE, JSON.stringify(body, null, 2), "utf-8");
|
await fs.writeFile(DATA_FILE, JSON.stringify(payload, null, 2), "utf-8");
|
||||||
return NextResponse.json({ ok: true, data: body });
|
return NextResponse.json({ ok: true, data: payload });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "Impossible de sauvegarder data.json", details: String(error) },
|
{ error: "Impossible de sauvegarder data.json", details: String(error) },
|
||||||
|
|||||||
@@ -12,12 +12,16 @@ import {
|
|||||||
Layers,
|
Layers,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
|
Sparkles,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
interface BentoGridProps {
|
interface BentoGridProps {
|
||||||
tiles: ShortcutTile[];
|
tiles: ShortcutTile[];
|
||||||
isEditMode: boolean;
|
isEditMode: boolean;
|
||||||
onLaunchTile: (tile: ShortcutTile, forceMode?: "workspace" | "popup" | "external") => void;
|
onLaunchTile: (
|
||||||
|
tile: ShortcutTile,
|
||||||
|
forceMode?: "workspace" | "popup" | "external"
|
||||||
|
) => void;
|
||||||
onAddTile: () => void;
|
onAddTile: () => void;
|
||||||
onEditTile: (tile: ShortcutTile) => void;
|
onEditTile: (tile: ShortcutTile) => void;
|
||||||
onDeleteTile: (tileId: string) => void;
|
onDeleteTile: (tileId: string) => void;
|
||||||
@@ -91,6 +95,7 @@ const accentStyles: Record<
|
|||||||
};
|
};
|
||||||
|
|
||||||
function formatHostname(url: string) {
|
function formatHostname(url: string) {
|
||||||
|
if (!url || url === "#gemini") return "Assistant intégré";
|
||||||
try {
|
try {
|
||||||
const parsed = new URL(url);
|
const parsed = new URL(url);
|
||||||
return parsed.host;
|
return parsed.host;
|
||||||
@@ -120,19 +125,22 @@ export function BentoGrid({
|
|||||||
|
|
||||||
const style =
|
const style =
|
||||||
accentStyles[tile.accentColor || "indigo"] || accentStyles.indigo;
|
accentStyles[tile.accentColor || "indigo"] || accentStyles.indigo;
|
||||||
const behavior = tile.openBehavior || "workspace";
|
const behavior = tile.openBehavior || "external";
|
||||||
|
const isGeminiWidget = tile.url === "#gemini";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={tile.id}
|
key={tile.id}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isEditMode) {
|
if (isEditMode) {
|
||||||
|
onEditTile(tile);
|
||||||
|
} else {
|
||||||
onLaunchTile(tile);
|
onLaunchTile(tile);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={`group relative flex flex-col justify-between overflow-hidden rounded-2xl border border-white/10 bg-zinc-900/60 p-4 sm:p-5 backdrop-blur-md transition-all duration-200 select-none ${spanClass} ${
|
className={`group relative flex flex-col justify-between overflow-hidden rounded-2xl border border-white/10 bg-zinc-900/60 p-4 sm:p-5 backdrop-blur-md transition-all duration-200 select-none ${spanClass} ${
|
||||||
isEditMode
|
isEditMode
|
||||||
? "border-dashed border-amber-500/40 bg-zinc-900/80"
|
? "cursor-pointer border-dashed border-amber-500/40 bg-zinc-900/80 hover:border-amber-400"
|
||||||
: `cursor-pointer ${style.borderHover} hover:-translate-y-0.5 hover:bg-zinc-900/90 hover:shadow-xl hover:shadow-black/40`
|
: `cursor-pointer ${style.borderHover} hover:-translate-y-0.5 hover:bg-zinc-900/90 hover:shadow-xl hover:shadow-black/40`
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -175,10 +183,11 @@ export function BentoGrid({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onEditTile(tile)}
|
onClick={() => onEditTile(tile)}
|
||||||
title="Modifier la tuile"
|
title="Modifier l'URL ou les détails"
|
||||||
className="rounded p-1 text-amber-300 hover:bg-amber-500/20"
|
className="flex items-center gap-1 rounded bg-amber-500/15 px-1.5 py-1 text-[10px] font-medium text-amber-300 hover:bg-amber-500/25"
|
||||||
>
|
>
|
||||||
<Pencil className="size-3.5" />
|
<Pencil className="size-3" />
|
||||||
|
<span>URL</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -203,8 +212,26 @@ export function BentoGrid({
|
|||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Quick override buttons on hover */}
|
{isGeminiWidget ? (
|
||||||
|
<span className="flex items-center gap-1 rounded-lg border border-blue-500/30 bg-blue-500/15 px-2 py-1 text-[10px] font-medium text-blue-300">
|
||||||
|
<Sparkles className="size-3" />
|
||||||
|
<span>Ouvrir IA</span>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
/* Quick override buttons on hover */
|
||||||
<div className="flex items-center gap-0.5 rounded-lg border border-white/8 bg-zinc-950/70 p-0.5 opacity-80 sm:opacity-0 transition-opacity group-hover:opacity-100">
|
<div className="flex items-center gap-0.5 rounded-lg border border-white/8 bg-zinc-950/70 p-0.5 opacity-80 sm:opacity-0 transition-opacity group-hover:opacity-100">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onLaunchTile(tile, "external")}
|
||||||
|
title="Ouvrir dans un nouvel onglet"
|
||||||
|
className={`rounded p-1 transition-colors ${
|
||||||
|
behavior === "external"
|
||||||
|
? "text-emerald-400 bg-emerald-500/15"
|
||||||
|
: "text-zinc-400 hover:text-white"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<ExternalLink className="size-3" />
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onLaunchTile(tile, "workspace")}
|
onClick={() => onLaunchTile(tile, "workspace")}
|
||||||
@@ -220,7 +247,7 @@ export function BentoGrid({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onLaunchTile(tile, "popup")}
|
onClick={() => onLaunchTile(tile, "popup")}
|
||||||
title="Ouvrir en fenêtre App Chromium (idéal Google Docs/Drive)"
|
title="Ouvrir en fenêtre App dédiée"
|
||||||
className={`rounded p-1 transition-colors ${
|
className={`rounded p-1 transition-colors ${
|
||||||
behavior === "popup"
|
behavior === "popup"
|
||||||
? "text-cyan-400 bg-cyan-500/15"
|
? "text-cyan-400 bg-cyan-500/15"
|
||||||
@@ -229,15 +256,8 @@ export function BentoGrid({
|
|||||||
>
|
>
|
||||||
<AppWindow className="size-3" />
|
<AppWindow className="size-3" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => onLaunchTile(tile, "external")}
|
|
||||||
title="Ouvrir dans un nouvel onglet navigateur"
|
|
||||||
className="rounded p-1 text-zinc-400 hover:text-white"
|
|
||||||
>
|
|
||||||
<ExternalLink className="size-3" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -248,7 +268,7 @@ export function BentoGrid({
|
|||||||
<h3 className="text-base font-semibold tracking-tight text-zinc-100 group-hover:text-white truncate">
|
<h3 className="text-base font-semibold tracking-tight text-zinc-100 group-hover:text-white truncate">
|
||||||
{tile.title}
|
{tile.title}
|
||||||
</h3>
|
</h3>
|
||||||
<span className="font-mono text-[10px] text-zinc-500 truncate max-w-[130px] shrink-0">
|
<span className="font-mono text-[10px] text-zinc-500 truncate max-w-[175px] shrink-0">
|
||||||
{formatHostname(tile.url)}
|
{formatHostname(tile.url)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export function DashboardShell({ initialData }: DashboardShellProps) {
|
|||||||
>({});
|
>({});
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [isEditMode, setIsEditMode] = useState(false);
|
const [isEditMode, setIsEditMode] = useState(false);
|
||||||
|
const [geminiTriggerCount, setGeminiTriggerCount] = useState(0);
|
||||||
|
|
||||||
// OS Window Manager state ("bento" or window.id)
|
// OS Window Manager state ("bento" or window.id)
|
||||||
const [openWindows, setOpenWindows] = useState<OpenWindowTab[]>([]);
|
const [openWindows, setOpenWindows] = useState<OpenWindowTab[]>([]);
|
||||||
@@ -122,7 +123,13 @@ export function DashboardShell({ initialData }: DashboardShellProps) {
|
|||||||
tile: ShortcutTile,
|
tile: ShortcutTile,
|
||||||
forceMode?: "workspace" | "popup" | "external"
|
forceMode?: "workspace" | "popup" | "external"
|
||||||
) => {
|
) => {
|
||||||
const mode = forceMode || tile.openBehavior || "workspace";
|
if (tile.url === "#gemini") {
|
||||||
|
setActiveViewId("bento");
|
||||||
|
setGeminiTriggerCount((c) => c + 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mode = forceMode || tile.openBehavior || "external";
|
||||||
|
|
||||||
if (mode === "external") {
|
if (mode === "external") {
|
||||||
window.open(tile.url, "_blank", "noopener,noreferrer");
|
window.open(tile.url, "_blank", "noopener,noreferrer");
|
||||||
@@ -134,7 +141,7 @@ export function DashboardShell({ initialData }: DashboardShellProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default: "workspace" -> Open or focus an in-OS tab
|
// "workspace" -> Open or focus an in-OS tab
|
||||||
const existing = openWindows.find((w) => w.tileId === tile.id);
|
const existing = openWindows.find((w) => w.tileId === tile.id);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
setActiveViewId(existing.id);
|
setActiveViewId(existing.id);
|
||||||
@@ -398,6 +405,7 @@ export function DashboardShell({ initialData }: DashboardShellProps) {
|
|||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
onSearchChange={setSearchQuery}
|
onSearchChange={setSearchQuery}
|
||||||
onOpenCustomUrl={handleOpenCustomUrl}
|
onOpenCustomUrl={handleOpenCustomUrl}
|
||||||
|
geminiTriggerCount={geminiTriggerCount}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<main className="flex-1 overflow-y-auto px-4 py-5 pb-24 sm:px-6 md:pb-8">
|
<main className="flex-1 overflow-y-auto px-4 py-5 pb-24 sm:px-6 md:pb-8">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -18,6 +18,7 @@ interface HeaderOmniboxProps {
|
|||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
onSearchChange: (q: string) => void;
|
onSearchChange: (q: string) => void;
|
||||||
onOpenCustomUrl: (url: string, title?: string) => void;
|
onOpenCustomUrl: (url: string, title?: string) => void;
|
||||||
|
geminiTriggerCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HeaderOmnibox({
|
export function HeaderOmnibox({
|
||||||
@@ -26,12 +27,23 @@ export function HeaderOmnibox({
|
|||||||
searchQuery,
|
searchQuery,
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
onOpenCustomUrl,
|
onOpenCustomUrl,
|
||||||
|
geminiTriggerCount = 0,
|
||||||
}: HeaderOmniboxProps) {
|
}: HeaderOmniboxProps) {
|
||||||
const [mode, setMode] = useState<"search" | "gemini" | "url">("search");
|
const [mode, setMode] = useState<"search" | "gemini" | "url">("search");
|
||||||
const [geminiPrompt, setGeminiPrompt] = useState("");
|
const [geminiPrompt, setGeminiPrompt] = useState("");
|
||||||
const [geminiLoading, setGeminiLoading] = useState(false);
|
const [geminiLoading, setGeminiLoading] = useState(false);
|
||||||
const [geminiAnswer, setGeminiAnswer] = useState<string | null>(null);
|
const [geminiAnswer, setGeminiAnswer] = useState<string | null>(null);
|
||||||
const [customUrlInput, setCustomUrlInput] = useState("");
|
const [customUrlInput, setCustomUrlInput] = useState("");
|
||||||
|
const geminiInputRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (geminiTriggerCount > 0) {
|
||||||
|
setMode("gemini");
|
||||||
|
setTimeout(() => {
|
||||||
|
geminiInputRef.current?.focus();
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
}, [geminiTriggerCount]);
|
||||||
|
|
||||||
const handleGeminiSubmit = async (e: React.FormEvent) => {
|
const handleGeminiSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -65,7 +77,6 @@ export function HeaderOmnibox({
|
|||||||
if (raw.includes(".") && !raw.includes(" ")) {
|
if (raw.includes(".") && !raw.includes(" ")) {
|
||||||
finalUrl = `https://${raw}`;
|
finalUrl = `https://${raw}`;
|
||||||
} else {
|
} else {
|
||||||
// DuckDuckGo / Wikipedia embed-friendly search or direct URL
|
|
||||||
finalUrl = `https://fr.wikipedia.org/w/index.php?search=${encodeURIComponent(raw)}`;
|
finalUrl = `https://fr.wikipedia.org/w/index.php?search=${encodeURIComponent(raw)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,7 +133,10 @@ export function HeaderOmnibox({
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setMode("gemini")}
|
onClick={() => {
|
||||||
|
setMode("gemini");
|
||||||
|
setTimeout(() => geminiInputRef.current?.focus(), 50);
|
||||||
|
}}
|
||||||
className={`flex items-center gap-1 rounded-lg px-2.5 py-1.5 text-xs font-medium transition-all ${
|
className={`flex items-center gap-1 rounded-lg px-2.5 py-1.5 text-xs font-medium transition-all ${
|
||||||
mode === "gemini"
|
mode === "gemini"
|
||||||
? "bg-gradient-to-r from-indigo-500 to-blue-500 text-white shadow-xs"
|
? "bg-gradient-to-r from-indigo-500 to-blue-500 text-white shadow-xs"
|
||||||
@@ -138,7 +152,7 @@ export function HeaderOmnibox({
|
|||||||
<Input
|
<Input
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => onSearchChange(e.target.value)}
|
onChange={(e) => onSearchChange(e.target.value)}
|
||||||
placeholder="Rechercher Nextcloud, Docs, Drive, Gitea..."
|
placeholder="Rechercher un service (*.lafabriquedesgenies.be)..."
|
||||||
className="h-8 border-0 bg-transparent text-sm text-zinc-100 placeholder:text-zinc-500 focus-visible:ring-0 dark:bg-transparent"
|
className="h-8 border-0 bg-transparent text-sm text-zinc-100 placeholder:text-zinc-500 focus-visible:ring-0 dark:bg-transparent"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -151,7 +165,7 @@ export function HeaderOmnibox({
|
|||||||
<Input
|
<Input
|
||||||
value={customUrlInput}
|
value={customUrlInput}
|
||||||
onChange={(e) => setCustomUrlInput(e.target.value)}
|
onChange={(e) => setCustomUrlInput(e.target.value)}
|
||||||
placeholder="Entrer une URL (ex: cloud.lafabriquedesgenies.be) à ouvrir en onglet..."
|
placeholder="Entrer une URL (ex: nc.lafabriquedesgenies.be) à ouvrir..."
|
||||||
className="h-8 border-0 bg-transparent text-sm text-zinc-100 placeholder:text-zinc-500 focus-visible:ring-0 dark:bg-transparent"
|
className="h-8 border-0 bg-transparent text-sm text-zinc-100 placeholder:text-zinc-500 focus-visible:ring-0 dark:bg-transparent"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@@ -170,9 +184,10 @@ export function HeaderOmnibox({
|
|||||||
className="flex flex-1 items-center"
|
className="flex flex-1 items-center"
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
|
ref={geminiInputRef}
|
||||||
value={geminiPrompt}
|
value={geminiPrompt}
|
||||||
onChange={(e) => setGeminiPrompt(e.target.value)}
|
onChange={(e) => setGeminiPrompt(e.target.value)}
|
||||||
placeholder="Demander à Gemini (résumer un cours, traduire, formule Excel)..."
|
placeholder="Poser une question à Google Gemini, traduire, générer une idée..."
|
||||||
className="h-8 border-0 bg-transparent text-sm text-zinc-100 placeholder:text-zinc-500 focus-visible:ring-0 dark:bg-transparent"
|
className="h-8 border-0 bg-transparent text-sm text-zinc-100 placeholder:text-zinc-500 focus-visible:ring-0 dark:bg-transparent"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -5,12 +5,21 @@ import {
|
|||||||
Wrench,
|
Wrench,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Container,
|
Container,
|
||||||
|
Shield,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
|
Archive,
|
||||||
Activity,
|
Activity,
|
||||||
Cloud,
|
Cloud,
|
||||||
|
Key,
|
||||||
KeyRound,
|
KeyRound,
|
||||||
Radar,
|
Radar,
|
||||||
Film,
|
Film,
|
||||||
|
Headphones,
|
||||||
|
Tv,
|
||||||
|
Video,
|
||||||
|
DownloadCloud,
|
||||||
|
Music,
|
||||||
|
HardDriveDownload,
|
||||||
Image,
|
Image,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Network,
|
Network,
|
||||||
@@ -20,6 +29,8 @@ import {
|
|||||||
Bookmark,
|
Bookmark,
|
||||||
Wallet,
|
Wallet,
|
||||||
FileText,
|
FileText,
|
||||||
|
FileCode,
|
||||||
|
Home,
|
||||||
GitBranch,
|
GitBranch,
|
||||||
Code2,
|
Code2,
|
||||||
BookOpen,
|
BookOpen,
|
||||||
@@ -36,11 +47,13 @@ import {
|
|||||||
Folder,
|
Folder,
|
||||||
Database,
|
Database,
|
||||||
Layers,
|
Layers,
|
||||||
|
LayoutGrid,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
HardDrive,
|
HardDrive,
|
||||||
Calendar,
|
Calendar,
|
||||||
GraduationCap,
|
GraduationCap,
|
||||||
AppWindow,
|
AppWindow,
|
||||||
|
Lock,
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
@@ -51,12 +64,21 @@ export const iconRegistry: Record<string, LucideIcon> = {
|
|||||||
Wrench,
|
Wrench,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Container,
|
Container,
|
||||||
|
Shield,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
|
Archive,
|
||||||
Activity,
|
Activity,
|
||||||
Cloud,
|
Cloud,
|
||||||
|
Key,
|
||||||
KeyRound,
|
KeyRound,
|
||||||
Radar,
|
Radar,
|
||||||
Film,
|
Film,
|
||||||
|
Headphones,
|
||||||
|
Tv,
|
||||||
|
Video,
|
||||||
|
DownloadCloud,
|
||||||
|
Music,
|
||||||
|
HardDriveDownload,
|
||||||
Image,
|
Image,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Network,
|
Network,
|
||||||
@@ -66,6 +88,8 @@ export const iconRegistry: Record<string, LucideIcon> = {
|
|||||||
Bookmark,
|
Bookmark,
|
||||||
Wallet,
|
Wallet,
|
||||||
FileText,
|
FileText,
|
||||||
|
FileCode,
|
||||||
|
Home,
|
||||||
GitBranch,
|
GitBranch,
|
||||||
Code2,
|
Code2,
|
||||||
BookOpen,
|
BookOpen,
|
||||||
@@ -82,11 +106,13 @@ export const iconRegistry: Record<string, LucideIcon> = {
|
|||||||
Folder,
|
Folder,
|
||||||
Database,
|
Database,
|
||||||
Layers,
|
Layers,
|
||||||
|
LayoutGrid,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
HardDrive,
|
HardDrive,
|
||||||
Calendar,
|
Calendar,
|
||||||
GraduationCap,
|
GraduationCap,
|
||||||
AppWindow,
|
AppWindow,
|
||||||
|
Lock,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const availableIconNames = Object.keys(iconRegistry);
|
export const availableIconNames = Object.keys(iconRegistry);
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function TileEditorModal({
|
|||||||
initialTile?.accentColor || "indigo"
|
initialTile?.accentColor || "indigo"
|
||||||
);
|
);
|
||||||
const [openBehavior, setOpenBehavior] = useState<OpenBehavior>(
|
const [openBehavior, setOpenBehavior] = useState<OpenBehavior>(
|
||||||
initialTile?.openBehavior || "workspace"
|
initialTile?.openBehavior || "external"
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
@@ -116,15 +116,16 @@ export function TileEditorModal({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-xs font-medium text-zinc-300">
|
<label className="text-xs font-semibold text-amber-300">
|
||||||
Adresse URL complète
|
Adresse URL complète (Sous-domaine HTTPS)
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
value={url}
|
value={url}
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
onChange={(e) => setUrl(e.target.value)}
|
||||||
placeholder="https://cloud.lafabriquedesgenies.be"
|
placeholder="https://service.lafabriquedesgenies.be"
|
||||||
required
|
required
|
||||||
className="border-white/10 bg-zinc-900 font-mono text-xs text-zinc-100"
|
autoFocus={Boolean(initialTile)}
|
||||||
|
className="border-amber-500/40 bg-zinc-900 font-mono text-xs text-zinc-100 focus-visible:ring-amber-500/30"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+331
-260
@@ -1,295 +1,366 @@
|
|||||||
import { DashboardData } from "@/types/dashboard";
|
import { DashboardData } from "@/types/dashboard";
|
||||||
|
|
||||||
export const initialDashboardData: DashboardData = {
|
export const initialDashboardData: DashboardData = {
|
||||||
version: "2.0.0",
|
version: "3.0.0",
|
||||||
dockTileIds: [
|
dockTileIds: [
|
||||||
"nextcloud-files",
|
"nextcloud",
|
||||||
"nextcloud-office",
|
|
||||||
"google-docs",
|
|
||||||
"google-drive",
|
|
||||||
"gitea-server",
|
|
||||||
"dockge",
|
"dockge",
|
||||||
|
"gitea",
|
||||||
|
"jellyfin",
|
||||||
|
"open-webui",
|
||||||
|
"vaultwarden",
|
||||||
],
|
],
|
||||||
quickNote:
|
quickNote:
|
||||||
"Bienvenue sur Home OS. Utilisez F11 sur CachyOS (Chromium) ou ajoutez l'application à l'écran d'accueil sur votre Galaxy S20 FE.",
|
"Bienvenue sur Home OS (*.lafabriquedesgenies.be). Cliquez sur « Éditer » pour ajuster n'importe quelle URL ou sous-domaine à la volée.",
|
||||||
categories: [
|
categories: [
|
||||||
{
|
{
|
||||||
id: "work",
|
id: "infrastructure",
|
||||||
name: "Bureautique & École",
|
name: "Infrastructure & Serveur",
|
||||||
subtitle: "Nextcloud Office, Google Docs, Drive & Cours",
|
subtitle: "Administration système, conteneurs Docker, sauvegardes & réseau",
|
||||||
icon: "Briefcase",
|
|
||||||
tabs: [
|
|
||||||
{
|
|
||||||
id: "cloud-office",
|
|
||||||
label: "Documents & Suites",
|
|
||||||
tiles: [
|
|
||||||
{
|
|
||||||
id: "nextcloud-files",
|
|
||||||
title: "Nextcloud Hub",
|
|
||||||
description: "Tous mes fichiers, dossiers personnels et synchronisation",
|
|
||||||
url: "https://cloud.lafabriquedesgenies.be",
|
|
||||||
icon: "Cloud",
|
|
||||||
size: "2x1",
|
|
||||||
badge: "Fichiers",
|
|
||||||
accentColor: "sky",
|
|
||||||
openBehavior: "workspace",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "nextcloud-office",
|
|
||||||
title: "Nextcloud Office",
|
|
||||||
description: "Traitement de texte (Word), Tableur (Excel) & Présentations",
|
|
||||||
url: "https://cloud.lafabriquedesgenies.be/apps/richdocuments",
|
|
||||||
icon: "FileSpreadsheet",
|
|
||||||
size: "2x1",
|
|
||||||
badge: "Word / Excel",
|
|
||||||
accentColor: "emerald",
|
|
||||||
openBehavior: "workspace",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "google-docs",
|
|
||||||
title: "Google Docs",
|
|
||||||
description: "Travaux d'école, rapports et rédaction collaborative",
|
|
||||||
url: "https://docs.google.com",
|
|
||||||
icon: "FileText",
|
|
||||||
size: "2x1",
|
|
||||||
badge: "École • Docs",
|
|
||||||
accentColor: "blue",
|
|
||||||
openBehavior: "popup",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "google-drive",
|
|
||||||
title: "Google Drive",
|
|
||||||
description: "Stockage scolaire, cours partagés et Google Sheets",
|
|
||||||
url: "https://drive.google.com",
|
|
||||||
icon: "HardDrive",
|
|
||||||
size: "1x1",
|
|
||||||
badge: "École • Drive",
|
|
||||||
accentColor: "amber",
|
|
||||||
openBehavior: "popup",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "nextcloud-notes",
|
|
||||||
title: "Notes & Aide-mémoire",
|
|
||||||
description: "Prise de notes rapide en Markdown sur Nextcloud",
|
|
||||||
url: "https://cloud.lafabriquedesgenies.be/apps/notes",
|
|
||||||
icon: "NotebookPen",
|
|
||||||
size: "1x1",
|
|
||||||
badge: "Markdown",
|
|
||||||
accentColor: "violet",
|
|
||||||
openBehavior: "workspace",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "dev-git",
|
|
||||||
label: "Code & Projets",
|
|
||||||
tiles: [
|
|
||||||
{
|
|
||||||
id: "gitea-server",
|
|
||||||
title: "Gitea — La Fabrique",
|
|
||||||
description: "Dépôts Git personnels (klemens/home) et CI",
|
|
||||||
url: "https://git.lafabriquedesgenies.be/klemens/home",
|
|
||||||
icon: "GitBranch",
|
|
||||||
size: "2x1",
|
|
||||||
badge: "Git Serveur",
|
|
||||||
accentColor: "emerald",
|
|
||||||
openBehavior: "workspace",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "github",
|
|
||||||
title: "GitHub",
|
|
||||||
description: "Projets open-source et dépôts externes",
|
|
||||||
url: "https://github.com",
|
|
||||||
icon: "Code2",
|
|
||||||
size: "1x1",
|
|
||||||
badge: "Externe",
|
|
||||||
accentColor: "slate",
|
|
||||||
openBehavior: "popup",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "excalidraw",
|
|
||||||
title: "Excalidraw",
|
|
||||||
description: "Schémas d'architecture, croquis et diagrammes de cours",
|
|
||||||
url: "https://excalidraw.com",
|
|
||||||
icon: "PenTool",
|
|
||||||
size: "1x1",
|
|
||||||
badge: "Tableau blanc",
|
|
||||||
accentColor: "indigo",
|
|
||||||
openBehavior: "workspace",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "server",
|
|
||||||
name: "Mon Serveur",
|
|
||||||
subtitle: "Infrastructure Docker (Core i3 / 8 Go RAM) & Réseau",
|
|
||||||
icon: "Server",
|
icon: "Server",
|
||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
id: "containers",
|
id: "infra-main",
|
||||||
label: "Conteneurs & Infra",
|
label: "Services Serveur",
|
||||||
tiles: [
|
tiles: [
|
||||||
{
|
{
|
||||||
id: "dockge",
|
id: "dockge",
|
||||||
title: "Dockge",
|
title: "Dockge",
|
||||||
description: "Gestionnaire de stacks Docker Compose ultra-léger",
|
description: "Gestionnaire Docker Compose",
|
||||||
url: "http://localhost:5001",
|
url: "https://dockge.lafabriquedesgenies.be",
|
||||||
icon: "Container",
|
icon: "Terminal",
|
||||||
size: "2x1",
|
size: "2x1",
|
||||||
badge: "Docker",
|
badge: "Docker",
|
||||||
accentColor: "emerald",
|
accentColor: "emerald",
|
||||||
openBehavior: "workspace",
|
openBehavior: "external",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "nginx-proxy",
|
id: "nginx-proxy-manager",
|
||||||
title: "Nginx Reverse Proxy",
|
title: "Nginx Proxy Manager",
|
||||||
description: "Gestion des domaines *.lafabriquedesgenies.be et SSL",
|
description: "Reverse Proxy & Certificats SSL",
|
||||||
url: "http://localhost:81",
|
url: "https://proxy.lafabriquedesgenies.be",
|
||||||
icon: "ShieldCheck",
|
icon: "Shield",
|
||||||
size: "1x1",
|
size: "1x1",
|
||||||
badge: "Proxy SSL",
|
badge: "Proxy SSL",
|
||||||
accentColor: "blue",
|
accentColor: "blue",
|
||||||
openBehavior: "workspace",
|
openBehavior: "external",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "beszel",
|
id: "duplicati",
|
||||||
title: "Monitoring i3",
|
title: "Duplicati",
|
||||||
description: "Charge CPU, mémoire 8 Go et stockage disque",
|
description: "Sauvegardes du serveur",
|
||||||
url: "http://localhost:8090",
|
url: "https://backup.lafabriquedesgenies.be",
|
||||||
|
icon: "Archive",
|
||||||
|
size: "1x1",
|
||||||
|
badge: "Backup",
|
||||||
|
accentColor: "sky",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "speedtest",
|
||||||
|
title: "Speedtest",
|
||||||
|
description: "Surveillance débit réseau",
|
||||||
|
url: "https://speedtest.lafabriquedesgenies.be",
|
||||||
icon: "Activity",
|
icon: "Activity",
|
||||||
size: "1x1",
|
size: "1x1",
|
||||||
badge: "Système",
|
badge: "Réseau",
|
||||||
|
accentColor: "cyan",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "adminer",
|
||||||
|
title: "Adminer",
|
||||||
|
description: "Gestionnaire bases SQL",
|
||||||
|
url: "https://db.lafabriquedesgenies.be",
|
||||||
|
icon: "Database",
|
||||||
|
size: "1x1",
|
||||||
|
badge: "SQL",
|
||||||
|
accentColor: "amber",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "adguard-home",
|
||||||
|
title: "AdGuard Home",
|
||||||
|
description: "Filtrage DNS & Antipub",
|
||||||
|
url: "https://adguard.lafabriquedesgenies.be",
|
||||||
|
icon: "ShieldCheck",
|
||||||
|
size: "1x1",
|
||||||
|
badge: "DNS",
|
||||||
|
accentColor: "teal",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "watchtower",
|
||||||
|
title: "Watchtower",
|
||||||
|
description: "Mises à jour automatiques",
|
||||||
|
url: "https://dockge.lafabriquedesgenies.be",
|
||||||
|
icon: "RefreshCw",
|
||||||
|
size: "1x1",
|
||||||
|
badge: "Daemon Auto",
|
||||||
accentColor: "violet",
|
accentColor: "violet",
|
||||||
openBehavior: "workspace",
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "private-security",
|
||||||
|
name: "Privé & Sécurité",
|
||||||
|
subtitle: "Cloud personnel, mots de passe, documents, notes & domotique",
|
||||||
|
icon: "ShieldCheck",
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
id: "private-main",
|
||||||
|
label: "Espace Privé",
|
||||||
|
tiles: [
|
||||||
|
{
|
||||||
|
id: "nextcloud",
|
||||||
|
title: "Nextcloud",
|
||||||
|
description: "Fichiers & Drive Cloud",
|
||||||
|
url: "https://nc.lafabriquedesgenies.be",
|
||||||
|
icon: "Cloud",
|
||||||
|
size: "2x1",
|
||||||
|
badge: "Cloud",
|
||||||
|
accentColor: "sky",
|
||||||
|
openBehavior: "external",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "vaultwarden",
|
id: "vaultwarden",
|
||||||
title: "Vaultwarden",
|
title: "Vaultwarden",
|
||||||
description: "Gestionnaire de mots de passe auto-hébergé",
|
description: "Gestionnaire de mots de passe",
|
||||||
url: "http://localhost:8222",
|
url: "https://vault.lafabriquedesgenies.be",
|
||||||
icon: "KeyRound",
|
icon: "Key",
|
||||||
size: "1x1",
|
size: "1x1",
|
||||||
badge: "Sécurité",
|
badge: "Coffre-fort",
|
||||||
accentColor: "amber",
|
accentColor: "amber",
|
||||||
openBehavior: "workspace",
|
openBehavior: "external",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "adguard",
|
id: "paperless-ngx",
|
||||||
title: "AdGuard DNS",
|
title: "Paperless-ngx",
|
||||||
description: "Filtrage publicitaire et résolution DNS locale",
|
description: "GED & Archivage intelligent",
|
||||||
url: "http://localhost:3000",
|
url: "https://paperless.lafabriquedesgenies.be",
|
||||||
icon: "Radar",
|
icon: "FileText",
|
||||||
size: "1x1",
|
size: "1x1",
|
||||||
badge: "DNS",
|
badge: "GED",
|
||||||
accentColor: "teal",
|
accentColor: "emerald",
|
||||||
openBehavior: "workspace",
|
openBehavior: "external",
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "private",
|
id: "wikijs",
|
||||||
name: "Privé",
|
title: "Wiki.js",
|
||||||
subtitle: "Quotidien, Mails, Agenda & Médias",
|
description: "Base de connaissances & notes",
|
||||||
icon: "User",
|
url: "https://wiki.lafabriquedesgenies.be",
|
||||||
tabs: [
|
icon: "BookOpen",
|
||||||
{
|
|
||||||
id: "daily",
|
|
||||||
label: "Quotidien",
|
|
||||||
tiles: [
|
|
||||||
{
|
|
||||||
id: "nextcloud-calendar",
|
|
||||||
title: "Agenda & Tâches",
|
|
||||||
description: "Planning des cours, échéances et rendez-vous",
|
|
||||||
url: "https://cloud.lafabriquedesgenies.be/apps/calendar",
|
|
||||||
icon: "Calendar",
|
|
||||||
size: "2x1",
|
|
||||||
badge: "Planning",
|
|
||||||
accentColor: "sky",
|
|
||||||
openBehavior: "workspace",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "webmail",
|
|
||||||
title: "Messagerie",
|
|
||||||
description: "Boîte mail personnelle et scolaire",
|
|
||||||
url: "https://mail.proton.me",
|
|
||||||
icon: "Mail",
|
|
||||||
size: "1x1",
|
size: "1x1",
|
||||||
badge: "Emails",
|
badge: "Wiki",
|
||||||
accentColor: "violet",
|
accentColor: "indigo",
|
||||||
openBehavior: "popup",
|
openBehavior: "external",
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "freshrss",
|
|
||||||
title: "Lecteur RSS",
|
|
||||||
description: "Veille technologique et actualités sans pub",
|
|
||||||
url: "http://localhost:8085",
|
|
||||||
icon: "Rss",
|
|
||||||
size: "1x1",
|
|
||||||
badge: "Flux",
|
|
||||||
accentColor: "amber",
|
|
||||||
openBehavior: "workspace",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "jellyfin",
|
|
||||||
title: "Médiathèque",
|
|
||||||
description: "Films, séries et musique en streaming local",
|
|
||||||
url: "http://localhost:8096",
|
|
||||||
icon: "Film",
|
|
||||||
size: "2x1",
|
|
||||||
badge: "Média",
|
|
||||||
accentColor: "purple",
|
|
||||||
openBehavior: "workspace",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "tools",
|
|
||||||
name: "Outils",
|
|
||||||
subtitle: "Utilitaires rapides PDF, Réseau & Partage S20 FE <-> CachyOS",
|
|
||||||
icon: "Wrench",
|
|
||||||
tabs: [
|
|
||||||
{
|
|
||||||
id: "utilities",
|
|
||||||
label: "Boîte à outils",
|
|
||||||
tiles: [
|
|
||||||
{
|
|
||||||
id: "pairdrop",
|
|
||||||
title: "PairDrop (CachyOS ↔ S20 FE)",
|
|
||||||
description: "Transfert instantané de fichiers entre le PC et le Galaxy S20 FE",
|
|
||||||
url: "https://pairdrop.net",
|
|
||||||
icon: "Share2",
|
|
||||||
size: "2x1",
|
|
||||||
badge: "AirDrop Local",
|
|
||||||
accentColor: "cyan",
|
|
||||||
openBehavior: "workspace",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "stirling-pdf",
|
id: "stirling-pdf",
|
||||||
title: "Stirling PDF",
|
title: "Stirling PDF",
|
||||||
description: "Convertir, fusionner et signer des PDF pour l'école",
|
description: "Boîte à outils PDF complète",
|
||||||
url: "http://localhost:8083",
|
url: "https://pdf.lafabriquedesgenies.be",
|
||||||
icon: "FileText",
|
icon: "FileCode",
|
||||||
size: "1x1",
|
size: "1x1",
|
||||||
badge: "PDF",
|
badge: "PDF",
|
||||||
accentColor: "rose",
|
accentColor: "rose",
|
||||||
openBehavior: "workspace",
|
openBehavior: "external",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "it-tools",
|
id: "home-assistant",
|
||||||
title: "IT-Tools",
|
title: "Home Assistant",
|
||||||
description: "Convertisseurs, générateurs de tokens, JSON et réseau",
|
description: "Domotique de la maison",
|
||||||
url: "https://it-tools.tech",
|
url: "https://hass.lafabriquedesgenies.be",
|
||||||
icon: "Cpu",
|
icon: "Home",
|
||||||
|
size: "2x1",
|
||||||
|
badge: "Domotique",
|
||||||
|
accentColor: "cyan",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "multimedia",
|
||||||
|
name: "Multimédia & Médias",
|
||||||
|
subtitle: "Streaming vidéo, livres audio, musique & téléchargements",
|
||||||
|
icon: "Film",
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
id: "media-main",
|
||||||
|
label: "Streaming & Téléchargements",
|
||||||
|
tiles: [
|
||||||
|
{
|
||||||
|
id: "jellyfin",
|
||||||
|
title: "Jellyfin",
|
||||||
|
description: "Streaming Films & Séries",
|
||||||
|
url: "https://jellyfin.lafabriquedesgenies.be",
|
||||||
|
icon: "Film",
|
||||||
|
size: "2x1",
|
||||||
|
badge: "Streaming",
|
||||||
|
accentColor: "purple",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "audiobookshelf",
|
||||||
|
title: "Audiobookshelf",
|
||||||
|
description: "Livres audio & Podcasts",
|
||||||
|
url: "https://audiobookshelf.lafabriquedesgenies.be",
|
||||||
|
icon: "Headphones",
|
||||||
size: "1x1",
|
size: "1x1",
|
||||||
badge: "DevTools",
|
badge: "Audio",
|
||||||
|
accentColor: "amber",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "sonarr",
|
||||||
|
title: "Sonarr",
|
||||||
|
description: "Suivi & gestion des séries",
|
||||||
|
url: "https://sonarr.lafabriquedesgenies.be",
|
||||||
|
icon: "Tv",
|
||||||
|
size: "1x1",
|
||||||
|
badge: "Séries",
|
||||||
|
accentColor: "sky",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "radarr",
|
||||||
|
title: "Radarr",
|
||||||
|
description: "Suivi & gestion des films",
|
||||||
|
url: "https://radarr.lafabriquedesgenies.be",
|
||||||
|
icon: "Video",
|
||||||
|
size: "1x1",
|
||||||
|
badge: "Films",
|
||||||
|
accentColor: "amber",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "qbittorrent",
|
||||||
|
title: "qBittorrent",
|
||||||
|
description: "Téléchargements VPN (Gluetun)",
|
||||||
|
url: "https://torrent.lafabriquedesgenies.be",
|
||||||
|
icon: "DownloadCloud",
|
||||||
|
size: "1x1",
|
||||||
|
badge: "VPN Gluetun",
|
||||||
|
accentColor: "blue",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "deemix",
|
||||||
|
title: "Deemix",
|
||||||
|
description: "Téléchargement Musique",
|
||||||
|
url: "https://deemix.lafabriquedesgenies.be",
|
||||||
|
icon: "Music",
|
||||||
|
size: "1x1",
|
||||||
|
badge: "Musique",
|
||||||
|
accentColor: "pink",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "sabnzbd",
|
||||||
|
title: "SABnzbd",
|
||||||
|
description: "Client Usenet",
|
||||||
|
url: "https://sabnzbd.lafabriquedesgenies.be",
|
||||||
|
icon: "HardDriveDownload",
|
||||||
|
size: "1x1",
|
||||||
|
badge: "Usenet",
|
||||||
|
accentColor: "emerald",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "projects-work",
|
||||||
|
name: "Projets & Travail",
|
||||||
|
subtitle: "Forge Git, applications La Fabrique des Génies & outils métiers",
|
||||||
|
icon: "Briefcase",
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
id: "work-main",
|
||||||
|
label: "Applications & Projets",
|
||||||
|
tiles: [
|
||||||
|
{
|
||||||
|
id: "gitea",
|
||||||
|
title: "Gitea",
|
||||||
|
description: "Dépôts de code Git",
|
||||||
|
url: "https://git.lafabriquedesgenies.be",
|
||||||
|
icon: "GitBranch",
|
||||||
|
size: "2x1",
|
||||||
|
badge: "Git",
|
||||||
|
accentColor: "emerald",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "wordpress-lafabrique",
|
||||||
|
title: "WordPress La Fabrique",
|
||||||
|
description: "Site officiel La Fabrique",
|
||||||
|
url: "https://lafabriquedesgenies.be",
|
||||||
|
icon: "Globe",
|
||||||
|
size: "1x1",
|
||||||
|
badge: "Site Web",
|
||||||
|
accentColor: "blue",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "teacher-app",
|
||||||
|
title: "Teacher App",
|
||||||
|
description: "Plateforme pédagogique",
|
||||||
|
url: "https://teacher.lafabriquedesgenies.be",
|
||||||
|
icon: "GraduationCap",
|
||||||
|
size: "1x1",
|
||||||
|
badge: "Pédagogie",
|
||||||
|
accentColor: "indigo",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "creaprof",
|
||||||
|
title: "CreaProf",
|
||||||
|
description: "Outil création enseignant",
|
||||||
|
url: "https://creaprof.lafabriquedesgenies.be",
|
||||||
|
icon: "PenTool",
|
||||||
|
size: "1x1",
|
||||||
|
badge: "Création",
|
||||||
|
accentColor: "violet",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "ubicraft",
|
||||||
|
title: "Ubicraft",
|
||||||
|
description: "Application métier",
|
||||||
|
url: "https://ubicraft.lafabriquedesgenies.be",
|
||||||
|
icon: "LayoutGrid",
|
||||||
|
size: "1x1",
|
||||||
|
badge: "Métier",
|
||||||
|
accentColor: "cyan",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "lma-v2",
|
||||||
|
title: "LMA V2",
|
||||||
|
description: "Plateforme Last Minute Activities",
|
||||||
|
url: "https://lma.lafabriquedesgenies.be",
|
||||||
|
icon: "Briefcase",
|
||||||
|
size: "1x1",
|
||||||
|
badge: "LMA",
|
||||||
|
accentColor: "amber",
|
||||||
|
openBehavior: "external",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "coo-app",
|
||||||
|
title: "Coo App",
|
||||||
|
description: "Application de gestion",
|
||||||
|
url: "https://coo.lafabriquedesgenies.be",
|
||||||
|
icon: "Layers",
|
||||||
|
size: "1x1",
|
||||||
|
badge: "Gestion",
|
||||||
accentColor: "teal",
|
accentColor: "teal",
|
||||||
openBehavior: "workspace",
|
openBehavior: "external",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -297,35 +368,35 @@ export const initialDashboardData: DashboardData = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "ai",
|
id: "ai",
|
||||||
name: "IA",
|
name: "Intelligence Artificielle",
|
||||||
subtitle: "Assistant Gemini intégré & Outils de recherche",
|
subtitle: "Modèles LLM auto-hébergés & Assistant Google Gemini intégré",
|
||||||
icon: "Sparkles",
|
icon: "Sparkles",
|
||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
id: "assistants",
|
id: "ai-main",
|
||||||
label: "Studios IA",
|
label: "Assistants IA",
|
||||||
tiles: [
|
tiles: [
|
||||||
{
|
{
|
||||||
id: "aistudio",
|
id: "open-webui",
|
||||||
title: "Google AI Studio",
|
title: "Open WebUI",
|
||||||
description: "Tests avancés Gemini et gestion de la clé API",
|
description: "Interface modèles LLM",
|
||||||
url: "https://aistudio.google.com",
|
url: "https://ai.lafabriquedesgenies.be",
|
||||||
icon: "Sparkles",
|
icon: "Bot",
|
||||||
size: "2x1",
|
size: "2x1",
|
||||||
badge: "Gemini",
|
badge: "LLM",
|
||||||
accentColor: "blue",
|
accentColor: "emerald",
|
||||||
openBehavior: "popup",
|
openBehavior: "external",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "wikipedia",
|
id: "google-gemini",
|
||||||
title: "Wikipédia FR",
|
title: "Google Gemini",
|
||||||
description: "Encyclopédie libre consultable directement dans un onglet Home",
|
description: "Assistant IA intégré",
|
||||||
url: "https://fr.wikipedia.org",
|
url: "#gemini",
|
||||||
icon: "BookOpen",
|
icon: "Sparkles",
|
||||||
size: "2x1",
|
size: "2x1",
|
||||||
badge: "Encyclopédie",
|
badge: "Omnibox IA",
|
||||||
accentColor: "slate",
|
accentColor: "blue",
|
||||||
openBehavior: "workspace",
|
openBehavior: "external",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user