Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
color: #581c87;
}

.announcementContent :global(p) {
margin-top: 0;
margin-bottom: 0;
}

[data-theme="dark"] .announcementContent a:hover {
color: #d8b4fe;
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/AnnouncementBanner/AnnouncementBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,16 @@ export default function AnnouncementBanner() {
"text-base",
].join(" ")}
>
<div className="mx-auto flex max-w-screen-xl items-start gap-3 px-4 py-3">
<div className="mx-auto flex max-w-screen-xl items-start gap-3 px-4 py-1">
<div
className={`min-w-0 flex-1 leading-5 pt-3 text-center ${styles.announcementContent}`}
className={`min-w-0 flex-1 leading-5 pt-1 text-center ${styles.announcementContent}`}
>
<AnnouncementContent />
</div>
<button
type="button"
className={[
"shrink-0 rounded-md p-1.5 mt-3",
"shrink-0 rounded-md p-1 mt-1",
"border-none bg-transparent cursor-pointer",
"text-current opacity-60 hover:opacity-100 hover:bg-black/5",
"transition-all duration-200",
Expand Down
6 changes: 5 additions & 1 deletion src/components/welcomePage/HowToUseSection.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react"
import Link from "@docusaurus/Link"
import KapaWidget from "@site/src/theme/KapaWidget"

export function HowToUseSection() {
const guides = [
Expand Down Expand Up @@ -36,8 +37,11 @@ export function HowToUseSection() {
<p className="ory-body max-w-[800px]">
Not sure where to start? Follow our guided paths—structured journeys
that walk you through Ory's products and solutions so you can learn
and build faster.
and build faster. Or <b>ask our AI assistant</b> for help with your
specific use case.
</p>

<KapaWidget placement="inline" />
</div>

<div
Expand Down
6 changes: 3 additions & 3 deletions src/content/announcement-banner.mdx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export const announcement = {
enabled: true, // Toggle the banner on/off.
id: "talos-launch-2026-06-04", // Change this for every new announcement.
id: "askAI-inline-2026-07-03", // Change this for every new announcement.
level: "info", // Visual emphasis: "info" | "warning" | "error" | "success"
}

Introducing Ory Talos — web-scale API Key server for secure (agentic) APIs.
[Learn more](/docs/talos).
**Ask AI is now on the home page — ask a question and get a sourced answer from
the Ory docs. You'll also find it in the bottom-right corner of any page.**
154 changes: 122 additions & 32 deletions src/theme/KapaWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,72 @@ import { useLocation } from "@docusaurus/router"

const EXCLUDED_PATHS = ["/docs/kratos/fallback/error"]

export default function KapaWidget() {
const [isScriptLoaded, setIsScriptLoaded] = useState(false)
let scriptInjected = false

// Kapa's official preinit stub: makes window.Kapa a queue before the bundle
// loads, so calls made early (e.g. a fast Enter press) are queued and run
// once initialization completes.
function preinitKapa() {
if (typeof window === "undefined") return
const w = window as any
if (!w.Kapa) {
const i: any = function () {
i.c(arguments)
}
i.q = []
i.c = function (args: any) {
i.q.push(args)
}
w.Kapa = i
}
}

function injectKapaScript() {
if (typeof window === "undefined" || scriptInjected) return
scriptInjected = true
preinitKapa()

const script = document.createElement("script")
script.src = "https://widget.kapa.ai/kapa-widget.bundle.js"
script.async = true
script.setAttribute("data-website-id", "e89e7663-df2c-4c7f-974a-1bf8accdd615")
script.setAttribute("data-project-name", "Ory")
script.setAttribute("data-project-color", "#1A237E")
script.setAttribute(
"data-modal-disclaimer",
"By utilizing this chatbot, you consent to the collection and transmission of data to kapa.ai, which may include your IP address. Please be advised that your privacy and data protection are of utmost importance to us. We assure you that any data collected will be handled in compliance with applicable laws and regulations. For further details on how your data is processed and used, we encourage you to review our Privacy Policy. If you do not agree with these terms, we kindly request that you refrain from using this chatbot.",
)
script.setAttribute("data-modal-title", "Ory AI Copilot")
script.setAttribute("data-button-text", "Ask AI")
script.setAttribute("data-project-logo", "/docs/img/kapa-logo.png")
script.setAttribute("data-consent-required", "true")
script.setAttribute("data-button-hide", "true")
// Removed: data-modal-open-by-default and data-modal-override-open-class.
// We now open the widget programmatically via window.Kapa("open", ...).
script.setAttribute("data-mcp-enabled", "true")
script.setAttribute("data-mcp-server-url", "https://ory-docs.mcp.kapa.ai")
script.setAttribute("data-bot-protection-mechanism", "hcaptcha")
document.body.appendChild(script)
}

// opts omitted -> open empty; { query, submit: true } -> open mid-generation.
function openKapa(opts?: { query: string; submit?: boolean }) {
if (typeof window === "undefined") return
preinitKapa()
injectKapaScript()
;(window as any).Kapa("open", opts)
}

type KapaWidgetProps = {
placement?: "floating" | "inline"
}

export default function KapaWidget({
placement = "floating",
}: KapaWidgetProps) {
const { pathname } = useLocation()
const isExcluded = EXCLUDED_PATHS.includes(pathname)
const [query, setQuery] = useState("")

useEffect(() => {
if (isExcluded && typeof window !== "undefined" && (window as any).Kapa) {
Expand All @@ -18,50 +80,79 @@ export default function KapaWidget() {
return null
}

const loadKapaWidget = () => {
if (isScriptLoaded) {
return
const PURPLE = "rgb(26, 35, 126)"

// ---- Inline ask bar: type a question, press Enter, opens mid-answer ----
if (placement === "inline") {
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
const q = query.trim()
if (!q) return
openKapa({ query: q, submit: true })
setQuery("")
}

const script = document.createElement("script")
script.src = "https://widget.kapa.ai/kapa-widget.bundle.js"
script.async = true
script.setAttribute(
"data-website-id",
"e89e7663-df2c-4c7f-974a-1bf8accdd615",
)
script.setAttribute("data-project-name", "Ory")
script.setAttribute("data-project-color", "#1A237E")
script.setAttribute(
"data-modal-disclaimer",
"By utilizing this chatbot, you consent to the collection and transmission of data to kapa.ai, which may include your IP address. Please be advised that your privacy and data protection are of utmost importance to us. We assure you that any data collected will be handled in compliance with applicable laws and regulations. For further details on how your data is processed and used, we encourage you to review our Privacy Policy. If you do not agree with these terms, we kindly request that you refrain from using this chatbot.",
return (
<form
onSubmit={handleSubmit}
style={{
display: "flex",
alignItems: "center",
gap: "0.625rem",
width: "100%",
margin: "1.5rem auto 0",
padding: "0.75rem 1rem",
backgroundColor: "var(--ifm-background-color)",
border: `2px solid ${PURPLE}`,
borderRadius: "0.5rem",
}}
>
<img
src="/docs/img/kapa-logo.png"
alt=""
style={{
width: "1.5rem",
height: "1.5rem",
objectFit: "cover",
borderRadius: "0.25rem",
flexShrink: 0,
}}
/>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
onFocus={injectKapaScript} // preload the bundle on intent
Comment thread
unatasha8 marked this conversation as resolved.
placeholder="Ask AI anything..."
aria-label="Ask AI anything"
style={{
flex: 1,
border: "none",
outline: "none",
background: "transparent",
color: "var(--ifm-font-color-base)",
fontSize: "1rem",
}}
Comment thread
unatasha8 marked this conversation as resolved.
/>
</form>
Comment thread
unatasha8 marked this conversation as resolved.
)
script.setAttribute("data-modal-title", "Ory AI Copilot")
script.setAttribute("data-button-text", "Ask AI")
script.setAttribute("data-project-logo", "/docs/img/kapa-logo.png")
script.setAttribute("data-consent-required", "true")
script.setAttribute("data-button-hide", "true")
script.setAttribute("data-modal-override-open-id", "kapa-ai-button")
script.setAttribute("data-modal-open-by-default", "true")
script.setAttribute("data-mcp-enabled", "true")
script.setAttribute("data-mcp-server-url", "https://ory-docs.mcp.kapa.ai")
script.setAttribute("data-bot-protection-mechanism", "hcaptcha")
script.onload = () => setIsScriptLoaded(true)
document.body.appendChild(script)
}

// ---- Floating button: opens the empty assistant ----
return (
<button
id="kapa-ai-button"
type="button"
aria-label="Ask AI"
onClick={() => openKapa()}
style={{
position: "fixed",
bottom: "20px",
right: "20px",
height: "5rem",
width: "4.5rem",
padding: 0,
backgroundColor: "rgb(26, 35, 126)",
backgroundColor: PURPLE,
boxShadow: "rgba(0, 0, 0, 0.2) 0px 0px 10px",
zIndex: 199,
border: "none",
Expand All @@ -73,11 +164,10 @@ export default function KapaWidget() {
justifyContent: "center",
gap: "4px",
}}
onClick={loadKapaWidget}
>
<img
src="/docs/img/kapa-logo.png"
alt="Ask AI"
alt=""
style={{
width: "2rem",
height: "2rem",
Expand Down
Loading