@fraza/module-ai 0.4.22 → 0.4.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,7 +4,13 @@ Historique des versions publiées de `@fraza/module-ai`.
4
4
 
5
5
  Ce fichier est généré par `fraza-module changelog` à partir des messages de commit. Ne pas le modifier à la main.
6
6
 
7
- ## 0.4.22 (2026-11-08)
7
+ ## 0.4.23 (2026-11-08)
8
+
9
+ ### Ajouts
10
+
11
+ - **ai** : add demo history seed for fraza-db (83306d4)
12
+
13
+ ## 0.4.22 (2026-11-07)
8
14
 
9
15
  Maintenance interne, aucun changement de comportement.
10
16
 
package/changelog.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@fraza/module-ai",
3
3
  "versions": [
4
4
  {
5
- "version": "0.4.22",
5
+ "version": "0.4.23",
6
6
  "date": "2026-11-08",
7
7
  "breaking": [],
8
8
  "envAdded": [],
@@ -10,6 +10,25 @@
10
10
  "envRenamed": [],
11
11
  "templateChanged": [],
12
12
  "action": [],
13
+ "entries": [
14
+ {
15
+ "type": "feat",
16
+ "scope": "ai",
17
+ "subject": "add demo history seed for fraza-db",
18
+ "hash": "83306d4",
19
+ "breaking": false
20
+ }
21
+ ]
22
+ },
23
+ {
24
+ "version": "0.4.22",
25
+ "date": "2026-11-07",
26
+ "breaking": [],
27
+ "envAdded": [],
28
+ "envRemoved": [],
29
+ "envRenamed": [],
30
+ "templateChanged": [],
31
+ "action": [],
13
32
  "entries": []
14
33
  },
15
34
  {
package/dist/index.js CHANGED
@@ -31,11 +31,13 @@ async function register() {
31
31
  await import("./register-server.js");
32
32
  }
33
33
  import { createTables, dropTables } from "./db.server.js";
34
+ import { seed } from "./seed.server.js";
34
35
  import { migrations } from "./migrations.js";
35
36
  export {
36
37
  createTables,
37
38
  dropTables,
38
39
  manifest,
39
40
  migrations,
40
- register
41
+ register,
42
+ seed
41
43
  };
@@ -0,0 +1,205 @@
1
+ const CAST = [
2
+ { key: "noemie", name: "No\xE9mie Lavoie", email: "noemie.lavoie@exemple.test" },
3
+ { key: "mathieu", name: "Mathieu Tremblay", email: "mathieu.tremblay@exemple.test" },
4
+ { key: "sophie", name: "Sophie Bouchard", email: "sophie.bouchard@exemple.test" },
5
+ { key: "olivier", name: "Olivier C\xF4t\xE9", email: "olivier.cote@exemple.test" },
6
+ { key: "julie", name: "Julie Roy", email: "julie.roy@exemple.test" },
7
+ { key: "alexandre", name: "Alexandre Morin", email: "alexandre.morin@exemple.test" }
8
+ ];
9
+ const STAFF = ["noemie", "mathieu", "sophie"];
10
+ const PROVIDER = "anthropic";
11
+ const MODEL = "claude-sonnet-5-5";
12
+ const DAYS = 28;
13
+ const DAY_MS = 24 * 60 * 60 * 1e3;
14
+ const PROMPTS = [
15
+ { prompt: "posts.write", source: "@fraza/module-posts", weight: 3, input: [1800, 2600], output: [900, 1600], ms: [9e3, 18e3] },
16
+ { prompt: "posts.revise", source: "@fraza/module-posts", weight: 4, input: [2200, 3500], output: [800, 1500], ms: [7e3, 15e3] },
17
+ { prompt: "projects.write", source: "@fraza/module-projects", weight: 2, input: [1200, 1900], output: [500, 900], ms: [5e3, 1e4] },
18
+ { prompt: "projects.restructure", source: "@fraza/module-projects", weight: 1, input: [1500, 2400], output: [600, 1e3], ms: [6e3, 11e3] },
19
+ { prompt: "feedback.item", source: "@fraza/module-feedback", weight: 3, input: [700, 1200], output: [200, 400], ms: [2500, 5e3] },
20
+ { prompt: "medias.describe", source: "@fraza/module-medias", weight: 2, input: [1500, 2200], output: [80, 160], ms: [3e3, 6e3] },
21
+ { prompt: "medias.alt", source: "@fraza/module-medias", weight: 3, input: [1300, 1900], output: [40, 90], ms: [2e3, 4500], trigger: "code" }
22
+ ];
23
+ function generator(seed2) {
24
+ let state = seed2 >>> 0;
25
+ return () => {
26
+ state = state + 1831565813 >>> 0;
27
+ let t = state;
28
+ t = Math.imul(t ^ t >>> 15, t | 1);
29
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
30
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
31
+ };
32
+ }
33
+ async function ensureCast(query, createUser) {
34
+ const ids = {};
35
+ for (const person of CAST) {
36
+ const found = await query("SELECT id FROM fraza_auth_users WHERE email = $1", [person.email]);
37
+ if (found.rows.length > 0) {
38
+ ids[person.key] = found.rows[0].id;
39
+ continue;
40
+ }
41
+ const user = await createUser({ email: person.email, name: person.name, emailVerified: true });
42
+ ids[person.key] = user.id;
43
+ }
44
+ return ids;
45
+ }
46
+ function at(now, days, hour, minute) {
47
+ const date = new Date(now.getTime() - days * DAY_MS);
48
+ date.setHours(hour, minute, Math.floor(minute / 2), 0);
49
+ return date;
50
+ }
51
+ function dailyRuns(now, users, random) {
52
+ const between = ([low, high]) => Math.round(low + random() * (high - low));
53
+ const totalWeight = PROMPTS.reduce((sum, p) => sum + p.weight, 0);
54
+ const pick = () => {
55
+ let roll = random() * totalWeight;
56
+ for (const p of PROMPTS) {
57
+ roll -= p.weight;
58
+ if (roll < 0) return p;
59
+ }
60
+ return PROMPTS[0];
61
+ };
62
+ const runs = [];
63
+ for (let days = DAYS - 1; days >= 0; days -= 1) {
64
+ const weekday = new Date(now.getTime() - days * DAY_MS).getDay();
65
+ const weekend = weekday === 0 || weekday === 6;
66
+ const count = weekend ? Math.floor(random() * 2) : 2 + Math.floor(random() * 4);
67
+ for (let i = 0; i < count; i += 1) {
68
+ const p = pick();
69
+ const createdAt = at(now, days, 8 + Math.floor(random() * 9), Math.floor(random() * 60));
70
+ if (createdAt > now) continue;
71
+ const button = p.trigger !== "code";
72
+ runs.push({
73
+ prompt: p.prompt,
74
+ source: p.source,
75
+ trigger: button ? "button" : "code",
76
+ status: "success",
77
+ inputTokens: between(p.input),
78
+ outputTokens: between(p.output),
79
+ durationMs: between(p.ms),
80
+ userId: button ? users[STAFF[Math.floor(random() * STAFF.length)]] : null,
81
+ createdAt
82
+ });
83
+ }
84
+ }
85
+ return runs;
86
+ }
87
+ function notableRuns(now, users) {
88
+ const test = { prompt: "ai.test", source: "@fraza/module-ai", trigger: "test", userId: users.noemie };
89
+ return [
90
+ // Set-up day: the key pasted with a space, then the right one.
91
+ {
92
+ ...test,
93
+ status: "error",
94
+ errorCode: "auth",
95
+ errorMessage: "Le fournisseur refuse la cl\xE9 d'API. V\xE9rifier FRAZA_MODULE_AI_API_KEY. (invalid x-api-key)",
96
+ durationMs: 412,
97
+ createdAt: at(now, DAYS - 1, 9, 12)
98
+ },
99
+ {
100
+ ...test,
101
+ status: "success",
102
+ inputTokens: 214,
103
+ outputTokens: 38,
104
+ durationMs: 1840,
105
+ createdAt: at(now, DAYS - 1, 9, 16)
106
+ },
107
+ {
108
+ prompt: "posts.write",
109
+ source: "@fraza/module-posts",
110
+ trigger: "button",
111
+ status: "error",
112
+ errorCode: "timeout",
113
+ errorMessage: "Le fournisseur n'a pas r\xE9pondu \xE0 temps.",
114
+ durationMs: 12e4,
115
+ userId: users.mathieu,
116
+ createdAt: at(now, 12, 14, 41)
117
+ },
118
+ {
119
+ prompt: "medias.alt",
120
+ source: "@fraza/module-medias",
121
+ trigger: "code",
122
+ status: "error",
123
+ errorCode: "rate_limited",
124
+ errorMessage: "Le fournisseur limite les appels pour l'instant. R\xE9essayer dans un moment. (Number of request tokens has exceeded your per-minute rate limit)",
125
+ durationMs: 690,
126
+ createdAt: at(now, 5, 10, 3)
127
+ },
128
+ // A second attempt that fitted the schema: one success, two answers paid.
129
+ {
130
+ prompt: "projects.write",
131
+ source: "@fraza/module-projects",
132
+ trigger: "button",
133
+ status: "success",
134
+ inputTokens: 3480,
135
+ outputTokens: 1520,
136
+ attempts: 2,
137
+ durationMs: 16400,
138
+ userId: users.sophie,
139
+ createdAt: at(now, 3, 11, 27)
140
+ },
141
+ {
142
+ prompt: "feedback.item",
143
+ source: "@fraza/module-feedback",
144
+ trigger: "button",
145
+ status: "error",
146
+ errorCode: "invalid_answer",
147
+ errorMessage: "R\xE9ponse hors sch\xE9ma : $.title: longer than 200 characters ; $.description: missing",
148
+ inputTokens: 2010,
149
+ outputTokens: 690,
150
+ attempts: 2,
151
+ durationMs: 7300,
152
+ userId: users.mathieu,
153
+ createdAt: at(now, 2, 15, 52)
154
+ },
155
+ // Checked again less than an hour ago.
156
+ {
157
+ ...test,
158
+ status: "success",
159
+ inputTokens: 214,
160
+ outputTokens: 41,
161
+ durationMs: 1520,
162
+ createdAt: new Date(now.getTime() - 50 * 60 * 1e3)
163
+ }
164
+ ];
165
+ }
166
+ async function seed({ query, createUser, info }) {
167
+ const users = await ensureCast(query, createUser);
168
+ const existing = await query("SELECT count(*)::int AS n FROM fraza_ai_run");
169
+ if (existing.rows[0].n > 0) {
170
+ info(`ai : historique d\xE9j\xE0 rempli (${existing.rows[0].n} appels), rien d'ajout\xE9`);
171
+ return;
172
+ }
173
+ const now = /* @__PURE__ */ new Date();
174
+ const runs = [...dailyRuns(now, users, generator(20261107)), ...notableRuns(now, users)].sort((a, b) => a.createdAt - b.createdAt);
175
+ for (const run of runs) {
176
+ await query(
177
+ `INSERT INTO fraza_ai_run
178
+ (prompt, source, trigger, provider, model, status, error_code, error_message,
179
+ input_tokens, output_tokens, attempts, duration_ms, user_id, created_at)
180
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)`,
181
+ [
182
+ run.prompt,
183
+ run.source,
184
+ run.trigger,
185
+ PROVIDER,
186
+ MODEL,
187
+ run.status,
188
+ run.errorCode ?? null,
189
+ run.errorMessage ?? null,
190
+ run.inputTokens ?? null,
191
+ run.outputTokens ?? null,
192
+ run.attempts ?? 1,
193
+ run.durationMs ?? null,
194
+ run.userId ?? null,
195
+ run.createdAt
196
+ ]
197
+ );
198
+ }
199
+ const errors = runs.filter((run) => run.status === "error").length;
200
+ const tokens = runs.reduce((sum, run) => sum + (run.inputTokens ?? 0) + (run.outputTokens ?? 0), 0);
201
+ info(`ai : ${runs.length} appels sur ${DAYS} jours, dont ${errors} en \xE9chec, ${tokens.toLocaleString("fr-CA")} jetons`);
202
+ }
203
+ export {
204
+ seed
205
+ };
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
- const MODULE_VERSION = "0.4.22";
1
+ const MODULE_VERSION = "0.4.23";
2
2
  export {
3
3
  MODULE_VERSION
4
4
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fraza/module-ai",
3
- "version": "0.4.22",
3
+ "version": "0.4.23",
4
4
  "description": "Module d'intelligence artificielle pour Fraza : un fournisseur au choix, des prompts à réponse structurée, des boutons dans l'admin et des tâches en arrière-plan.",
5
5
  "publishConfig": {
6
6
  "access": "public"