Aller au contenu principal

Tutoriel : Construisez Votre Première Application

Dans ce tutoriel, vous allez construire un mini-programme Compteur de Mots qui lit l'historique de chat actif, compte les mots par rôle (utilisateur vs assistant) et affiche les statistiques. À la fin, vous saurez comment créer, tester, empaqueter et publier un mini-programme.

Ce que vous apprendrez :

  • Créer un manifest.json
  • Écrire le HTML de l'application avec le SDK window.ais
  • Utiliser ais.chat.getHistory() pour lire les messages
  • Utiliser ais.storage pour persister les données entre sessions
  • Tester localement avec le sideloading
  • Empaqueter en bundle .ais
  • Publier sur le registre communautaire

Temps requis : Environ 15 minutes.


Étape 1 : Créer le Manifeste

Chaque mini-programme a besoin d'un manifest.json qui décrit l'application, ses permissions et son point d'entrée. Créez un nouveau répertoire et ajoutez ce fichier :

mkdir word-counter
cd word-counter

Créez manifest.json :

{
"name": "word-counter",
"version": "1.0.0",
"abi": 1,
"type": "mini-program",
"title": "Word Counter",
"description": "Count words in your chat history by role",
"author": { "name": "Your Name" },
"entry": "index.html",
"base_url": "https://localhost:8080/",
"permissions": ["storage", "chat:read", "ui:toast"],
"keywords": ["statistics", "utility"]
}

Champs clés :

ChampValeurPourquoi
nameword-counterIdentifiant unique (minuscules, tirets uniquement)
abi1Requis — correspond à l'ABI actuelle de la plateforme
typemini-programIndique à la plateforme qu'il s'agit d'une app iframe sandboxée
entryindex.htmlLe fichier HTML à charger
base_urlhttps://localhost:8080/Où les ressources sont hébergées (à mettre à jour pour la production)
permissions["storage", "chat:read", "ui:toast"]Nous devons lire le chat et sauvegarder les stats
info

La permission storage est toujours accordée automatiquement, mais c'est une bonne pratique de la lister explicitement pour que les utilisateurs sachent que votre application stocke des données.


Étape 2 : Créer le HTML

Créez index.html dans le même répertoire. C'est votre application entière — HTML, CSS et JavaScript dans un seul fichier.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family:
system-ui,
-apple-system,
sans-serif;
padding: 20px;
color: #e0e0e0;
background: #1a1a2e;
min-height: 100vh;
}
h1 {
font-size: 22px;
margin-bottom: 16px;
}

.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 12px;
margin-bottom: 20px;
}
.stat-card {
background: #16213e;
border: 1px solid #333;
border-radius: 8px;
padding: 16px;
text-align: center;
}
.stat-value {
font-size: 32px;
font-weight: 700;
color: #58a6ff;
font-variant-numeric: tabular-nums;
}
.stat-label {
font-size: 14px;
color: #888;
margin-top: 4px;
}

.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
button {
min-height: 48px;
padding: 10px 20px;
font-size: 16px;
border: 1px solid #444;
border-radius: 6px;
background: #2a2a4e;
color: #e0e0e0;
cursor: pointer;
}
button:hover {
background: #3a3a5e;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background: #1a73e8;
border-color: #1a73e8;
}
.btn-primary:hover {
background: #1557b0;
}
.btn-close {
background: none;
border-color: #666;
color: #888;
}

#last-updated {
margin-top: 16px;
font-size: 14px;
color: #666;
}
</style>
</head>
<body>
<h1>Compteur de Mots</h1>

<div class="stats-grid">
<div class="stat-card">
<div class="stat-value" id="total-words">--</div>
<div class="stat-label">Total Mots</div>
</div>
<div class="stat-card">
<div class="stat-value" id="user-words">--</div>
<div class="stat-label">Vos Mots</div>
</div>
<div class="stat-card">
<div class="stat-value" id="ai-words">--</div>
<div class="stat-label">Mots IA</div>
</div>
<div class="stat-card">
<div class="stat-value" id="msg-count">--</div>
<div class="stat-label">Messages</div>
</div>
</div>

