@lastbrain/ai-ui-react 1.0.10 → 1.0.11

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.
@@ -1,20 +1,114 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useRef, useEffect } from "react";
3
+ import { useState, useRef, useLayoutEffect } from "react";
4
+ import { createPortal } from "react-dom";
4
5
  import { aiStyles, calculateTooltipPosition } from "../styles/inline";
5
6
  export function AiStatusButton({ status, loading = false, className = "", }) {
7
+ const formatNumber = (value) => typeof value === "number" ? value.toLocaleString() : "0";
8
+ const formatFixed = (value, digits) => typeof value === "number" ? value.toFixed(digits) : "0.00";
9
+ const safeNumber = (value) => typeof value === "number" ? value : 0;
10
+ const clampPercentage = (value) => Math.min(100, Math.max(0, safeNumber(value)));
11
+ const getUsageColor = (percentage) => {
12
+ if (percentage > 90) {
13
+ return aiStyles.tooltipValueWarning.color;
14
+ }
15
+ if (percentage > 75) {
16
+ return aiStyles.tooltipValueWarning.color;
17
+ }
18
+ return aiStyles.tooltipValueSuccess.color;
19
+ };
20
+ const renderUsageCircle = (percentageValue) => {
21
+ const percentage = clampPercentage(percentageValue);
22
+ const size = 28;
23
+ const stroke = 3;
24
+ const radius = (size - stroke) / 2;
25
+ const circumference = 2 * Math.PI * radius;
26
+ const offset = circumference - (percentage / 100) * circumference;
27
+ const color = getUsageColor(percentage);
28
+ return (_jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, children: [_jsx("circle", { cx: size / 2, cy: size / 2, r: radius, stroke: aiStyles.tooltipLabel.color, strokeWidth: stroke, fill: "transparent", opacity: 0.3 }), _jsx("circle", { cx: size / 2, cy: size / 2, r: radius, stroke: color, strokeWidth: stroke, fill: "transparent", strokeDasharray: circumference, strokeDashoffset: offset, strokeLinecap: "round", transform: `rotate(-90 ${size / 2} ${size / 2})` })] }));
29
+ };
30
+ const balanceUsage = status?.balance;
31
+ const storageUsage = status?.storage;
32
+ const balanceTotal = balanceUsage?.total ??
33
+ safeNumber(balanceUsage?.purchased) + safeNumber(balanceUsage?.quota);
34
+ const balanceUsed = balanceUsage?.used ?? balanceUsage?.total ?? 0;
35
+ const balancePercentage = balanceUsage?.percentage ??
36
+ (balanceTotal > 0 ? Math.round((balanceUsed / balanceTotal) * 100) : 0);
37
+ const storageAllocated = storageUsage?.allocated_mb ?? storageUsage?.total_mb ?? 0;
38
+ const storageUsed = storageUsage?.used_mb ?? storageUsage?.total_mb ?? 0;
39
+ const storagePercentage = storageUsage?.percentage ??
40
+ (storageAllocated > 0
41
+ ? Math.round((storageUsed / storageAllocated) * 100)
42
+ : 0);
6
43
  const [showTooltip, setShowTooltip] = useState(false);
7
44
  const [isHovered, setIsHovered] = useState(false);
8
45
  const [tooltipPosition, setTooltipPosition] = useState({});
9
46
  const buttonRef = useRef(null);
10
47
  const tooltipRef = useRef(null);
