Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced new components for managing admin settings, including AdminListControls, AdminSettingsCard, and TechBrandSelectLabel. - Implemented dynamic loading for admin settings sections to optimize performance. - Enhanced the layout of various admin settings sections for better user experience. - Updated the AiAssistantSection to include LLM provider management and improved model selection. - Refactored authentication settings to streamline configuration and improve accessibility.
91 lines
2.8 KiB
TypeScript
91 lines
2.8 KiB
TypeScript
import { useEffect } from "react"
|
|
import type { RefObject } from "react"
|
|
import { resolveAiEmbedOrigin } from "@/lib/ai/embed-url"
|
|
|
|
function normalizePublicPath(publicPath: string): string {
|
|
const path = (publicPath || "/ai").replace(/\/$/, "") || "/ai"
|
|
return path.startsWith("/") ? path : `/${path}`
|
|
}
|
|
|
|
/** Ultimail suite routes — never prefix with /ai (would 404 in OpenWebUI). */
|
|
const SUITE_ROOT_PATHS = new Set([
|
|
"/mail",
|
|
"/drive",
|
|
"/chat",
|
|
"/contacts",
|
|
"/cloud",
|
|
"/agenda",
|
|
"/office",
|
|
])
|
|
|
|
function isSuiteRoute(pathname: string): boolean {
|
|
for (const root of SUITE_ROOT_PATHS) {
|
|
if (pathname === root || pathname.startsWith(`${root}/`)) return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/** Keep UltiAI iframe on the OpenWebUI subpath (e.g. /ai) — block suite landing at /. */
|
|
export function useAiIframeNavigation(
|
|
iframeRef: RefObject<HTMLIFrameElement | null>,
|
|
publicPath: string
|
|
) {
|
|
useEffect(() => {
|
|
const iframe = iframeRef.current
|
|
if (!iframe) return
|
|
|
|
const base = normalizePublicPath(publicPath)
|
|
const embedOrigin = resolveAiEmbedOrigin(publicPath)
|
|
|
|
const enforceBasePath = () => {
|
|
try {
|
|
const win = iframe.contentWindow
|
|
if (!win) return
|
|
|
|
const { pathname, search, hash, origin } = win.location
|
|
if (embedOrigin && origin !== embedOrigin) return
|
|
|
|
const inBase = pathname === base || pathname.startsWith(`${base}/`)
|
|
if (inBase) return
|
|
|
|
const target =
|
|
pathname === "/" || pathname === "" || isSuiteRoute(pathname)
|
|
? `${base}/${search}${hash}`
|
|
: `${base}${pathname}${search}${hash}`
|
|
|
|
const targetUrl = target.startsWith("http") ? target : `${origin}${target}`
|
|
if (win.location.href === targetUrl) return
|
|
|
|
// One-shot fix only — polling caused SvelteKit "Redirect loop" → 500: Internal Error
|
|
win.location.replace(targetUrl)
|
|
} catch {
|
|
// Cross-origin — parent cannot read location.
|
|
}
|
|
}
|
|
|
|
const onLoad = () => window.setTimeout(enforceBasePath, 0)
|
|
iframe.addEventListener("load", onLoad)
|
|
|
|
return () => {
|
|
iframe.removeEventListener("load", onLoad)
|
|
}
|
|
}, [iframeRef, publicPath])
|
|
}
|
|
|
|
/** Open external links from the embed in the parent tab, not inside the iframe. */
|
|
export function useAiIframeExternalLinks(embedOrigin: string) {
|
|
useEffect(() => {
|
|
if (!embedOrigin) return
|
|
|
|
const onMessage = (event: MessageEvent) => {
|
|
if (event.origin !== embedOrigin) return
|
|
const data = event.data as { type?: string; href?: string } | null
|
|
if (data?.type !== "ULTI_OPEN_LINK" || typeof data.href !== "string") return
|
|
window.open(data.href, "_blank", "noopener,noreferrer")
|
|
}
|
|
|
|
window.addEventListener("message", onMessage)
|
|
return () => window.removeEventListener("message", onMessage)
|
|
}, [embedOrigin])
|
|
}
|