<div class="actions">
<button class="btn-primary" id="analyze">Analyser le Chat</button>
<button class="btn-close" id="close">Fermer</button>
</div>

<div id="last-updated"></div>

<script>
// Helper: count words in a string
function countWords(text) {
if (!text || typeof text !== "string") return 0;
return text.trim().split(/\s+/).filter(Boolean).length;
}

// Format numbers with commas (1234 -> "1,234")
function fmt(n) {
return n.toLocaleString();
}

ais.ready(async function () {
// Set the panel title
ais.ui.setTitle("Compteur de Mots");

// Try to restore last saved stats
var saved = await ais.storage.get("last-stats");
if (saved) {
showStats(saved);
}

// Analyze button
document
.getElementById("analyze")
.addEventListener("click", async function () {
this.disabled = true;
this.textContent = "Analyse...";

try {
// Fetch up to 500 messages from chat history
var messages = await ais.chat.getHistory(500);

var userWords = 0;
var aiWords = 0;
var msgCount = messages.length;

for (var i = 0; i < messages.length; i++) {
var msg = messages[i];
var words = countWords(msg.content);
if (msg.role === "user") {
userWords += words;
} else if (msg.role === "assistant") {
aiWords += words;
}
}

var stats = {
total: userWords + aiWords,
user: userWords,
ai: aiWords,
messages: msgCount,
timestamp: Date.now(),
};

showStats(stats);

// Save stats for next time
await ais.storage.set("last-stats", stats);

ais.ui.toast(msgCount + " messages analysés !");
} catch (err) {
ais.ui.toast("Erreur : " + err.message);
}

this.disabled = false;
this.textContent = "Analyser le Chat";
});

// Close button
document.getElementById("close").addEventListener("click", function () {
ais.close();
});
});

function showStats(stats) {
document.getElementById("total-words").textContent = fmt(stats.total);
document.getElementById("user-words").textContent = fmt(stats.user);
document.getElementById("ai-words").textContent = fmt(stats.ai);
document.getElementById("msg-count").textContent = fmt(stats.messages);

if (stats.timestamp) {
var date = new Date(stats.timestamp);
document.getElementById("last-updated").textContent =
"Dernière analyse : " + date.toLocaleString();
}
}
</script>
</body>
</html>

Ce que fait ce code

  1. ais.ready() — Attend que le pont de la plateforme soit connecté avant d'exécuter toute logique.
  2. ais.storage.get('last-stats') — Restaure les statistiques précédemment sauvegardées pour que l'utilisateur voie les données immédiatement au lancement.
  3. ais.chat.getHistory(500) — Récupère jusqu'à 500 messages de la conversation active.
  4. Comptage de mots — Itère à travers les messages, divisant le contenu sur les espaces et comptabilisant par rôle.
  5. ais.storage.set('last-stats', stats) — Persiste les résultats pour la prochaine fois.
  6. ais.ui.toast() — Affiche une notification lorsque l'analyse est terminée.
  7. ais.close() — Retourne à la vue de chat lorsque l'utilisateur clique sur Fermer.

Étape 3 : Tester Localement

Vous avez besoin d'un serveur HTTP local pour servir le manifeste et le fichier HTML. Utilisez l'outil de votre choix :

# Python 3
cd word-counter
python3 -m http.server 8080

# ou Node.js
npx serve -p 8080

# ou PHP
php -S localhost:8080

Maintenant installez l'application dans la plateforme :

  1. Ouvrez aiscouncil.net et connectez-vous
  2. Cliquez sur l'icône Apps dans la barre latérale gauche
  3. Dans la section Sideload, collez : http://localhost:8080/manifest.json
  4. Cliquez sur Install
  5. Passez en revue les permissions (storage, chat:read, ui:toast) et cliquez sur Allow
  6. Cliquez sur Open sur la carte de l'application installée