11
- useEffect(() => {
12
- if (showTooltip && buttonRef.current) {
13
- const buttonRect = buttonRef.current.getBoundingClientRect();
14
- const position = calculateTooltipPosition(buttonRect);
15
- setTooltipPosition(position);
48
+ const canPortal = typeof document !== "undefined";
49
+ useLayoutEffect(() => {
50
+ if (!showTooltip || !buttonRef.current) {
51
+ return;
16
52
  }
17
- }, [showTooltip]);
53
+ const updatePosition = () => {
54
+ if (!buttonRef.current) {
55
+ return;
56
+ }
57
+ const buttonRect = buttonRef.current.getBoundingClientRect();
58
+ if (canPortal) {
59
+ const tooltipRect = tooltipRef.current?.getBoundingClientRect();
60
+ const tooltipWidth = tooltipRect?.width ?? 360;
61
+ const tooltipHeight = tooltipRect?.height ?? 520;
62
+ const viewportWidth = window.innerWidth;
63
+ const viewportHeight = window.innerHeight;
64
+ const margin = 8;
65
+ const spaceBelow = viewportHeight - buttonRect.bottom;
66
+ const spaceAbove = buttonRect.top;
67
+ const spaceRight = viewportWidth - buttonRect.right;
68
+ const spaceLeft = buttonRect.left;
69
+ const preferBelow = spaceBelow >= spaceAbove;
70
+ const preferRight = spaceRight >= spaceLeft;
71
+ let top = preferBelow
72
+ ? buttonRect.bottom + margin
73
+ : buttonRect.top - tooltipHeight - margin;
74
+ if (top < margin) {
75
+ top = margin;
76
+ }
77
+ if (top + tooltipHeight > viewportHeight - margin) {
78
+ top = Math.max(margin, viewportHeight - tooltipHeight - margin);
79
+ }
80
+ let left = preferRight
81
+ ? buttonRect.left
82
+ : buttonRect.right - tooltipWidth;
83
+ if (left < margin) {
84
+ left = margin;
85
+ }
86
+ if (left + tooltipWidth > viewportWidth - margin) {
87
+ left = Math.max(margin, viewportWidth - tooltipWidth - margin);
88
+ }
89
+ setTooltipPosition({
90
+ top: `${top}px`,
91
+ left: `${left}px`,
92
+ position: "fixed",
93
+ });
94
+ }
95
+ else {
96
+ const position = calculateTooltipPosition(buttonRect);
97
+ setTooltipPosition(position);
98
+ }
99
+ };
100
+ const rafId = requestAnimationFrame(updatePosition);
101
+ const rafId2 = requestAnimationFrame(updatePosition);
102
+ const handleResize = () => updatePosition();
103
+ window.addEventListener("resize", handleResize);
104
+ window.addEventListener("scroll", handleResize, true);
105
+ return () => {
106
+ cancelAnimationFrame(rafId);
107
+ cancelAnimationFrame(rafId2);
108
+ window.removeEventListener("resize", handleResize);
109
+ window.removeEventListener("scroll", handleResize, true);
110
+ };
111
+ }, [showTooltip, canPortal]);
18
112
  const handleMouseEnter = () => {
19
113
  setShowTooltip(true);
20
114
  setIsHovered(true);
@@ -40,40 +134,38 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
40
134
  ...aiStyles.statusButton,
41
135
  color: "#ef4444",
42
136
  ...(isHovered && aiStyles.statusButtonHover),
43
- }, className: className, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: _jsx("polyline", { points: "22 12 18 12 15 21 9 3 6 12 2 12" }) }) }), showTooltip && (_jsx("div", { ref: tooltipRef, style: { ...aiStyles.tooltip, ...tooltipPosition }, onMouseEnter: () => setShowTooltip(true), onMouseLeave: handleMouseLeave, children: "No status available" }))] }));
137
+ }, className: className, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: _jsx("polyline", { points: "22 12 18 12 15 21 9 3 6 12 2 12" }) }) }), showTooltip &&
138
+ canPortal &&
139
+ createPortal(_jsx("div", { ref: tooltipRef, style: {
140
+ ...aiStyles.tooltip,
141
+ ...tooltipPosition,
142
+ zIndex: 50,
143
+ }, onMouseEnter: () => setShowTooltip(true), onMouseLeave: handleMouseLeave, children: "No status available" }), document.body)] }));
44
144
  }
