Skip to content

Commit b7d0346

Browse files
authored
[Website] Use a kapa.ai widget for blueprint error troubleshooting (#2987)
This adds a "Troubleshoot with AI" button below the error message when running a blueprint. For example for a URL like ?plugin=doesntexist <img width="1808" height="1660" alt="Screenshot 2025-12-17 at 15 25 20" src="https://github.com/user-attachments/assets/e8b8c910-bbee-4650-9ae3-d2c7b7e27165" /> kapa.ai will then try to provide an answer: <img width="1790" height="1882" alt="Screenshot 2025-12-17 at 15 26 56" src="https://github.com/user-attachments/assets/83d91b70-2e74-4b41-a68e-045e9539ec55" /> To trigger the above error you can run http://127.0.0.1:5400/website-server/?plugin=doesntexist locally.
1 parent 54d9caa commit b7d0346

File tree

3 files changed

+130
-3
lines changed

3 files changed

+130
-3
lines changed

packages/playground/website/src/components/site-error-modal/site-error-modal.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type {
2020
} from './types';
2121
import { getSiteErrorView } from './get-site-error-view';
2222
import type { SiteInfo } from '../../lib/state/redux/slice-sites';
23+
import { useKapaAI } from './use-kapa-ai';
2324

2425
export function SiteErrorModal({
2526
error,
@@ -38,6 +39,7 @@ export function SiteErrorModal({
3839
isSubmittingReport,
3940
handleSubmitReport,
4041
} = useErrorReporting(site);
42+
const kapaAI = useKapaAI();
4143

4244
const helpers: PresentationHelpers = {
4345
deleteSite: () => {
@@ -69,6 +71,9 @@ export function SiteErrorModal({
6971
});
7072

7173
const detailText = formatErrorDetails(errorDetails);
74+
const shouldShowKapaButton =
75+
!isReporting && detailText && kapaAI.isEnabled();
76+
7277
return (
7378
<Modal
7479
title={
@@ -143,8 +148,18 @@ export function SiteErrorModal({
143148
</p>
144149
)}
145150
</div>
146-
{view.actions.length || !view.isDeveloperError ? (
151+
{view.actions.length || !view.isDeveloperError || detailText ? (
147152
<div className={css.errorModalFooter}>
153+
{shouldShowKapaButton && (
154+
<Button
155+
variant="secondary"
156+
onClick={() =>
157+
kapaAI.openWithErrorMessage(detailText!)
158+
}
159+
>
160+
Troubleshoot with AI
161+
</Button>
162+
)}
148163
{!view.isDeveloperError &&
149164
!view.hideReportButton &&
150165
!isReporting &&
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { useCallback, useRef } from 'react';
2+
3+
const KAPA_WEBSITE_ID = 'a8b85529-1773-4710-b35f-c9ebc70ffcb6';
4+
const KAPA_SCRIPT_ID = 'kapa-widget-script';
5+
6+
declare global {
7+
interface Window {
8+
Kapa?: {
9+
open: (options?: {
10+
mode?: 'ai' | 'search';
11+
query?: string;
12+
submit?: boolean;
13+
}) => void;
14+
close: () => void;
15+
render: (options?: { onRender?: () => void }) => void;
16+
unmount: () => void;
17+
};
18+
}
19+
}
20+
21+
function loadKapaScript(): Promise<void> {
22+
return new Promise((resolve) => {
23+
// Check if script already exists and loaded
24+
if (document.getElementById(KAPA_SCRIPT_ID)) {
25+
if (window.Kapa) {
26+
resolve();
27+
} else {
28+
// Script exists but not loaded yet, wait for it
29+
const checkKapa = setInterval(() => {
30+
if (window.Kapa) {
31+
clearInterval(checkKapa);
32+
resolve();
33+
}
34+
}, 100);
35+
setTimeout(() => clearInterval(checkKapa), 5000);
36+
}
37+
return;
38+
}
39+
40+
const script = document.createElement('script');
41+
script.id = KAPA_SCRIPT_ID;
42+
script.src = 'https://widget.kapa.ai/kapa-widget.bundle.js';
43+
script.async = true;
44+
script.setAttribute('data-website-id', KAPA_WEBSITE_ID);
45+
script.setAttribute(
46+
'data-project-name',
47+
'WordPress Playground AI Assistant'
48+
);
49+
script.setAttribute('data-project-color', '#3858e9');
50+
script.setAttribute(
51+
'data-project-logo',
52+
'https://wordpress.github.io/wordpress-playground/img/playground-logo.svg'
53+
);
54+
script.setAttribute('data-button-hide', 'true');
55+
script.setAttribute('data-modal-z-index', '100001');
56+
script.setAttribute('data-scale-factor', '1.3');
57+
58+
script.onload = () => {
59+
const checkKapa = setInterval(() => {
60+
if (window.Kapa) {
61+
clearInterval(checkKapa);
62+
resolve();
63+
}
64+
}, 100);
65+
setTimeout(() => clearInterval(checkKapa), 5000);
66+
};
67+
68+
document.body.appendChild(script);
69+
});
70+
}
71+
72+
export function useKapaAI() {
73+
const hasSubmittedQuery = useRef(false);
74+
const isEnabled = () => {
75+
return (
76+
window.location.hostname === 'playground.wordpress.net' ||
77+
process.env.NODE_ENV === 'development'
78+
);
79+
};
80+
81+
const openWithErrorMessage = useCallback(async (errorMessage: string) => {
82+
if (!isEnabled()) {
83+
return;
84+
}
85+
86+
await loadKapaScript();
87+
88+
if (window.Kapa) {
89+
if (hasSubmittedQuery.current) {
90+
window.Kapa.open();
91+
} else {
92+
const urlParams = new URLSearchParams(window.location.search);
93+
const hasExternalBlueprint = urlParams.has('blueprint-url');
94+
const contextPrefix = hasExternalBlueprint
95+
? ''
96+
: `Given the URL query parameters ${window.location.search}, `;
97+
98+
window.Kapa.open({
99+
mode: 'ai',
100+
query: `${contextPrefix}Suggest a solution or troubleshooting steps for the following error: ${errorMessage}`,
101+
submit: true,
102+
});
103+
hasSubmittedQuery.current = true;
104+
}
105+
}
106+
}, []);
107+
108+
return {
109+
isEnabled,
110+
openWithErrorMessage,
111+
};
112+
}

packages/playground/website/vite.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ export default defineConfig(({ command, mode }) => {
4343
'CORS_PROXY_URL' in process.env
4444
? process.env.CORS_PROXY_URL
4545
: mode === 'production'
46-
? 'https://wordpress-playground-cors-proxy.net/?'
47-
: '/cors-proxy/?';
46+
? 'https://wordpress-playground-cors-proxy.net/?'
47+
: '/cors-proxy/?';
4848

4949
return {
5050
// Split traffic from this server on dev so that the iframe content and

0 commit comments

Comments
 (0)