@lastbrain/ai-ui-react 1.0.79 → 1.0.80

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 +1 @@
1
- {"version":3,"file":"AiContextButton.d.ts","sourceRoot":"","sources":["../../src/components/AiContextButton.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAY5D,KAAK,WAAW,GACZ,MAAM,GACN,MAAM,GACN,OAAO,GACP,MAAM,GACN,OAAO,EAAE,GACT;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,CAAC;AAE3B,MAAM,WAAW,oBACf,SACE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC,EACrC,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACvE,WAAW,EAAE,WAAW,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,CACT,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAC7C,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,WAAW,EACX,kBAAkB,EAClB,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,IAAW,EACX,MAAe,EACf,OAAmB,EACnB,OAAO,EAAE,QAAQ,EACjB,GAAG,WAAW,EACf,EAAE,oBAAoB,2CA4TtB"}
1
+ {"version":3,"file":"AiContextButton.d.ts","sourceRoot":"","sources":["../../src/components/AiContextButton.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAY5D,KAAK,WAAW,GACZ,MAAM,GACN,MAAM,GACN,OAAO,GACP,MAAM,GACN,OAAO,EAAE,GACT;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,CAAC;AAE3B,MAAM,WAAW,oBACf,SACE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC,EACrC,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACvE,WAAW,EAAE,WAAW,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,CACT,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAC7C,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,WAAW,EACX,kBAAkB,EAClB,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,IAAW,EACX,MAAe,EACf,OAAmB,EACnB,OAAO,EAAE,QAAQ,EACjB,GAAG,WAAW,EACf,EAAE,oBAAoB,2CAuUtB"}
@@ -24,12 +24,14 @@ export function AiContextButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId,
24
24
  let lbStatus;
25
25
  let lbHasSession = false;
26
26
  let lbHasSelectedApiKeyCookie = false;
27
+ let lbHasSelectedKey = false;
27
28
  let hasLBProvider = false;
28
29
  try {
29
30
  const lbContext = useLB();
30
31
  lbStatus = lbContext.status;
31
32
  lbHasSession = Boolean(lbContext.session?.sessionToken);
32
33
  lbHasSelectedApiKeyCookie = lbContext.hasSelectedApiKeyCookie;
34
+ lbHasSelectedKey = Boolean(lbContext.selectedKey?.id);
33
35
  hasLBProvider = true;
34
36
  }
35
37
  catch {
@@ -47,7 +49,8 @@ export function AiContextButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId,
47
49
  const needsApiKeySelection = hasLBProvider &&
48
50
  lbStatus === "ready" &&
49
51
  lbHasSession &&
50
- !lbHasSelectedApiKeyCookie;
52
+ !lbHasSelectedApiKeyCookie &&
53
+ !lbHasSelectedKey;
51
54
  const isAuthReady = !needsApiKeySelection &&
52
55
  (lbStatus === "ready" || Boolean(process.env.LB_API_KEY));
53
56
  const handleOpenPanel = () => {
@@ -153,9 +156,15 @@ export function AiContextButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId,
153
156
  const sizeClass = `ai-size-${size}`;
154
157
  const radiusClass = `ai-radius-${radius}`;
155
158
  const variantClass = variant === "light" ? "ai-btn--light" : "";
156
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "relative inline-block ai-glow", children: [_jsx("button", { ...buttonProps, onClick: handleOpenPanel, disabled: disabled || loading || !isAuthReady, className: `ai-btn ai-context-btn ${variantClass} ${sizeClass} ${radiusClass} ${className || ""}`, title: !isAuthReady
159
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "relative inline-block ai-glow", children: [_jsx("button", { ...buttonProps, onClick: () => {
160
+ if (!isAuthReady) {
161
+ setShowAuthModal(true);
162
+ return;
163
+ }
164
+ handleOpenPanel();
165
+ }, disabled: disabled || loading, className: `ai-btn ai-context-btn ${variantClass} ${sizeClass} ${radiusClass} ${className || ""}`, title: !isAuthReady
157
166
  ? t("auth.required", "Authentication required")
158
- : t("ai.analyze", "Analyze with AI"), children: loading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 16, className: "ai-spinner" }), _jsx("span", { children: t("ai.analyzing", "Analyzing...") })] })) : !isAuthReady ? (_jsxs(_Fragment, { children: [_jsx(Lock, { size: 16 }), children || (_jsx("span", { children: t("auth.connectRequired", "Connection required") }))] })) : (_jsxs(_Fragment, { children: [_jsx(Sparkles, { size: 16 }), children || (_jsx("span", { children: t("ai.context.buttonAnalyze", "Analyze") }))] })) }), isOpen ? (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: () => setIsOpen(false), onSubmit: handleSubmit, uiMode: uiMode, models: [], enableModelManagement: true, modelCategory: "text", baseUrl: baseUrl, apiKey: apiKeyId })) : null] }), isResultOpen && analysisResult ? (_jsx("div", { className: "ai-signin-overlay ai-overlay-panel", onClick: (e) => {
167
+ : t("ai.analyze", "Analyze with AI"), children: loading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 16, className: "ai-spinner" }), _jsx("span", { children: t("ai.analyzing", "Analyzing...") })] })) : !isAuthReady ? (_jsxs(_Fragment, { children: [_jsx(Lock, { size: 16 }), children || (_jsx("span", { children: t("auth.connectRequired", "Connection required") }))] })) : (_jsxs(_Fragment, { children: [_jsx(Sparkles, { size: 16 }), children || (_jsx("span", { children: t("ai.context.buttonAnalyze", "Analyze") }))] })) }), isOpen ? (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: () => setIsOpen(false), onSubmit: handleSubmit, uiMode: uiMode, models: [], enableModelManagement: true, modelCategory: "text", baseUrl: baseUrl, apiKey: apiKeyId, contextPreview: formatContextData(contextData), contextPreviewTitle: resolvedContextDescription })) : null] }), isResultOpen && analysisResult ? (_jsx("div", { className: "ai-signin-overlay ai-overlay-panel", onClick: (e) => {
159
168
  if (e.target === e.currentTarget) {
160
169
  setIsResultOpen(false);
161
170
  setAnalysisResult(null);
@@ -1 +1 @@
1
- {"version":3,"file":"AiImageButton.d.ts","sourceRoot":"","sources":["../../src/components/AiImageButton.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAS5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAY5D,MAAM,WAAW,kBACf,SACE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC,EACrC,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACvE,OAAO,CAAC,EAAE,CACR,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAC7C,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,EACf,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,aAAoB,EACpB,WAAW,EACX,YAAY,EACZ,aAAa,EACb,IAAW,EACX,MAAe,EACf,OAAmB,EACnB,GAAG,WAAW,EACf,EAAE,kBAAkB,2CAmSpB"}
1
+ {"version":3,"file":"AiImageButton.d.ts","sourceRoot":"","sources":["../../src/components/AiImageButton.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAS5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAY5D,MAAM,WAAW,kBACf,SACE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC,EACrC,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACvE,OAAO,CAAC,EAAE,CACR,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAC7C,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,EACf,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,aAAoB,EACpB,WAAW,EACX,YAAY,EACZ,aAAa,EACb,IAAW,EACX,MAAe,EACf,OAAmB,EACnB,GAAG,WAAW,EACf,EAAE,kBAAkB,2CA4SpB"}
@@ -24,12 +24,14 @@ export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, ui
24
24
  let lbStatus;
25
25
  let lbHasSession = false;
26
26
  let lbHasSelectedApiKeyCookie = false;
27
+ let lbHasSelectedKey = false;
27
28
  let hasLBProvider = false;
28
29
  try {
29
30
  const lbContext = useLB();
30
31
  lbStatus = lbContext.status;
31
32
  lbHasSession = Boolean(lbContext.session?.sessionToken);
32
33
  lbHasSelectedApiKeyCookie = lbContext.hasSelectedApiKeyCookie;
34
+ lbHasSelectedKey = Boolean(lbContext.selectedKey?.id);
33
35
  hasLBProvider = true;
34
36
  }
35
37
  catch {
@@ -47,7 +49,8 @@ export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, ui
47
49
  const needsApiKeySelection = hasLBProvider &&
48
50
  lbStatus === "ready" &&
49
51
  lbHasSession &&
50
- !lbHasSelectedApiKeyCookie;
52
+ !lbHasSelectedApiKeyCookie &&
53
+ !lbHasSelectedKey;
51
54
  const isAuthReady = !needsApiKeySelection &&
52
55
  (lbStatus === "ready" || Boolean(process.env.LB_API_KEY));
53
56
  const handleOpenPanel = () => {
@@ -147,7 +150,13 @@ export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, ui
147
150
  const sizeClass = `ai-size-${size}`;
148
151
  const radiusClass = `ai-radius-${radius}`;
149
152
  const variantClass = variant === "light" ? "ai-btn--light" : "";
150
- return (_jsxs("div", { className: "flex items-start gap-4", children: [_jsxs("div", { className: "relative inline-block ai-glow", children: [_jsx("button", { ...buttonProps, onClick: handleOpenPanel, disabled: disabled || loading || !isAuthReady, className: `ai-btn ai-image-btn ${variantClass} ${sizeClass} ${radiusClass} ${className || ""}`, style: buttonProps.style, "data-ai-image-button": true, title: !isAuthReady
153
+ return (_jsxs("div", { className: "flex items-start gap-4", children: [_jsxs("div", { className: "relative inline-block ai-glow", children: [_jsx("button", { ...buttonProps, onClick: () => {
154
+ if (!isAuthReady) {
155
+ setShowAuthModal(true);
156
+ return;
157
+ }
158
+ handleOpenPanel();
159
+ }, disabled: disabled || loading, className: `ai-btn ai-image-btn ${variantClass} ${sizeClass} ${radiusClass} ${className || ""}`, style: buttonProps.style, "data-ai-image-button": true, title: !isAuthReady
151
160
  ? t("auth.required", "Authentication required")
152
161
  : t("ai.image.generate", "Generate image"), children: loading ? (_jsxs("span", { className: "ai-loading-stack", children: [_jsxs("span", { className: "ai-loading-row", children: [_jsx(Loader2, { size: 18, className: "ai-spinner" }), _jsx("span", { className: "ai-text-microtracking", children: t("ai.image.generating", "Generating...") })] }), _jsx("span", { className: "ai-loading-meta", children: t("ai.loading.elapsed", "{seconds}", {
153
162
  seconds: loadingElapsed,
@@ -1 +1 @@
1
- {"version":3,"file":"AiInput.d.ts","sourceRoot":"","sources":["../../src/components/AiInput.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAc,EAAoB,KAAK,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAE1E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAY9D,MAAM,WAAW,YACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IACjE,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED,wBAAgB,OAAO,CAAC,EACtB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,IAAW,EACX,MAAe,EACf,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAQ,EAAE,SAAiB,EAC3B,qBAA4B,EAC5B,YAAY,EACZ,aAAa,EACb,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,UAAU,EACd,EAAE,YAAY,2CA6Od"}
1
+ {"version":3,"file":"AiInput.d.ts","sourceRoot":"","sources":["../../src/components/AiInput.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAc,EAAoB,KAAK,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAE1E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAY9D,MAAM,WAAW,YACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IACjE,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED,wBAAgB,OAAO,CAAC,EACtB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,IAAW,EACX,MAAe,EACf,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAQ,EAAE,SAAiB,EAC3B,qBAA4B,EAC5B,YAAY,EACZ,aAAa,EACb,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,UAAU,EACd,EAAE,YAAY,2CAgPd"}
@@ -24,12 +24,14 @@ export function AiInput({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMode =
24
24
  let lbStatus;
25
25
  let lbHasSession = false;
26
26
  let lbHasSelectedApiKeyCookie = false;
27
+ let lbHasSelectedKey = false;
27
28
  let hasLBProvider = false;
28
29
  try {
29
30
  const lbContext = useLB();
30
31
  lbStatus = lbContext.status;
31
32
  lbHasSession = Boolean(lbContext.session?.sessionToken);
32
33
  lbHasSelectedApiKeyCookie = lbContext.hasSelectedApiKeyCookie;
34
+ lbHasSelectedKey = Boolean(lbContext.selectedKey?.id);
33
35
  hasLBProvider = true;
34
36
  }
35
37
  catch {
@@ -62,7 +64,8 @@ export function AiInput({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMode =
62
64
  const needsApiKeySelection = hasLBProvider &&
63
65
  lbStatus === "ready" &&
64
66
  lbHasSession &&
65
- !lbHasSelectedApiKeyCookie;
67
+ !lbHasSelectedApiKeyCookie &&
68
+ !lbHasSelectedKey;
66
69
  const isAuthReady = !needsApiKeySelection &&
67
70
  (lbStatus === "ready" || Boolean(process.env.LB_API_KEY));
68
71
  const shouldShowSparkles = isAuthReady && !disabled;
@@ -19,6 +19,8 @@ export interface AiPromptPanelProps {
19
19
  apiKey?: string;
20
20
  baseUrl?: string;
21
21
  showOnlyUserModels?: boolean;
22
+ contextPreview?: string;
23
+ contextPreviewTitle?: string;
22
24
  }
23
25
  export interface AiPromptPanelRenderProps {
24
26
  models?: ModelRef[];
@@ -1 +1 @@
1
- {"version":3,"file":"AiPromptPanel.d.ts","sourceRoot":"","sources":["../../src/components/AiPromptPanel.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAWf,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AASvC,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAKrD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,SAAS,CAAC;IAE1D,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,eAAe,CAAC,EAAE,OAAO,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,IAAI,CAAC;IAExB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,eAAe,CAAC,EAAE,OAAO,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,2CA0BtD"}
1
+ {"version":3,"file":"AiPromptPanel.d.ts","sourceRoot":"","sources":["../../src/components/AiPromptPanel.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAcf,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AASvC,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAkBrD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,SAAS,CAAC;IAE1D,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,eAAe,CAAC,EAAE,OAAO,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,IAAI,CAAC;IAExB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,eAAe,CAAC,EAAE,OAAO,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,2CA0BtD"}
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import "../styles/register";
4
- import { useState, useEffect, useRef, useLayoutEffect, } from "react";
4
+ import { useState, useEffect, useRef, useLayoutEffect, useMemo, } from "react";
5
5
  import { createPortal } from "react-dom";
6
- import { BookOpen, Search, Sparkles, Star, Tag, Settings, Loader2, } from "lucide-react";
6
+ import { BookOpen, Check, Copy, Eye, Search, Sparkles, Star, Tag, Settings, Loader2, } from "lucide-react";
7
7
  import { aiStyles } from "../styles/inline";
8
8
  import { handleAIError } from "../utils/errorHandler";
9
9
  import { usePrompts, } from "../hooks/usePrompts";
@@ -11,6 +11,19 @@ import { useModelManagement } from "../hooks/useModelManagement";
11
11
  import { AiProvider, useAiContext } from "../context/AiProvider";
12
12
  import { useI18n } from "../context/I18nContext";
13
13
  import { useLoadingTimer } from "../hooks/useLoadingTimer";
14
+ function tryFormatJson(input) {
15
+ const raw = (input || "").trim();
16
+ if (!raw) {
17
+ return { formatted: "", isJson: false };
18
+ }
19
+ try {
20
+ const parsed = JSON.parse(raw);
21
+ return { formatted: JSON.stringify(parsed, null, 2), isJson: true };
22
+ }
23
+ catch {
24
+ return { formatted: input || "", isJson: false };
25
+ }
26
+ }
14
27
  export function AiPromptPanel(props) {
15
28
  const { apiKey, baseUrl } = props;
16
29
  let hasContext = false;
@@ -32,7 +45,7 @@ export function AiPromptPanel(props) {
32
45
  // Sinon, utiliser le contexte existant
33
46
  return _jsx(AiPromptPanelInternal, { ...props });
34
47
  }
35
- function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode = "modal", models = [], sourceText, children, enableModelManagement = true, modelCategory = "text", availableModels = [], userModels = [], onModelToggle, apiKey, baseUrl, showOnlyUserModels = false, }) {
48
+ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode = "modal", models = [], sourceText, children, enableModelManagement = true, modelCategory = "text", availableModels = [], userModels = [], onModelToggle, apiKey, baseUrl, showOnlyUserModels = false, contextPreview, contextPreviewTitle, }) {
36
49
  const { t } = useI18n();
37
50
  const [selectedModel, setSelectedModel] = useState("");
38
51
  const [prompt, setPrompt] = useState("");
@@ -45,6 +58,8 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode = "modal", mo
45
58
  const [selectedTag, setSelectedTag] = useState("all");
46
59
  const [isGenerating, setIsGenerating] = useState(false);
47
60
  const [isClosing, setIsClosing] = useState(false);
61
+ const [showContextPreview, setShowContextPreview] = useState(false);
62
+ const [contextCopied, setContextCopied] = useState(false);
48
63
  const [portalRoot, setPortalRoot] = useState(null);
49
64
  const promptRef = useRef(null);
50
65
  const closeTimeoutRef = useRef(null);
@@ -157,6 +172,8 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode = "modal", mo
157
172
  setPrompt("");
158
173
  setPromptId(undefined);
159
174
  setShowPromptLibrary(false);
175
+ setShowContextPreview(false);
176
+ setContextCopied(false);
160
177
  setSearchQuery("");
161
178
  setSelectedTag("all");
162
179
  setIsClosing(false);
@@ -177,6 +194,12 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode = "modal", mo
177
194
  }
178
195
  };
179
196
  }, []);
197
+ useEffect(() => {
198
+ if (!contextCopied)
199
+ return;
200
+ const timeout = window.setTimeout(() => setContextCopied(false), 1200);
201
+ return () => window.clearTimeout(timeout);
202
+ }, [contextCopied]);
180
203
  useEffect(() => {
181
204
  setPortalRoot(document.body);
182
205
  }, []);
@@ -221,6 +244,8 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode = "modal", mo
221
244
  useLayoutEffect(() => {
222
245
  adjustPromptHeight();
223
246
  }, [prompt]);
247
+ const contextPreviewData = useMemo(() => tryFormatJson(contextPreview), [contextPreview]);
248
+ const contextPreviewLines = useMemo(() => contextPreviewData.formatted.split("\n"), [contextPreviewData.formatted]);
224
249
  if (!isOpen)
225
250
  return null;
226
251
  const activeModelId = selectedModel || modelOptions[0]?.id || "";
@@ -270,6 +295,8 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode = "modal", mo
270
295
  return null;
271
296
  }
272
297
  const isDrawer = uiMode === "drawer";
298
+ const hasContextPreview = Boolean(contextPreview?.trim());
299
+ const contextCharCount = contextPreview?.length || 0;
273
300
  const panelContainerStyle = isDrawer
274
301
  ? {
275
302
  ...aiStyles.modal,
@@ -288,6 +315,40 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode = "modal", mo
288
315
  borderRadius: "16px 0 0 16px",
289
316
  }
290
317
  : aiStyles.modalContent;
318
+ const renderContextLine = (line, lineIndex) => {
319
+ if (!contextPreviewData.isJson) {
320
+ return (_jsx("span", { style: { color: "var(--ai-text-secondary)" }, children: line || " " }, `line-${lineIndex}`));
321
+ }
322
+ const tokenRegex = /("(?:\\.|[^"\\])*"(?=\s*:)|"(?:\\.|[^"\\])*"|true|false|null|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|[{}[\],:])/g;
323
+ const parts = [];
324
+ let lastIndex = 0;
325
+ let match;
326
+ while ((match = tokenRegex.exec(line)) !== null) {
327
+ const token = match[0];
328
+ const index = match.index;
329
+ if (index > lastIndex) {
330
+ parts.push(_jsx("span", { style: { color: "var(--ai-text-secondary)" }, children: line.slice(lastIndex, index) }, `txt-${lineIndex}-${lastIndex}`));
331
+ }
332
+ const isKey = /^".*"$/.test(token) && line.slice(match.index).includes(":");
333
+ const color = isKey
334
+ ? "#7cc5ff"
335
+ : /^".*"$/.test(token)
336
+ ? "#9ad07f"
337
+ : /^(true|false)$/.test(token)
338
+ ? "#f4a259"
339
+ : token === "null"
340
+ ? "#d3869b"
341
+ : /^-?\d/.test(token)
342
+ ? "#f7dc6f"
343
+ : "var(--ai-text-secondary)";
344
+ parts.push(_jsx("span", { style: { color }, children: token }, `tok-${lineIndex}-${index}`));
345
+ lastIndex = index + token.length;
346
+ }
347
+ if (lastIndex < line.length) {
348
+ parts.push(_jsx("span", { style: { color: "var(--ai-text-secondary)" }, children: line.slice(lastIndex) }, `txt-end-${lineIndex}`));
349
+ }
350
+ return (_jsx("span", { style: { whiteSpace: "pre" }, children: parts.length > 0 ? parts : " " }, `line-${lineIndex}`));
351
+ };
291
352
  if (children) {
292
353
  return createPortal(_jsx("div", { style: panelContainerStyle, onKeyDown: handleKeyDown, children: children(renderProps) }), portalRoot);
293
354
  }
@@ -417,7 +478,13 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode = "modal", mo
417
478
  marginLeft: "4px",
418
479
  fontSize: "12px",
419
480
  fontWeight: 400,
420
- }, children: t("prompt.modal.promptHint", "(Cmd/Ctrl + Enter to submit)") })] }), promptsLoading ? (_jsx("span", { className: "ai-inline-skeleton", "aria-hidden": "true" })) : filteredPrompts.length > 0 ? (_jsxs("button", { onClick: () => setShowPromptLibrary(true), className: "ai-inline-btn", children: [_jsx(BookOpen, { size: 14 }), t("prompt.modal.browsePrompts", "Browse Prompts"), " (", filteredPrompts.length, ")"] })) : null] }), _jsx("textarea", { id: "prompt-input", ref: promptRef, value: prompt, onChange: (e) => setPrompt(e.target.value), onFocus: () => setPromptFocused(true), onBlur: () => setPromptFocused(false), placeholder: sourceText
481
+ }, children: t("prompt.modal.promptHint", "(Cmd/Ctrl + Enter to submit)") })] }), _jsxs("div", { style: {
482
+ display: "flex",
483
+ alignItems: "center",
484
+ flexWrap: "wrap",
485
+ justifyContent: "flex-end",
486
+ gap: "8px",
487
+ }, children: [hasContextPreview ? (_jsxs("button", { type: "button", onClick: () => setShowContextPreview(true), className: "ai-inline-btn", style: { whiteSpace: "nowrap" }, children: [_jsx(Eye, { size: 14 }), t("prompt.modal.contextPreview", "Context preview")] })) : null, promptsLoading ? (_jsx("span", { className: "ai-inline-skeleton", "aria-hidden": "true" })) : filteredPrompts.length > 0 ? (_jsxs("button", { onClick: () => setShowPromptLibrary(true), className: "ai-inline-btn", style: { whiteSpace: "nowrap" }, children: [_jsx(BookOpen, { size: 14 }), t("prompt.modal.browsePrompts", "Browse Prompts"), " (", filteredPrompts.length, ")"] })) : null] })] }), _jsx("textarea", { id: "prompt-input", ref: promptRef, value: prompt, onChange: (e) => setPrompt(e.target.value), onFocus: () => setPromptFocused(true), onBlur: () => setPromptFocused(false), placeholder: sourceText
421
488
  ? t("prompt.modal.promptPlaceholderWithSource", "Enter your AI prompt... e.g., 'Fix grammar', 'Make it more professional', 'Translate to English'")
422
489
  : t("prompt.modal.promptPlaceholderNoSource", "Enter your AI prompt... e.g., 'Write a blog post about AI', 'Generate product description'"), rows: 6, style: {
423
490
  ...aiStyles.textarea,
@@ -575,7 +642,73 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode = "modal", mo
575
642
  marginTop: "8px",
576
643
  fontSize: "11px",
577
644
  color: "var(--ai-primary)",
578
- }, children: String(promptData.category) })) : null] }, promptData.id))) })] }))] }))] })) }), _jsxs("div", { style: {
645
+ }, children: String(promptData.category) })) : null] }, promptData.id))) })] }))] }))] })) }), showContextPreview ? (_jsx("div", { style: {
646
+ position: "absolute",
647
+ inset: 0,
648
+ zIndex: 12,
649
+ background: "var(--ai-overlay)",
650
+ backdropFilter: "blur(3px)",
651
+ WebkitBackdropFilter: "blur(3px)",
652
+ display: "flex",
653
+ alignItems: "stretch",
654
+ justifyContent: "center",
655
+ padding: "10px",
656
+ }, onClick: () => setShowContextPreview(false), children: _jsxs("div", { style: {
657
+ width: "min(980px, 100%)",
658
+ maxHeight: "100%",
659
+ background: "var(--ai-bg-secondary)",
660
+ border: "1px solid var(--ai-border)",
661
+ borderRadius: "12px",
662
+ boxShadow: "var(--ai-shadow-lg)",
663
+ overflow: "hidden",
664
+ display: "flex",
665
+ flexDirection: "column",
666
+ }, onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { style: {
667
+ padding: "12px 14px",
668
+ borderBottom: "1px solid var(--ai-border)",
669
+ display: "flex",
670
+ alignItems: "center",
671
+ justifyContent: "space-between",
672
+ gap: "10px",
673
+ }, children: [_jsxs("div", { style: { minWidth: 0 }, children: [_jsx("div", { style: { fontSize: "14px", fontWeight: 600 }, children: contextPreviewTitle ||
674
+ t("prompt.modal.contextPreview", "Context preview") }), _jsx("div", { style: {
675
+ marginTop: "2px",
676
+ fontSize: "12px",
677
+ color: "var(--ai-text-secondary)",
678
+ }, children: t("prompt.modal.contextChars", "{count} chars", {
679
+ count: contextCharCount.toLocaleString(),
680
+ }) })] }), _jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [_jsxs("button", { type: "button", className: "ai-inline-btn", onClick: async () => {
681
+ try {
682
+ await navigator.clipboard.writeText(contextPreviewData.formatted || "");
683
+ setContextCopied(true);
684
+ }
685
+ catch {
686
+ setContextCopied(false);
687
+ }
688
+ }, style: { whiteSpace: "nowrap" }, children: [contextCopied ? _jsx(Check, { size: 14 }) : _jsx(Copy, { size: 14 }), contextCopied
689
+ ? t("common.copied", "Copied")
690
+ : t("common.copy", "Copy")] }), _jsx("button", { type: "button", className: "ai-icon-btn", onClick: () => setShowContextPreview(false), "aria-label": t("common.closeLabel", "Close"), children: "\u00D7" })] })] }), _jsx("pre", { style: {
691
+ margin: 0,
692
+ padding: "14px",
693
+ flex: 1,
694
+ overflow: "auto",
695
+ background: "color-mix(in srgb, var(--ai-bg) 70%, transparent)",
696
+ borderTop: "1px solid var(--ai-border)",
697
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
698
+ fontSize: "12px",
699
+ lineHeight: 1.5,
700
+ }, children: contextPreviewLines.map((line, index) => (_jsxs("div", { style: {
701
+ display: "grid",
702
+ gridTemplateColumns: "44px 1fr",
703
+ gap: "12px",
704
+ minHeight: "18px",
705
+ }, children: [_jsx("span", { style: {
706
+ color: "var(--ai-text-tertiary)",
707
+ textAlign: "right",
708
+ userSelect: "none",
709
+ paddingRight: "8px",
710
+ borderRight: "1px solid var(--ai-border)",
711
+ }, children: index + 1 }), renderContextLine(line, index)] }, `context-line-${index}`))) })] }) })) : null, _jsxs("div", { style: {
579
712
  ...aiStyles.modalFooter,
580
713
  position: "sticky",
581
714
  bottom: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"AiSelect.d.ts","sourceRoot":"","sources":["../../src/components/AiSelect.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAEnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAW9D,MAAM,WAAW,aACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IACnE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,IAAW,EACX,MAAe,EACf,OAAO,EACP,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,EACf,YAAY,EACZ,aAAa,EACb,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,GAAG,WAAW,EACf,EAAE,aAAa,2CA+Jf"}
1
+ {"version":3,"file":"AiSelect.d.ts","sourceRoot":"","sources":["../../src/components/AiSelect.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAEnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAW9D,MAAM,WAAW,aACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IACnE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,IAAW,EACX,MAAe,EACf,OAAO,EACP,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,EACf,YAAY,EACZ,aAAa,EACb,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,GAAG,WAAW,EACf,EAAE,aAAa,2CAkKf"}
@@ -21,12 +21,14 @@ export function AiSelect({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMode
21
21
  let lbStatus;
22
22
  let lbHasSession = false;
23
23
  let lbHasSelectedApiKeyCookie = false;
24
+ let lbHasSelectedKey = false;
24
25
  let hasLBProvider = false;
25
26
  try {
26
27
  const lbContext = useLB();
27
28
  lbStatus = lbContext.status;
28
29
  lbHasSession = Boolean(lbContext.session?.sessionToken);
29
30
  lbHasSelectedApiKeyCookie = lbContext.hasSelectedApiKeyCookie;
31
+ lbHasSelectedKey = Boolean(lbContext.selectedKey?.id);
30
32
  hasLBProvider = true;
31
33
  }
32
34
  catch {
@@ -57,7 +59,8 @@ export function AiSelect({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMode
57
59
  const needsApiKeySelection = hasLBProvider &&
58
60
  lbStatus === "ready" &&
59
61
  lbHasSession &&
60
- !lbHasSelectedApiKeyCookie;
62
+ !lbHasSelectedApiKeyCookie &&
63
+ !lbHasSelectedKey;
61
64
  const isAuthReady = !needsApiKeySelection &&
62
65
  (lbStatus === "ready" || Boolean(process.env.LB_API_KEY));
63
66
  const shouldShowSparkles = isAuthReady && !disabled;
@@ -1 +1 @@
1
- {"version":3,"file":"AiStatusButton.d.ts","sourceRoot":"","sources":["../../src/components/AiStatusButton.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAU,MAAM,uBAAuB,CAAC;AA8B9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGjD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAmHD,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,OAAe,EACf,SAAc,EACd,IAAW,EACX,MAAe,GAChB,EAAE,mBAAmB,2CA0jBrB"}
1
+ {"version":3,"file":"AiStatusButton.d.ts","sourceRoot":"","sources":["../../src/components/AiStatusButton.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,QAAQ,EAAU,MAAM,uBAAuB,CAAC;AA8B9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGjD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAmHD,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,OAAe,EACf,SAAc,EACd,IAAW,EACX,MAAe,GAChB,EAAE,mBAAmB,2CA6jBrB"}
@@ -133,7 +133,10 @@ export function AiStatusButton({ status, loading = false, className = "", size =
133
133
  ? effectiveStatus.authType
134
134
  : undefined;
135
135
  const hasLbSession = Boolean(lbSessionToken);
136
- const requiresApiKeySelection = lbStatus === "ready" && hasLbSession && !lbHasSelectedApiKeyCookie;
136
+ const requiresApiKeySelection = lbStatus === "ready" &&
137
+ hasLbSession &&
138
+ !lbHasSelectedApiKeyCookie &&
139
+ !hasApiKeySelected;
137
140
  const isApiKeyAuthMode = authTypeValue === "api_key";
138
141
  const [tooltipStyle, setTooltipStyle] = useState({});
139
142
  useEffect(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"AiTextarea.d.ts","sourceRoot":"","sources":["../../src/components/AiTextarea.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAc,EAIZ,KAAK,sBAAsB,EAC5B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAY9D,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;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CAC/C;AAED,wBAAgB,UAAU,CAAC,EACzB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,IAAW,EACX,MAAa,EACb,iBAAiB,EACjB,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAQ,EAAE,SAAiB,EAC3B,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,aAAa,EACjB,EAAE,eAAe,2CAmQjB"}
1
+ {"version":3,"file":"AiTextarea.d.ts","sourceRoot":"","sources":["../../src/components/AiTextarea.tsx"],"names":[],"mappings":"AAEA,OAAO,oBAAoB,CAAC;AAC5B,OAAc,EAIZ,KAAK,sBAAsB,EAC5B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAY9D,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;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CAC/C;AAED,wBAAgB,UAAU,CAAC,EACzB,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,IAAW,EACX,MAAa,EACb,iBAAiB,EACjB,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAQ,EAAE,SAAiB,EAC3B,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,aAAa,EACjB,EAAE,eAAe,2CAgRjB"}
@@ -26,12 +26,14 @@ export function AiTextarea({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMod
26
26
  let lbStatus;
27
27
  let lbHasSession = false;
28
28
  let lbHasSelectedApiKeyCookie = false;
29
+ let lbHasSelectedKey = false;
29
30
  let hasLBProvider = false;
30
31
  try {
31
32
  const lbContext = useLB();
32
33
  lbStatus = lbContext.status;
33
34
  lbHasSession = Boolean(lbContext.session?.sessionToken);
34
35
  lbHasSelectedApiKeyCookie = lbContext.hasSelectedApiKeyCookie;
36
+ lbHasSelectedKey = Boolean(lbContext.selectedKey?.id);
35
37
  hasLBProvider = true;
36
38
  }
37
39
  catch {
@@ -55,7 +57,8 @@ export function AiTextarea({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMod
55
57
  const needsApiKeySelection = hasLBProvider &&
56
58
  lbStatus === "ready" &&
57
59
  lbHasSession &&
58
- !lbHasSelectedApiKeyCookie;
60
+ !lbHasSelectedApiKeyCookie &&
61
+ !lbHasSelectedKey;
59
62
  const { models: _models } = useAiModels({
60
63
  baseUrl,
61
64
  apiKeyId,
@@ -175,7 +178,17 @@ export function AiTextarea({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMod
175
178
  adjustHeight();
176
179
  }, onBlur: (e) => {
177
180
  textareaProps.onBlur?.(e);
178
- }, "aria-invalid": Boolean(textareaProps["aria-invalid"]), disabled: disabled || loading }), _jsx("button", { className: `ai-control-action ai-spark ${sizeClass} ${radiusClass}`, onClick: hasConfiguration ? handleQuickGenerate : handleOpenPanel, disabled: disabled || loading || !isAuthReady, type: "button", title: !isAuthReady
181
+ }, "aria-invalid": Boolean(textareaProps["aria-invalid"]), disabled: disabled || loading }), _jsx("button", { className: `ai-control-action ai-spark ${sizeClass} ${radiusClass}`, onClick: () => {
182
+ if (!isAuthReady) {
183
+ setShowAuthModal(true);
184
+ return;
185
+ }
186
+ if (hasConfiguration) {
187
+ void handleQuickGenerate();
188
+ return;
189
+ }
190
+ handleOpenPanel();
191
+ }, disabled: disabled || loading, type: "button", title: !isAuthReady
179
192
  ? t("auth.required", "Authentication required")
180
193
  : hasConfiguration
181
194
  ? t("ai.generate", "Generate with AI")
@@ -1 +1 @@
1
- {"version":3,"file":"LBAuthProvider.d.ts","sourceRoot":"","sources":["../../src/context/LBAuthProvider.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LBAuthProvider.d.ts","sourceRoot":"","sources":["../../src/context/LBAuthProvider.tsx"],"names":[],"mappings":"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"}
@@ -113,20 +113,32 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
113
113
  }, [checkSession]);
114
114
  const fetchWalletSummary = useCallback(async () => {
115
115
  const headers = lbClient.getAuthHeaders(state.session?.sessionToken);
116
- const response = await fetch(`${proxyUrl}/auth/wallet`, {
117
- method: "GET",
118
- credentials: "include",
119
- headers,
120
- });
121
- if (!response.ok)
122
- return null;
123
- const data = (await response.json());
124
- return {
125
- used: Number(data.totalUsed || 0),
126
- total: Number(data.totalAdded || 0),
127
- percentage: Number(data.percentUsed ?? data.percentage ?? 0),
128
- remaining: Number(data.walletSellValueUsd || 0),
129
- };
116
+ const walletUrls = Array.from(new Set([
117
+ `${proxyUrl}/auth/wallet`,
118
+ `${proxyUrl.replace("/api/lastbrain", "/api/ai")}/auth/wallet`,
119
+ ]));
120
+ for (const url of walletUrls) {
121
+ try {
122
+ const response = await fetch(url, {
123
+ method: "GET",
124
+ credentials: "include",
125
+ headers,
126
+ });
127
+ if (!response.ok)
128
+ continue;
129
+ const data = (await response.json());
130
+ return {
131
+ used: Number(data.totalUsed || 0),
132
+ total: Number(data.totalAdded || 0),
133
+ percentage: Number(data.percentUsed ?? data.percentage ?? 0),
134
+ remaining: Number(data.walletSellValueUsd || 0),
135
+ };
136
+ }
137
+ catch {
138
+ // try next endpoint
139
+ }
140
+ }
141
+ return null;
130
142
  }, [lbClient, proxyUrl, state.session?.sessionToken]);
131
143
  /**
132
144
  * Récupère les clés API de l'utilisateur
@@ -396,8 +408,11 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
396
408
  }));
397
409
  }
398
410
  setHasSelectedApiKeyCookie(true);
399
- await refreshBasicStatus();
400
- setTimeout(() => refreshStorageStatus(), 100);
411
+ // Ne pas bloquer la validation UI de sélection sur des requêtes status/wallet/storage.
412
+ void refreshBasicStatus();
413
+ setTimeout(() => {
414
+ void refreshStorageStatus();
415
+ }, 100);
401
416
  }
402
417
  else {
403
418
  throw new Error("No valid authentication method available");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/ai-ui-react",
3
- "version": "1.0.79",
3
+ "version": "1.0.80",
4
4
  "description": "Headless React components for LastBrain AI UI Kit",
5
5
  "private": false,
6
6
  "type": "module",
@@ -85,12 +85,14 @@ export function AiContextButton({
85
85
  let lbStatus: string | undefined;
86
86
  let lbHasSession = false;
87
87
  let lbHasSelectedApiKeyCookie = false;
88
+ let lbHasSelectedKey = false;
88
89
  let hasLBProvider = false;
89
90
  try {
90
91
  const lbContext = useLB();
91
92
  lbStatus = lbContext.status;
92
93
  lbHasSession = Boolean(lbContext.session?.sessionToken);
93
94
  lbHasSelectedApiKeyCookie = lbContext.hasSelectedApiKeyCookie;
95
+ lbHasSelectedKey = Boolean(lbContext.selectedKey?.id);
94
96
  hasLBProvider = true;
95
97
  } catch {
96
98
  lbStatus = undefined;
@@ -111,7 +113,8 @@ export function AiContextButton({
111
113
  hasLBProvider &&
112
114
  lbStatus === "ready" &&
113
115
  lbHasSession &&
114
- !lbHasSelectedApiKeyCookie;
116
+ !lbHasSelectedApiKeyCookie &&
117
+ !lbHasSelectedKey;
115
118
  const isAuthReady =
116
119
  !needsApiKeySelection &&
117
120
  (lbStatus === "ready" || Boolean(process.env.LB_API_KEY));
@@ -247,8 +250,14 @@ export function AiContextButton({
247
250
  <div className="relative inline-block ai-glow">
248
251
  <button
249
252
  {...buttonProps}
250
- onClick={handleOpenPanel}
251
- disabled={disabled || loading || !isAuthReady}
253
+ onClick={() => {
254
+ if (!isAuthReady) {
255
+ setShowAuthModal(true);
256
+ return;
257
+ }
258
+ handleOpenPanel();
259
+ }}
260
+ disabled={disabled || loading}
252
261
  className={`ai-btn ai-context-btn ${variantClass} ${sizeClass} ${radiusClass} ${className || ""}`}
253
262
  title={
254
263
  !isAuthReady
@@ -289,6 +298,8 @@ export function AiContextButton({
289
298
  modelCategory="text"
290
299
  baseUrl={baseUrl}
291
300
  apiKey={apiKeyId}
301
+ contextPreview={formatContextData(contextData)}
302
+ contextPreviewTitle={resolvedContextDescription}
292
303
  />
293
304
  ) : null}
294
305
  </div>
@@ -81,12 +81,14 @@ export function AiImageButton({
81
81
  let lbStatus: string | undefined;
82
82
  let lbHasSession = false;
83
83
  let lbHasSelectedApiKeyCookie = false;
84
+ let lbHasSelectedKey = false;
84
85
  let hasLBProvider = false;
85
86
  try {
86
87
  const lbContext = useLB();
87
88
  lbStatus = lbContext.status;
88
89
  lbHasSession = Boolean(lbContext.session?.sessionToken);
89
90
  lbHasSelectedApiKeyCookie = lbContext.hasSelectedApiKeyCookie;
91
+ lbHasSelectedKey = Boolean(lbContext.selectedKey?.id);
90
92
  hasLBProvider = true;
91
93
  } catch {
92
94
  // LBProvider n'est pas disponible, ignorer
@@ -107,7 +109,8 @@ export function AiImageButton({
107
109
  hasLBProvider &&
108
110
  lbStatus === "ready" &&
109
111
  lbHasSession &&
110
- !lbHasSelectedApiKeyCookie;
112
+ !lbHasSelectedApiKeyCookie &&
113
+ !lbHasSelectedKey;
111
114
  const isAuthReady =
112
115
  !needsApiKeySelection &&
113
116
  (lbStatus === "ready" || Boolean(process.env.LB_API_KEY));
@@ -226,8 +229,14 @@ export function AiImageButton({
226
229
  <div className="relative inline-block ai-glow">
227
230
  <button
228
231
  {...buttonProps}
229
- onClick={handleOpenPanel}
230
- disabled={disabled || loading || !isAuthReady}
232
+ onClick={() => {
233
+ if (!isAuthReady) {
234
+ setShowAuthModal(true);
235
+ return;
236
+ }
237
+ handleOpenPanel();
238
+ }}
239
+ disabled={disabled || loading}
231
240
  className={`ai-btn ai-image-btn ${variantClass} ${sizeClass} ${radiusClass} ${className || ""}`}
232
241
  style={buttonProps.style}
233
242
  data-ai-image-button
@@ -62,12 +62,14 @@ export function AiInput({
62
62
  let lbStatus: string | undefined;
63
63
  let lbHasSession = false;
64
64
  let lbHasSelectedApiKeyCookie = false;
65
+ let lbHasSelectedKey = false;
65
66
  let hasLBProvider = false;
66
67
  try {
67
68
  const lbContext = useLB();
68
69
  lbStatus = lbContext.status;
69
70
  lbHasSession = Boolean(lbContext.session?.sessionToken);
70
71
  lbHasSelectedApiKeyCookie = lbContext.hasSelectedApiKeyCookie;
72
+ lbHasSelectedKey = Boolean(lbContext.selectedKey?.id);
71
73
  hasLBProvider = true;
72
74
  } catch {
73
75
  // LBProvider n'est pas disponible, ignorer
@@ -103,7 +105,8 @@ export function AiInput({
103
105
  hasLBProvider &&
104
106
  lbStatus === "ready" &&
105
107
  lbHasSession &&
106
- !lbHasSelectedApiKeyCookie;
108
+ !lbHasSelectedApiKeyCookie &&
109
+ !lbHasSelectedKey;
107
110
  const isAuthReady =
108
111
  !needsApiKeySelection &&
109
112
  (lbStatus === "ready" || Boolean(process.env.LB_API_KEY));
@@ -6,11 +6,15 @@ import {
6
6
  useEffect,
7
7
  useRef,
8
8
  useLayoutEffect,
9
+ useMemo,
9
10
  type ReactNode,
10
11
  } from "react";
11
12
  import { createPortal } from "react-dom";
12
13
  import {
13
14
  BookOpen,
15
+ Check,
16
+ Copy,
17
+ Eye,
14
18
  Search,
15
19
  Sparkles,
16
20
  Star,
@@ -33,6 +37,19 @@ import { AiProvider, useAiContext } from "../context/AiProvider";
33
37
  import { useI18n } from "../context/I18nContext";
34
38
  import { useLoadingTimer } from "../hooks/useLoadingTimer";
35
39
 
40
+ function tryFormatJson(input?: string): { formatted: string; isJson: boolean } {
41
+ const raw = (input || "").trim();
42
+ if (!raw) {
43
+ return { formatted: "", isJson: false };
44
+ }
45
+ try {
46
+ const parsed = JSON.parse(raw);
47
+ return { formatted: JSON.stringify(parsed, null, 2), isJson: true };
48
+ } catch {
49
+ return { formatted: input || "", isJson: false };
50
+ }
51
+ }
52
+
36
53
  export interface AiPromptPanelProps {
37
54
  isOpen: boolean;
38
55
  onClose: () => void;
@@ -50,6 +67,8 @@ export interface AiPromptPanelProps {
50
67
  apiKey?: string;
51
68
  baseUrl?: string;
52
69
  showOnlyUserModels?: boolean;
70
+ contextPreview?: string;
71
+ contextPreviewTitle?: string;
53
72
  }
54
73
 
55
74
  export interface AiPromptPanelRenderProps {
@@ -114,6 +133,8 @@ function AiPromptPanelInternal({
114
133
  apiKey,
115
134
  baseUrl,
116
135
  showOnlyUserModels = false,
136
+ contextPreview,
137
+ contextPreviewTitle,
117
138
  }: AiPromptPanelProps) {
118
139
  const { t } = useI18n();
119
140
  const [selectedModel, setSelectedModel] = useState("");
@@ -127,6 +148,8 @@ function AiPromptPanelInternal({
127
148
  const [selectedTag, setSelectedTag] = useState<string | "all">("all");
128
149
  const [isGenerating, setIsGenerating] = useState(false);
129
150
  const [isClosing, setIsClosing] = useState(false);
151
+ const [showContextPreview, setShowContextPreview] = useState(false);
152
+ const [contextCopied, setContextCopied] = useState(false);
130
153
  const [portalRoot, setPortalRoot] = useState<HTMLElement | null>(null);
131
154
  const promptRef = useRef<HTMLTextAreaElement>(null);
132
155
  const closeTimeoutRef = useRef<number | null>(null);
@@ -265,6 +288,8 @@ function AiPromptPanelInternal({
265
288
  setPrompt("");
266
289
  setPromptId(undefined);
267
290
  setShowPromptLibrary(false);
291
+ setShowContextPreview(false);
292
+ setContextCopied(false);
268
293
  setSearchQuery("");
269
294
  setSelectedTag("all");
270
295
  setIsClosing(false);
@@ -287,6 +312,12 @@ function AiPromptPanelInternal({
287
312
  };
288
313
  }, []);
289
314
 
315
+ useEffect(() => {
316
+ if (!contextCopied) return;
317
+ const timeout = window.setTimeout(() => setContextCopied(false), 1200);
318
+ return () => window.clearTimeout(timeout);
319
+ }, [contextCopied]);
320
+
290
321
  useEffect(() => {
291
322
  setPortalRoot(document.body);
292
323
  }, []);
@@ -340,6 +371,15 @@ function AiPromptPanelInternal({
340
371
  adjustPromptHeight();
341
372
  }, [prompt]);
342
373
 
374
+ const contextPreviewData = useMemo(
375
+ () => tryFormatJson(contextPreview),
376
+ [contextPreview]
377
+ );
378
+ const contextPreviewLines = useMemo(
379
+ () => contextPreviewData.formatted.split("\n"),
380
+ [contextPreviewData.formatted]
381
+ );
382
+
343
383
  if (!isOpen) return null;
344
384
 
345
385
  const activeModelId = selectedModel || modelOptions[0]?.id || "";
@@ -406,6 +446,8 @@ function AiPromptPanelInternal({
406
446
  }
407
447
 
408
448
  const isDrawer = uiMode === "drawer";
449
+ const hasContextPreview = Boolean(contextPreview?.trim());
450
+ const contextCharCount = contextPreview?.length || 0;
409
451
  const panelContainerStyle = isDrawer
410
452
  ? {
411
453
  ...aiStyles.modal,
@@ -425,6 +467,78 @@ function AiPromptPanelInternal({
425
467
  }
426
468
  : aiStyles.modalContent;
427
469
 
470
+ const renderContextLine = (line: string, lineIndex: number) => {
471
+ if (!contextPreviewData.isJson) {
472
+ return (
473
+ <span
474
+ key={`line-${lineIndex}`}
475
+ style={{ color: "var(--ai-text-secondary)" }}
476
+ >
477
+ {line || " "}
478
+ </span>
479
+ );
480
+ }
481
+
482
+ const tokenRegex =
483
+ /("(?:\\.|[^"\\])*"(?=\s*:)|"(?:\\.|[^"\\])*"|true|false|null|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|[{}[\],:])/g;
484
+ const parts: ReactNode[] = [];
485
+ let lastIndex = 0;
486
+ let match: RegExpExecArray | null;
487
+
488
+ while ((match = tokenRegex.exec(line)) !== null) {
489
+ const token = match[0];
490
+ const index = match.index;
491
+ if (index > lastIndex) {
492
+ parts.push(
493
+ <span
494
+ key={`txt-${lineIndex}-${lastIndex}`}
495
+ style={{ color: "var(--ai-text-secondary)" }}
496
+ >
497
+ {line.slice(lastIndex, index)}
498
+ </span>
499
+ );
500
+ }
501
+
502
+ const isKey = /^".*"$/.test(token) && line.slice(match.index).includes(":");
503
+ const color = isKey
504
+ ? "#7cc5ff"
505
+ : /^".*"$/.test(token)
506
+ ? "#9ad07f"
507
+ : /^(true|false)$/.test(token)
508
+ ? "#f4a259"
509
+ : token === "null"
510
+ ? "#d3869b"
511
+ : /^-?\d/.test(token)
512
+ ? "#f7dc6f"
513
+ : "var(--ai-text-secondary)";
514
+
515
+ parts.push(
516
+ <span key={`tok-${lineIndex}-${index}`} style={{ color }}>
517
+ {token}
518
+ </span>
519
+ );
520
+
521
+ lastIndex = index + token.length;
522
+ }
523
+
524
+ if (lastIndex < line.length) {
525
+ parts.push(
526
+ <span
527
+ key={`txt-end-${lineIndex}`}
528
+ style={{ color: "var(--ai-text-secondary)" }}
529
+ >
530
+ {line.slice(lastIndex)}
531
+ </span>
532
+ );
533
+ }
534
+
535
+ return (
536
+ <span key={`line-${lineIndex}`} style={{ whiteSpace: "pre" }}>
537
+ {parts.length > 0 ? parts : " "}
538
+ </span>
539
+ );
540
+ };
541
+
428
542
  if (children) {
429
543
  return createPortal(
430
544
  <div style={panelContainerStyle} onKeyDown={handleKeyDown}>
@@ -755,18 +869,40 @@ function AiPromptPanelInternal({
755
869
  )}
756
870
  </span>
757
871
  </label>
758
- {promptsLoading ? (
759
- <span className="ai-inline-skeleton" aria-hidden="true" />
760
- ) : filteredPrompts.length > 0 ? (
761
- <button
762
- onClick={() => setShowPromptLibrary(true)}
763
- className="ai-inline-btn"
764
- >
765
- <BookOpen size={14} />
766
- {t("prompt.modal.browsePrompts", "Browse Prompts")} (
767
- {filteredPrompts.length})
768
- </button>
769
- ) : null}
872
+ <div
873
+ style={{
874
+ display: "flex",
875
+ alignItems: "center",
876
+ flexWrap: "wrap",
877
+ justifyContent: "flex-end",
878
+ gap: "8px",
879
+ }}
880
+ >
881
+ {hasContextPreview ? (
882
+ <button
883
+ type="button"
884
+ onClick={() => setShowContextPreview(true)}
885
+ className="ai-inline-btn"
886
+ style={{ whiteSpace: "nowrap" }}
887
+ >
888
+ <Eye size={14} />
889
+ {t("prompt.modal.contextPreview", "Context preview")}
890
+ </button>
891
+ ) : null}
892
+ {promptsLoading ? (
893
+ <span className="ai-inline-skeleton" aria-hidden="true" />
894
+ ) : filteredPrompts.length > 0 ? (
895
+ <button
896
+ onClick={() => setShowPromptLibrary(true)}
897
+ className="ai-inline-btn"
898
+ style={{ whiteSpace: "nowrap" }}
899
+ >
900
+ <BookOpen size={14} />
901
+ {t("prompt.modal.browsePrompts", "Browse Prompts")} (
902
+ {filteredPrompts.length})
903
+ </button>
904
+ ) : null}
905
+ </div>
770
906
  </div>
771
907
  <textarea
772
908
  id="prompt-input"
@@ -1124,6 +1260,137 @@ function AiPromptPanelInternal({
1124
1260
  )}
1125
1261
  </div>
1126
1262
 
1263
+ {showContextPreview ? (
1264
+ <div
1265
+ style={{
1266
+ position: "absolute",
1267
+ inset: 0,
1268
+ zIndex: 12,
1269
+ background: "var(--ai-overlay)",
1270
+ backdropFilter: "blur(3px)",
1271
+ WebkitBackdropFilter: "blur(3px)",
1272
+ display: "flex",
1273
+ alignItems: "stretch",
1274
+ justifyContent: "center",
1275
+ padding: "10px",
1276
+ }}
1277
+ onClick={() => setShowContextPreview(false)}
1278
+ >
1279
+ <div
1280
+ style={{
1281
+ width: "min(980px, 100%)",
1282
+ maxHeight: "100%",
1283
+ background: "var(--ai-bg-secondary)",
1284
+ border: "1px solid var(--ai-border)",
1285
+ borderRadius: "12px",
1286
+ boxShadow: "var(--ai-shadow-lg)",
1287
+ overflow: "hidden",
1288
+ display: "flex",
1289
+ flexDirection: "column",
1290
+ }}
1291
+ onClick={(e) => e.stopPropagation()}
1292
+ >
1293
+ <div
1294
+ style={{
1295
+ padding: "12px 14px",
1296
+ borderBottom: "1px solid var(--ai-border)",
1297
+ display: "flex",
1298
+ alignItems: "center",
1299
+ justifyContent: "space-between",
1300
+ gap: "10px",
1301
+ }}
1302
+ >
1303
+ <div style={{ minWidth: 0 }}>
1304
+ <div style={{ fontSize: "14px", fontWeight: 600 }}>
1305
+ {contextPreviewTitle ||
1306
+ t("prompt.modal.contextPreview", "Context preview")}
1307
+ </div>
1308
+ <div
1309
+ style={{
1310
+ marginTop: "2px",
1311
+ fontSize: "12px",
1312
+ color: "var(--ai-text-secondary)",
1313
+ }}
1314
+ >
1315
+ {t("prompt.modal.contextChars", "{count} chars", {
1316
+ count: contextCharCount.toLocaleString(),
1317
+ })}
1318
+ </div>
1319
+ </div>
1320
+ <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
1321
+ <button
1322
+ type="button"
1323
+ className="ai-inline-btn"
1324
+ onClick={async () => {
1325
+ try {
1326
+ await navigator.clipboard.writeText(
1327
+ contextPreviewData.formatted || ""
1328
+ );
1329
+ setContextCopied(true);
1330
+ } catch {
1331
+ setContextCopied(false);
1332
+ }
1333
+ }}
1334
+ style={{ whiteSpace: "nowrap" }}
1335
+ >
1336
+ {contextCopied ? <Check size={14} /> : <Copy size={14} />}
1337
+ {contextCopied
1338
+ ? t("common.copied", "Copied")
1339
+ : t("common.copy", "Copy")}
1340
+ </button>
1341
+ <button
1342
+ type="button"
1343
+ className="ai-icon-btn"
1344
+ onClick={() => setShowContextPreview(false)}
1345
+ aria-label={t("common.closeLabel", "Close")}
1346
+ >
1347
+ ×
1348
+ </button>
1349
+ </div>
1350
+ </div>
1351
+ <pre
1352
+ style={{
1353
+ margin: 0,
1354
+ padding: "14px",
1355
+ flex: 1,
1356
+ overflow: "auto",
1357
+ background: "color-mix(in srgb, var(--ai-bg) 70%, transparent)",
1358
+ borderTop: "1px solid var(--ai-border)",
1359
+ fontFamily:
1360
+ "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
1361
+ fontSize: "12px",
1362
+ lineHeight: 1.5,
1363
+ }}
1364
+ >
1365
+ {contextPreviewLines.map((line, index) => (
1366
+ <div
1367
+ key={`context-line-${index}`}
1368
+ style={{
1369
+ display: "grid",
1370
+ gridTemplateColumns: "44px 1fr",
1371
+ gap: "12px",
1372
+ minHeight: "18px",
1373
+ }}
1374
+ >
1375
+ <span
1376
+ style={{
1377
+ color: "var(--ai-text-tertiary)",
1378
+ textAlign: "right",
1379
+ userSelect: "none",
1380
+ paddingRight: "8px",
1381
+ borderRight: "1px solid var(--ai-border)",
1382
+ }}
1383
+ >
1384
+ {index + 1}
1385
+ </span>
1386
+ {renderContextLine(line, index)}
1387
+ </div>
1388
+ ))}
1389
+ </pre>
1390
+ </div>
1391
+ </div>
1392
+ ) : null}
1393
+
1127
1394
  <div
1128
1395
  style={{
1129
1396
  ...aiStyles.modalFooter,
@@ -51,12 +51,14 @@ export function AiSelect({
51
51
  let lbStatus: string | undefined;
52
52
  let lbHasSession = false;
53
53
  let lbHasSelectedApiKeyCookie = false;
54
+ let lbHasSelectedKey = false;
54
55
  let hasLBProvider = false;
55
56
  try {
56
57
  const lbContext = useLB();
57
58
  lbStatus = lbContext.status;
58
59
  lbHasSession = Boolean(lbContext.session?.sessionToken);
59
60
  lbHasSelectedApiKeyCookie = lbContext.hasSelectedApiKeyCookie;
61
+ lbHasSelectedKey = Boolean(lbContext.selectedKey?.id);
60
62
  hasLBProvider = true;
61
63
  } catch {
62
64
  // LBProvider n'est pas disponible, ignorer
@@ -90,7 +92,8 @@ export function AiSelect({
90
92
  hasLBProvider &&
91
93
  lbStatus === "ready" &&
92
94
  lbHasSession &&
93
- !lbHasSelectedApiKeyCookie;
95
+ !lbHasSelectedApiKeyCookie &&
96
+ !lbHasSelectedKey;
94
97
  const isAuthReady =
95
98
  !needsApiKeySelection &&
96
99
  (lbStatus === "ready" || Boolean(process.env.LB_API_KEY));
@@ -243,7 +243,10 @@ export function AiStatusButton({
243
243
  : undefined;
244
244
  const hasLbSession = Boolean(lbSessionToken);
245
245
  const requiresApiKeySelection =
246
- lbStatus === "ready" && hasLbSession && !lbHasSelectedApiKeyCookie;
246
+ lbStatus === "ready" &&
247
+ hasLbSession &&
248
+ !lbHasSelectedApiKeyCookie &&
249
+ !hasApiKeySelected;
247
250
  const isApiKeyAuthMode = authTypeValue === "api_key";
248
251
 
249
252
  const [tooltipStyle, setTooltipStyle] = useState<Record<string, string>>({});
@@ -65,12 +65,14 @@ export function AiTextarea({
65
65
  let lbStatus: string | undefined;
66
66
  let lbHasSession = false;
67
67
  let lbHasSelectedApiKeyCookie = false;
68
+ let lbHasSelectedKey = false;
68
69
  let hasLBProvider = false;
69
70
  try {
70
71
  const lbContext = useLB();
71
72
  lbStatus = lbContext.status;
72
73
  lbHasSession = Boolean(lbContext.session?.sessionToken);
73
74
  lbHasSelectedApiKeyCookie = lbContext.hasSelectedApiKeyCookie;
75
+ lbHasSelectedKey = Boolean(lbContext.selectedKey?.id);
74
76
  hasLBProvider = true;
75
77
  } catch {
76
78
  // LBProvider n'est pas disponible, ignorer
@@ -95,7 +97,8 @@ export function AiTextarea({
95
97
  hasLBProvider &&
96
98
  lbStatus === "ready" &&
97
99
  lbHasSession &&
98
- !lbHasSelectedApiKeyCookie;
100
+ !lbHasSelectedApiKeyCookie &&
101
+ !lbHasSelectedKey;
99
102
 
100
103
  const { models: _models } = useAiModels({
101
104
  baseUrl,
@@ -252,8 +255,18 @@ export function AiTextarea({
252
255
  />
253
256
  <button
254
257
  className={`ai-control-action ai-spark ${sizeClass} ${radiusClass}`}
255
- onClick={hasConfiguration ? handleQuickGenerate : handleOpenPanel}
256
- disabled={disabled || loading || !isAuthReady}
258
+ onClick={() => {
259
+ if (!isAuthReady) {
260
+ setShowAuthModal(true);
261
+ return;
262
+ }
263
+ if (hasConfiguration) {
264
+ void handleQuickGenerate();
265
+ return;
266
+ }
267
+ handleOpenPanel();
268
+ }}
269
+ disabled={disabled || loading}
257
270
  type="button"
258
271
  title={
259
272
  !isAuthReady
@@ -258,19 +258,34 @@ export function LBProvider({
258
258
  remaining?: number;
259
259
  } | null> => {
260
260
  const headers = lbClient.getAuthHeaders(state.session?.sessionToken);
261
- const response = await fetch(`${proxyUrl}/auth/wallet`, {
262
- method: "GET",
263
- credentials: "include",
264
- headers,
265
- });
266
- if (!response.ok) return null;
267
- const data = (await response.json()) as WalletStatusResponse;
268
- return {
269
- used: Number(data.totalUsed || 0),
270
- total: Number(data.totalAdded || 0),
271
- percentage: Number(data.percentUsed ?? data.percentage ?? 0),
272
- remaining: Number(data.walletSellValueUsd || 0),
273
- };
261
+ const walletUrls = Array.from(
262
+ new Set([
263
+ `${proxyUrl}/auth/wallet`,
264
+ `${proxyUrl.replace("/api/lastbrain", "/api/ai")}/auth/wallet`,
265
+ ])
266
+ );
267
+
268
+ for (const url of walletUrls) {
269
+ try {
270
+ const response = await fetch(url, {
271
+ method: "GET",
272
+ credentials: "include",
273
+ headers,
274
+ });
275
+ if (!response.ok) continue;
276
+ const data = (await response.json()) as WalletStatusResponse;
277
+ return {
278
+ used: Number(data.totalUsed || 0),
279
+ total: Number(data.totalAdded || 0),
280
+ percentage: Number(data.percentUsed ?? data.percentage ?? 0),
281
+ remaining: Number(data.walletSellValueUsd || 0),
282
+ };
283
+ } catch {
284
+ // try next endpoint
285
+ }
286
+ }
287
+
288
+ return null;
274
289
  }, [lbClient, proxyUrl, state.session?.sessionToken]);
275
290
 
276
291
  /**
@@ -604,8 +619,11 @@ export function LBProvider({
604
619
  }));
605
620
  }
606
621
  setHasSelectedApiKeyCookie(true);
607
- await refreshBasicStatus();
608
- setTimeout(() => refreshStorageStatus(), 100);
622
+ // Ne pas bloquer la validation UI de sélection sur des requêtes status/wallet/storage.
623
+ void refreshBasicStatus();
624
+ setTimeout(() => {
625
+ void refreshStorageStatus();
626
+ }, 100);
609
627
  } else {
610
628
  throw new Error("No valid authentication method available");
611
629
  }