Skip to content

Commit cdd8c4d

Browse files
committed
Much cleaner implementation for Kapa
1 parent 627b312 commit cdd8c4d

File tree

3 files changed

+98
-104
lines changed

3 files changed

+98
-104
lines changed

apps/webapp/app/components/navigation/SideMenu.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import { useLocation, useNavigation } from "@remix-run/react";
2121
import { useEffect, useRef, useState, type ReactNode } from "react";
2222
import simplur from "simplur";
23+
import { AISparkleIcon } from "~/assets/icons/AISparkleIcon";
2324
import { RunsIconExtraSmall } from "~/assets/icons/RunsIcon";
2425
import { TaskIconSmall } from "~/assets/icons/TaskIcon";
2526
import { WaitpointTokenIcon } from "~/assets/icons/WaitpointTokenIcon";
@@ -55,6 +56,7 @@ import {
5556
v3UsagePath,
5657
v3WaitpointTokensPath,
5758
} from "~/utils/pathBuilder";
59+
import { useKapaWidget } from "../../hooks/useKapaWidget";
5860
import { FreePlanUsage } from "../billing/FreePlanUsage";
5961
import { ConnectionIcon, DevPresencePanel, useDevPresence } from "../DevPresence";
6062
import { ImpersonationBanner } from "../ImpersonationBanner";
@@ -68,22 +70,16 @@ import {
6870
PopoverMenuItem,
6971
PopoverTrigger,
7072
} from "../primitives/Popover";
73+
import { ShortcutKey } from "../primitives/ShortcutKey";
7174
import { TextLink } from "../primitives/TextLink";
7275
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip";
76+
import { ShortcutsAutoOpen } from "../Shortcuts";
7377
import { UserProfilePhoto } from "../UserProfilePhoto";
7478
import { EnvironmentSelector } from "./EnvironmentSelector";
7579
import { HelpAndFeedback } from "./HelpAndFeedbackPopover";
7680
import { SideMenuHeader } from "./SideMenuHeader";
7781
import { SideMenuItem } from "./SideMenuItem";
7882
import { SideMenuSection } from "./SideMenuSection";
79-
import { useShortcutKeys } from "~/hooks/useShortcutKeys";
80-
import { AISparkleIcon } from "~/assets/icons/AISparkleIcon";
81-
import { ShortcutKey } from "../primitives/ShortcutKey";
82-
import { useFeatures } from "~/hooks/useFeatures";
83-
import { useKapaConfig } from "~/root";
84-
import { useShortcuts } from "~/components/primitives/ShortcutsProvider";
85-
import { useKapaWidget } from "../../hooks/useKapaWidget";
86-
import { ShortcutsAutoOpen } from "../Shortcuts";
8783

8884
type SideMenuUser = Pick<User, "email" | "admin"> & { isImpersonating: boolean };
8985
export type SideMenuProject = Pick<

apps/webapp/app/hooks/useKapaWidget.tsx

Lines changed: 91 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,122 @@
1-
import { useKapaConfig } from "~/root";
21
import { useShortcuts } from "../components/primitives/ShortcutsProvider";
32
import { useFeatures } from "~/hooks/useFeatures";
43
import { useCallback, useEffect, useState } from "react";
4+
import { useMatches } from "@remix-run/react";
5+
import { useTypedMatchesData } from "./useTypedMatchData";
6+
import { type loader } from "~/root";
7+
8+
type OpenOptions = { mode: string; query: string; submit: boolean };
59

610
declare global {
711
interface Window {
8-
Kapa: ((
12+
Kapa: (
913
command: string,
10-
options?: () => void,
14+
options?: (() => void) | OpenOptions,
1115
remove?: string | { onRender?: () => void }
12-
) => void) & {
13-
open: (options?: { mode: string; query: string; submit: boolean }) => void;
14-
};
16+
) => void;
1517
}
1618
}
1719

