@fraza/module-ai 0.0.0-stage → 0.4.22

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +152 -0
  2. package/LICENSE +21 -0
  3. package/README.md +72 -2
  4. package/THIRD-PARTY-NOTICES.md +11 -0
  5. package/changelog.json +459 -0
  6. package/dist/admin/AiActions.client.js +166 -0
  7. package/dist/admin/AiChoices.client.js +67 -0
  8. package/dist/admin/AiInputForm.client.js +57 -0
  9. package/dist/admin/AiPage.client.js +181 -0
  10. package/dist/admin/AiResults.client.js +76 -0
  11. package/dist/admin/AiStudio.client.js +453 -0
  12. package/dist/admin/AiUsageWidget.client.js +28 -0
  13. package/dist/admin/StudioBar.client.js +33 -0
  14. package/dist/admin/StudioCompose.client.js +457 -0
  15. package/dist/admin/StudioComposer.client.js +217 -0
  16. package/dist/admin/StudioFields.client.js +124 -0
  17. package/dist/admin/StudioPlan.client.js +94 -0
  18. package/dist/admin/StudioWorking.client.js +157 -0
  19. package/dist/admin/choices.js +22 -0
  20. package/dist/admin/format.js +19 -0
  21. package/dist/admin/input-form.js +59 -0
  22. package/dist/admin/paths.js +4 -0
  23. package/dist/admin/studio-compose.js +94 -0
  24. package/dist/admin/studio-diff.js +240 -0
  25. package/dist/admin/widgets.js +14 -0
  26. package/dist/admin.js +9 -0
  27. package/dist/api.server.js +155 -0
  28. package/dist/config.server.js +113 -0
  29. package/dist/cron.server.js +26 -0
  30. package/dist/db.server.js +65 -0
  31. package/dist/index.js +41 -0
  32. package/dist/integration.server.js +28 -0
  33. package/dist/migrations.js +4 -0
  34. package/dist/permissions.js +9 -0
  35. package/dist/prompts.server.js +149 -0
  36. package/dist/providers/anthropic.server.js +42 -0
  37. package/dist/providers/chat-completions.server.js +63 -0
  38. package/dist/providers/google.server.js +52 -0
  39. package/dist/providers/http.server.js +98 -0
  40. package/dist/providers/index.server.js +22 -0
  41. package/dist/providers/ollama.server.js +45 -0
  42. package/dist/register-server.js +34 -0
  43. package/dist/results.server.js +74 -0
  44. package/dist/run.server.js +166 -0
  45. package/dist/runs.server.js +106 -0
  46. package/dist/schema.js +190 -0
  47. package/dist/server.js +19 -0
  48. package/dist/source.js +4 -0
  49. package/dist/studio.js +205 -0
  50. package/dist/test-prompt.server.js +24 -0
  51. package/dist/version.js +4 -0
  52. package/dist/widgets.server.js +29 -0
  53. package/package.json +67 -4