45
145
  return (_jsxs("div", { style: { position: "relative", display: "inline-block" }, children: [_jsx("button", { ref: buttonRef, style: {
46
146
  ...aiStyles.statusButton,
47
147
  color: "#10b981",
48
148
  ...(isHovered && aiStyles.statusButtonHover),
49
- }, className: className, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: _jsx("polyline", { points: "22 12 18 12 15 21 9 3 6 12 2 12" }) }) }), showTooltip && (_jsxs("div", { ref: tooltipRef, style: { ...aiStyles.tooltip, ...tooltipPosition }, onMouseEnter: () => setShowTooltip(true), onMouseLeave: handleMouseLeave, children: [_jsx("div", { style: aiStyles.tooltipHeader, children: "API Status" }), _jsxs("div", { style: {
50
- ...aiStyles.tooltipSection,
51
- ...aiStyles.tooltipSectionFirst,
52
- }, children: [_jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "API Key:" }), _jsx("span", { style: aiStyles.tooltipValue, children: status.api_key.name })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Env:" }), _jsx("span", { style: aiStyles.tooltipValue, children: status.api_key.env })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Rate Limit:" }), _jsxs("span", { style: aiStyles.tooltipValue, children: [status.api_key.rate_limit_rpm, " req/min"] })] })] }), _jsxs("div", { style: aiStyles.tooltipSection, children: [_jsx("div", { style: aiStyles.tooltipSubtitle, children: "Balance" }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Total:" }), _jsx("span", { style: {
53
- ...aiStyles.tooltipValue,
54
- ...aiStyles.tooltipValueBold,
55
- }, children: status.balance.total.toLocaleString() })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Purchased:" }), _jsx("span", { style: aiStyles.tooltipValue, children: status.balance.purchased.toLocaleString() })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Quota:" }), _jsx("span", { style: aiStyles.tooltipValue, children: status.balance.quota.toLocaleString() })] })] }), _jsxs("div", { style: aiStyles.tooltipSection, children: [_jsx("div", { style: aiStyles.tooltipSubtitle, children: "Plan" }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Type:" }), _jsx("span", { style: {
56
- ...aiStyles.tooltipValue,
57
- textTransform: "capitalize",
58
- }, children: status.quota.plan })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Remaining:" }), _jsxs("span", { style: aiStyles.tooltipValue, children: [status.quota.remaining_quota.toLocaleString(), " /", " ", status.quota.effective_quota.toLocaleString()] })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Status:" }), _jsx("span", { style: {
59
- ...aiStyles.tooltipValue,
60
- ...(status.quota.is_active
61
- ? aiStyles.tooltipValueSuccess
62
- : aiStyles.tooltipValueWarning),
63
- }, children: status.quota.is_active ? "Active" : "Inactive" })] })] }), _jsxs("div", { style: aiStyles.tooltipSection, children: [_jsx("div", { style: aiStyles.tooltipSubtitle, children: "Storage" }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Database:" }), _jsxs("span", { style: aiStyles.tooltipValue, children: [status.storage.db_mb.toFixed(2), " MB"] })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Files:" }), _jsxs("span", { style: aiStyles.tooltipValue, children: [status.storage.files_mb.toFixed(2), " MB"] })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Total:" }), _jsxs("span", { style: {
64
- ...aiStyles.tooltipValue,
65
- ...aiStyles.tooltipValueBold,
66
- }, children: [(status.storage.db_mb + status.storage.files_mb).toFixed(2), " MB"] })] })] }), _jsxs("div", { style: aiStyles.tooltipActions, children: [_jsx("a", { href: "https://prompt.lastbrain.io/fr/auth/dashboard", target: "_blank", rel: "noopener noreferrer", style: aiStyles.tooltipLink, onMouseEnter: (e) => {
67
- Object.assign(e.currentTarget.style, aiStyles.tooltipLinkHover);
68
- }, onMouseLeave: (e) => {
69
- Object.assign(e.currentTarget.style, aiStyles.tooltipLink);
70
- }, children: "Dashboard" }), _jsx("a", { href: "https://prompt.lastbrain.io/fr/auth/billing", target: "_blank", rel: "noopener noreferrer", style: aiStyles.tooltipLink, onMouseEnter: (e) => {
71
- Object.assign(e.currentTarget.style, aiStyles.tooltipLinkHover);
72
- }, onMouseLeave: (e) => {
73
- Object.assign(e.currentTarget.style, aiStyles.tooltipLink);
74
- }, children: "History" }), _jsx("a", { href: "https://prompt.lastbrain.io/fr/auth/billing", target: "_blank", rel: "noopener noreferrer", style: aiStyles.tooltipLink, onMouseEnter: (e) => {
75
- Object.assign(e.currentTarget.style, aiStyles.tooltipLinkHover);
76
- }, onMouseLeave: (e) => {
77
- Object.assign(e.currentTarget.style, aiStyles.tooltipLink);
78
- }, children: "Settings" })] })] }))] }));
149
+ }, className: className, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: _jsx("polyline", { points: "22 12 18 12 15 21 9 3 6 12 2 12" }) }) }), showTooltip &&
150
+ canPortal &&
151
+ createPortal(_jsxs("div", { ref: tooltipRef, style: {
152
+ ...aiStyles.tooltip,
153
+ ...tooltipPosition,
154
+ zIndex: 50,
155
+ }, onMouseEnter: () => setShowTooltip(true), onMouseLeave: handleMouseLeave, children: [_jsx("div", { style: aiStyles.tooltipHeader, children: "API Status" }), _jsxs("div", { style: {
156
+ ...aiStyles.tooltipSection,
157
+ ...aiStyles.tooltipSectionFirst,
158
+ }, children: [_jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "API Key:" }), _jsx("span", { style: aiStyles.tooltipValue, children: status.api_key.name })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Env:" }), _jsx("span", { style: aiStyles.tooltipValue, children: status.api_key.env })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Rate Limit:" }), _jsxs("span", { style: aiStyles.tooltipValue, children: [status.api_key.rate_limit_rpm, " req/min"] })] })] }), _jsxs("div", { style: aiStyles.tooltipSection, children: [_jsx("div", { style: aiStyles.tooltipSubtitle, children: "Balance" }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Total:" }), _jsxs("span", { style: aiStyles.tooltipValue, children: ["$", formatFixed(balanceUsed, 6), " / $", formatNumber(balanceTotal)] }), renderUsageCircle(balancePercentage)] })] }), _jsxs("div", { style: aiStyles.tooltipSection, children: [_jsx("div", { style: aiStyles.tooltipSubtitle, children: "Storage" }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Total:" }), _jsxs("span", { style: aiStyles.tooltipValue, children: [formatFixed(storageUsed, 2), " MB /", " ", formatFixed(storageAllocated, 2), " MB"] }), renderUsageCircle(storagePercentage)] })] }), _jsxs("div", { style: aiStyles.tooltipActions, children: [_jsx("a", { href: "https://prompt.lastbrain.io/fr/auth/dashboard", target: "_blank", rel: "noopener noreferrer", style: aiStyles.tooltipLink, onMouseEnter: (e) => {
159
+ Object.assign(e.currentTarget.style, aiStyles.tooltipLinkHover);
160
+ }, onMouseLeave: (e) => {
161
+ Object.assign(e.currentTarget.style, aiStyles.tooltipLink);
162
+ }, children: "Dashboard" }), _jsx("a", { href: "https://prompt.lastbrain.io/fr/auth/billing", target: "_blank", rel: "noopener noreferrer", style: aiStyles.tooltipLink, onMouseEnter: (e) => {
163
+ Object.assign(e.currentTarget.style, aiStyles.tooltipLinkHover);
164
+ }, onMouseLeave: (e) => {
165
+ Object.assign(e.currentTarget.style, aiStyles.tooltipLink);
166
+ }, children: "History" }), _jsx("a", { href: "https://prompt.lastbrain.io/fr/auth/billing", target: "_blank", rel: "noopener noreferrer", style: aiStyles.tooltipLink, onMouseEnter: (e) => {
167
+ Object.assign(e.currentTarget.style, aiStyles.tooltipLinkHover);
168
+ }, onMouseLeave: (e) => {
169
+ Object.assign(e.currentTarget.style, aiStyles.tooltipLink);
170
+ }, children: "Settings" })] })] }), document.body)] }));
79
171
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AiTextarea.d.ts","sourceRoot":"","sources":["../../src/components/AiTextarea.tsx"],"names":[],"mappings":"AAEA,OAAc,EAAoB,KAAK,sBAAsB,EAAE,MAAM,OAAO,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAM5C,MAAM,WAAW,eACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,sBAAsB,CAAC,mBAAmB,CAAC,EAAE,SAAS,CAAC;IAC9D,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7B;AAED,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,aAAa,EACjB,EAAE,eAAe,2CAqKjB"}
1
+ {"version":3,"file":"AiTextarea.d.ts","sourceRoot":"","sources":["../../src/components/AiTextarea.tsx"],"names":[],"mappings":"AAEA,OAAc,EAIZ,KAAK,sBAAsB,EAC5B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAO5C,MAAM,WAAW,eACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,sBAAsB,CAAC,mBAAmB,CAAC,EAAE,SAAS,CAAC;IAC9D,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7B;AAED,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,aAAa,EACjB,EAAE,eAAe,2CAyLjB"}
@@ -1,9 +1,11 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useRef } from "react";
3
+ import { useState, useRef, useLayoutEffect, } from "react";
4
+ import { Sparkles } from "lucide-react";
4
5
  import { useAiCallText } from "../hooks/useAiCallText";