20+
export function KapaScripts({ websiteId }: { websiteId?: string }) {
21+
if (!websiteId) return null;
22+
23+
return (
24+
<>
25+
<script
26+
async
27+
src="https://widget.kapa.ai/kapa-widget.bundle.js"
28+
data-website-id={websiteId}
29+
data-project-name={"Trigger.dev"}
30+
data-project-color={"#C7D2FE"}
31+
data-project-logo={"https://content.trigger.dev/trigger-logo-circle.png"}
32+
data-render-on-load={"false"}
33+
data-button-hide={"true"}
34+
data-modal-disclaimer-bg-color={"#1A1B1F"}
35+
data-modal-disclaimer-text-color={"#878C99"}
36+
data-modal-header-bg-color={"#2C3034"}
37+
data-modal-body-bg-color={"#4D525B"}
38+
data-query-input-text-color={"#15171A"}
39+
data-query-input-placeholder-text-color={"#878C99"}
40+
data-modal-title-color={"#D7D9DD"}
41+
data-button-text-color={"#D7D9DD"}
42+
></script>
43+
<script
44+
dangerouslySetInnerHTML={{
45+
__html: `
46+
(function () {
47+
let k = window.Kapa;
48+
if (!k) {
49+
let i = function () {
50+
i.c(arguments);
51+
};
52+
i.q = [];
53+
i.c = function (args) {
54+
i.q.push(args);
55+
};
56+
window.Kapa = i;
57+
}
58+
})();
59+
`,
60+
}}
61+
/>
62+
</>
63+
);
64+
}
65+
66+
export function useKapaConfig() {
67+
const matches = useMatches();
68+
const routeMatch = useTypedMatchesData<typeof loader>({
69+
id: "root",
70+
matches,
71+
});
72+
return routeMatch?.kapa;
73+
}
74+
1875
export function useKapaWidget() {
1976
const kapa = useKapaConfig();
2077
const features = useFeatures();
2178
const { disableShortcuts, enableShortcuts, areShortcutsEnabled } = useShortcuts();
2279
const [isKapaOpen, setIsKapaOpen] = useState(false);
2380

24-
useEffect(() => {
25-
if (!features.isManagedCloud || !kapa?.websiteId) return;
26-
27-
loadScriptIfNotExists(kapa.websiteId);
28-
29-
// Define the handler function
30-
const handleModalClose = () => {
31-
setIsKapaOpen(false);
32-
enableShortcuts();
33-
};
81+
const handleModalClose = useCallback(() => {
82+
setIsKapaOpen(false);
83+
enableShortcuts();
84+
}, [enableShortcuts]);
3485

35-
const handleModalOpen = () => {
36-
setIsKapaOpen(true);
37-
disableShortcuts();
38-
};
86+
const handleModalOpen = useCallback(() => {
87+
setIsKapaOpen(true);
88+
disableShortcuts();
89+
}, [disableShortcuts]);
3990

40-
const kapaInterval = setInterval(() => {
41-
if (typeof window.Kapa === "function") {
42-
clearInterval(kapaInterval);
43-
window.Kapa("render");
44-
window.Kapa("onModalClose", handleModalClose);
91+
useEffect(() => {
92+
if (!features.isManagedCloud || !kapa?.websiteId) return;
4593

46-
// Register onModalOpen handler
47-
window.Kapa("onModalOpen", handleModalOpen);
48-
}
49-
}, 100);
94+
window.Kapa("render");
95+
window.Kapa("onModalOpen", handleModalOpen);
96+
window.Kapa("onModalClose", handleModalClose);
5097

51-
// Clear interval on unmount to prevent memory leaks
5298
return () => {
53-
clearInterval(kapaInterval);
54-
if (typeof window.Kapa === "function") {
55-
window.Kapa("unmount");
56-
57-
window.Kapa("onModalOpen", handleModalOpen, "remove");
58-
window.Kapa("onModalClose", handleModalClose, "remove");
59-
}
99+
window.Kapa("onModalOpen", handleModalOpen, "remove");
100+
window.Kapa("onModalClose", handleModalClose, "remove");
60101
};
61-
}, [features.isManagedCloud, kapa?.websiteId, disableShortcuts, enableShortcuts]);
102+
}, [features.isManagedCloud, kapa?.websiteId]);
62103

63104
const openKapa = useCallback(
64105
(query?: string) => {
65106
if (!features.isManagedCloud || !kapa?.websiteId) return;
66107

67-
if (typeof window.Kapa === "function") {
68-
window.Kapa.open(
69-
query
70-
? {
71-
mode: "ai",
72-
query,
73-
submit: true,
74-
}
75-
: undefined
76-
);
77-
setIsKapaOpen(true);
78-
disableShortcuts();
79-
}
108+
window.Kapa(
109+
"open",
110+
query
111+
? {
112+
mode: "ai",
113+
query,
114+
submit: true,
115+
}
116+
: undefined
117+
);
118+
setIsKapaOpen(true);
119+
disableShortcuts();
80120
},
81121
[disableShortcuts, features.isManagedCloud, kapa?.websiteId]
82122
);
@@ -87,38 +127,3 @@ export function useKapaWidget() {
87127
isKapaOpen,
88128
};
89129
}
90-
91-
function loadScriptIfNotExists(websiteId: string) {
92-
const scriptSrc = "https://widget.kapa.ai/kapa-widget.bundle.js";
93-
94-
if (document.querySelector(`script[src="${scriptSrc}"]`)) {
95-
return;
96-
}
97-
98-
const script = document.createElement("script");
99-
script.async = true;
100-
script.src = scriptSrc;
101-
102-
const attributes = {
103-
"data-website-id": websiteId,
104-
"data-project-name": "Trigger.dev",
105-
"data-project-color": "#C7D2FE",
106-
"data-project-logo": "https://content.trigger.dev/trigger-logo-circle.png",
107-
"data-render-on-load": "false",
108-
"data-button-hide": "true",
109-
"data-modal-disclaimer-bg-color": "#1A1B1F",
110-
"data-modal-disclaimer-text-color": "#878C99",
111-
"data-modal-header-bg-color": "#2C3034",
112-
"data-modal-body-bg-color": "#4D525B",
113-
"data-query-input-text-color": "#15171A",
114-
"data-query-input-placeholder-text-color": "#878C99",
115-
"data-modal-title-color": "#D7D9DD",
116-
"data-button-text-color": "#D7D9DD",
117-
};
118-
119-
Object.entries(attributes).forEach(([key, value]) => {
120-
script.setAttribute(key, value);
121-
});
122-
123-
document.head.appendChild(script);
124-
}

