Date: 24-01-2026 20:15 Status: En cours de definition Version: 1.0
On refait le web, mais personnalise pour chaque utilisateur.
┌─────────────────────────────────────────────────────────────────┐
│ │
│ AVANT (Web classique) │
│ ───────────────────── │
│ Developpeur cree UI fixe → Tous les users s'adaptent │
│ GitHub.com = meme interface pour tout le monde │
│ Tu veux autrement ? Dommage. │
│ │
│ APRES (QIG) │
│ ─────────── │
│ APIs existent → IA genere UI personnalisee pour TOI │
│ "Mes repos GitHub" → tableau, cards, liste, comme TU veux │
│ Chaque user a SON web. │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ PROMPT │ │ IA │ │ CONNECTORS │ │ AFFICHAGE │
│ intelligent │ │ cloud/local │ │ hub │ │ (QIG) │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │ │
│ "Montre mes │ Comprend │ Connait les │ Genere
│ projets en │ l'intention │ APIs, auth, │ l'UI
│ cards" │ + contexte │ schemas │ parfaite
│ │ │ │
└────────────────┴────────────────┴────────────────┘
│
▼
Interface UNIQUE pour TOI
| User dit | Resultat |
|---|---|
| "Mes projets GitLab" | Table triable avec stats |
| "Mes projets GitLab en cards" | Grille visuelle |
| "Projets GitLab actifs cette semaine" | Liste filtree + graphe |
| "Compare mes 2 derniers projets" | Vue comparative |
Meme API, infinite UIs.
┌─────────────────────────────────────────────────────────────────┐
│ MON DASHBOARD │
│ "Cree un dashboard avec mes projets, mes mails et la meteo" │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ GITLAB PROJETS │ │ MAILJET INBOX │ │
│ │ ┌───┬───┬───┐ │ │ 5 nouveaux mails │ │
│ │ │ A │ B │ C │ │ │ - Client X: ... │ │
│ │ └───┴───┴───┘ │ │ - Equipe: ... │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ METEO BORDEAUX │ │ MES DOMAINES │ │
│ │ ☀️ 18°C │ │ o2switch: 12 │ │
│ │ Ensoleille │ │ actifs: 12 ✓ │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Chaque widget = 1 connector + 1 display
Dashboard = composition de widgets
Tout genere par IA selon TA demande
| Type | Usage | Exemple |
|---|---|---|
| Single | Une entite, une vue | "Mes projets GitLab" |
| Detail | Un item en profondeur | "Projet X avec issues" |
| Compare | Plusieurs items cote a cote | "Compare projet A et B" |
| Dashboard | Multi-sources, widgets | "Mon espace de travail" |
| Timeline | Flux chronologique | "Activite de la semaine" |
| Report | Synthese avec graphes | "Stats du mois" |
L'utilisateur dit ce qu'il veut en langage naturel, le systeme:
┌─────────────────────────────────────────────────────────────────────────┐
│ CONNECTORS-HUB │
│ (Le cerveau qui CONNAIT) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Auth │ │ Schemas │ │ Cache │ │ Logs │ │
│ │ (tokens) │ │ (metadata) │ │ (data) │ │ (Loki) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ Endpoints: │
│ • GET /api/connectors Liste des connecteurs │
│ • GET /api/connectors/:id/schema Schema complet de l'API │
│ • GET /api/connectors/:id/endpoints Endpoints disponibles │
│ • POST /api/fetch Proxy universel │
│ • POST /api/discover/:id Decouverte auto du schema │
│ │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ QIG │
│ (Le generateur d'interfaces) │
│ │
│ Input naturel → Ollama (intention) → Connectors (schema) → Render │
│ │
│ Modes: │
│ • FORM : create/update → champs a remplir │
│ • DISPLAY : read/list → table/cards/liste │
│ • CONFIRM : delete → confirmation + action │
│ • DETAIL : single item → vue complete avec liens │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Chaque connecteur stocke son schema - pas devine, CONNU.
-- Table principale des connecteurs
CREATE TABLE connectors.config (
id UUID PRIMARY KEY,
name TEXT UNIQUE, -- "gitlab", "o2switch", "github"
display_name TEXT, -- "GitLab"
base_url TEXT, -- "https://gitlab.com/api/v4"
auth_type TEXT, -- oauth2, bearer, basic, apikey, custom
auth_config JSONB, -- Config specifique auth
-- SCHEMA API (le coeur)
schema_version TEXT, -- "v1.2.0"
schema_source TEXT, -- "openapi", "discovered", "manual"
schema_url TEXT, -- URL OpenAPI si dispo
schema_spec JSONB, -- Spec complete cachee
schema_updated_at TIMESTAMPTZ, -- Derniere sync
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ
);
-- Endpoints indexes (pour recherche rapide)
CREATE TABLE connectors.endpoints (
id UUID PRIMARY KEY,
connector_id UUID REFERENCES connectors.config(id),
path TEXT, -- "/projects"
method TEXT, -- "GET", "POST"
operation_id TEXT, -- "listProjects"
summary TEXT, -- "List all projects"
-- Schema des donnees
request_schema JSONB, -- Body attendu (POST/PUT)
response_schema JSONB, -- Structure reponse
parameters JSONB, -- Query/path params
-- Relations decouvertes
relations JSONB, -- Liens vers autres endpoints
-- Metadata
requires_auth BOOLEAN,
tags TEXT[],
discovered_at TIMESTAMPTZ
);
Le probleme: Toutes les APIs n'ont pas de Swagger/OpenAPI.
La solution: Decouverte automatique par analyse des reponses.
┌─────────────────────────────────────────────────────────────────┐
│ DISCOVERY LOOP │
│ │
│ 1. Appel initial (ex: GET /projects) │
│ ↓ │
│ 2. Analyse de la reponse │
│ • Types des champs (string, number, date, array, object) │
│ • Champs requis vs optionnels │
│ • Patterns (email, url, id) │
│ ↓ │
│ 3. Detection des LIENS │
│ • "members_url": "/projects/123/members" → relation │
│ • "owner": { "id": 456 } → lien vers /users/456 │
│ • "_links": { "issues": "..." } → HATEOAS │
│ ↓ │
│ 4. Exploration recursive (si autorise) │
│ • Suivre les liens decouverts │
│ • Enrichir le schema │
│ • Limiter la profondeur (max 3 niveaux) │
│ ↓ │
│ 5. Stockage du schema enrichi │
│ • Versionne │
│ • Avec relations │
│ • Reutilisable │
│ │
└─────────────────────────────────────────────────────────────────┘
Exemple concret - GitLab Projects:
// Reponse GET /projects/123
{
"id": 123,
"name": "mon-projet",
"description": "...",
"visibility": "private",
"owner": {
"id": 456,
"username": "gouroubleu"
},
"namespace": {
"id": 789,
"name": "gouroubleu"
},
"_links": {
"self": "/api/v4/projects/123",
"issues": "/api/v4/projects/123/issues",
"merge_requests": "/api/v4/projects/123/merge_requests",
"members": "/api/v4/projects/123/members"
}
}
// Schema DECOUVERT automatiquement:
{
"entity": "project",
"fields": {
"id": { "type": "integer", "readonly": true },
"name": { "type": "string", "required": true },
"description": { "type": "string" },
"visibility": { "type": "enum", "values": ["private", "internal", "public"] }
},
"relations": {
"owner": { "entity": "user", "path": "/users/{owner.id}" },
"namespace": { "entity": "namespace", "path": "/namespaces/{namespace.id}" },
"issues": { "entity": "issue", "path": "/projects/{id}/issues", "type": "many" },
"merge_requests": { "entity": "merge_request", "path": "/projects/{id}/merge_requests", "type": "many" },
"members": { "entity": "member", "path": "/projects/{id}/members", "type": "many" }
}
}
CREATE TABLE connectors.cache (
id UUID PRIMARY KEY,
connector_id UUID REFERENCES connectors.config(id),
user_id UUID, -- Null = cache global
cache_key TEXT, -- "GET:/projects:page=1"
cache_data JSONB, -- Donnees
ttl_seconds INTEGER, -- Duree de vie
created_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ,
-- Stats
hit_count INTEGER DEFAULT 0,
last_hit_at TIMESTAMPTZ
);
-- Index pour lookup rapide
CREATE INDEX idx_cache_lookup ON connectors.cache(connector_id, cache_key, user_id);
CREATE INDEX idx_cache_expiry ON connectors.cache(expires_at);
Strategies de cache: | Type | TTL | Usage | |------|-----|-------| | Schema API | 24h | Metadata des endpoints | | Liste | 5min | GET /projects, GET /domains | | Detail | 1min | GET /projects/123 | | User data | 30s | Donnees sensibles |
Chaque action loguee pour tracabilite:
interface ConnectorLog {
timestamp: string;
level: "info" | "warn" | "error";
// Context
connector: string;
user_id?: string;
request_id: string;
// Action
action: "fetch" | "discover" | "cache_hit" | "cache_miss" | "auth_refresh";
method: string;
path: string;
// Result
status: number;
duration_ms: number;
cached: boolean;
// Errors
error?: string;
stack?: string;
}
┌─────────────────────────────────────────────────────────────────────────┐
│ QIG FLOW │
│ │
│ "Liste mes domaines O2switch" │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ STEP 1: Intent Analysis (Ollama) │ │
│ │ │ │
│ │ Input: "Liste mes domaines O2switch" │ │
│ │ Output: { │ │
│ │ "action": "read", │ │
│ │ "connector": "o2switch", │ │
│ │ "entity": "domains" │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ STEP 2: Schema Lookup (Connectors-Hub) │ │
│ │ │ │
│ │ GET /api/connectors/o2switch/endpoints?entity=domains │ │
│ │ Response: { │ │
│ │ "list": { "method": "GET", "path": "/domains" }, │ │
│ │ "create": { "method": "POST", "path": "/domains" }, │ │
│ │ "fields": [ │ │
│ │ { "name": "domain", "type": "string" }, │ │
│ │ { "name": "status", "type": "enum" }, │ │
│ │ { "name": "documentroot", "type": "string" } │ │
│ │ ] │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ STEP 3: Data Fetch (si action=read) │ │
│ │ │ │
│ │ POST /api/fetch { connector: "o2switch", path: "/domains" } │ │
│ │ Response: [ │ │
│ │ { "domain": "nowhere84.com", "status": "active", ... }, │ │
│ │ { "domain": "33800.nowhere84.com", "status": "active" } │ │
│ │ ] │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ STEP 4: Display Generation (Ollama + Schema) │ │
│ │ │ │
│ │ Ollama recoit: │ │
│ │ - Schema des champs (types, labels) │ │
│ │ - Echantillon de donnees │ │
│ │ - Langue utilisateur │ │
│ │ │ │
│ │ Genere DisplaySpec: │ │
│ │ { │ │
│ │ "type": "table", │ │
│ │ "title": "Mes domaines O2switch", │ │
│ │ "columns": [ │ │
│ │ { "id": "domain", "label": "Domaine", "type": "link" }, │ │
│ │ { "id": "status", "label": "Statut", "type": "badge" } │ │
│ │ ] │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ STEP 5: Render (Qwik) │ │
│ │ │ │
│ │ DisplaySpec → TableDisplay component │ │
│ │ Micro-bundles, resumability, 0kb JS initial │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
UISpec (pour forms):
interface UISpec {
title: string;
description?: string;
groups: FieldGroup[];
submit: {
connector: string;
method: "POST" | "PUT" | "PATCH";
path: string;
label: string;
};
}
DisplaySpec (pour affichage):
interface DisplaySpec {
title: string;
type: "table" | "cards" | "list" | "detail";
// Source des donnees
source: {
connector: string;
method: "GET";
path: string;
};
// Pour table
columns?: Column[];
sortable?: boolean;
filterable?: boolean;
// Pour cards/list
template?: ItemTemplate;
// Relations decouvertes (liens cliquables)
relations?: Relation[];
// Actions par item
actions?: Action[];
}
interface Relation {
field: string; // "owner.id"
label: string; // "Voir le proprietaire"
entity: string; // "user"
path: string; // "/users/{value}"
}
DashboardSpec (pour dashboards multi-sources):
interface DashboardSpec {
title: string;
description?: string;
layout: "grid" | "columns" | "rows" | "freeform";
// Widgets
widgets: Widget[];
// Refresh global
refresh_interval?: number; // secondes, 0 = manuel
}
interface Widget {
id: string;
title: string;
// Position dans le layout
position: {
x: number; // colonne (grid)
y: number; // ligne
w: number; // largeur (1-12)
h: number; // hauteur
};
// Contenu = un DisplaySpec
display: DisplaySpec;
// Options widget
collapsible?: boolean;
refreshable?: boolean;
}
Quand l'utilisateur clique sur un lien decouvert:
Affichage projet GitLab
│
│ [Clic sur "owner: gouroubleu"]
▼
Nouvelle intention automatique:
"Voir l'utilisateur 456 sur GitLab"
│
▼
Fetch schema user + data
│
▼
Genere DisplaySpec pour user
│
▼
Affiche detail utilisateur
│
│ [Clic sur "projects"]
▼
Liste des projets de cet utilisateur
...
Navigation contextuelle - l'UI se construit en suivant les liens.
| Donnee | Stockage | TTL | Partage |
|---|---|---|---|
| Schema API | connectors.config | 24h | Tous users |
| Endpoints | connectors.endpoints | 24h | Tous users |
| Relations | connectors.endpoints | 24h | Tous users |
| Version | connectors.config | - | Tous users |
| Donnee | Stockage | TTL | Partage |
|---|---|---|---|
| Tokens auth | connectors.tokens | Variable | User seul |
| Cache perso | connectors.cache | 5min | User seul |
| Historique | qig.history | 30j | User seul |
USER: "Liste mes domaines O2switch"
1. INTENT
→ action: read, connector: o2switch, entity: domains
2. SCHEMA (depuis cache ou discover)
→ GET /api/connectors/o2switch/endpoints
→ fields: domain, status, documentroot, addon, ...
3. FETCH
→ POST /api/fetch { connector: o2switch, method: GET, path: /domains }
→ [{ domain: "nowhere84.com", status: "active", ... }, ...]
4. DISPLAY GENERATION (Ollama + schema + data sample)
→ DisplaySpec: table avec colonnes domain, status, actions
5. RENDER
→ Qwik TableDisplay avec donnees
USER: [Clic sur "nowhere84.com"]
6. DETAIL GENERATION
→ Fetch GET /domains/nowhere84.com
→ Relations decouvertes: subdomains, emails, databases
→ DisplaySpec: detail avec liens vers sous-entites
USER: [Clic sur "Voir les sous-domaines"]
7. LOOP CONTINUE
→ Fetch GET /domains/nowhere84.com/subdomains
→ Affiche liste sous-domaines
→ etc.
Sauvegarder et partager des affichages (sans les donnees).
CREATE TABLE qig.display_templates (
id UUID PRIMARY KEY,
user_id UUID, -- Createur
-- Identifiant
name TEXT, -- "Mes projets GitLab"
slug TEXT UNIQUE, -- "gitlab-projects-grid"
-- Contenu
connector TEXT, -- "gitlab"
entity TEXT, -- "projects"
display_spec JSONB, -- Le DisplaySpec complet
-- Partage
visibility TEXT, -- "private", "public", "unlisted"
shared_url TEXT, -- qig.../t/gitlab-projects-grid
-- Stats
use_count INTEGER DEFAULT 0,
fork_count INTEGER DEFAULT 0,
-- Versioning
version INTEGER DEFAULT 1,
parent_id UUID, -- Si fork d'un autre template
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ
);
┌─────────────────────────────────────────────────────────────────┐
│ SAVED DISPLAYS │
│ │
│ SAUVEGARDER │
│ • User genere un affichage qu'il aime │
│ • Clic "Sauvegarder" → stocke le DisplaySpec │
│ • Reutilisable: "Afficher mes projets" → template sauve │
│ │
│ PARTAGER │
│ • Genere URL: qig.33800.nowhere84.com/t/gitlab-projects-grid │
│ • Autre user ouvre → voit le TEMPLATE (pas les donnees) │
│ • Peut l'utiliser avec SES propres credentials │
│ • "Fork" pour personnaliser │
│ │
│ GALERIE │
│ • Templates publics visibles par tous │
│ • Tri par popularite, connector, entite │
│ • "Les meilleurs affichages GitLab" │
│ │
└─────────────────────────────────────────────────────────────────┘
// Template sauvegarde: "Projets GitLab en Cards"
{
"id": "abc-123",
"name": "Projets GitLab en Cards",
"slug": "gitlab-projects-cards",
"connector": "gitlab",
"entity": "projects",
"visibility": "public",
"display_spec": {
"type": "cards",
"title": "{{count}} Projets",
"template": {
"title": "{{name}}",
"subtitle": "{{namespace.name}}",
"badge": "{{visibility}}",
"image": "{{avatar_url}}"
},
"actions": ["view", "edit", "delete"]
}
}
// User B ouvre /t/gitlab-projects-cards
// → Voit le template
// → Se connecte a SON GitLab
// → Affiche SES projets avec CE template
Creer de nouveaux connecteurs en live via recherche web ou lien direct.
┌─────────────────────────────────────────────────────────────────┐
│ CONNECTOR DISCOVERY MODE │
│ │
│ OPTION 1: Via lien │
│ ──────────────────── │
│ User: "Ajouter l'API SNCF: https://www.sncf.com/api" │
│ │ │
│ ▼ │
│ 1. Fetch la page / recherche OpenAPI spec │
│ 2. Si trouve spec → parse automatiquement │
│ 3. Si pas de spec → analyse la doc HTML │
│ 4. Ollama extrait: base_url, auth_type, endpoints principaux │
│ 5. Propose le connector a valider │
│ │ │
│ ▼ │
│ Nouveau connector "sncf" cree et disponible │
│ │
│ OPTION 2: Via recherche web │
│ ──────────────────────────── │
│ User: "Trouver une API pour les horaires de train" │
│ │ │
│ ▼ │
│ 1. Web search "train schedule API" / "SNCF API" │
│ 2. Ollama analyse les resultats │
│ 3. Propose: "J'ai trouve l'API SNCF, Trainline, DB..." │
│ 4. User choisit │
│ 5. Discovery du connector choisi │
│ │
│ OPTION 3: Via exemple de requete │
│ ───────────────────────────────── │
│ User: "curl https://api.sncf.com/v1/stations -H 'Auth: xxx'" │
│ │ │
│ ▼ │
│ 1. Parse la commande curl │
│ 2. Extrait: base_url, path, headers, auth │
│ 3. Execute pour decouvrir la structure reponse │
│ 4. Cree le connector avec schema │
│ │
└─────────────────────────────────────────────────────────────────┘
-- Nouveau connector cree par discovery
INSERT INTO connectors.config (
name, -- "sncf"
display_name, -- "SNCF (Trains France)"
base_url, -- "https://api.sncf.com/v1"
auth_type, -- "apikey"
auth_config, -- { "header": "Authorization", "prefix": "Basic" }
schema_source, -- "discovered"
schema_spec, -- { endpoints decouverts }
-- Metadata creation
created_by, -- user_id du createur
discovery_source, -- "url" | "search" | "curl" | "manual"
discovery_url, -- URL source utilisee
-- Validation
is_verified, -- false (pas encore valide par admin)
is_public -- false (visible que par createur au debut)
);
Connector cree (non verifie)
│
▼
Utilisable par createur
│
├─── Marche bien ? → Proposer en public
│
▼
Review par admins/communaute
│
├─── Valide → is_verified = true, is_public = true
│
▼
Disponible pour tous
┌─────────────────────────────────────────────────────────────────┐
│ CONNECTOR MARKETPLACE │
│ │
│ OFFICIELS (pre-configures) │
│ • GitLab, GitHub, O2switch, Mailjet, etc. │
│ • Maintenus par l'equipe │
│ • Schemas a jour │
│ │
│ COMMUNAUTAIRES (crees par users) │
│ • SNCF, Trainline, Meteo France, etc. │
│ • Crees via discovery │
│ • Votes/reviews par la communaute │
│ • Les meilleurs deviennent "verified" │
│ │
│ PRIVES │
│ • APIs internes d'entreprise │
│ • Visibles que par le createur/equipe │
│ │
└─────────────────────────────────────────────────────────────────┘
| Phase | Description | Priorite |
|---|---|---|
| 1 | Connectors: Schema storage + discovery loop | HAUTE |
| 2 | Connectors: Cache + Logs Loki | HAUTE |
| 3 | QIG: Display mode (table, cards, list, detail) | HAUTE |
| 4 | QIG: Dashboards multi-widgets | HAUTE |
| 5 | QIG: Saved displays/dashboards + partage | MOYENNE |
| 6 | Connectors: Discovery mode (creer connectors live) | MOYENNE |
| 7 | Marketplace: Connectors + templates communautaires | BASSE |