astuce

Pour la boucle de développement la plus rapide, utilisez HTML Upload au lieu du sideloading par URL. Uploadez votre index.html directement — pas de serveur nécessaire. La plateforme crée un manifeste synthétique automatiquement. Vous pouvez désinstaller et ré-uploader à chaque modification.

Dépannage

ProblèmeSolution
"Failed to fetch manifest"Assurez-vous que votre serveur local fonctionne et sert des en-têtes CORS. Essayez python3 -m http.server 8080 qui sert des réponses CORS-safe.
L'application affiche une page blancheVérifiez la console du navigateur pour les erreurs. Le problème le plus courant est d'appeler les méthodes ais.* avant ais.ready().
"PermissionDenied: chat:read"Votre manifeste n'inclut pas chat:read dans le tableau des permissions. Mettez à jour le manifeste et réinstallez.
L'application ne se met pas à jour après les changements de codeDésinstallez d'abord l'application (cliquez sur le bouton X sur la carte), puis réinstallez. Le HTML d'entrée est mis en cache au moment de l'installation.

Étape 4 : Ajouter de la Finition

Ajoutons une fonctionnalité : le comptage de mots en temps réel à mesure que de nouveaux messages arrivent.

Ajoutez ce code à l'intérieur du callback ais.ready(), après le gestionnaire du bouton de fermeture :

// Subscribe to new messages for real-time counting
ais.chat.onMessage(function (msg) {
// Re-read saved stats and add the new message's words
ais.storage.get("last-stats").then(function (stats) {
if (!stats) return;
var words = countWords(msg.content);
if (msg.role === "user") stats.user += words;
else if (msg.role === "assistant") stats.ai += words;
stats.total = stats.user + stats.ai;
stats.messages++;
stats.timestamp = Date.now();
showStats(stats);
ais.storage.set("last-stats", stats);
});
});

Maintenant les compteurs se mettent à jour en direct à mesure que l'utilisateur discute sans avoir besoin de cliquer sur "Analyser" à nouveau.


Étape 5 : Empaqueter en Bundle .ais

Un bundle .ais est une archive ZIP contenant votre manifeste et tous les fichiers de l'application. La plateforme extrait le ZIP, lit le manifeste et intègre toutes les ressources (CSS, JS, images) dans le HTML d'entrée.

Pour une application mono-fichier comme la nôtre, le bundle est simple :

cd word-counter
zip -r ../word-counter.ais manifest.json index.html

Cela crée word-counter.ais dans le répertoire parent.

Tester le bundle

  1. Dans la plateforme, allez dans Apps et cliquez sur Upload App
  2. Sélectionnez word-counter.ais
  3. Passez en revue les permissions et approuvez
  4. L'application s'installe depuis le bundle avec toutes les ressources intégrées
info

Les bundles sont autonomes. L'utilisateur n'a pas besoin d'un accès réseau vers le base_url original — tout est intégré au moment de l'installation. Cela rend les bundles idéaux pour la distribution hors-ligne et le partage.

Bundles multi-fichiers

Si votre application a des fichiers CSS, JavaScript ou images séparés, incluez-les tous dans le ZIP :

zip -r ../word-counter.ais manifest.json index.html style.css app.js icon.png

La plateforme intègre automatiquement :

  • <link rel="stylesheet" href="style.css"> devient <style>...</style>
  • <script src="app.js"></script> devient <script>...</script>
  • <img src="icon.png"> devient <img src="data:image/png;base64,...">

Étape 6 : Publier sur le Registre

Une fois votre application prête pour les autres, publiez-la sur le registre communautaire.

1. Hébergez vos fichiers

Uploadez votre manifeste et HTML d'entrée sur un CDN public. GitHub Pages est gratuit et simple :