apps/webapp/app/root.tsx

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { usePostHog } from "./hooks/usePostHog";
2424
import { useTypedMatchesData } from "./hooks/useTypedMatchData";
2525
import { getUser } from "./services/session.server";
2626
import { appEnvTitleTag } from "./utils";
27+
import { KapaScripts } from "./hooks/useKapaWidget";
2728

2829
export const links: LinksFunction = () => {
2930
return [{ rel: "stylesheet", href: tailwindStylesheetUrl }];
@@ -44,15 +45,6 @@ export const meta: MetaFunction = ({ data }) => {
4445
];
4546
};
4647

47-
export function useKapaConfig() {
48-
const matches = useMatches();
49-
const routeMatch = useTypedMatchesData<typeof loader>({
50-
id: "root",
51-
matches,
52-
});
53-
return routeMatch?.kapa;
54-
}
55-
5648
export const loader = async ({ request }: LoaderFunctionArgs) => {
5749
const session = await getSession(request.headers.get("cookie"));
5850
const toastMessage = session.get("toastMessage") as ToastMessage;
@@ -111,7 +103,7 @@ export function ErrorBoundary() {
111103
}
112104

113105
export default function App() {
114-
const { posthogProjectKey } = useTypedLoaderData<typeof loader>();
106+
const { posthogProjectKey, kapa } = useTypedLoaderData<typeof loader>();
115107
usePostHog(posthogProjectKey);
116108

117109
return (
@@ -120,6 +112,7 @@ export default function App() {
120112
<head>
121113
<Meta />
122114
<Links />
115+
<KapaScripts websiteId={kapa.websiteId} />
123116
</head>
124117
<body className="h-full overflow-hidden bg-background-dimmed">
125118
<ShortcutsProvider>

0 commit comments

Comments
 (0)