Skip to content

Commit 1bb440a

Browse files
committed
Kapa is working
1 parent c1fa01e commit 1bb440a

4 files changed

Lines changed: 162 additions & 97 deletions

File tree

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

Lines changed: 34 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ import { useShortcutKeys } from "~/hooks/useShortcutKeys";
8080
import { AISparkleIcon } from "~/assets/icons/AISparkleIcon";
8181
import { ShortcutKey } from "../primitives/ShortcutKey";
8282
import { useFeatures } from "~/hooks/useFeatures";
83-
import { useKapa } from "~/root";
83+
import { useKapaConfig } from "~/root";
8484
import { useShortcuts } from "~/components/primitives/ShortcutsProvider";
85+
import { useKapaWidget } from "../../hooks/useKapaWidget";
86+
import { ShortcutsAutoOpen } from "../Shortcuts";
8587

8688
type SideMenuUser = Pick<User, "email" | "admin"> & { isImpersonating: boolean };
8789
export type SideMenuProject = Pick<
@@ -548,104 +550,41 @@ function SelectorDivider() {
548550
}
549551

550552
function HelpAndAI() {
551-
const [isKapaOpen, setIsKapaOpen] = useState(false);
552-
const features = useFeatures();
553-
const kapa = useKapa();
554-
const { disableShortcuts, enableShortcuts } = useShortcuts();
555-
556-
useEffect(() => {
557-
if (!features.isManagedCloud || !kapa?.websiteId) return;
558-
559-
loadScriptIfNotExists(kapa.websiteId);
560-
561-
// Define the handler function
562-
const handleModalClose = () => {
563-
setIsKapaOpen(false);
564-
enableShortcuts();
565-
};
566-
567-
const kapaInterval = setInterval(() => {
568-
if (typeof window.Kapa === "function") {
569-
clearInterval(kapaInterval);
570-
window.Kapa("render");
571-
window.Kapa("onModalClose", handleModalClose);
572-
// Register onModalOpen handler
573-
window.Kapa("onModalOpen", () => {
574-
setIsKapaOpen(true);
575-
disableShortcuts();
576-
});
577-
}
578-
}, 100);
579-
580-
// Clear interval on unmount to prevent memory leaks
581-
return () => {
582-
clearInterval(kapaInterval);
583-
if (typeof window.Kapa === "function") {
584-
window.Kapa("unmount");
585-
}
586-
};
587-
}, [features.isManagedCloud, kapa?.websiteId, disableShortcuts, enableShortcuts]);
553+
const { isKapaEnabled, openKapa, isKapaOpen } = useKapaWidget();
588554

589555
return (
590556
<>
557+
<ShortcutsAutoOpen />
591558
<HelpAndFeedback disableShortcut={isKapaOpen} />
592-
<TooltipProvider disableHoverableContent>
593-
<Tooltip>
594-
<TooltipTrigger asChild>
595-
<div className="inline-flex">
596-
<Button
597-
variant="small-menu-item"
598-
data-action="ask-ai"
599-
shortcut={{ modifiers: ["mod"], key: "/", enabledOnInputElements: true }}
600-
hideShortcutKey
601-
data-modal-override-open-class-ask-ai="true"
602-
onClick={() => {
603-
if (typeof window.Kapa === "function") {
604-
window.Kapa("open");
605-
setIsKapaOpen(true);
606-
disableShortcuts();
607-
}
608-
}}
609-
>
610-
<AISparkleIcon className="size-5" />
611-
</Button>
612-
</div>
613-
</TooltipTrigger>
614-
<TooltipContent side="top" className="flex items-center gap-1 py-1.5 pl-2.5 pr-2 text-xs">
615-
Ask AI
616-
<ShortcutKey shortcut={{ modifiers: ["mod"], key: "/" }} variant="medium/bright" />
617-
</TooltipContent>
618-
</Tooltip>
619-
</TooltipProvider>
559+
{isKapaEnabled && (
560+
<TooltipProvider disableHoverableContent>
561+
<Tooltip>
562+
<TooltipTrigger asChild>
563+
<div className="inline-flex">
564+
<Button
565+
variant="small-menu-item"
566+
data-action="ask-ai"
567+
shortcut={{ modifiers: ["mod"], key: "/", enabledOnInputElements: true }}
568+
hideShortcutKey
569+
data-modal-override-open-class-ask-ai="true"
570+
onClick={() => {
571+
openKapa();
572+
}}
573+
>
574+
<AISparkleIcon className="size-5" />
575+
</Button>
576+
</div>
577+
</TooltipTrigger>
578+
<TooltipContent
579+
side="top"
580+
className="flex items-center gap-1 py-1.5 pl-2.5 pr-2 text-xs"
581+
>
582+
Ask AI
583+
<ShortcutKey shortcut={{ modifiers: ["mod"], key: "/" }} variant="medium/bright" />
584+
</TooltipContent>
585+
</Tooltip>
586+
</TooltipProvider>
587+
)}
620588
</>
621589
);
622590
}
623-
624-
function loadScriptIfNotExists(websiteId: string) {
625-
const scriptSrc = "https://widget.kapa.ai/kapa-widget.bundle.js";
626-
627-
if (document.querySelector(`script[src="${scriptSrc}"]`)) {
628-
return;
629-
}
630-
631-
const script = document.createElement("script");
632-
script.async = true;
633-
script.src = scriptSrc;
634-
635-
const attributes = {
636-
"data-website-id": websiteId,
637-
"data-project-name": "Trigger.dev",
638-
"data-project-color": "#6366F1",
639-
"data-project-logo": "https://content.trigger.dev/trigger-logo-triangle.png",
640-
"data-render-on-load": "false",
641-
"data-button-hide": "true",
642-
"data-modal-disclaimer-bg-color": "#1A1B1F",
643-
"data-modal-disclaimer-text-color": "#878C99",
644-
};
645-
646-
Object.entries(attributes).forEach(([key, value]) => {
647-
script.setAttribute(key, value);
648-
});
649-
650-
document.head.appendChild(script);
651-
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { useKapaConfig } from "~/root";
2+
import { useShortcuts } from "../components/primitives/ShortcutsProvider";
3+
import { useFeatures } from "~/hooks/useFeatures";
4+
import { useCallback, useEffect, useState } from "react";
5+
6+
export function useKapaWidget() {
7+
const kapa = useKapaConfig();
8+
const features = useFeatures();
9+
const { disableShortcuts, enableShortcuts, areShortcutsEnabled } = useShortcuts();
10+
const [isKapaOpen, setIsKapaOpen] = useState(false);
11+
12+
useEffect(() => {
13+
if (!features.isManagedCloud || !kapa?.websiteId) return;
14+
15+
loadScriptIfNotExists(kapa.websiteId);
16+
17+
// Define the handler function
18+
const handleModalClose = () => {
19+
setIsKapaOpen(false);
20+
enableShortcuts();
21+
};
22+
23+
const kapaInterval = setInterval(() => {
24+
if (typeof window.Kapa === "function") {
25+
clearInterval(kapaInterval);
26+
window.Kapa("render");
27+
window.Kapa("onModalClose", handleModalClose);
28+
29+
// Register onModalOpen handler
30+
window.Kapa("onModalOpen", () => {
31+
setIsKapaOpen(true);
32+
disableShortcuts();
33+
});
34+
}
35+
}, 100);
36+
37+
// Clear interval on unmount to prevent memory leaks
38+
return () => {
39+
clearInterval(kapaInterval);
40+
if (typeof window.Kapa === "function") {
41+
window.Kapa("unmount");
42+
}
43+
};
44+
}, [features.isManagedCloud, kapa?.websiteId, disableShortcuts, enableShortcuts]);
45+
46+
const openKapa = useCallback(() => {
47+
if (!features.isManagedCloud || !kapa?.websiteId) return;
48+
49+
if (typeof window.Kapa === "function") {
50+
window.Kapa("open");
51+
setIsKapaOpen(true);
52+
disableShortcuts();
53+
}
54+
}, [disableShortcuts, features.isManagedCloud, kapa?.websiteId]);
55+
56+
return {
57+
isKapaEnabled: features.isManagedCloud && kapa?.websiteId,
58+
openKapa,
59+
isKapaOpen,
60+
};
61+
}
62+
63+
function loadScriptIfNotExists(websiteId: string) {
64+
const scriptSrc = "https://widget.kapa.ai/kapa-widget.bundle.js";
65+
66+
if (document.querySelector(`script[src="${scriptSrc}"]`)) {
67+
return;
68+
}
69+
70+
const script = document.createElement("script");
71+
script.async = true;
72+
script.src = scriptSrc;
73+
74+
const attributes = {
75+
"data-website-id": websiteId,
76+
"data-project-name": "Trigger.dev",
77+
"data-project-color": "#6366F1",
78+
"data-project-logo": "https://content.trigger.dev/trigger-logo-triangle.png",
79+
"data-render-on-load": "false",
80+
"data-button-hide": "true",
81+
"data-modal-disclaimer-bg-color": "#1A1B1F",
82+
"data-modal-disclaimer-text-color": "#878C99",
83+
};
84+
85+
Object.entries(attributes).forEach(([key, value]) => {
86+
script.setAttribute(key, value);
87+
});
88+
89+
document.head.appendChild(script);
90+
}

apps/webapp/app/root.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export const meta: MetaFunction = ({ data }) => {
5252
];
5353
};
5454

