@lastbrain/module-ai 0.1.0
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/dist/ai.build.config.d.ts +4 -0
- package/dist/ai.build.config.d.ts.map +1 -0
- package/dist/ai.build.config.js +86 -0
- package/dist/api/admin/user-token/[id].d.ts +29 -0
- package/dist/api/admin/user-token/[id].d.ts.map +1 -0
- package/dist/api/admin/user-token/[id].js +57 -0
- package/dist/api/admin/user-token.d.ts +20 -0
- package/dist/api/admin/user-token.d.ts.map +1 -0
- package/dist/api/admin/user-token.js +92 -0
- package/dist/api/admin/user_prompts.d.ts +17 -0
- package/dist/api/admin/user_prompts.d.ts.map +1 -0
- package/dist/api/admin/user_prompts.js +94 -0
- package/dist/api/admin/user_token_ledger.d.ts +17 -0
- package/dist/api/admin/user_token_ledger.d.ts.map +1 -0
- package/dist/api/admin/user_token_ledger.js +94 -0
- package/dist/api/auth/generate-image.d.ts +11 -0
- package/dist/api/auth/generate-image.d.ts.map +1 -0
- package/dist/api/auth/generate-image.js +104 -0
- package/dist/api/auth/generate-text.d.ts +11 -0
- package/dist/api/auth/generate-text.d.ts.map +1 -0
- package/dist/api/auth/generate-text.js +96 -0
- package/dist/api/auth/user_prompts.d.ts +17 -0
- package/dist/api/auth/user_prompts.d.ts.map +1 -0
- package/dist/api/auth/user_prompts.js +94 -0
- package/dist/api/auth/user_token_ledger.d.ts +17 -0
- package/dist/api/auth/user_token_ledger.d.ts.map +1 -0
- package/dist/api/auth/user_token_ledger.js +94 -0
- package/dist/components/Doc.d.ts +2 -0
- package/dist/components/Doc.d.ts.map +1 -0
- package/dist/components/Doc.js +5 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/server.d.ts +61 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +186 -0
- package/dist/web/admin/UserTokenIdPage.d.ts +6 -0
- package/dist/web/admin/UserTokenIdPage.d.ts.map +1 -0
- package/dist/web/admin/UserTokenIdPage.js +8 -0
- package/dist/web/admin/UserTokenPage.d.ts +2 -0
- package/dist/web/admin/UserTokenPage.d.ts.map +1 -0
- package/dist/web/admin/UserTokenPage.js +6 -0
- package/dist/web/auth/TokenPage.d.ts +2 -0
- package/dist/web/auth/TokenPage.d.ts.map +1 -0
- package/dist/web/auth/TokenPage.js +6 -0
- package/dist/web/components/ImageGenerative.d.ts +22 -0
- package/dist/web/components/ImageGenerative.d.ts.map +1 -0
- package/dist/web/components/ImageGenerative.js +87 -0
- package/dist/web/components/TextareaGenerative.d.ts +23 -0
- package/dist/web/components/TextareaGenerative.d.ts.map +1 -0
- package/dist/web/components/TextareaGenerative.js +60 -0
- package/package.json +72 -0
- package/supabase/migrations/20251121000000_ai_tokens.sql +189 -0
- package/supabase/migrations/20251121093113_module-ai_init.sql +122 -0
- package/supabase/migrations-down/20251121000000_ai_tokens.sql +23 -0
- package/supabase/migrations-down/20251121093113_module-ai_init.sql +11 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Card, CardBody, CardHeader } from "@lastbrain/ui";
|
|
4
|
+
import { use } from "react";
|
|
5
|
+
export function UserTokenIdPage({ params, }) {
|
|
6
|
+
const { id } = use(params);
|
|
7
|
+
return (_jsx("div", { className: "container mx-auto p-6", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h1", { className: "text-2xl font-bold", children: "D\u00E9tails Token Utilisateur" }) }), _jsx(CardBody, { children: _jsxs("p", { className: "text-default-600", children: ["D\u00E9tails pour l'utilisateur: ", _jsx("strong", { children: id })] }) })] }) }));
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UserTokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/admin/UserTokenPage.tsx"],"names":[],"mappings":"AAIA,wBAAgB,aAAa,4CAe5B"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Card, CardBody, CardHeader } from "@lastbrain/ui";
|
|
4
|
+
export function UserTokenPage() {
|
|
5
|
+
return (_jsx("div", { className: "container mx-auto p-6", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h1", { className: "text-2xl font-bold", children: "UserToken" }) }), _jsx(CardBody, { children: _jsx("p", { className: "text-default-600", children: "Contenu de la page UserToken (section: admin)" }) })] }) }));
|
|
6
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/auth/TokenPage.tsx"],"names":[],"mappings":"AAIA,wBAAgB,SAAS,4CAexB"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Card, CardBody, CardHeader } from "@lastbrain/ui";
|
|
4
|
+
export function TokenPage() {
|
|
5
|
+
return (_jsx("div", { className: "container mx-auto p-6", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h1", { className: "text-2xl font-bold", children: "Token" }) }), _jsx(CardBody, { children: _jsx("p", { className: "text-default-600", children: "Contenu de la page token (section: auth)" }) })] }) }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface GenerativeImageResponse {
|
|
2
|
+
imageUrl: string;
|
|
3
|
+
tokensUsed: number;
|
|
4
|
+
tokensRemaining: number;
|
|
5
|
+
model: string;
|
|
6
|
+
cost?: number;
|
|
7
|
+
prompt: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ImageGenerativeProps {
|
|
10
|
+
prompt: string;
|
|
11
|
+
model?: string;
|
|
12
|
+
size?: "256x256" | "512x512" | "1024x1024" | "1792x1024" | "1024x1792";
|
|
13
|
+
quality?: "standard" | "hd";
|
|
14
|
+
onChange?: (response: GenerativeImageResponse) => void;
|
|
15
|
+
onError?: (error: Error) => void;
|
|
16
|
+
className?: string;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
apiEndpoint?: string;
|
|
19
|
+
showTokenBalance?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function ImageGenerative({ prompt, model, size, quality, onChange, onError, className, disabled, apiEndpoint, showTokenBalance, }: ImageGenerativeProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
//# sourceMappingURL=ImageGenerative.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageGenerative.d.ts","sourceRoot":"","sources":["../../../src/web/components/ImageGenerative.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;IACvE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,uBAAuB,KAAK,IAAI,CAAC;IACvD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,KAAkB,EAClB,IAAkB,EAClB,OAAoB,EACpB,QAAQ,EACR,OAAO,EACP,SAAS,EACT,QAAgB,EAChB,WAAwC,EACxC,gBAAuB,GACxB,EAAE,oBAAoB,2CA6LtB"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { Button, Chip, Progress, Card, CardBody } from "@lastbrain/ui";
|
|
5
|
+
import { Sparkles, Loader2, AlertCircle, Download } from "lucide-react";
|
|
6
|
+
import Image from "next/image";
|
|
7
|
+
export function ImageGenerative({ prompt, model = "dall-e-3", size = "1024x1024", quality = "standard", onChange, onError, className, disabled = false, apiEndpoint = "/api/auth/generate-image", showTokenBalance = true, }) {
|
|
8
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
9
|
+
const [generatedImage, setGeneratedImage] = useState(null);
|
|
10
|
+
const [error, setError] = useState(null);
|
|
11
|
+
const [tokenBalance, setTokenBalance] = useState(null);
|
|
12
|
+
const handleGenerate = useCallback(async () => {
|
|
13
|
+
if (!prompt || isGenerating)
|
|
14
|
+
return;
|
|
15
|
+
setIsGenerating(true);
|
|
16
|
+
setError(null);
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(apiEndpoint, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: { "Content-Type": "application/json" },
|
|
21
|
+
body: JSON.stringify({
|
|
22
|
+
prompt,
|
|
23
|
+
model,
|
|
24
|
+
size,
|
|
25
|
+
quality,
|
|
26
|
+
}),
|
|
27
|
+
});
|
|
28
|
+
if (!response.ok) {
|
|
29
|
+
const errorData = await response.json();
|
|
30
|
+
throw new Error(errorData.error || "Erreur lors de la génération");
|
|
31
|
+
}
|
|
32
|
+
const data = await response.json();
|
|
33
|
+
const imageResponse = {
|
|
34
|
+
imageUrl: data.imageUrl,
|
|
35
|
+
tokensUsed: data.tokensUsed || 0,
|
|
36
|
+
tokensRemaining: data.tokensRemaining || 0,
|
|
37
|
+
model: data.model || model,
|
|
38
|
+
cost: data.cost,
|
|
39
|
+
prompt: prompt,
|
|
40
|
+
};
|
|
41
|
+
setGeneratedImage(imageResponse);
|
|
42
|
+
setTokenBalance(data.tokensRemaining);
|
|
43
|
+
if (onChange) {
|
|
44
|
+
onChange(imageResponse);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
const errorMessage = err instanceof Error ? err.message : "Erreur inconnue";
|
|
49
|
+
setError(errorMessage);
|
|
50
|
+
if (onError) {
|
|
51
|
+
onError(err);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
setIsGenerating(false);
|
|
56
|
+
}
|
|
57
|
+
}, [
|
|
58
|
+
prompt,
|
|
59
|
+
model,
|
|
60
|
+
size,
|
|
61
|
+
quality,
|
|
62
|
+
apiEndpoint,
|
|
63
|
+
onChange,
|
|
64
|
+
onError,
|
|
65
|
+
isGenerating,
|
|
66
|
+
]);
|
|
67
|
+
const handleDownload = useCallback(async () => {
|
|
68
|
+
if (!generatedImage?.imageUrl)
|
|
69
|
+
return;
|
|
70
|
+
try {
|
|
71
|
+
const response = await fetch(generatedImage.imageUrl);
|
|
72
|
+
const blob = await response.blob();
|
|
73
|
+
const url = window.URL.createObjectURL(blob);
|
|
74
|
+
const link = document.createElement("a");
|
|
75
|
+
link.href = url;
|
|
76
|
+
link.download = `generated-image-${Date.now()}.png`;
|
|
77
|
+
document.body.appendChild(link);
|
|
78
|
+
link.click();
|
|
79
|
+
document.body.removeChild(link);
|
|
80
|
+
window.URL.revokeObjectURL(url);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
console.error("Erreur lors du téléchargement:", error);
|
|
84
|
+
}
|
|
85
|
+
}, [generatedImage]);
|
|
86
|
+
return (_jsx("div", { className: className, children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { color: "primary", size: "sm", onClick: handleGenerate, disabled: disabled || isGenerating || !prompt, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(Sparkles, { size: 16 })), children: isGenerating ? "Génération..." : "Générer l'image" }), showTokenBalance && tokenBalance !== null && (_jsxs(Chip, { size: "sm", variant: "flat", color: "success", children: [tokenBalance.toLocaleString(), " tokens restants"] }))] }), generatedImage && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs(Chip, { size: "sm", variant: "flat", children: [generatedImage.tokensUsed, " tokens utilis\u00E9s"] }), _jsx(Chip, { size: "sm", variant: "flat", color: "primary", children: generatedImage.model }), generatedImage.cost && (_jsxs(Chip, { size: "sm", variant: "flat", color: "warning", children: ["$", generatedImage.cost.toFixed(4)] })), _jsx(Button, { size: "sm", variant: "flat", onClick: handleDownload, startContent: _jsx(Download, { size: 16 }), children: "T\u00E9l\u00E9charger" })] }))] }), isGenerating && (_jsxs("div", { className: "space-y-2", children: [_jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": "G\u00E9n\u00E9ration en cours...", className: "max-w-md" }), _jsx("p", { className: "text-sm text-default-500", children: "G\u00E9n\u00E9ration de l'image en cours... Cela peut prendre quelques instants." })] })), error && (_jsxs("div", { className: "flex items-center gap-2 text-danger text-sm p-3 bg-danger-50 rounded-md", children: [_jsx(AlertCircle, { size: 16 }), _jsx("span", { children: error })] })), generatedImage && !isGenerating && (_jsx(Card, { children: _jsxs(CardBody, { children: [_jsx("div", { className: "relative w-full aspect-square", children: _jsx(Image, { src: generatedImage.imageUrl, alt: generatedImage.prompt, fill: true, className: "object-contain rounded-lg", priority: true }) }), _jsxs("div", { className: "mt-4", children: [_jsxs("p", { className: "text-sm text-default-600", children: [_jsx("strong", { children: "Prompt:" }), " ", generatedImage.prompt] }), _jsxs("p", { className: "text-xs text-default-400 mt-1", children: [size, " \u2022 ", quality] })] })] }) }))] }) }));
|
|
87
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface GenerativeResponse {
|
|
2
|
+
text: string;
|
|
3
|
+
tokensUsed: number;
|
|
4
|
+
tokensRemaining: number;
|
|
5
|
+
model: string;
|
|
6
|
+
cost?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface TextareaGenerativeProps {
|
|
9
|
+
prompt: string;
|
|
10
|
+
model?: string;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
defaultValue?: string;
|
|
13
|
+
onChange?: (response: GenerativeResponse) => void;
|
|
14
|
+
onError?: (error: Error) => void;
|
|
15
|
+
className?: string;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
minRows?: number;
|
|
18
|
+
maxRows?: number;
|
|
19
|
+
apiEndpoint?: string;
|
|
20
|
+
showTokenBalance?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare function TextareaGenerative({ prompt, model, placeholder, defaultValue, onChange, onError, className, disabled, minRows, maxRows, apiEndpoint, showTokenBalance, }: TextareaGenerativeProps): import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
//# sourceMappingURL=TextareaGenerative.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TextareaGenerative.d.ts","sourceRoot":"","sources":["../../../src/web/components/TextareaGenerative.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAClD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,KAAqB,EACrB,WAAmC,EACnC,YAAiB,EACjB,QAAQ,EACR,OAAO,EACP,SAAS,EACT,QAAgB,EAChB,OAAW,EACX,OAAY,EACZ,WAAuC,EACvC,gBAAuB,GACxB,EAAE,uBAAuB,2CAwIzB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { Textarea, Button, Chip, Progress } from "@lastbrain/ui";
|
|
5
|
+
import { Sparkles, Loader2, AlertCircle } from "lucide-react";
|
|
6
|
+
export function TextareaGenerative({ prompt, model = "gpt-4o-mini", placeholder = "Générer du texte...", defaultValue = "", onChange, onError, className, disabled = false, minRows = 4, maxRows = 20, apiEndpoint = "/api/auth/generate-text", showTokenBalance = true, }) {
|
|
7
|
+
const [value, setValue] = useState(defaultValue);
|
|
8
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
9
|
+
const [lastResponse, setLastResponse] = useState(null);
|
|
10
|
+
const [error, setError] = useState(null);
|
|
11
|
+
const [tokenBalance, setTokenBalance] = useState(null);
|
|
12
|
+
const handleGenerate = useCallback(async () => {
|
|
13
|
+
if (!prompt || isGenerating)
|
|
14
|
+
return;
|
|
15
|
+
setIsGenerating(true);
|
|
16
|
+
setError(null);
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(apiEndpoint, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: { "Content-Type": "application/json" },
|
|
21
|
+
body: JSON.stringify({
|
|
22
|
+
prompt,
|
|
23
|
+
model,
|
|
24
|
+
context: value,
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
const errorData = await response.json();
|
|
29
|
+
throw new Error(errorData.error || "Erreur lors de la génération");
|
|
30
|
+
}
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
const generativeResponse = {
|
|
33
|
+
text: data.text,
|
|
34
|
+
tokensUsed: data.tokensUsed || 0,
|
|
35
|
+
tokensRemaining: data.tokensRemaining || 0,
|
|
36
|
+
model: data.model || model,
|
|
37
|
+
cost: data.cost,
|
|
38
|
+
};
|
|
39
|
+
setValue(data.text);
|
|
40
|
+
setLastResponse(generativeResponse);
|
|
41
|
+
setTokenBalance(data.tokensRemaining);
|
|
42
|
+
if (onChange) {
|
|
43
|
+
onChange(generativeResponse);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const errorMessage = err instanceof Error ? err.message : "Erreur inconnue";
|
|
48
|
+
setError(errorMessage);
|
|
49
|
+
if (onError) {
|
|
50
|
+
onError(err);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
setIsGenerating(false);
|
|
55
|
+
}
|
|
56
|
+
}, [prompt, model, value, apiEndpoint, onChange, onError, isGenerating]);
|
|
57
|
+
return (_jsx("div", { className: className, children: _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Textarea, { value: value, onChange: (e) => setValue(e.target.value), placeholder: placeholder, disabled: disabled || isGenerating, minRows: minRows, maxRows: maxRows, classNames: {
|
|
58
|
+
input: "resize-y",
|
|
59
|
+
} }), _jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { color: "primary", size: "sm", onClick: handleGenerate, disabled: disabled || isGenerating || !prompt, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(Sparkles, { size: 16 })), children: isGenerating ? "Génération..." : "Générer" }), showTokenBalance && tokenBalance !== null && (_jsxs(Chip, { size: "sm", variant: "flat", color: "success", children: [tokenBalance.toLocaleString(), " tokens restants"] }))] }), lastResponse && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs(Chip, { size: "sm", variant: "flat", children: [lastResponse.tokensUsed, " tokens utilis\u00E9s"] }), _jsx(Chip, { size: "sm", variant: "flat", color: "primary", children: lastResponse.model }), lastResponse.cost && (_jsxs(Chip, { size: "sm", variant: "flat", color: "warning", children: ["$", lastResponse.cost.toFixed(4)] }))] }))] }), isGenerating && (_jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": "G\u00E9n\u00E9ration en cours...", className: "max-w-md" })), error && (_jsxs("div", { className: "flex items-center gap-2 text-danger text-sm p-2 bg-danger-50 rounded-md", children: [_jsx(AlertCircle, { size: 16 }), _jsx("span", { children: error })] }))] }) }));
|
|
60
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lastbrain/module-ai",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Module de génération IA (texte et images) avec gestion de tokens pour LastBrain",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"lastbrain",
|
|
11
|
+
"module",
|
|
12
|
+
"ai",
|
|
13
|
+
"openai",
|
|
14
|
+
"generation",
|
|
15
|
+
"dalle",
|
|
16
|
+
"gpt",
|
|
17
|
+
"tokens"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/lastpublication/starter.git",
|
|
22
|
+
"directory": "packages/module-ai"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"supabase"
|
|
30
|
+
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@heroui/react": "^2.4.23",
|
|
33
|
+
"@heroui/system": "^2.4.23",
|
|
34
|
+
"@heroui/theme": "^2.4.23",
|
|
35
|
+
"@lastbrain/core": "0.1.0",
|
|
36
|
+
"@lastbrain/ui": "0.1.0",
|
|
37
|
+
"lucide-react": "^0.554.0",
|
|
38
|
+
"next": "^15.5.6",
|
|
39
|
+
"openai": "^4.76.0",
|
|
40
|
+
"react": "^19.0.0",
|
|
41
|
+
"react-dom": "^19.0.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"next": ">=15.0.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"typescript": "^5.4.0"
|
|
48
|
+
},
|
|
49
|
+
"exports": {
|
|
50
|
+
".": {
|
|
51
|
+
"types": "./dist/index.d.ts",
|
|
52
|
+
"default": "./dist/index.js"
|
|
53
|
+
},
|
|
54
|
+
"./server": {
|
|
55
|
+
"types": "./dist/server.d.ts",
|
|
56
|
+
"default": "./dist/server.js"
|
|
57
|
+
},
|
|
58
|
+
"./ai.build.config": {
|
|
59
|
+
"types": "./dist/ai.build.config.d.ts",
|
|
60
|
+
"default": "./dist/ai.build.config.js"
|
|
61
|
+
},
|
|
62
|
+
"./api/*": {
|
|
63
|
+
"types": "./dist/api/*.d.ts",
|
|
64
|
+
"default": "./dist/api/*.js"
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"sideEffects": false,
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "tsc -p tsconfig.json",
|
|
70
|
+
"dev": "tsc -p tsconfig.json --watch"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
-- AI Token System Migration
|
|
2
|
+
-- Gestion des tokens pour l'utilisation de l'IA
|
|
3
|
+
|
|
4
|
+
-- =====================================================
|
|
5
|
+
-- Table: user_token_ledger
|
|
6
|
+
-- =====================================================
|
|
7
|
+
-- Un seul ledger pour tous les mouvements de tokens
|
|
8
|
+
-- Crédit (amount > 0): purchase, gift, adjust+
|
|
9
|
+
-- Débit (amount < 0): use, adjust-
|
|
10
|
+
|
|
11
|
+
CREATE TABLE IF NOT EXISTS public.user_token_ledger (
|
|
12
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
13
|
+
owner_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
14
|
+
ts TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
15
|
+
type TEXT NOT NULL CHECK (type IN ('purchase', 'gift', 'use', 'adjust')),
|
|
16
|
+
amount BIGINT NOT NULL,
|
|
17
|
+
model TEXT NULL,
|
|
18
|
+
prompt TEXT NULL,
|
|
19
|
+
meta JSONB NOT NULL DEFAULT '{}'::JSONB,
|
|
20
|
+
created_by UUID NULL REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
21
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
-- =====================================================
|
|
25
|
+
-- Indexes
|
|
26
|
+
-- =====================================================
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_user_token_ledger_owner_ts ON public.user_token_ledger(owner_id, ts DESC);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_user_token_ledger_owner ON public.user_token_ledger(owner_id);
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_user_token_ledger_type_ts ON public.user_token_ledger(type, ts DESC);
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_user_token_ledger_usages ON public.user_token_ledger(owner_id, ts DESC) WHERE amount < 0;
|
|
31
|
+
|
|
32
|
+
-- =====================================================
|
|
33
|
+
-- Vue: user_token_balance_v
|
|
34
|
+
-- =====================================================
|
|
35
|
+
-- Solde courant par utilisateur
|
|
36
|
+
|
|
37
|
+
DROP VIEW IF EXISTS public.user_token_balance_v;
|
|
38
|
+
CREATE VIEW public.user_token_balance_v AS
|
|
39
|
+
SELECT
|
|
40
|
+
owner_id,
|
|
41
|
+
COALESCE(SUM(amount), 0)::BIGINT AS balance
|
|
42
|
+
FROM public.user_token_ledger
|
|
43
|
+
GROUP BY owner_id;
|
|
44
|
+
|
|
45
|
+
-- =====================================================
|
|
46
|
+
-- Trigger: Anti-solde négatif
|
|
47
|
+
-- =====================================================
|
|
48
|
+
-- Empêche une utilisation (amount < 0) si le solde devient négatif
|
|
49
|
+
|
|
50
|
+
CREATE OR REPLACE FUNCTION public.check_token_balance()
|
|
51
|
+
RETURNS TRIGGER AS $$
|
|
52
|
+
DECLARE
|
|
53
|
+
current_balance BIGINT;
|
|
54
|
+
BEGIN
|
|
55
|
+
-- Ne vérifier que pour les débits (use, adjust négatif)
|
|
56
|
+
IF NEW.amount >= 0 THEN
|
|
57
|
+
RETURN NEW;
|
|
58
|
+
END IF;
|
|
59
|
+
|
|
60
|
+
-- Calculer le solde actuel
|
|
61
|
+
SELECT COALESCE(SUM(amount), 0)
|
|
62
|
+
INTO current_balance
|
|
63
|
+
FROM public.user_token_ledger
|
|
64
|
+
WHERE owner_id = NEW.owner_id;
|
|
65
|
+
|
|
66
|
+
-- Vérifier si le nouveau solde serait négatif
|
|
67
|
+
IF (current_balance + NEW.amount) < 0 THEN
|
|
68
|
+
RAISE EXCEPTION 'INSUFFICIENT_TOKEN_BALANCE: current=%, requested=%',
|
|
69
|
+
current_balance, ABS(NEW.amount);
|
|
70
|
+
END IF;
|
|
71
|
+
|
|
72
|
+
RETURN NEW;
|
|
73
|
+
END;
|
|
74
|
+
$$ LANGUAGE plpgsql;
|
|
75
|
+
|
|
76
|
+
DROP TRIGGER IF EXISTS trigger_check_token_balance ON public.user_token_ledger;
|
|
77
|
+
CREATE TRIGGER trigger_check_token_balance
|
|
78
|
+
BEFORE INSERT ON public.user_token_ledger
|
|
79
|
+
FOR EACH ROW
|
|
80
|
+
EXECUTE FUNCTION public.check_token_balance();
|
|
81
|
+
|
|
82
|
+
-- =====================================================
|
|
83
|
+
-- RLS (Row Level Security)
|
|
84
|
+
-- =====================================================
|
|
85
|
+
|
|
86
|
+
ALTER TABLE public.user_token_ledger ENABLE ROW LEVEL SECURITY;
|
|
87
|
+
|
|
88
|
+
-- Policy: Les utilisateurs voient uniquement leurs propres tokens
|
|
89
|
+
DROP POLICY IF EXISTS user_token_ledger_select_own ON public.user_token_ledger;
|
|
90
|
+
CREATE POLICY user_token_ledger_select_own
|
|
91
|
+
ON public.user_token_ledger
|
|
92
|
+
FOR SELECT
|
|
93
|
+
USING (
|
|
94
|
+
owner_id = auth.uid()
|
|
95
|
+
OR is_superadmin(auth.uid())
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
-- Policy: Les utilisateurs peuvent créer leurs propres entrées de type 'use'
|
|
99
|
+
DROP POLICY IF EXISTS user_token_ledger_insert_own ON public.user_token_ledger;
|
|
100
|
+
CREATE POLICY user_token_ledger_insert_own
|
|
101
|
+
ON public.user_token_ledger
|
|
102
|
+
FOR INSERT
|
|
103
|
+
WITH CHECK (
|
|
104
|
+
(owner_id = auth.uid() AND type = 'use')
|
|
105
|
+
OR is_superadmin(auth.uid())
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
-- Policy: Seuls les superadmins peuvent update
|
|
109
|
+
DROP POLICY IF EXISTS user_token_ledger_update_admin ON public.user_token_ledger;
|
|
110
|
+
CREATE POLICY user_token_ledger_update_admin
|
|
111
|
+
ON public.user_token_ledger
|
|
112
|
+
FOR UPDATE
|
|
113
|
+
USING (is_superadmin(auth.uid()));
|
|
114
|
+
|
|
115
|
+
-- Policy: Seuls les superadmins peuvent delete
|
|
116
|
+
DROP POLICY IF EXISTS user_token_ledger_delete_admin ON public.user_token_ledger;
|
|
117
|
+
CREATE POLICY user_token_ledger_delete_admin
|
|
118
|
+
ON public.user_token_ledger
|
|
119
|
+
FOR DELETE
|
|
120
|
+
USING (is_superadmin(auth.uid()));
|
|
121
|
+
|
|
122
|
+
-- =====================================================
|
|
123
|
+
-- Table: user_prompts
|
|
124
|
+
-- =====================================================
|
|
125
|
+
-- Prompts personnalisés des utilisateurs
|
|
126
|
+
|
|
127
|
+
CREATE TABLE IF NOT EXISTS public.user_prompts (
|
|
128
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
129
|
+
owner_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
130
|
+
type TEXT NOT NULL CHECK (type IN ('text', 'image')),
|
|
131
|
+
prompt TEXT NOT NULL,
|
|
132
|
+
label TEXT,
|
|
133
|
+
category TEXT,
|
|
134
|
+
is_favorite BOOLEAN DEFAULT FALSE,
|
|
135
|
+
usage_count INTEGER DEFAULT 0,
|
|
136
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
137
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
-- Index pour requêtes rapides
|
|
141
|
+
CREATE INDEX IF NOT EXISTS idx_user_prompts_owner_id ON public.user_prompts(owner_id);
|
|
142
|
+
CREATE INDEX IF NOT EXISTS idx_user_prompts_type ON public.user_prompts(type);
|
|
143
|
+
CREATE INDEX IF NOT EXISTS idx_user_prompts_is_favorite ON public.user_prompts(is_favorite);
|
|
144
|
+
|
|
145
|
+
-- RLS (Row Level Security)
|
|
146
|
+
ALTER TABLE public.user_prompts ENABLE ROW LEVEL SECURITY;
|
|
147
|
+
|
|
148
|
+
-- Politique: Les utilisateurs peuvent voir leurs propres prompts
|
|
149
|
+
DROP POLICY IF EXISTS "Users can view own prompts" ON public.user_prompts;
|
|
150
|
+
CREATE POLICY "Users can view own prompts"
|
|
151
|
+
ON public.user_prompts
|
|
152
|
+
FOR SELECT
|
|
153
|
+
USING (auth.uid() = owner_id);
|
|
154
|
+
|
|
155
|
+
-- Politique: Les utilisateurs peuvent insérer leurs propres prompts
|
|
156
|
+
DROP POLICY IF EXISTS "Users can insert own prompts" ON public.user_prompts;
|
|
157
|
+
CREATE POLICY "Users can insert own prompts"
|
|
158
|
+
ON public.user_prompts
|
|
159
|
+
FOR INSERT
|
|
160
|
+
WITH CHECK (auth.uid() = owner_id);
|
|
161
|
+
|
|
162
|
+
-- Politique: Les utilisateurs peuvent modifier leurs propres prompts
|
|
163
|
+
DROP POLICY IF EXISTS "Users can update own prompts" ON public.user_prompts;
|
|
164
|
+
CREATE POLICY "Users can update own prompts"
|
|
165
|
+
ON public.user_prompts
|
|
166
|
+
FOR UPDATE
|
|
167
|
+
USING (auth.uid() = owner_id);
|
|
168
|
+
|
|
169
|
+
-- Politique: Les utilisateurs peuvent supprimer leurs propres prompts
|
|
170
|
+
DROP POLICY IF EXISTS "Users can delete own prompts" ON public.user_prompts;
|
|
171
|
+
CREATE POLICY "Users can delete own prompts"
|
|
172
|
+
ON public.user_prompts
|
|
173
|
+
FOR DELETE
|
|
174
|
+
USING (auth.uid() = owner_id);
|
|
175
|
+
|
|
176
|
+
-- Trigger pour updated_at
|
|
177
|
+
CREATE OR REPLACE FUNCTION public.update_user_prompts_updated_at()
|
|
178
|
+
RETURNS TRIGGER AS $$
|
|
179
|
+
BEGIN
|
|
180
|
+
NEW.updated_at = NOW();
|
|
181
|
+
RETURN NEW;
|
|
182
|
+
END;
|
|
183
|
+
$$ LANGUAGE plpgsql;
|
|
184
|
+
|
|
185
|
+
DROP TRIGGER IF EXISTS update_user_prompts_updated_at ON public.user_prompts;
|
|
186
|
+
CREATE TRIGGER update_user_prompts_updated_at
|
|
187
|
+
BEFORE UPDATE ON public.user_prompts
|
|
188
|
+
FOR EACH ROW
|
|
189
|
+
EXECUTE FUNCTION public.update_user_prompts_updated_at();
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
-- module-ai module initial migration
|
|
2
|
+
-- Auto-generated by module-create.ts
|
|
3
|
+
-- NOTE: uses helper function set_updated_at() from base migration
|
|
4
|
+
|
|
5
|
+
-- ===========================================================================
|
|
6
|
+
-- Helper: set_updated_at trigger function (if not already present)
|
|
7
|
+
-- ===========================================================================
|
|
8
|
+
CREATE OR REPLACE FUNCTION public.set_updated_at()
|
|
9
|
+
RETURNS trigger
|
|
10
|
+
LANGUAGE plpgsql
|
|
11
|
+
AS $$
|
|
12
|
+
BEGIN
|
|
13
|
+
NEW.updated_at := now();
|
|
14
|
+
RETURN NEW;
|
|
15
|
+
END;
|
|
16
|
+
$$;
|
|
17
|
+
|
|
18
|
+
-- ===========================================================================
|
|
19
|
+
-- Table: public.user_token_ledger
|
|
20
|
+
-- ===========================================================================
|
|
21
|
+
CREATE TABLE IF NOT EXISTS public.user_token_ledger (
|
|
22
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
23
|
+
owner_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
24
|
+
title text NOT NULL,
|
|
25
|
+
description text,
|
|
26
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
27
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
-- RLS
|
|
31
|
+
ALTER TABLE public.user_token_ledger ENABLE ROW LEVEL SECURITY;
|
|
32
|
+
|
|
33
|
+
-- Politique: Les utilisateurs peuvent voir leurs propres enregistrements
|
|
34
|
+
DROP POLICY IF EXISTS user_token_ledger_owner_select ON public.user_token_ledger;
|
|
35
|
+
CREATE POLICY user_token_ledger_owner_select ON public.user_token_ledger
|
|
36
|
+
FOR SELECT TO authenticated
|
|
37
|
+
USING (owner_id = auth.uid());
|
|
38
|
+
|
|
39
|
+
-- Politique: Les utilisateurs peuvent créer leurs propres enregistrements
|
|
40
|
+
DROP POLICY IF EXISTS user_token_ledger_owner_insert ON public.user_token_ledger;
|
|
41
|
+
CREATE POLICY user_token_ledger_owner_insert ON public.user_token_ledger
|
|
42
|
+
FOR INSERT TO authenticated
|
|
43
|
+
WITH CHECK (owner_id = auth.uid());
|
|
44
|
+
|
|
45
|
+
-- Politique: Les utilisateurs peuvent modifier leurs propres enregistrements
|
|
46
|
+
DROP POLICY IF EXISTS user_token_ledger_owner_update ON public.user_token_ledger;
|
|
47
|
+
CREATE POLICY user_token_ledger_owner_update ON public.user_token_ledger
|
|
48
|
+
FOR UPDATE TO authenticated
|
|
49
|
+
USING (owner_id = auth.uid())
|
|
50
|
+
WITH CHECK (owner_id = auth.uid());
|
|
51
|
+
|
|
52
|
+
-- Politique: Les utilisateurs peuvent supprimer leurs propres enregistrements
|
|
53
|
+
DROP POLICY IF EXISTS user_token_ledger_owner_delete ON public.user_token_ledger;
|
|
54
|
+
CREATE POLICY user_token_ledger_owner_delete ON public.user_token_ledger
|
|
55
|
+
FOR DELETE TO authenticated
|
|
56
|
+
USING (owner_id = auth.uid());
|
|
57
|
+
|
|
58
|
+
-- Trigger updated_at
|
|
59
|
+
DROP TRIGGER IF EXISTS set_user_token_ledger_updated_at ON public.user_token_ledger;
|
|
60
|
+
CREATE TRIGGER set_user_token_ledger_updated_at
|
|
61
|
+
BEFORE UPDATE ON public.user_token_ledger
|
|
62
|
+
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
63
|
+
|
|
64
|
+
-- Index
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_user_token_ledger_owner_id ON public.user_token_ledger(owner_id);
|
|
66
|
+
|
|
67
|
+
-- Grants
|
|
68
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.user_token_ledger TO service_role;
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
-- ===========================================================================
|
|
72
|
+
-- Table: public.user_prompts
|
|
73
|
+
-- ===========================================================================
|
|
74
|
+
CREATE TABLE IF NOT EXISTS public.user_prompts (
|
|
75
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
76
|
+
owner_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
77
|
+
title text NOT NULL,
|
|
78
|
+
description text,
|
|
79
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
80
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
-- RLS
|
|
84
|
+
ALTER TABLE public.user_prompts ENABLE ROW LEVEL SECURITY;
|
|
85
|
+
|
|
86
|
+
-- Politique: Les utilisateurs peuvent voir leurs propres enregistrements
|
|
87
|
+
DROP POLICY IF EXISTS user_prompts_owner_select ON public.user_prompts;
|
|
88
|
+
CREATE POLICY user_prompts_owner_select ON public.user_prompts
|
|
89
|
+
FOR SELECT TO authenticated
|
|
90
|
+
USING (owner_id = auth.uid());
|
|
91
|
+
|
|
92
|
+
-- Politique: Les utilisateurs peuvent créer leurs propres enregistrements
|
|
93
|
+
DROP POLICY IF EXISTS user_prompts_owner_insert ON public.user_prompts;
|
|
94
|
+
CREATE POLICY user_prompts_owner_insert ON public.user_prompts
|
|
95
|
+
FOR INSERT TO authenticated
|
|
96
|
+
WITH CHECK (owner_id = auth.uid());
|
|
97
|
+
|
|
98
|
+
-- Politique: Les utilisateurs peuvent modifier leurs propres enregistrements
|
|
99
|
+
DROP POLICY IF EXISTS user_prompts_owner_update ON public.user_prompts;
|
|
100
|
+
CREATE POLICY user_prompts_owner_update ON public.user_prompts
|
|
101
|
+
FOR UPDATE TO authenticated
|
|
102
|
+
USING (owner_id = auth.uid())
|
|
103
|
+
WITH CHECK (owner_id = auth.uid());
|
|
104
|
+
|
|
105
|
+
-- Politique: Les utilisateurs peuvent supprimer leurs propres enregistrements
|
|
106
|
+
DROP POLICY IF EXISTS user_prompts_owner_delete ON public.user_prompts;
|
|
107
|
+
CREATE POLICY user_prompts_owner_delete ON public.user_prompts
|
|
108
|
+
FOR DELETE TO authenticated
|
|
109
|
+
USING (owner_id = auth.uid());
|
|
110
|
+
|
|
111
|
+
-- Trigger updated_at
|
|
112
|
+
DROP TRIGGER IF EXISTS set_user_prompts_updated_at ON public.user_prompts;
|
|
113
|
+
CREATE TRIGGER set_user_prompts_updated_at
|
|
114
|
+
BEFORE UPDATE ON public.user_prompts
|
|
115
|
+
FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
|
|
116
|
+
|
|
117
|
+
-- Index
|
|
118
|
+
CREATE INDEX IF NOT EXISTS idx_user_prompts_owner_id ON public.user_prompts(owner_id);
|
|
119
|
+
|
|
120
|
+
-- Grants
|
|
121
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.user_prompts TO service_role;
|
|
122
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
-- Migration DOWN pour ai_tokens
|
|
2
|
+
-- Supprimer tous les éléments créés dans la migration up
|
|
3
|
+
|
|
4
|
+
-- Supprimer le trigger
|
|
5
|
+
DROP TRIGGER IF EXISTS check_token_balance_trigger ON public.user_token_ledger;
|
|
6
|
+
|
|
7
|
+
-- Supprimer la fonction trigger
|
|
8
|
+
DROP FUNCTION IF EXISTS public.check_token_balance() CASCADE;
|
|
9
|
+
|
|
10
|
+
-- Supprimer les policies RLS
|
|
11
|
+
DROP POLICY IF EXISTS "Users can view their own token balance" ON public.user_token_balance_v;
|
|
12
|
+
DROP POLICY IF EXISTS "Users can view their own token ledger" ON public.user_token_ledger;
|
|
13
|
+
|
|
14
|
+
-- Supprimer la vue
|
|
15
|
+
DROP VIEW IF EXISTS public.user_token_balance_v CASCADE;
|
|
16
|
+
|
|
17
|
+
-- Supprimer les indexes
|
|
18
|
+
DROP INDEX IF EXISTS idx_user_token_ledger_owner_id;
|
|
19
|
+
DROP INDEX IF EXISTS idx_user_token_ledger_ts;
|
|
20
|
+
DROP INDEX IF EXISTS idx_user_token_ledger_type;
|
|
21
|
+
|
|
22
|
+
-- Supprimer la table user_token_ledger
|
|
23
|
+
DROP TABLE IF EXISTS public.user_token_ledger CASCADE;
|