5
6
  import { useAiModels } from "../hooks/useAiModels";
6
7
  import { AiPromptPanel } from "./AiPromptPanel";
8
+ import { UsageToast, useUsageToast } from "./UsageToast";
7
9
  import { aiStyles } from "../styles/inline";
8
10
  export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model, prompt, editMode = false, onValue, onToast, disabled, className, ...textareaProps }) {
9
11
  const [isOpen, setIsOpen] = useState(false);
@@ -13,6 +15,7 @@ export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model
13
15
  const [isFocused, setIsFocused] = useState(false);
14
16
  const [isButtonHovered, setIsButtonHovered] = useState(false);
15
17
  const textareaRef = useRef(null);
18
+ const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
16
19
  const { models } = useAiModels({ baseUrl, apiKeyId });
17
20
  const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
18
21
  const hasConfiguration = Boolean(model && prompt);
@@ -24,11 +27,16 @@ export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model
24
27
  };
25
28
  const handleSubmit = async (selectedModel, selectedPrompt, promptId) => {
26
29
  try {
30
+ const resolvedContext = textareaValue || context || undefined;
31
+ const hasContext = Boolean(resolvedContext && String(resolvedContext).trim());
32
+ const promptWithContext = hasContext
33
+ ? `${selectedPrompt}\n\nTexte:\n${String(resolvedContext)}`
34
+ : selectedPrompt;
27
35
  const result = await generateText({
28
36
  model: selectedModel,
29
- prompt: selectedPrompt,
30
- context: textareaValue || context || undefined,
31
- actionType: "autocomplete",
37
+ prompt: promptWithContext,
38
+ context: resolvedContext,
39
+ actionType: hasContext ? "generate-text" : "autocomplete",
32
40
  });
33
41
  if (result.text) {
34
42
  setTextareaValue(result.text);
@@ -37,6 +45,7 @@ export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model
37
45
  }
38
46
  onValue?.(result.text);
39
47
  onToast?.({ type: "success", message: "AI generation successful" });
48
+ showUsageToast(result);
40
49
  }
41
50
  }