# Dans votre repo GitHub (ex., github.com/yourname/word-counter)
# Poussez manifest.json et index.html sur la branche main
# Activez GitHub Pages dans les paramètres du repo (source: branche main, racine)

Vos fichiers seront disponibles à :

  • https://yourname.github.io/word-counter/manifest.json
  • https://yourname.github.io/word-counter/index.html

Mettez à jour base_url dans votre manifeste pour correspondre :

"base_url": "https://yourname.github.io/word-counter/"

2. Forkez le repo aiscouncil

Allez sur github.com/nicholasgasior/bcz et cliquez sur Fork.

3. Ajoutez votre entrée de package

Éditez registry/packages.json et ajoutez une entrée au tableau packages :

{
"name": "word-counter",
"type": "mini-program",
"version": "1.0.0",
"manifest": "https://yourname.github.io/word-counter/manifest.json",
"tier": "community",
"category": "utilities",
"description": "Count words in your chat history by role",
"icon": "https://yourname.github.io/word-counter/icon.png",
"added": "2026-02-19",
"price": 0,
"currency": "USD",
"seller": null
}

4. Validez

Exécutez le script de validation pour vérifier votre entrée :

python3 registry/validate.py packages

Si la validation passe, vous êtes prêt à soumettre.

5. Soumettez une pull request

Poussez vos changements vers votre fork et créez une PR contre le repo principal. Si la validation automatisée passe, la PR peut être fusionnée et votre application apparaîtra dans la section App Store de la plateforme.

Voir Publication sur le Registre pour tous les détails sur la tarification, la configuration vendeur et les niveaux de vérification.


Conseils et Meilleures Pratiques

Design

  • Thème sombre par défaut — La plupart des utilisateurs de la plateforme utilisent le mode sombre. Concevez pour des fonds sombres (#1a1a2e ou similaire) avec du texte clair (#e0e0e0).
  • Cibles tactiles minimum 48px — Les boutons et éléments interactifs doivent faire au moins 48px de hauteur pour l'accessibilité et l'interaction VLM-friendly.
  • Taille de police minimum 14px — Tout le texte doit faire au moins 14px pour la lisibilité.
  • Layout responsive — La largeur du panneau des applications varie. Utilisez CSS Grid ou Flexbox avec auto-fit pour s'adapter.

Performance

  • Cachez les résultats dans le stockage — Utilisez ais.storage pour sauvegarder les résultats calculés. Restaurez-les au lancement pour que l'utilisateur voie les données immédiatement.
  • Limitez les requêtes d'historique de chatais.chat.getHistory(500) est généralement suffisant. Évitez de demander un historique illimité.
  • Pas de polling — Utilisez ais.chat.onMessage() pour les mises à jour en temps réel au lieu d'appeler getHistory de façon répétée.

Sécurité

  • Demandez des permissions minimales — Listez uniquement les permissions que votre application utilise réellement. Moins de permissions signifie plus d'utilisateurs feront confiance et installeront votre application.
  • Validez toutes les entrées — Les données de ais.chat.getHistory() contiennent du contenu généré par l'utilisateur. Assainissez avant d'insérer dans le DOM.
  • Ne stockez pas de données sensiblesais.storage n'est pas chiffré. Ne stockez jamais de mots de passe, tokens ou clés API (sauf si votre application a secrets:sync et gère explicitement le transfert d'identifiants).

Compatibilité

  • Vérifiez ais.platform.abi — Si votre application dépend de fonctionnalités SDK spécifiques, vérifiez la version ABI et affichez un message utile si la plateforme est plus ancienne.
  • Enveloppez les appels SDK dans try/catch — Les erreurs de permission et les différences de version de plateforme peuvent causer des rejets. Gérez-les avec élégance.
  • Testez avec l'exemple hello-world — La plateforme est livrée avec un mini-programme exemple que vous pouvez utiliser comme référence.