package/dist/schema.js ADDED
@@ -0,0 +1,190 @@
1
+ const TYPES = /* @__PURE__ */ new Set(["object", "string", "number", "integer", "boolean", "array", "null"]);
2
+ const COMMON_KEYS = ["type", "title", "description"];
3
+ const ALLOWED_KEYS = {
4
+ object: /* @__PURE__ */ new Set([...COMMON_KEYS, "properties", "required", "additionalProperties"]),
5
+ string: /* @__PURE__ */ new Set([...COMMON_KEYS, "enum", "maxLength", "format"]),
6
+ number: new Set(COMMON_KEYS),
7
+ integer: new Set(COMMON_KEYS),
8
+ boolean: new Set(COMMON_KEYS),
9
+ array: /* @__PURE__ */ new Set([...COMMON_KEYS, "items", "minItems", "maxItems"]),
10
+ null: new Set(COMMON_KEYS)
11
+ };
12
+ const MAX_DEPTH = 8;
13
+ const KEPT_CONSTRAINTS = {
14
+ anthropic: /* @__PURE__ */ new Set(),
15
+ openai: /* @__PURE__ */ new Set(["minItems", "maxItems"]),
16
+ "openai-compatible": /* @__PURE__ */ new Set(),
17
+ google: /* @__PURE__ */ new Set(["minItems", "maxItems", "maxLength"]),
18
+ mistral: /* @__PURE__ */ new Set(),
19
+ ollama: /* @__PURE__ */ new Set(["minItems", "maxItems", "maxLength"])
20
+ };
21
+ const CONSTRAINTS = ["maxLength", "minItems", "maxItems"];
22
+ const FORMATS = /* @__PURE__ */ new Set(["html"]);
23
+ function typesOf(node) {
24
+ return Array.isArray(node.type) ? node.type : [node.type];
25
+ }
26
+ function mainType(node) {
27
+ return typesOf(node).find((type) => type !== "null") ?? "null";
28
+ }
29
+ function isNonNegativeInteger(value) {
30
+ return Number.isInteger(value) && value >= 0;
31
+ }
32
+ function typeProblems(node, at) {
33
+ const types = typesOf(node);
34
+ if (types.length === 0 || types.some((type) => !TYPES.has(type))) {
35
+ return [`${at}: type must be one of ${[...TYPES].join(", ")}`];
36
+ }
37
+ if (types.length > 2 || types.length === 2 && !types.includes("null") || new Set(types).size !== types.length) {
38
+ return [`${at}: a type array is a type and null, e.g. ['string', 'null']`];
39
+ }
40
+ return [];
41
+ }
42
+ function objectProblems(node, at, depth) {
43
+ const problems = [];
44
+ if (node.additionalProperties !== void 0 && node.additionalProperties !== false) {
45
+ problems.push(`${at}: additionalProperties may only be false`);
46
+ }
47
+ if (!node.properties || typeof node.properties !== "object" || Array.isArray(node.properties)) {
48
+ return [...problems, `${at}: an object needs its properties`];
49
+ }
50
+ const keys = Object.keys(node.properties);
51
+ if (keys.length === 0) problems.push(`${at}: an object needs at least one property`);
52
+ const required = node.required ?? [];
53
+ if (!Array.isArray(required) || keys.some((key) => !required.includes(key)) || required.some((key) => !keys.includes(key))) {
54
+ problems.push(`${at}: every property is listed in required; an optional value is a type with null`);
55
+ }
56
+ for (const key of keys) problems.push(...nodeProblems(node.properties[key], `${at}.${key}`, depth + 1));
57
+ return problems;
58
+ }
59
+ function stringProblems(node, at) {
60
+ const problems = [];
61
+ if (node.enum !== void 0) {
62
+ if (!Array.isArray(node.enum) || node.enum.length === 0 || node.enum.some((v) => typeof v !== "string")) {
63
+ problems.push(`${at}: enum is a non-empty array of strings`);
64
+ }
65
+ }
66
+ if (node.maxLength !== void 0 && !(Number.isInteger(node.maxLength) && node.maxLength > 0)) {
67
+ problems.push(`${at}: maxLength is a positive integer`);
68
+ }
69
+ if (node.format !== void 0 && !FORMATS.has(node.format)) problems.push(`${at}: format may only be 'html'`);
70
+ if (node.format !== void 0 && node.enum !== void 0) problems.push(`${at}: an enum has no format`);
71
+ return problems;
72
+ }
73
+ function arrayProblems(node, at, depth) {
74
+ const problems = [];
75
+ if (!node.items || typeof node.items !== "object" || Array.isArray(node.items)) {
76
+ return [`${at}: an array needs its items schema`];
77
+ }
78
+ for (const key of ["minItems", "maxItems"]) {
79
+ if (node[key] !== void 0 && !isNonNegativeInteger(node[key])) problems.push(`${at}: ${key} is a non-negative integer`);
80
+ }
81
+ if (isNonNegativeInteger(node.minItems) && isNonNegativeInteger(node.maxItems) && node.minItems > node.maxItems) {
82
+ problems.push(`${at}: minItems is above maxItems`);
83
+ }
84
+ problems.push(...nodeProblems(node.items, `${at}[]`, depth + 1));
85
+ return problems;
86
+ }
87
+ function nodeProblems(node, at, depth) {
88
+ if (depth > MAX_DEPTH) return [`${at}: the schema is nested more than ${MAX_DEPTH} levels`];
89
+ if (!node || typeof node !== "object" || Array.isArray(node)) return [`${at}: a schema is an object`];
90
+ const typeIssues = typeProblems(node, at);
91
+ if (typeIssues.length > 0) return typeIssues;
92
+ const type = mainType(node);
93
+ const unknown = Object.keys(node).filter((key) => !ALLOWED_KEYS[type].has(key));
94
+ if (unknown.length > 0) return [`${at}: ${unknown.join(", ")} is not supported on a ${type}`];
95
+ for (const key of ["title", "description"]) {
96
+ if (node[key] !== void 0 && typeof node[key] !== "string") return [`${at}: ${key} is a string`];
97
+ }
98
+ if (type === "object") return objectProblems(node, at, depth);
99
+ if (type === "string") return stringProblems(node, at);
100
+ if (type === "array") return arrayProblems(node, at, depth);
101
+ return [];
102
+ }
103
+ function checkSchema(schema, label = "schema") {
104
+ const problems = nodeProblems(schema, label, 0);
105
+ if (problems.length === 0 && mainType(schema) !== "object") problems.push(`${label}: the root of a schema is an object`);
106
+ if (problems.length > 0) throw new TypeError(`Invalid AI schema:
107
+ - ${problems.join("\n - ")}`);
108
+ }
109
+ function constraintHint(node) {
110
+ const hints = [];
111
+ if (node.minItems !== void 0 && node.minItems === node.maxItems) hints.push(`Exactly ${node.minItems} items.`);
112
+ else {
113
+ if (node.minItems !== void 0) hints.push(`At least ${node.minItems} items.`);
114
+ if (node.maxItems !== void 0) hints.push(`At most ${node.maxItems} items.`);
115
+ }
116
+ if (node.maxLength !== void 0) hints.push(`At most ${node.maxLength} characters.`);
117
+ return hints.join(" ");
118
+ }
119
+ function normalizeNode(node, kept) {
120
+ const out = {};
121
+ for (const [key, value] of Object.entries(node)) {
122
+ if (CONSTRAINTS.includes(key)) {
123
+ if (kept.has(key)) out[key] = value;
124
+ } else if (key !== "properties" && key !== "items" && key !== "description" && key !== "format") {
125
+ out[key] = Array.isArray(value) ? [...value] : value;
126
+ }
127
+ }
128
+ const hint = constraintHint(node);
129
+ const description = [node.description, hint].filter(Boolean).join(" ");
130
+ if (description) out.description = description;
131
+ if (mainType(node) === "object") {
132
+ out.properties = Object.fromEntries(Object.entries(node.properties).map(([key, child]) => [key, normalizeNode(child, kept)]));
133
+ out.required = Object.keys(node.properties);
134
+ out.additionalProperties = false;
135
+ }
136
+ if (mainType(node) === "array") out.items = normalizeNode(node.items, kept);
137
+ return out;
138
+ }
139
+ function normalizeForProvider(schema, provider) {
140
+ return normalizeNode(schema, KEPT_CONSTRAINTS[provider] ?? /* @__PURE__ */ new Set());
141
+ }
142
+ function matchesType(type, value) {
143
+ switch (type) {
144
+ case "null":
145
+ return value === null;
146
+ case "object":
147
+ return value !== null && typeof value === "object" && !Array.isArray(value);
148
+ case "array":
149
+ return Array.isArray(value);
150
+ case "integer":
151
+ return Number.isInteger(value);
152
+ case "number":
153
+ return typeof value === "number" && Number.isFinite(value);
154
+ default:
155
+ return typeof value === type;
156
+ }
157
+ }
158
+ function valueProblems(node, value, at) {
159
+ const types = typesOf(node);
160
+ const type = types.find((candidate) => matchesType(candidate, value));
161
+ if (!type) return [`${at}: expected ${types.join(" or ")}`];
162
+ if (type === "string") {
163
+ if (node.enum && !node.enum.includes(value)) return [`${at}: expected one of ${node.enum.join(", ")}`];
164
+ if (node.maxLength !== void 0 && [...value].length > node.maxLength) return [`${at}: longer than ${node.maxLength} characters`];
165
+ }
166
+ if (type === "array") {
167
+ if (node.minItems !== void 0 && value.length < node.minItems) return [`${at}: fewer than ${node.minItems} items`];
168
+ if (node.maxItems !== void 0 && value.length > node.maxItems) return [`${at}: more than ${node.maxItems} items`];
169
+ return value.flatMap((item, index) => valueProblems(node.items, item, `${at}[${index}]`));
170
+ }
171
+ if (type === "object") {
172
+ const keys = Object.keys(node.properties);
173
+ const extra = Object.keys(value).filter((key) => !keys.includes(key));
174
+ const problems = extra.map((key) => `${at}.${key}: not expected`);
175
+ for (const key of keys) {
176
+ if (!Object.hasOwn(value, key)) problems.push(`${at}.${key}: missing`);
177
+ else problems.push(...valueProblems(node.properties[key], value[key], `${at}.${key}`));
178
+ }
179
+ return problems;
180
+ }
181
+ return [];
182
+ }
183
+ function validate(schema, value) {
184
+ return valueProblems(schema, value, "$");
185
+ }
186
+ export {
187
+ checkSchema,
188
+ normalizeForProvider,
189
+ validate
190
+ };
package/dist/server.js ADDED
@@ -0,0 +1,19 @@
1
+ import { definePrompt, customizePrompt, configureAi, listPrompts } from "./prompts.server.js";
2
+ import { runPrompt } from "./run.server.js";
3
+ import { saveResult, getResults, dismissResult, deleteResults } from "./results.server.js";
4
+ import { getUsage } from "./runs.server.js";
5
+ import { isAiConfigured, describeAiConfig } from "./config.server.js";
6
+ export {
7
+ configureAi,
8
+ customizePrompt,
9
+ definePrompt,
10
+ deleteResults,
11
+ describeAiConfig,
12
+ dismissResult,
13
+ getResults,
14
+ getUsage,
15
+ isAiConfigured,
16
+ listPrompts,
17
+ runPrompt,
18
+ saveResult
19
+ };
package/dist/source.js ADDED
@@ -0,0 +1,4 @@
1
+ const SOURCE = "@fraza/module-ai";
2
+ export {
3
+ SOURCE
4
+ };
package/dist/studio.js ADDED
@@ -0,0 +1,205 @@
1
+ const MAX_VARIANTS = 3;
2
+ const REQUEST_MAX = 5e3;
3
+ const MAX_ASKS = 20;
4
+ const MAX_SUGGESTIONS = 8;
5
+ const PLAN_TITLES = 3;
6
+ const PLAN_SECTIONS = 12;
7
+ const PLAN_TITLE_MAX = 200;
8
+ const PLAN_SUMMARY_MAX = 400;
9
+ const MAX_HINTS = 8;
10
+ const STUDIO_INPUT = Object.freeze({
11
+ type: "object",
12
+ properties: {
13
+ request: { type: "string", maxLength: REQUEST_MAX, title: "Demande" }
14
+ },
15
+ required: ["request"]
16
+ });
17
+ const UNCHANGED = "null si tu ne changes pas ce texte.";
18
+ function versionField(node) {
19
+ const free = node.type === "string" && node.enum === void 0;
20
+ if (!free) return node;
21
+ return { ...node, type: ["string", "null"], description: [node.description, UNCHANGED].filter(Boolean).join(" ") };
22
+ }
23
+ function studioOutput(output, variants) {
24
+ const properties = Object.fromEntries(Object.entries(output.properties).map(([key, node]) => [key, versionField(node)]));
25
+ return {
26
+ type: "object",
27
+ properties: {
28
+ message: {
29
+ type: "string",
30
+ maxLength: 600,
31
+ description: "Ta r\xE9ponse \xE0 la personne, une ou deux phrases dans sa langue : ce que tu as chang\xE9 et pourquoi."
32
+ },
33
+ versions: {
34
+ type: "array",
35
+ minItems: variants,
36
+ maxItems: variants,
37
+ items: { type: "object", properties, required: Object.keys(properties) }
38
+ }
39
+ },
40
+ required: ["message", "versions"]
41
+ };
42
+ }
43
+ function planOutput() {
44
+ const line = { type: "string", maxLength: PLAN_TITLE_MAX };
45
+ return {
46
+ type: "object",
47
+ properties: {
48
+ message: {
49
+ type: "string",
50
+ maxLength: 600,
51
+ description: "Ta r\xE9ponse \xE0 la personne, une ou deux phrases dans sa langue : l'angle du plan."
52
+ },
53
+ titles: {
54
+ type: "array",
55
+ minItems: PLAN_TITLES,
56
+ maxItems: PLAN_TITLES,
57
+ items: line,
58
+ description: "Des titres nettement diff\xE9rents les uns des autres, le meilleur d'abord."
59
+ },
60
+ sections: {
61
+ type: "array",
62
+ minItems: 1,
63
+ maxItems: PLAN_SECTIONS,
64
+ items: {
65
+ type: "object",
66
+ properties: {
67
+ title: { ...line, description: "L'intertitre de la section." },
68
+ summary: { type: "string", maxLength: PLAN_SUMMARY_MAX, description: "Une phrase : ce que la section couvre." }
69
+ },
70
+ required: ["title", "summary"]
71
+ }
72
+ }
73
+ },
74
+ required: ["message", "titles", "sections"]
75
+ };
76
+ }
77
+ function planText(plan) {
78
+ const lines = [];
79
+ if (plan.title) lines.push(`Titre retenu : ${plan.title}`);
80
+ lines.push("Sections, dans l'ordre :");
81
+ plan.sections.forEach((section, index) => {
82
+ lines.push(`${index + 1}. ${section.title}${section.summary ? ` : ${section.summary}` : ""}`);
83
+ });
84
+ return lines.join("\n");
85
+ }
86
+ function studioFollowUp({ asks = [], previous = null, variants, phase = null, plan = null }) {
87
+ const lines = [];
88
+ if (asks.length > 0) {
89
+ lines.push("Demandes pr\xE9c\xE9dentes, dans l'ordre :", ...asks.map((ask) => `- ${ask}`), "");
90
+ }
91
+ if (previous && Object.keys(previous).length > 0) {
92
+ lines.push(
93
+ "Ce que la personne a gard\xE9 de tes propositions, au format JSON (un champ absent est tel que dans le contenu actuel) :",
94
+ JSON.stringify(previous),
95
+ "",
96
+ "Pars de l\xE0 : applique la nouvelle demande \xE0 ce texte."
97
+ );
98
+ }
99
+ if (phase === "plan") {
100
+ if (plan) lines.push("Le plan actuel, tel que la personne l'a retouch\xE9 : pars de lui et de la demande.", planText(plan), "");
101
+ lines.push(
102
+ "Ne r\xE9dige pas encore le contenu. Propose d'abord son plan :",
103
+ `${PLAN_TITLES} titres nettement diff\xE9rents, puis les sections dans l'ordre, chacune avec son intertitre et une phrase qui dit ce qu'elle couvre.`,
104
+ "Pour un contenu d\xE9j\xE0 \xE9crit, pars de sa structure et propose celle qui le servirait le mieux."
105
+ );
106
+ return lines.join("\n");
107
+ }
108
+ if (plan) {
109
+ lines.push("Suis ce plan, valid\xE9 par la personne, section par section :", planText(plan), "");
110
+ }
111
+ lines.push(variants > 1 ? `Propose ${variants} versions nettement diff\xE9rentes les unes des autres.` : "Propose une version.");
112
+ lines.push("Dans chaque version, renvoie null pour un texte que tu ne changes pas.");
113
+ return lines.join("\n");
114
+ }
115
+ function checkSuggestions(suggestions, at) {
116
+ if (suggestions === void 0) return [];
117
+ if (!Array.isArray(suggestions) || suggestions.length > MAX_SUGGESTIONS) {
118
+ throw new TypeError(`${at}: suggestions is a list of ${MAX_SUGGESTIONS} at most`);
119
+ }
120
+ return suggestions.map((item, index) => {
121
+ const label = typeof (item == null ? void 0 : item.label) === "string" ? item.label.trim() : "";
122
+ const request = typeof (item == null ? void 0 : item.request) === "string" ? item.request.trim() : label;
123
+ const variants = (item == null ? void 0 : item.variants) ?? 1;
124
+ if (!label || label.length > 40) throw new TypeError(`${at}: suggestion ${index} needs a label of 40 characters at most`);
125
+ if (!request || request.length > REQUEST_MAX) throw new TypeError(`${at}: suggestion ${index} has an empty or too long request`);
126
+ if (!Number.isInteger(variants) || variants < 1 || variants > MAX_VARIANTS) {
127
+ throw new TypeError(`${at}: suggestion ${index} asks for 1 to ${MAX_VARIANTS} variants`);
128
+ }
129
+ return Object.freeze({ label, request, variants });
130
+ });
131
+ }
132
+ function checkHints(hints, at) {
133
+ if (hints === void 0) return [];
134
+ if (!Array.isArray(hints) || hints.length > MAX_HINTS) {
135
+ throw new TypeError(`${at}: hints is a list of ${MAX_HINTS} at most`);
136
+ }
137
+ const seen = /* @__PURE__ */ new Set();
138
+ return hints.map((item, index) => {
139
+ var _a;
140
+ const label = typeof (item == null ? void 0 : item.label) === "string" ? item.label.trim() : "";
141
+ if (seen.has(label)) throw new TypeError(`${at}: hint ${index} repeats the label ${JSON.stringify(label)}`);
142
+ seen.add(label);
143
+ if ((item == null ? void 0 : item.text) !== void 0 && typeof item.text !== "string") throw new TypeError(`${at}: hint ${index} has a text that is not a string`);
144
+ const text = (item == null ? void 0 : item.text) === void 0 ? label : item.text.trim();
145
+ if (!label || label.length > 40) throw new TypeError(`${at}: hint ${index} needs a label of 40 characters at most`);
146
+ if (!text || text.length > 500) throw new TypeError(`${at}: hint ${index} has an empty or too long text`);
147
+ if ((item == null ? void 0 : item.group) !== void 0 && !(typeof item.group === "string" && item.group.trim() && item.group.trim().length <= 30)) {
148
+ throw new TypeError(`${at}: hint ${index} has a group that is not a name of 30 characters at most`);
149
+ }
150
+ return Object.freeze({ label, text, group: ((_a = item == null ? void 0 : item.group) == null ? void 0 : _a.trim()) ?? null });
151
+ });
152
+ }
153
+ function readPlan(plan) {
154
+ if (plan === null || plan === void 0) return null;
155
+ if (typeof plan !== "object" || Array.isArray(plan)) return void 0;
156
+ const title = plan.title ?? "";
157
+ const sections = plan.sections;
158
+ if (typeof title !== "string" || title.length > PLAN_TITLE_MAX) return void 0;
159
+ if (!Array.isArray(sections) || sections.length === 0 || sections.length > PLAN_SECTIONS) return void 0;
160
+ const checked = [];
161
+ for (const section of sections) {
162
+ const sectionTitle = section == null ? void 0 : section.title;
163
+ const summary = (section == null ? void 0 : section.summary) ?? "";
164
+ if (typeof sectionTitle !== "string" || !sectionTitle.trim() || sectionTitle.length > PLAN_TITLE_MAX) return void 0;
165
+ if (typeof summary !== "string" || summary.length > PLAN_SUMMARY_MAX) return void 0;
166
+ checked.push({ title: sectionTitle.trim(), summary: summary.trim() });
167
+ }
168
+ return { title: title.trim(), sections: checked };
169
+ }
170
+ function readTurn(body) {
171
+ const asks = (body == null ? void 0 : body.asks) ?? [];
172
+ const previous = (body == null ? void 0 : body.previous) ?? null;
173
+ const variants = (body == null ? void 0 : body.variants) ?? 1;
174
+ if (!Array.isArray(asks) || asks.length > MAX_ASKS) return null;
175
+ if (asks.some((ask) => typeof ask !== "string" || ask.length > REQUEST_MAX)) return null;
176
+ if (previous !== null && (typeof previous !== "object" || Array.isArray(previous))) return null;
177
+ if (!Number.isInteger(variants) || variants < 1 || variants > MAX_VARIANTS) return null;
178
+ const phase = (body == null ? void 0 : body.phase) ?? null;
179
+ if (phase !== null && phase !== "plan") return null;
180
+ const plan = readPlan(body == null ? void 0 : body.plan);
181
+ if (plan === void 0) return null;
182
+ return { asks, previous, variants, phase, plan };
183
+ }
184
+ function hasHtmlField(output) {
185
+ return Object.values((output == null ? void 0 : output.properties) ?? {}).some((node) => (node == null ? void 0 : node.format) === "html");
186
+ }
187
+ export {
188
+ MAX_ASKS,
189
+ MAX_HINTS,
190
+ MAX_SUGGESTIONS,
191
+ MAX_VARIANTS,
192
+ PLAN_SECTIONS,
193
+ PLAN_SUMMARY_MAX,
194
+ PLAN_TITLES,
195
+ PLAN_TITLE_MAX,
196
+ REQUEST_MAX,
197
+ STUDIO_INPUT,
198
+ checkHints,
199
+ checkSuggestions,
200
+ hasHtmlField,
201
+ planOutput,
202
+ readTurn,
203
+ studioFollowUp,
204
+ studioOutput
205
+ };
@@ -0,0 +1,24 @@
1
+ import { definePrompt } from "./prompts.server.js";
2
+ import { SOURCE } from "./source.js";
3
+ const TEST_PROMPT = "ai.test";
4
+ function defineTestPrompt() {
5
+ definePrompt({
6
+ name: TEST_PROMPT,
7
+ label: "Tester la configuration",
8
+ instructions: "R\xE9ponds par une courte phrase en fran\xE7ais qui confirme que tu fonctionnes.",
9
+ output: {
10
+ type: "object",
11
+ properties: {
12
+ message: { type: "string", maxLength: 200, description: "Une phrase de confirmation." }
13
+ },
14
+ required: ["message"]
15
+ },
16
+ // Room for the reasoning of a model that always thinks first.
17
+ maxTokens: 8e3,
18
+ build: ({ instructions }) => instructions
19
+ }, { source: SOURCE });
20
+ }
21
+ export {
22
+ TEST_PROMPT,
23
+ defineTestPrompt
24
+ };
@@ -0,0 +1,4 @@
1
+ const MODULE_VERSION = "0.4.22";
2
+ export {
3
+ MODULE_VERSION
4
+ };
@@ -0,0 +1,29 @@
1
+ import { registerWidgetFetcher } from "@fraza/core/features/admin";
2
+ import { AI_WIDGETS } from "./admin/widgets.js";
3
+ import { describeAiConfig } from "./config.server.js";
4
+ import { getUsage } from "./runs.server.js";
5
+ import { SOURCE } from "./source.js";
6
+ async function fetchUsage() {
7
+ const to = /* @__PURE__ */ new Date();
8
+ const from = new Date(Date.UTC(to.getUTCFullYear(), to.getUTCMonth(), 1));
9
+ const { totals } = await getUsage({ from, to });
10
+ const config = describeAiConfig();
11
+ return {
12
+ configured: config.configured,
13
+ provider: config.providerLabel,
14
+ model: config.model,
15
+ calls: totals.calls,
16
+ errors: totals.errors,
17
+ inputTokens: totals.inputTokens,
18
+ outputTokens: totals.outputTokens
19
+ };
20
+ }
21
+ function registerWidgetFetchers() {
22
+ registerWidgetFetcher(AI_WIDGETS.USAGE.id, fetchUsage, {
23
+ permission: AI_WIDGETS.USAGE.permission,
24
+ source: SOURCE
25
+ });
26
+ }
27
+ export {
28
+ registerWidgetFetchers
29
+ };
package/package.json CHANGED
@@ -1,6 +1,69 @@
1
1
  {
2
2
  "name": "@fraza/module-ai",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.4.22",
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
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "license": "MIT",
9
+ "author": "HYKO",
10
+ "type": "module",
11
+ "main": "./dist/index.js",
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.js"
15
+ },
16
+ "./admin": {
17
+ "import": "./dist/admin.js"
18
+ },
19
+ "./server": {
20
+ "import": "./dist/server.js"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "README.md",
26
+ "LICENSE",
27
+ "THIRD-PARTY-NOTICES.md",
28
+ "CHANGELOG.md",
29
+ "changelog.json"
30
+ ],
31
+ "keywords": [
32
+ "fraza-module"
33
+ ],
34
+ "scripts": {
35
+ "build": "fraza-module build",
36
+ "test": "node --import @fraza/core/tools/test/register --test test/",
37
+ "sync:version": "fraza-module sync-version",
38
+ "build:changelog": "fraza-module changelog",
39
+ "build:notices": "fraza-module notices",
40
+ "prepublishOnly": "fraza-module sync-version --check && npm run build && fraza-module check",
41
+ "audit": "npm audit --omit=dev --omit=peer",
42
+ "release": "fraza-module release",
43
+ "release:publish": "fraza-module publish"
44
+ },
45
+ "engines": {
46
+ "node": ">=20.12"
47
+ },
48
+ "devDependencies": {
49
+ "@fraza/icons": "*",
50
+ "tsup": "^8.0.0",
51
+ "typescript": "^6.0.2"
52
+ },
53
+ "peerDependencies": {
54
+ "@fraza/core": "*",
55
+ "@fraza/icons": "*",
56
+ "next": ">=16.0.0",
57
+ "react": "^19.0.0",
58
+ "react-dom": "^19.0.0"
59
+ },
60
+ "fraza": {
61
+ "contract": 1,
62
+ "breaking": false,
63
+ "action": [],
64
+ "envAdded": [],
65
+ "envRemoved": [],
66
+ "template": false,
67
+ "envRenamed": []
68
+ }
69
+ }