phase58 doctrine 200 WEVIA Gemini Auto-Apply handler v1 - CSS generation E2E partial

Handler deploye:
- /var/www/html/api/wevia-gemini-ux-apply.sh (5.4KB)
- Pipeline 4 etapes: Playwright shot -> Gemini generate CSS patch -> parser JSON -> optional apply
- Modes: review_only (default, safe) + apply (backup GOLD + injection)
- Safety: safe_to_apply flag Gemini required + DOCTRINE-200 marker idempotent

Test E2E leadforge:
- before.png 58KB Playwright OK
- gemini-raw.json 1.2KB Gemini REPOND avec CSS patch premium
- Preview extrait: --wtp-color-primary #FF5C6E --wtp-color-secondary #00C896
  --wtp-color-background #1A1A2E --wtp-gradient-hero linear-gradient(135deg...)
- Gemini detecte couleurs existantes Voir-tarifs Creer-compte et propose tokens coherents

Gaps identifies a traiter phase suivante:
- maxOutputTokens 6000 insuffisant (Gemini tronque a 237 tokens CSS = totalTokenCount 6528)
- Parser Python heredoc inline echoue silencieusement parse.log vide
- Besoin: Gemini Flash 2.5 max 8192 ou decouper en multi-call CSS chunks
- Besoin: parser separe dans fichier .py dedie pas inline bash heredoc

Ce qui MARCHE:
- E2E complet Playwright + Gemini vision analysis
- Gemini genere du VRAI CSS premium avec design tokens coherents
- Architecture modes review_only/apply + GOLD backup pattern

Phase prochaine: refiner parser + increase tokens + validate full CSS generation
Puis WEVIA pourra appliquer auto UX Gemini premium via chat NL.

Cumul:
- 56 tags Opus
- 39 doctrines (146-200)
- NR 153/153 invariant 58 phases

Pattern approuve reusable via intent refactor phase 59.
This commit is contained in:
Opus
2026-04-24 17:11:29 +02:00
parent bda0d8ee93
commit a6bae29b89
3 changed files with 171 additions and 0 deletions

137
api/wevia-gemini-ux-apply.sh Executable file
View File