42
51
  catch (error) {
@@ -50,11 +59,16 @@ export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model
50
59
  if (!model || !prompt)
51
60
  return;
52
61
  try {
62
+ const resolvedContext = textareaValue || context || undefined;
63
+ const hasContext = Boolean(resolvedContext && String(resolvedContext).trim());
64
+ const promptWithContext = hasContext
65
+ ? `${prompt}\n\nTexte:\n${String(resolvedContext)}`
66
+ : prompt;
53
67
  const result = await generateText({
54
68
  model,
55
- prompt,
56
- context: textareaValue || context || undefined,
57
- actionType: "autocomplete",
69
+ prompt: promptWithContext,
70
+ context: resolvedContext,
71
+ actionType: hasContext ? "generate-text" : "autocomplete",
58
72
  });
59
73
  if (result.text) {
60
74
  setTextareaValue(result.text);
@@ -63,6 +77,7 @@ export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model
63
77
  }
64
78
  onValue?.(result.text);
65
79
  onToast?.({ type: "success", message: "AI generation successful" });
80
+ showUsageToast(result);
66
81
  }
67
82
  }
68
83
  catch (error) {
@@ -74,12 +89,24 @@ export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model
74
89
  setTextareaValue(newValue);
75
90
  textareaProps.onChange?.(e);
76
91
  };
92
+ const adjustHeight = () => {
93
+ const element = textareaRef.current;
94
+ if (!element) {
95
+ return;
96
+ }
97
+ element.style.height = "auto";
98
+ element.style.height = `${element.scrollHeight}px`;
99
+ };
100
+ useLayoutEffect(() => {
101
+ adjustHeight();
102
+ }, [textareaValue]);
77
103
  return (_jsxs("div", { style: aiStyles.textareaWrapper, className: className, children: [_jsx("textarea", { ref: textareaRef, ...textareaProps, style: {
78
104
  ...aiStyles.textarea,
79
105
  ...(isFocused && aiStyles.textareaFocus),
80
106
  }, value: textareaValue, onChange: handleTextareaChange, onFocus: (e) => {
81
107
  setIsFocused(true);
82
108
  textareaProps.onFocus?.(e);
109
+ adjustHeight();
83
110
  }, onBlur: (e) => {
84
111
  setIsFocused(false);
85
112
  textareaProps.onBlur?.(e);
@@ -89,5 +116,5 @@ export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model
89
116
  ...(disabled || loading
90
117
  ? { opacity: 0.5, cursor: "not-allowed" }
91
118
  : {}),
92
- }, onClick: hasConfiguration ? handleQuickGenerate : handleOpenPanel, onMouseEnter: () => setIsButtonHovered(true), onMouseLeave: () => setIsButtonHovered(false), disabled: disabled || loading, type: "button", title: hasConfiguration ? "Generate with AI" : "Setup AI", children: loading ? (_jsx("svg", { style: aiStyles.spinner, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: _jsx("path", { d: "M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83" }) })) : hasConfiguration ? (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("path", { d: "M12 5v14M5 12h14" }) })) : (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("path", { d: "M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" }) })) }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models || [], sourceText: textareaValue || undefined }))] }));
119
+ }, onClick: hasConfiguration ? handleQuickGenerate : handleOpenPanel, onMouseEnter: () => setIsButtonHovered(true), onMouseLeave: () => setIsButtonHovered(false), disabled: disabled || loading, type: "button", title: hasConfiguration ? "Generate with AI" : "Setup AI", children: loading ? (_jsx("svg", { style: aiStyles.spinner, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: _jsx("path", { d: "M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83" }) })) : (_jsx(Sparkles, { size: 16 })) }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models || [], sourceText: textareaValue || undefined })), Boolean(toastData) && (_jsx(UsageToast, { result: toastData, position: "bottom-right", onComplete: clearToast }, toastKey))] }));
93
120
  }