55-
export function useKapa() {
55+
export function useKapaConfig() {
5656
const matches = useMatches();
5757
const routeMatch = useTypedMatchesData<typeof loader>({
5858
id: "root",

apps/webapp/app/routes/storybook.shortcuts/route.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { useState } from "react";
12
import { Button } from "~/components/primitives/Buttons";
23
import { Header1 } from "~/components/primitives/Headers";
34
import { OperatingSystemContextProvider } from "~/components/primitives/OperatingSystemProvider";
45
import { ShortcutKey } from "~/components/primitives/ShortcutKey";
5-
import { ShortcutDefinition } from "~/hooks/useShortcutKeys";
6+
import { useShortcuts } from "~/components/primitives/ShortcutsProvider";
7+
import { type ShortcutDefinition } from "~/hooks/useShortcutKeys";
68

79
const shortcuts: ShortcutDefinition[] = [
810
{ key: "esc" },
@@ -19,6 +21,10 @@ const shortcuts: ShortcutDefinition[] = [
1921
export default function Story() {
2022
return (
2123
<div className="flex flex-col items-start gap-y-4 p-12">
24+
<div className="flex flex-col gap-y-4">
25+
<Header1 spacing>Enable/disable</Header1>
26+
<DisableTester />
27+
</div>
2228
<Collection platform="mac" />
2329
<Collection platform="windows" />
2430
</div>
@@ -66,3 +72,33 @@ function Collection({ platform }: { platform: "mac" | "windows" }) {
6672
</OperatingSystemContextProvider>
6773
);
6874
}
75+
76+
function DisableTester() {
77+
const { disableShortcuts, enableShortcuts, areShortcutsEnabled } = useShortcuts();
78+
const [count, setCount] = useState(0);
79+
80+
return (
81+
<div className="flex flex-col gap-y-4">
82+
<div className="flex items-center gap-x-4">
83+
<div>Shortcuts are: {areShortcutsEnabled ? "Enabled" : "Disabled"}</div>
84+
<Button
85+
variant="primary/small"
86+
onClick={() => (areShortcutsEnabled ? disableShortcuts() : enableShortcuts())}
87+
>
88+
{areShortcutsEnabled ? "Disable" : "Enable"} Shortcuts
89+
</Button>
90+
</div>
91+
92+
<div className="flex items-center gap-x-4">
93+
<Button
94+
variant="secondary/medium"
95+
shortcut={{ key: "i" }}
96+
onClick={() => setCount((c) => c + 1)}
97+
>
98+
Increment Counter
99+
</Button>
100+
<div>Count: {count}</div>
101+
</div>
102+
</div>
103+
);
104+
}

0 commit comments

Comments
 (0)