@@ -0,0 +1,137 @@
#!/bin/bash
# Doctrine 200: WEVIA Gemini UX Auto-Apply premium patch
# Pipeline: screenshot -> Gemini review -> Gemini generate premium HTML/CSS patch -> backup GOLD -> inject -> verify no regression
set -u
PAGE="${1:-weval-technology-platform}"
PAGE="${PAGE%.html}"
MODE="${2:-review_only}" # review_only | apply
TARGET=""
if [ -f "/var/www/html/${PAGE}.html" ]; then
TARGET="/var/www/html/${PAGE}.html"
elif [ -f "/var/www/html/products/${PAGE}.html" ]; then
TARGET="/var/www/html/products/${PAGE}.html"
else
echo "{\"ok\":false,\"err\":\"page_not_found\",\"page\":\"$PAGE\"}"
exit 1
fi
KG=$(grep "^GEMINI_KEY=" /etc/weval/secrets.env 2>/dev/null | cut -d= -f2- | tr -d '"' | head -c 80)
[ -z "$KG" ] && { echo '{"ok":false,"err":"no_gemini_key"}'; exit 1; }
TS=$(date +%Y%m%d-%H%M%S)
OUT="/var/www/html/proofs/wevia-gemini-ux-apply-$TS"
mkdir -p "$OUT"
# 1) Screenshot
URL="https://weval-consulting.com/${TARGET#/var/www/html/}"
timeout 45 bash -c "cd /var/www/html/api && node wgux-shot.js \"$URL\" \"$OUT/before.png\"" > /tmp/wguxA-shot.log 2>&1
[ ! -f "$OUT/before.png" ] && { echo "{\"ok\":false,\"err\":\"playwright_fail\"}"; exit 1; }
# 2) Gemini: generate a standalone premium UX patch (CSS+HTML snippet injectable)
base64 -w0 < "$OUT/before.png" > "$OUT/before.b64"
python3 > "$OUT/payload.json" << PYEOF
import json
with open('$OUT/before.b64') as f: b64=f.read().strip()[:400000]
prompt = '''Tu es agent UX premium WEVAL Technology Platform.
Analyse cette page web (screenshot).
Genere un PATCH HTML+CSS premium INJECTABLE avant </head> pour ameliorer drastiquement UX de cette page SANS la reecrire.
Le patch doit ajouter:
- CSS avec design tokens premium (--wtp-gradient, --wtp-card-premium, --wtp-kpi, etc.)
- Classes .wtp-hero-premium, .wtp-kpi-card, .wtp-status-badge, .wtp-action-btn
- Sparkline SVG inline CSS
- Status LED animations (live/degraded/offline)
- Hover states premium avec transform/shadow
- Responsive mobile media query
- ZERO chauvauchement top-right bot-right
- Ne touche aux selecteurs existants QUE pour les ameliorer, pas les casser
JSON strict sans markdown, sans backticks:
{
"css_patch": "<style id=\\\"wtp-doctrine200-premium-inject\\\">...CSS complet...</style>",
"critique": "resume court",
"score_before": N,
"score_after_estimated": N,
"safe_to_apply": true|false
}'''
print(json.dumps({'contents':[{'parts':[{'text':prompt},{'inline_data':{'mime_type':'image/png','data':b64}}]}],'generationConfig':{'temperature':0.3,'maxOutputTokens':6000}}))
PYEOF
curl -sk -m 120 -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=$KG" \
-H "Content-Type: application/json" -d @"$OUT/payload.json" > "$OUT/gemini-raw.json" 2>&1
# 3) Parse
python3 << PYEOF > "$OUT/parse.log" 2>&1
import json, re, os
with open('$OUT/gemini-raw.json') as f: resp = json.load(f)
try:
text = resp['candidates'][0]['content']['parts'][0]['text']
text = re.sub(r'```[a-z]*', '', text).strip()
m = re.search(r'\{.*\}', text, re.DOTALL)
if m:
try:
parsed = json.loads(m.group(0))
out = {'ok':True,'plan':parsed,'finishReason':resp['candidates'][0].get('finishReason')}
except Exception as e:
out = {'ok':False,'raw':text[:5000],'parse_err':str(e)[:100],'finishReason':resp['candidates'][0].get('finishReason')}
else:
out = {'ok':False,'raw':text[:5000],'finishReason':resp['candidates'][0].get('finishReason')}
with open('$OUT/plan.json','w') as f: json.dump(out,f,ensure_ascii=False,indent=2)
print('OK')
except Exception as e:
print('ERR', str(e))
PYEOF
# 4) If MODE=apply AND plan.ok==true AND safe_to_apply==true -> apply
PATCH_APPLIED="false"
if [ "$MODE" = "apply" ]; then
CSS_PATCH=$(python3 -c "
import json
try:
with open('$OUT/plan.json') as f: d=json.load(f)
if d.get('ok') and d.get('plan',{}).get('safe_to_apply') and d.get('plan',{}).get('css_patch'):
print(d['plan']['css_patch'])
else:
print('')
except: print('')
" 2>/dev/null)
if [ -n "$CSS_PATCH" ]; then
# Backup GOLD
BACKUP="/var/www/html/vault-gold/opus/${PAGE}.html.doctrine200-gemini-apply-$TS.bak"
mkdir -p /var/www/html/vault-gold/opus
sudo cp "$TARGET" "$BACKUP"
# Insert before </head> — unlock/patch/relock
sudo chattr -i "$TARGET" 2>/dev/null
# Use Python for safe replace
python3 << PYAPPLY
import sys
target = '$TARGET'
patch_file = '$OUT/plan.json'
import json
with open(patch_file) as f: d = json.load(f)
css = d['plan']['css_patch']
marker_start = "<!-- DOCTRINE-200-GEMINI-UX-APPLY-$TS -->"
marker_end = "<!-- END-DOCTRINE-200 -->"
full_patch = f"\n{marker_start}\n{css}\n{marker_end}\n"
with open(target) as f: html = f.read()
if 'DOCTRINE-200-GEMINI-UX-APPLY' in html:
print("ALREADY")
elif '</head>' in html:
html = html.replace('</head>', full_patch + '</head>', 1)
with open(target, 'w') as f: f.write(html)
print("APPLIED")
else:
print("NO_HEAD")
PYAPPLY
sudo chattr +i "$TARGET" 2>/dev/null
PATCH_APPLIED="true"
echo "$BACKUP" > "$OUT/backup-path.txt"
fi
fi
rm -f "$OUT/before.b64" "$OUT/payload.json"
PROOF_URL="https://weval-consulting.com/proofs/wevia-gemini-ux-apply-$TS"
echo "{\"ok\":true,\"page\":\"$PAGE\",\"mode\":\"$MODE\",\"applied\":\"$PATCH_APPLIED\",\"proofs\":\"$PROOF_URL\",\"plan\":\"$PROOF_URL/plan.json\",\"shot\":\"$PROOF_URL/before.png\"}"

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -0,0 +1,34 @@
{
"candidates": [
{
"content": {
"parts": [
{
"text": "{\n \"css_patch\": \"\u003cstyle id=\\\"wtp-doctrine200-premium-inject\\\"\u003e\\n:root {\\n --wtp-color-primary: #FF5C6E; /* Red from 'Voir les tarifs' and 'vérifiés' */\\n --wtp-color-secondary: #00C896; /* Green from 'Créer mon compte gratuit' and 'Self-service' */\\n --wtp-color-background: #1A1A2E; /* Dark background */\\n --wtp-color-text-light: #E0E0E0; /* Light grey text */\\n --wtp-color-text-muted: #A0A0B0; /* Muted text for descriptions */\\n --wtp-color-border: #444455;\\n --wtp-color-card-bg: #222233;\\n\\n --wtp-gradient-hero: linear-gradient(135deg, #1A1A"
}
],
"role": "model"
},
"finishReason": "MAX_TOKENS",
"index": 0
}
],
"usageMetadata": {
"promptTokenCount": 532,
"candidatesTokenCount": 237,
"totalTokenCount": 6528,
"promptTokensDetails": [
{
"modality": "TEXT",
"tokenCount": 274
},
{
"modality": "IMAGE",
"tokenCount": 258
}
],
"thoughtsTokenCount": 5759
},
"modelVersion": "gemini-2.5-flash",
"responseId": "d4fracmfNc2hvdIP97j3-AM"
}