@@ -0,0 +1,14 @@
1
+ interface UsageToastProps {
2
+ result: unknown;
3
+ position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
4
+ onComplete?: () => void;
5
+ }
6
+ export declare function UsageToast({ result, position, onComplete, }: UsageToastProps): import("react/jsx-runtime").JSX.Element | null;
7
+ export declare function useUsageToast(): {
8
+ showUsageToast: (result: unknown) => void;
9
+ toastData: unknown;
10
+ toastKey: number;
11
+ clearToast: () => void;
12
+ };
13
+ export {};
14
+ //# sourceMappingURL=UsageToast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UsageToast.d.ts","sourceRoot":"","sources":["../../src/components/UsageToast.tsx"],"names":[],"mappings":"AAKA,UAAU,eAAe;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,CAAC;IACrE,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,QAAyB,EACzB,UAAU,GACX,EAAE,eAAe,kDAgJjB;AAED,wBAAgB,aAAa;6BAIK,OAAO;;;;EAgBxC"}
@@ -0,0 +1,144 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { X } from "lucide-react";
5
+ export function UsageToast({ result, position = "bottom-right", onComplete, }) {
6
+ const [isVisible, setIsVisible] = useState(false);
7
+ const [isClosing, setIsClosing] = useState(false);
8
+ const fadeTimeoutRef = useRef(null);
9
+ useEffect(() => {
10
+ if (result) {
11
+ // Show toast immediately
12
+ setIsVisible(true);
13
+ setIsClosing(false);
14
+ }
15
+ return () => {
16
+ if (fadeTimeoutRef.current) {
17
+ window.clearTimeout(fadeTimeoutRef.current);
18
+ }
19
+ };
20
+ }, [result]);
21
+ const handleClose = () => {
22
+ if (isClosing)
23
+ return;
24
+ setIsClosing(true);
25
+ fadeTimeoutRef.current = window.setTimeout(() => {
26
+ setIsVisible(false);
27
+ onComplete?.();
28
+ }, 200);
29
+ };
30
+ const extractUsageMessage = (data) => {
31
+ const result = data;
32
+ // Extract cost from various possible locations
33
+ const rawCost = result?.cost ??
34
+ result?.price ??
35
+ result?.usage?.cost ??
36
+ result?.usage?.total ??
37
+ result?.usage?.usd ??
38
+ result?.usage?.credits ??
39
+ result?.tokens?.cost ??
40
+ result?.tokens?.price ??
41
+ result?.balance?.cost ??
42
+ result?.balance?.used ??
43
+ result?.balance?.spent ??
44
+ result?.credits_used ??
45
+ result?.usd ??
46
+ 0;
47
+ const cost = Number.isFinite(Number(rawCost)) ? Number(rawCost) : 0;
48
+ // Smart formatting: show meaningful decimals
49
+ let formatted;
50
+ if (cost >= 1) {
51
+ formatted = cost.toFixed(2);
52
+ }
53
+ else if (cost >= 0.01) {
54
+ formatted = cost.toFixed(4);
55
+ }
56
+ else if (cost >= 0.0001) {
57
+ formatted = cost.toFixed(6);
58
+ }
59
+ else if (cost > 0) {
60
+ formatted = cost.toFixed(8);
61
+ }
62
+ else {
63
+ formatted = "0.0000";
64
+ }
65
+ // Remove trailing zeros
66
+ formatted = parseFloat(formatted).toString();
67
+ return `${formatted}$ used`;
68
+ };
69
+ const message = extractUsageMessage(result);
70
+ if (!result)
71
+ return null;
72
+ const getPositionStyles = () => {
73
+ const baseStyles = {
74
+ position: "absolute",
75
+ zIndex: 1000,
76
+ };
77
+ switch (position) {
78
+ case "bottom-right":
79
+ return { ...baseStyles, bottom: "8px", right: "8px" };
80
+ case "bottom-left":
81
+ return { ...baseStyles, bottom: "8px", left: "8px" };
82
+ case "top-right":
83
+ return { ...baseStyles, top: "8px", right: "8px" };
84
+ case "top-left":
85
+ return { ...baseStyles, top: "8px", left: "8px" };
86
+ default:
87
+ return { ...baseStyles, bottom: "8px", right: "8px" };
88
+ }
89
+ };
90
+ return (_jsxs("div", { style: {
91
+ ...getPositionStyles(),
92
+ opacity: isVisible && !isClosing ? 1 : 0,
93
+ transform: `translateY(${isVisible && !isClosing ? "0" : "8px"})`,
94
+ transition: "opacity 200ms ease, transform 200ms ease",
95
+ padding: "4px 6px",
96
+ borderRadius: "6px",
97
+ marginBottom: "4px",
98
+ right: "6px",
99
+ background: "rgba(22, 163, 74, 0.12)",
100
+ border: "1px solid rgba(22, 163, 74, 0.3)",
101
+ color: "#16a34a",
102
+ fontSize: "9px",
103
+ fontWeight: 600,
104
+ display: "flex",
105
+ alignItems: "center",
106
+ gap: "8px",
107
+ boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
108
+ backdropFilter: "blur(4px)",
109
+ WebkitBackdropFilter: "blur(4px)",
110
+ }, children: [_jsx("span", { children: message }), _jsx("button", { onClick: handleClose, style: {
111
+ background: "transparent",
112
+ border: "none",
113
+ color: "#16a34a",
114
+ cursor: "pointer",
115
+ padding: "2px",
116
+ display: "flex",
117
+ alignItems: "center",
118
+ justifyContent: "center",
119
+ borderRadius: "4px",
120
+ transition: "background-color 150ms ease",
121
+ }, onMouseEnter: (e) => {
122
+ e.currentTarget.style.backgroundColor = "rgba(22, 163, 74, 0.2)";
123
+ }, onMouseLeave: (e) => {
124
+ e.currentTarget.style.backgroundColor = "transparent";
125
+ }, title: "Close", children: _jsx(X, { size: 12 }) })] }));
126
+ }
127
+ export function useUsageToast() {
128
+ const [toastData, setToastData] = useState(null);
129
+ const [toastKey, setToastKey] = useState(0);
130
+ const showUsageToast = (result) => {
131
+ // Replace any existing toast with new one
132
+ setToastKey((prev) => prev + 1);
133
+ setToastData(result);
134
+ };
135
+ const clearToast = () => {
136
+ setToastData(null);
137
+ };
138
+ return {
139
+ showUsageToast,
140
+ toastData,
141
+ toastKey,
142
+ clearToast,
143
+ };
144
+ }
@@ -7,6 +7,7 @@ export interface Prompt {
7
7
  is_public: boolean;
8
8
  favorite: boolean;
9
9
  tags: string[];
10
+ model?: string | null;
10
11
  created_at: string;
11
12
  updated_at?: string;
12
13
  }
@@ -1 +1 @@
1
- {"version":3,"file":"usePrompts.d.ts","sourceRoot":"","sources":["../../src/hooks/usePrompts.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAa,SAAQ,MAAM;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,CAAC,OAAO,CAAC,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,YAAY,EAAE,CACZ,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,GAAG,YAAY,GAAG,YAAY,CAAC,KACnD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5B,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5E,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,aAAa,EAAE,CACb,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,KAClC,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB;AAED,wBAAgB,UAAU,IAAI,gBAAgB,CA2I7C"}
1
+ {"version":3,"file":"usePrompts.d.ts","sourceRoot":"","sources":["../../src/hooks/usePrompts.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAa,SAAQ,MAAM;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,CAAC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,CAAC,OAAO,CAAC,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,YAAY,EAAE,CACZ,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,GAAG,YAAY,GAAG,YAAY,CAAC,KACnD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5B,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5E,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,aAAa,EAAE,CACb,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,KAClC,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB;AAED,wBAAgB,UAAU,IAAI,gBAAgB,CA0I7C"}
@@ -109,7 +109,6 @@ export function usePrompts() {
109
109
  }
110
110
  catch (err) {
111
111
  // Silent fail for stats
112
- console.error("Failed to increment stat:", err);
113
112
  }
114
113
  }, []);
115
114
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"inline.d.ts","sourceRoot":"","sources":["../../src/styles/inline.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA+DH,eAAO,MAAM,QAAQ;WAcd,KAAK,CAAC,aAAa;gBAKnB,KAAK,CAAC,aAAa;gBAInB,KAAK,CAAC,aAAa;;;;;mBAyBnB,KAAK,CAAC,aAAa;wBAMnB,KAAK,CAAC,aAAa;cAiBnB,KAAK,CAAC,aAAa;mBAKnB,KAAK,CAAC,aAAa;;;;;sBAwBnB,KAAK,CAAC,aAAa;2BAMnB,KAAK,CAAC,aAAa;YAoBnB,KAAK,CAAC,aAAa;iBAMnB,KAAK,CAAC,aAAa;oBAMnB,KAAK,CAAC,aAAa;YAgBnB,KAAK,CAAC,aAAa;iBAKnB,KAAK,CAAC,aAAa;kBAgBnB,KAAK,CAAC,aAAa;uBAMnB,KAAK,CAAC,aAAa;0BAKnB,KAAK,CAAC,aAAa;aAgBnB,KAAK,CAAC,aAAa;mBASnB,KAAK,CAAC,aAAa;oBAKnB,KAAK,CAAC,aAAa;yBAKnB,KAAK,CAAC,aAAa;qBASnB,KAAK,CAAC,aAAa;gBAQnB,KAAK,CAAC,aAAa;kBAKnB,KAAK,CAAC,aAAa;kBAQnB,KAAK,CAAC,aAAa;sBAKnB,KAAK,CAAC,aAAa;yBAKnB,KAAK,CAAC,aAAa;yBAKnB,KAAK,CAAC,aAAa;oBASnB,KAAK,CAAC,aAAa;iBAcnB,KAAK,CAAC,aAAa;sBAKnB,KAAK,CAAC,aAAa;UAcnB,KAAK,CAAC,aAAa;WAenB,KAAK,CAAC,aAAa;kBAWnB,KAAK,CAAC,aAAa;kBAanB,KAAK,CAAC,aAAa;iBAQnB,KAAK,CAAC,aAAa;gBAOnB,KAAK,CAAC,aAAa;sBAiBnB,KAAK,CAAC,aAAa;2BAKnB,KAAK,CAAC,aAAa;eAMnB,KAAK,CAAC,aAAa;iBAQnB,KAAK,CAAC,aAAa;gBAQnB,KAAK,CAAC,aAAa;qBAInB,KAAK,CAAC,aAAa;qBAOnB,KAAK,CAAC,aAAa;0BAKnB,KAAK,CAAC,aAAa;aAUnB,KAAK,CAAC,aAAa;CACzB,CAAC;AA+BF,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,OAAO,EACnB,YAAY,GAAE,MAAY,EAC1B,aAAa,GAAE,MAAY,GAC1B;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAoDlE"}
1
+ {"version":3,"file":"inline.d.ts","sourceRoot":"","sources":["../../src/styles/inline.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA6HH,eAAO,MAAM,QAAQ;WAgBd,KAAK,CAAC,aAAa;gBAKnB,KAAK,CAAC,aAAa;gBAInB,KAAK,CAAC,aAAa;;;;;mBAyBnB,KAAK,CAAC,aAAa;wBAMnB,KAAK,CAAC,aAAa;cAmBnB,KAAK,CAAC,aAAa;mBAKnB,KAAK,CAAC,aAAa;;;;;sBAwBnB,KAAK,CAAC,aAAa;2BAMnB,KAAK,CAAC,aAAa;YAoBnB,KAAK,CAAC,aAAa;iBAMnB,KAAK,CAAC,aAAa;oBAMnB,KAAK,CAAC,aAAa;YAgBnB,KAAK,CAAC,aAAa;iBAKnB,KAAK,CAAC,aAAa;kBAgBnB,KAAK,CAAC,aAAa;uBAMnB,KAAK,CAAC,aAAa;0BAKnB,KAAK,CAAC,aAAa;aAgBnB,KAAK,CAAC,aAAa;mBASnB,KAAK,CAAC,aAAa;oBAKnB,KAAK,CAAC,aAAa;yBAKnB,KAAK,CAAC,aAAa;qBASnB,KAAK,CAAC,aAAa;gBAQnB,KAAK,CAAC,aAAa;kBAKnB,KAAK,CAAC,aAAa;kBAQnB,KAAK,CAAC,aAAa;sBAKnB,KAAK,CAAC,aAAa;yBAKnB,KAAK,CAAC,aAAa;yBAKnB,KAAK,CAAC,aAAa;oBASnB,KAAK,CAAC,aAAa;iBAcnB,KAAK,CAAC,aAAa;sBAKnB,KAAK,CAAC,aAAa;UAcnB,KAAK,CAAC,aAAa;WAenB,KAAK,CAAC,aAAa;kBAWnB,KAAK,CAAC,aAAa;kBAanB,KAAK,CAAC,aAAa;iBAQnB,KAAK,CAAC,aAAa;gBAOnB,KAAK,CAAC,aAAa;sBAiBnB,KAAK,CAAC,aAAa;2BAKnB,KAAK,CAAC,aAAa;eAMnB,KAAK,CAAC,aAAa;iBAQnB,KAAK,CAAC,aAAa;gBAQnB,KAAK,CAAC,aAAa;qBAInB,KAAK,CAAC,aAAa;qBAOnB,KAAK,CAAC,aAAa;0BAKnB,KAAK,CAAC,aAAa;aAUnB,KAAK,CAAC,aAAa;CACzB,CAAC;AA+BF,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,OAAO,EACnB,YAAY,GAAE,MAAY,EAC1B,aAAa,GAAE,MAAY,GAC1B;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAoDlE"}