@athenaintel/react 0.60.1 → 0.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -5
- package/dist/agent-ui/AgentTarget.cjs +11 -0
- package/dist/agent-ui/AgentTarget.cjs.map +1 -0
- package/dist/agent-ui/AgentTarget.d.ts +17 -0
- package/dist/agent-ui/AgentTarget.js +11 -0
- package/dist/agent-ui/AgentTarget.js.map +1 -0
- package/dist/agent-ui/AgentUiLayer.cjs +387 -0
- package/dist/agent-ui/AgentUiLayer.cjs.map +1 -0
- package/dist/agent-ui/AgentUiLayer.d.ts +40 -0
- package/dist/agent-ui/AgentUiLayer.js +387 -0
- package/dist/agent-ui/AgentUiLayer.js.map +1 -0
- package/dist/agent-ui/contract.cjs +104 -0
- package/dist/agent-ui/contract.cjs.map +1 -0
- package/dist/agent-ui/contract.d.ts +113 -0
- package/dist/agent-ui/contract.js +104 -0
- package/dist/agent-ui/contract.js.map +1 -0
- package/dist/agent-ui/driver.cjs +335 -0
- package/dist/agent-ui/driver.cjs.map +1 -0
- package/dist/agent-ui/driver.d.ts +66 -0
- package/dist/agent-ui/driver.js +335 -0
- package/dist/agent-ui/driver.js.map +1 -0
- package/dist/agent-ui/index.cjs +33 -0
- package/dist/agent-ui/index.cjs.map +1 -0
- package/dist/agent-ui/index.d.ts +19 -0
- package/dist/agent-ui/index.js +33 -0
- package/dist/agent-ui/index.js.map +1 -0
- package/dist/agent-ui/pace.cjs +121 -0
- package/dist/agent-ui/pace.cjs.map +1 -0
- package/dist/agent-ui/pace.d.ts +66 -0
- package/dist/agent-ui/pace.js +121 -0
- package/dist/agent-ui/pace.js.map +1 -0
- package/dist/agent-ui/screen.cjs +471 -0
- package/dist/agent-ui/screen.cjs.map +1 -0
- package/dist/agent-ui/screen.d.ts +97 -0
- package/dist/agent-ui/screen.js +471 -0
- package/dist/agent-ui/screen.js.map +1 -0
- package/dist/agent-ui/secret-fields.cjs +50 -0
- package/dist/agent-ui/secret-fields.cjs.map +1 -0
- package/dist/agent-ui/secret-fields.d.ts +15 -0
- package/dist/agent-ui/secret-fields.js +50 -0
- package/dist/agent-ui/secret-fields.js.map +1 -0
- package/dist/agent-ui/stage.cjs +196 -0
- package/dist/agent-ui/stage.cjs.map +1 -0
- package/dist/agent-ui/stage.d.ts +110 -0
- package/dist/agent-ui/stage.js +196 -0
- package/dist/agent-ui/stage.js.map +1 -0
- package/dist/agent-ui/tools.cjs +448 -0
- package/dist/agent-ui/tools.cjs.map +1 -0
- package/dist/agent-ui/tools.d.ts +103 -0
- package/dist/agent-ui/tools.js +448 -0
- package/dist/agent-ui/tools.js.map +1 -0
- package/dist/agent-ui/use-agent-target.cjs +63 -0
- package/dist/agent-ui/use-agent-target.cjs.map +1 -0
- package/dist/agent-ui/use-agent-target.d.ts +27 -0
- package/dist/agent-ui/use-agent-target.js +63 -0
- package/dist/agent-ui/use-agent-target.js.map +1 -0
- package/dist/agent-ui/use-agent-ui.cjs +15 -0
- package/dist/agent-ui/use-agent-ui.cjs.map +1 -0
- package/dist/agent-ui/use-agent-ui.d.ts +24 -0
- package/dist/agent-ui/use-agent-ui.js +15 -0
- package/dist/agent-ui/use-agent-ui.js.map +1 -0
- package/dist/chat/AthenaChat.cjs +17 -4
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.d.ts +13 -0
- package/dist/chat/AthenaChat.js +17 -4
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/StatewireClientToolBridge.cjs +17 -12
- package/dist/chat/StatewireClientToolBridge.cjs.map +1 -1
- package/dist/chat/StatewireClientToolBridge.d.ts +4 -2
- package/dist/chat/StatewireClientToolBridge.js +17 -12
- package/dist/chat/StatewireClientToolBridge.js.map +1 -1
- package/dist/chat/StatewireClientToolConsentCard.cjs +3 -2
- package/dist/chat/StatewireClientToolConsentCard.cjs.map +1 -1
- package/dist/chat/StatewireClientToolConsentCard.d.ts +2 -1
- package/dist/chat/StatewireClientToolConsentCard.js +3 -2
- package/dist/chat/StatewireClientToolConsentCard.js.map +1 -1
- package/dist/chat/chat-voice-controls.cjs +5 -2
- package/dist/chat/chat-voice-controls.cjs.map +1 -1
- package/dist/chat/chat-voice-controls.d.ts +6 -1
- package/dist/chat/chat-voice-controls.js +6 -3
- package/dist/chat/chat-voice-controls.js.map +1 -1
- package/dist/chat/client-tool-write-consent.cjs +16 -5
- package/dist/chat/client-tool-write-consent.cjs.map +1 -1
- package/dist/chat/client-tool-write-consent.d.ts +12 -4
- package/dist/chat/client-tool-write-consent.js +16 -5
- package/dist/chat/client-tool-write-consent.js.map +1 -1
- package/dist/chat-ui/messages/message-components.cjs +1 -1
- package/dist/chat-ui/messages/message-components.cjs.map +1 -1
- package/dist/chat-ui/messages/message-components.d.ts +2 -2
- package/dist/chat-ui/messages/message-components.js +1 -1
- package/dist/chat-ui/messages/message-components.js.map +1 -1
- package/dist/index.cjs +7 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +37 -8
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +18 -1
- package/dist/provider/AthenaProvider.js +33 -4
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/styles.css +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AgentUiLayer.cjs","sources":["../../src/agent-ui/AgentUiLayer.tsx"],"sourcesContent":["'use client';\n\nimport {\n ChevronLeftIcon,\n ChevronRightIcon,\n MousePointer2Icon,\n PauseIcon,\n PlayIcon,\n XIcon,\n} from 'lucide-react';\nimport {\n type CSSProperties,\n type ReactNode,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { useOwnerDocument, usePortalContainer } from '../lib/owner-document';\nimport { cn } from '../lib/utils';\nimport { useAthenaThemeStyle } from '../provider/theme-style-context';\nimport { AGENT_ATTR } from './contract';\nimport type { AgentUiMarkTone, AgentUiQuestion, AgentUiStage, AgentUiTour } from './stage';\nimport type { AgentUi } from './tools';\n\n/*\n * The overlay: what the person sees of the agent on their page. A box around\n * the element it means, a note beside it, a cursor where it clicks, the\n * walkthrough's card and the question it asks before acting.\n *\n * It draws the stage and nothing else. Every piece is restyled through\n * `classNames`; a host that wants something else entirely reads\n * `agentUi.stage` and draws its own.\n */\n\nexport interface AgentUiLayerClassNames {\n /** The box around the element. */\n mark?: string;\n /** The note beside a marked element. */\n caption?: string;\n /** The agent's cursor. */\n cursor?: string;\n /** The walkthrough's card. */\n tour?: string;\n /** The question asked before acting. */\n question?: string;\n}\n\nexport interface AgentUiLayerProps {\n agentUi: AgentUi;\n classNames?: AgentUiLayerClassNames;\n}\n\ninterface Box {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\n/** Space between an element and the box drawn around it. */\nconst MARK_INSET_PX = 4;\n/** Space between the box and a card anchored to it. */\nconst CARD_GAP_PX = 10;\n/** A card's width, and the room a card needs below an element to sit there. */\nconst CARD_WIDTH_PX = 320;\nconst CARD_ROOM_PX = 150;\nconst EDGE_PX = 8;\n\n/** Follow an element's box frame by frame while it is shown: it rides scrolling and layout changes. */\nfunction useElementBox(element: Element | null): Box | null {\n const [box, setBox] = useState<Box | null>(null);\n useEffect(() => {\n const view = element?.ownerDocument.defaultView;\n if (!element || !view) {\n setBox(null);\n return;\n }\n let frame = 0;\n let last = '';\n const read = () => {\n const rect = element.isConnected ? element.getBoundingClientRect() : null;\n const next = rect ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height } : null;\n const signature = next\n ? [next.top, next.left, next.width, next.height].map(Math.round).join(':')\n : 'gone';\n if (signature !== last) {\n last = signature;\n setBox(next);\n }\n frame = view.requestAnimationFrame(read);\n };\n read();\n return () => view.cancelAnimationFrame(frame);\n }, [element]);\n return box;\n}\n\ninterface Viewport {\n width: number;\n height: number;\n}\n\nfunction viewportOf(doc: Document | null): Viewport {\n const view = doc?.defaultView;\n return { width: view?.innerWidth ?? 1024, height: view?.innerHeight ?? 768 };\n}\n\n/** Where a card anchored to an element sits: under it when there is room, else over it; at the bottom of the page without one. */\nexport function cardPosition({ box, viewport }: { box: Box | null; viewport: Viewport }): CSSProperties {\n const width = Math.min(CARD_WIDTH_PX, viewport.width - EDGE_PX * 2);\n if (!box) {\n return { left: Math.max(EDGE_PX, (viewport.width - width) / 2), bottom: 24, width };\n }\n const left = Math.min(Math.max(box.left, EDGE_PX), Math.max(EDGE_PX, viewport.width - width - EDGE_PX));\n const below = box.top + box.height + MARK_INSET_PX + CARD_GAP_PX;\n if (below + CARD_ROOM_PX <= viewport.height || box.top < CARD_ROOM_PX) {\n return { left, top: Math.min(below, Math.max(EDGE_PX, viewport.height - CARD_ROOM_PX)), width };\n }\n return { left, bottom: viewport.height - box.top + MARK_INSET_PX + CARD_GAP_PX, width };\n}\n\nconst TONE_CLASS: Record<AgentUiMarkTone, string> = {\n point: 'border-primary ring-primary/25',\n act: 'border-primary ring-primary/25 border-dashed',\n ask: 'border-amber-500 ring-amber-500/25',\n};\n\nfunction Mark({\n box,\n tone,\n spotlight,\n leaving,\n className,\n}: {\n box: Box;\n tone: AgentUiMarkTone;\n spotlight: boolean;\n leaving: boolean;\n className?: string;\n}) {\n return (\n <div\n data-testid=\"agent-ui-mark\"\n data-tone={tone}\n className={cn(\n 'pointer-events-none fixed rounded-lg border-2 ring-4 transition-opacity duration-500 motion-reduce:transition-none',\n TONE_CLASS[tone],\n spotlight && 'shadow-[0_0_0_100vmax_rgb(0_0_0/0.45)]',\n leaving ? 'opacity-0' : 'opacity-100',\n className,\n )}\n style={{\n top: box.top - MARK_INSET_PX,\n left: box.left - MARK_INSET_PX,\n width: box.width + MARK_INSET_PX * 2,\n height: box.height + MARK_INSET_PX * 2,\n }}\n />\n );\n}\n\nfunction Card({\n box,\n viewport,\n fit = false,\n className,\n children,\n ...props\n}: {\n box: Box | null;\n viewport: Viewport;\n /** As wide as its words need, up to a card's width (a note); otherwise a card's full width. */\n fit?: boolean;\n className?: string;\n children: ReactNode;\n role?: string;\n 'aria-label'?: string;\n 'data-testid'?: string;\n}) {\n const { width, ...position } = cardPosition({ box, viewport });\n return (\n <div\n {...props}\n className={cn(\n 'pointer-events-auto fixed rounded-xl border border-border bg-popover px-3.5 py-3 text-popover-foreground text-sm shadow-lg',\n className,\n )}\n style={fit ? { ...position, maxWidth: width } : { ...position, width }}\n >\n {children}\n </div>\n );\n}\n\nfunction Cursor({\n box,\n presses,\n glideMs,\n className,\n}: {\n box: Box;\n presses: number;\n glideMs: number;\n className?: string;\n}) {\n const x = box.left + box.width / 2;\n const y = box.top + box.height / 2;\n // The cursor starts a little off the element and glides onto it.\n const [arrived, setArrived] = useState(false);\n useEffect(() => {\n const frame = requestAnimationFrame(() => setArrived(true));\n return () => cancelAnimationFrame(frame);\n }, []);\n return (\n <div\n data-testid=\"agent-ui-cursor\"\n className={cn('pointer-events-none fixed top-0 left-0 motion-reduce:transition-none', className)}\n style={{\n transform: `translate(${arrived ? x : x + 48}px, ${arrived ? y : y + 48}px)`,\n transitionProperty: 'transform',\n transitionTimingFunction: 'ease-out',\n transitionDuration: `${glideMs}ms`,\n }}\n >\n <MousePointer2Icon className=\"size-5 fill-primary text-primary-foreground drop-shadow\" />\n {presses > 0 ? (\n <span\n key={presses}\n data-testid=\"agent-ui-press\"\n className=\"-top-2 -left-2 absolute size-6 animate-ping rounded-full bg-primary/50 [animation-iteration-count:1]\"\n />\n ) : null}\n </div>\n );\n}\n\nfunction TourCard({\n tour,\n stage,\n box,\n viewport,\n className,\n}: {\n tour: AgentUiTour;\n stage: AgentUiStage;\n box: Box | null;\n viewport: Viewport;\n className?: string;\n}) {\n const step = tour.steps[tour.index];\n const last = tour.index >= tour.steps.length - 1;\n const progressRef = useRef<HTMLDivElement>(null);\n const advancing = tour.playing && !tour.searching;\n const durationMs = step?.durationMs ?? 0;\n // The bar fills over the step's time, so the person sees it will move on by itself.\n // `tour.index` restarts it for a step of the same length.\n // biome-ignore lint/correctness/useExhaustiveDependencies: a new step restarts the bar\n useEffect(() => {\n const bar = progressRef.current;\n if (!bar || typeof bar.animate !== 'function') return;\n if (!advancing) return;\n const animation = bar.animate([{ width: '0%' }, { width: '100%' }], {\n duration: durationMs,\n fill: 'forwards',\n easing: 'linear',\n });\n return () => animation.cancel();\n }, [advancing, durationMs, tour.index]);\n if (!step) return null;\n return (\n <Card\n box={box}\n viewport={viewport}\n className={className}\n role=\"dialog\"\n aria-label={tour.title ?? 'Walkthrough'}\n data-testid=\"agent-ui-tour\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"min-w-0 truncate text-muted-foreground text-xs\">\n {tour.title ? `${tour.title} · ` : ''}\n Step {tour.index + 1} of {tour.steps.length}\n </p>\n <button\n type=\"button\"\n aria-label=\"Close the walkthrough\"\n onClick={() => stage.endTour()}\n className=\"shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n <XIcon className=\"size-3.5\" />\n </button>\n </div>\n <p className=\"break-words pt-1.5 leading-snug\" data-testid=\"agent-ui-tour-text\">\n {step.text}\n </p>\n {!tour.searching && !tour.element ? (\n <p className=\"pt-1 text-muted-foreground text-xs\">This one is not on the page you have open.</p>\n ) : null}\n <div className=\"mt-2.5 h-0.5 overflow-hidden rounded-full bg-muted\">\n <div ref={progressRef} className=\"h-full w-0 bg-primary\" />\n </div>\n <div className=\"flex items-center gap-1 pt-2\">\n <button\n type=\"button\"\n aria-label=\"Previous step\"\n disabled={tour.index === 0}\n onClick={() => stage.tourBack()}\n className=\"rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40\"\n >\n <ChevronLeftIcon className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n aria-label={tour.playing ? 'Pause the walkthrough' : 'Play the walkthrough'}\n onClick={() => stage.tourTogglePlaying()}\n className=\"rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n {tour.playing ? <PauseIcon className=\"size-4\" /> : <PlayIcon className=\"size-4\" />}\n </button>\n <button\n type=\"button\"\n onClick={() => stage.tourNext()}\n className=\"ml-auto flex items-center gap-1 rounded-md bg-primary px-2.5 py-1 font-medium text-primary-foreground text-xs transition-colors hover:bg-primary/90\"\n >\n {last ? 'Done' : 'Next'}\n {last ? null : <ChevronRightIcon className=\"size-3.5\" />}\n </button>\n </div>\n </Card>\n );\n}\n\nfunction QuestionCard({\n question,\n stage,\n box,\n viewport,\n className,\n}: {\n question: AgentUiQuestion;\n stage: AgentUiStage;\n box: Box | null;\n viewport: Viewport;\n className?: string;\n}) {\n return (\n <Card\n box={box}\n viewport={viewport}\n className={className}\n role=\"alertdialog\"\n aria-label={question.title}\n data-testid=\"agent-ui-question\"\n >\n <p className=\"break-words font-semibold leading-snug\">{question.title}</p>\n {question.body ? (\n <p className=\"break-words pt-1 text-muted-foreground leading-snug\">{question.body}</p>\n ) : null}\n <div className=\"flex flex-wrap items-center gap-2 pt-2.5\">\n <button\n type=\"button\"\n onClick={() => stage.answer(true)}\n className=\"rounded-md bg-primary px-2.5 py-1 font-medium text-primary-foreground text-xs transition-colors hover:bg-primary/90\"\n >\n {question.allowLabel}\n </button>\n <button\n type=\"button\"\n onClick={() => stage.answer(false)}\n className=\"rounded-md border border-border px-2.5 py-1 font-medium text-xs transition-colors hover:bg-muted\"\n >\n {question.declineLabel}\n </button>\n </div>\n </Card>\n );\n}\n\n/**\n * Draws the agent's stage over the page. Mount one per `AgentUi`, anywhere\n * under the provider; it portals to the document's body and takes no clicks\n * except on its own cards.\n */\nexport function AgentUiLayer({ agentUi, classNames }: AgentUiLayerProps) {\n const { stage } = agentUi;\n const state = useSyncExternalStore(stage.subscribe, stage.getState, stage.getState);\n const ownerDocument = useOwnerDocument();\n const portalContainer = usePortalContainer();\n const themeStyle = useAthenaThemeStyle();\n\n const tourElement = state.tour?.element ?? null;\n const markedElement = state.tour ? tourElement : (state.mark?.element ?? null);\n const markBox = useElementBox(markedElement);\n const cursorBox = useElementBox(state.cursor?.element ?? null);\n const questionBox = useElementBox(state.question?.element ?? null);\n\n // Escape closes what the agent is showing: the person's page is theirs.\n const showing = state.tour !== null || state.mark !== null;\n useEffect(() => {\n if (!showing || !ownerDocument) return;\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n stage.endTour();\n stage.clearMark();\n };\n ownerDocument.addEventListener('keydown', onKeyDown);\n return () => ownerDocument.removeEventListener('keydown', onKeyDown);\n }, [ownerDocument, showing, stage]);\n\n const container = portalContainer ?? ownerDocument?.body;\n if (!container) return null;\n const idle = !state.mark && !state.cursor && !state.tour && !state.question;\n if (idle) return null;\n const viewport = viewportOf(ownerDocument);\n const caption = state.tour ? null : (state.mark?.caption ?? null);\n\n return createPortal(\n <div\n {...{ [AGENT_ATTR.layer]: '' }}\n className=\"athena-sdk athena-sdk-chrome pointer-events-none fixed inset-0 z-[2147483000]\"\n style={themeStyle as CSSProperties | undefined}\n >\n {markBox && (state.tour || state.mark) ? (\n <Mark\n box={markBox}\n tone={state.tour ? 'point' : (state.mark?.tone ?? 'point')}\n spotlight={state.tour ? false : (state.mark?.spotlight ?? false)}\n leaving={state.tour ? false : (state.mark?.leaving ?? false)}\n className={classNames?.mark}\n />\n ) : null}\n {/* What the agent is doing, said aloud for a screen reader too. */}\n <div aria-live=\"polite\">\n {caption && markBox && !state.question ? (\n <Card\n box={markBox}\n viewport={viewport}\n fit\n className={cn(\n 'pointer-events-none transition-opacity duration-500 motion-reduce:transition-none',\n state.mark?.leaving ? 'opacity-0' : 'opacity-100',\n classNames?.caption,\n )}\n data-testid=\"agent-ui-caption\"\n >\n <p className=\"break-words leading-snug\">{caption}</p>\n </Card>\n ) : null}\n </div>\n {state.tour ? (\n <TourCard\n tour={state.tour}\n stage={stage}\n box={markBox}\n viewport={viewport}\n className={classNames?.tour}\n />\n ) : null}\n {state.question ? (\n <QuestionCard\n question={state.question}\n stage={stage}\n box={questionBox}\n viewport={viewport}\n className={classNames?.question}\n />\n ) : null}\n {state.cursor && cursorBox ? (\n <Cursor\n box={cursorBox}\n presses={state.cursor.presses}\n glideMs={agentUi.pace().cursorGlideMs}\n className={classNames?.cursor}\n />\n ) : null}\n </div>,\n container,\n );\n}\n"],"names":["useState","useEffect","jsx","cn","jsxs","MousePointer2Icon","useRef","XIcon","ChevronLeftIcon","PauseIcon","PlayIcon","ChevronRightIcon","useSyncExternalStore","useOwnerDocument","usePortalContainer","useAthenaThemeStyle","createPortal","AGENT_ATTR"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type CSSProperties } from 'react';
|
|
2
|
+
import type { AgentUi } from './tools';
|
|
3
|
+
export interface AgentUiLayerClassNames {
|
|
4
|
+
/** The box around the element. */
|
|
5
|
+
mark?: string;
|
|
6
|
+
/** The note beside a marked element. */
|
|
7
|
+
caption?: string;
|
|
8
|
+
/** The agent's cursor. */
|
|
9
|
+
cursor?: string;
|
|
10
|
+
/** The walkthrough's card. */
|
|
11
|
+
tour?: string;
|
|
12
|
+
/** The question asked before acting. */
|
|
13
|
+
question?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface AgentUiLayerProps {
|
|
16
|
+
agentUi: AgentUi;
|
|
17
|
+
classNames?: AgentUiLayerClassNames;
|
|
18
|
+
}
|
|
19
|
+
interface Box {
|
|
20
|
+
top: number;
|
|
21
|
+
left: number;
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
}
|
|
25
|
+
interface Viewport {
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
}
|
|
29
|
+
/** Where a card anchored to an element sits: under it when there is room, else over it; at the bottom of the page without one. */
|
|
30
|
+
export declare function cardPosition({ box, viewport }: {
|
|
31
|
+
box: Box | null;
|
|
32
|
+
viewport: Viewport;
|
|
33
|
+
}): CSSProperties;
|
|
34
|
+
/**
|
|
35
|
+
* Draws the agent's stage over the page. Mount one per `AgentUi`, anywhere
|
|
36
|
+
* under the provider; it portals to the document's body and takes no clicks
|
|
37
|
+
* except on its own cards.
|
|
38
|
+
*/
|
|
39
|
+
export declare function AgentUiLayer({ agentUi, classNames }: AgentUiLayerProps): import("react").ReactPortal | null;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { XIcon, ChevronLeftIcon, PauseIcon, PlayIcon, ChevronRightIcon, MousePointer2Icon } from "lucide-react";
|
|
3
|
+
import { useSyncExternalStore, useEffect, useState, useRef } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
import { useOwnerDocument, usePortalContainer } from "../lib/owner-document.js";
|
|
6
|
+
import { useAthenaThemeStyle } from "../provider/theme-style-context.js";
|
|
7
|
+
import { AGENT_ATTR } from "./contract.js";
|
|
8
|
+
import { cn } from "cnfast";
|
|
9
|
+
const MARK_INSET_PX = 4;
|
|
10
|
+
const CARD_GAP_PX = 10;
|
|
11
|
+
const CARD_WIDTH_PX = 320;
|
|
12
|
+
const CARD_ROOM_PX = 150;
|
|
13
|
+
const EDGE_PX = 8;
|
|
14
|
+
function useElementBox(element) {
|
|
15
|
+
const [box, setBox] = useState(null);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const view = element == null ? void 0 : element.ownerDocument.defaultView;
|
|
18
|
+
if (!element || !view) {
|
|
19
|
+
setBox(null);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
let frame = 0;
|
|
23
|
+
let last = "";
|
|
24
|
+
const read = () => {
|
|
25
|
+
const rect = element.isConnected ? element.getBoundingClientRect() : null;
|
|
26
|
+
const next = rect ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height } : null;
|
|
27
|
+
const signature = next ? [next.top, next.left, next.width, next.height].map(Math.round).join(":") : "gone";
|
|
28
|
+
if (signature !== last) {
|
|
29
|
+
last = signature;
|
|
30
|
+
setBox(next);
|
|
31
|
+
}
|
|
32
|
+
frame = view.requestAnimationFrame(read);
|
|
33
|
+
};
|
|
34
|
+
read();
|
|
35
|
+
return () => view.cancelAnimationFrame(frame);
|
|
36
|
+
}, [element]);
|
|
37
|
+
return box;
|
|
38
|
+
}
|
|
39
|
+
function viewportOf(doc) {
|
|
40
|
+
const view = doc == null ? void 0 : doc.defaultView;
|
|
41
|
+
return { width: (view == null ? void 0 : view.innerWidth) ?? 1024, height: (view == null ? void 0 : view.innerHeight) ?? 768 };
|
|
42
|
+
}
|
|
43
|
+
function cardPosition({ box, viewport }) {
|
|
44
|
+
const width = Math.min(CARD_WIDTH_PX, viewport.width - EDGE_PX * 2);
|
|
45
|
+
if (!box) {
|
|
46
|
+
return { left: Math.max(EDGE_PX, (viewport.width - width) / 2), bottom: 24, width };
|
|
47
|
+
}
|
|
48
|
+
const left = Math.min(Math.max(box.left, EDGE_PX), Math.max(EDGE_PX, viewport.width - width - EDGE_PX));
|
|
49
|
+
const below = box.top + box.height + MARK_INSET_PX + CARD_GAP_PX;
|
|
50
|
+
if (below + CARD_ROOM_PX <= viewport.height || box.top < CARD_ROOM_PX) {
|
|
51
|
+
return { left, top: Math.min(below, Math.max(EDGE_PX, viewport.height - CARD_ROOM_PX)), width };
|
|
52
|
+
}
|
|
53
|
+
return { left, bottom: viewport.height - box.top + MARK_INSET_PX + CARD_GAP_PX, width };
|
|
54
|
+
}
|
|
55
|
+
const TONE_CLASS = {
|
|
56
|
+
point: "border-primary ring-primary/25",
|
|
57
|
+
act: "border-primary ring-primary/25 border-dashed",
|
|
58
|
+
ask: "border-amber-500 ring-amber-500/25"
|
|
59
|
+
};
|
|
60
|
+
function Mark({
|
|
61
|
+
box,
|
|
62
|
+
tone,
|
|
63
|
+
spotlight,
|
|
64
|
+
leaving,
|
|
65
|
+
className
|
|
66
|
+
}) {
|
|
67
|
+
return /* @__PURE__ */ jsx(
|
|
68
|
+
"div",
|
|
69
|
+
{
|
|
70
|
+
"data-testid": "agent-ui-mark",
|
|
71
|
+
"data-tone": tone,
|
|
72
|
+
className: cn(
|
|
73
|
+
"pointer-events-none fixed rounded-lg border-2 ring-4 transition-opacity duration-500 motion-reduce:transition-none",
|
|
74
|
+
TONE_CLASS[tone],
|
|
75
|
+
spotlight && "shadow-[0_0_0_100vmax_rgb(0_0_0/0.45)]",
|
|
76
|
+
leaving ? "opacity-0" : "opacity-100",
|
|
77
|
+
className
|
|
78
|
+
),
|
|
79
|
+
style: {
|
|
80
|
+
top: box.top - MARK_INSET_PX,
|
|
81
|
+
left: box.left - MARK_INSET_PX,
|
|
82
|
+
width: box.width + MARK_INSET_PX * 2,
|
|
83
|
+
height: box.height + MARK_INSET_PX * 2
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
function Card({
|
|
89
|
+
box,
|
|
90
|
+
viewport,
|
|
91
|
+
fit = false,
|
|
92
|
+
className,
|
|
93
|
+
children,
|
|
94
|
+
...props
|
|
95
|
+
}) {
|
|
96
|
+
const { width, ...position } = cardPosition({ box, viewport });
|
|
97
|
+
return /* @__PURE__ */ jsx(
|
|
98
|
+
"div",
|
|
99
|
+
{
|
|
100
|
+
...props,
|
|
101
|
+
className: cn(
|
|
102
|
+
"pointer-events-auto fixed rounded-xl border border-border bg-popover px-3.5 py-3 text-popover-foreground text-sm shadow-lg",
|
|
103
|
+
className
|
|
104
|
+
),
|
|
105
|
+
style: fit ? { ...position, maxWidth: width } : { ...position, width },
|
|
106
|
+
children
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
function Cursor({
|
|
111
|
+
box,
|
|
112
|
+
presses,
|
|
113
|
+
glideMs,
|
|
114
|
+
className
|
|
115
|
+
}) {
|
|
116
|
+
const x = box.left + box.width / 2;
|
|
117
|
+
const y = box.top + box.height / 2;
|
|
118
|
+
const [arrived, setArrived] = useState(false);
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
const frame = requestAnimationFrame(() => setArrived(true));
|
|
121
|
+
return () => cancelAnimationFrame(frame);
|
|
122
|
+
}, []);
|
|
123
|
+
return /* @__PURE__ */ jsxs(
|
|
124
|
+
"div",
|
|
125
|
+
{
|
|
126
|
+
"data-testid": "agent-ui-cursor",
|
|
127
|
+
className: cn("pointer-events-none fixed top-0 left-0 motion-reduce:transition-none", className),
|
|
128
|
+
style: {
|
|
129
|
+
transform: `translate(${arrived ? x : x + 48}px, ${arrived ? y : y + 48}px)`,
|
|
130
|
+
transitionProperty: "transform",
|
|
131
|
+
transitionTimingFunction: "ease-out",
|
|
132
|
+
transitionDuration: `${glideMs}ms`
|
|
133
|
+
},
|
|
134
|
+
children: [
|
|
135
|
+
/* @__PURE__ */ jsx(MousePointer2Icon, { className: "size-5 fill-primary text-primary-foreground drop-shadow" }),
|
|
136
|
+
presses > 0 ? /* @__PURE__ */ jsx(
|
|
137
|
+
"span",
|
|
138
|
+
{
|
|
139
|
+
"data-testid": "agent-ui-press",
|
|
140
|
+
className: "-top-2 -left-2 absolute size-6 animate-ping rounded-full bg-primary/50 [animation-iteration-count:1]"
|
|
141
|
+
},
|
|
142
|
+
presses
|
|
143
|
+
) : null
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
function TourCard({
|
|
149
|
+
tour,
|
|
150
|
+
stage,
|
|
151
|
+
box,
|
|
152
|
+
viewport,
|
|
153
|
+
className
|
|
154
|
+
}) {
|
|
155
|
+
const step = tour.steps[tour.index];
|
|
156
|
+
const last = tour.index >= tour.steps.length - 1;
|
|
157
|
+
const progressRef = useRef(null);
|
|
158
|
+
const advancing = tour.playing && !tour.searching;
|
|
159
|
+
const durationMs = (step == null ? void 0 : step.durationMs) ?? 0;
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
const bar = progressRef.current;
|
|
162
|
+
if (!bar || typeof bar.animate !== "function") return;
|
|
163
|
+
if (!advancing) return;
|
|
164
|
+
const animation = bar.animate([{ width: "0%" }, { width: "100%" }], {
|
|
165
|
+
duration: durationMs,
|
|
166
|
+
fill: "forwards",
|
|
167
|
+
easing: "linear"
|
|
168
|
+
});
|
|
169
|
+
return () => animation.cancel();
|
|
170
|
+
}, [advancing, durationMs, tour.index]);
|
|
171
|
+
if (!step) return null;
|
|
172
|
+
return /* @__PURE__ */ jsxs(
|
|
173
|
+
Card,
|
|
174
|
+
{
|
|
175
|
+
box,
|
|
176
|
+
viewport,
|
|
177
|
+
className,
|
|
178
|
+
role: "dialog",
|
|
179
|
+
"aria-label": tour.title ?? "Walkthrough",
|
|
180
|
+
"data-testid": "agent-ui-tour",
|
|
181
|
+
children: [
|
|
182
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
183
|
+
/* @__PURE__ */ jsxs("p", { className: "min-w-0 truncate text-muted-foreground text-xs", children: [
|
|
184
|
+
tour.title ? `${tour.title} · ` : "",
|
|
185
|
+
"Step ",
|
|
186
|
+
tour.index + 1,
|
|
187
|
+
" of ",
|
|
188
|
+
tour.steps.length
|
|
189
|
+
] }),
|
|
190
|
+
/* @__PURE__ */ jsx(
|
|
191
|
+
"button",
|
|
192
|
+
{
|
|
193
|
+
type: "button",
|
|
194
|
+
"aria-label": "Close the walkthrough",
|
|
195
|
+
onClick: () => stage.endTour(),
|
|
196
|
+
className: "shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
197
|
+
children: /* @__PURE__ */ jsx(XIcon, { className: "size-3.5" })
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
] }),
|
|
201
|
+
/* @__PURE__ */ jsx("p", { className: "break-words pt-1.5 leading-snug", "data-testid": "agent-ui-tour-text", children: step.text }),
|
|
202
|
+
!tour.searching && !tour.element ? /* @__PURE__ */ jsx("p", { className: "pt-1 text-muted-foreground text-xs", children: "This one is not on the page you have open." }) : null,
|
|
203
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2.5 h-0.5 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx("div", { ref: progressRef, className: "h-full w-0 bg-primary" }) }),
|
|
204
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 pt-2", children: [
|
|
205
|
+
/* @__PURE__ */ jsx(
|
|
206
|
+
"button",
|
|
207
|
+
{
|
|
208
|
+
type: "button",
|
|
209
|
+
"aria-label": "Previous step",
|
|
210
|
+
disabled: tour.index === 0,
|
|
211
|
+
onClick: () => stage.tourBack(),
|
|
212
|
+
className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40",
|
|
213
|
+
children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-4" })
|
|
214
|
+
}
|
|
215
|
+
),
|
|
216
|
+
/* @__PURE__ */ jsx(
|
|
217
|
+
"button",
|
|
218
|
+
{
|
|
219
|
+
type: "button",
|
|
220
|
+
"aria-label": tour.playing ? "Pause the walkthrough" : "Play the walkthrough",
|
|
221
|
+
onClick: () => stage.tourTogglePlaying(),
|
|
222
|
+
className: "rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
223
|
+
children: tour.playing ? /* @__PURE__ */ jsx(PauseIcon, { className: "size-4" }) : /* @__PURE__ */ jsx(PlayIcon, { className: "size-4" })
|
|
224
|
+
}
|
|
225
|
+
),
|
|
226
|
+
/* @__PURE__ */ jsxs(
|
|
227
|
+
"button",
|
|
228
|
+
{
|
|
229
|
+
type: "button",
|
|
230
|
+
onClick: () => stage.tourNext(),
|
|
231
|
+
className: "ml-auto flex items-center gap-1 rounded-md bg-primary px-2.5 py-1 font-medium text-primary-foreground text-xs transition-colors hover:bg-primary/90",
|
|
232
|
+
children: [
|
|
233
|
+
last ? "Done" : "Next",
|
|
234
|
+
last ? null : /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5" })
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
] })
|
|
239
|
+
]
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
function QuestionCard({
|
|
244
|
+
question,
|
|
245
|
+
stage,
|
|
246
|
+
box,
|
|
247
|
+
viewport,
|
|
248
|
+
className
|
|
249
|
+
}) {
|
|
250
|
+
return /* @__PURE__ */ jsxs(
|
|
251
|
+
Card,
|
|
252
|
+
{
|
|
253
|
+
box,
|
|
254
|
+
viewport,
|
|
255
|
+
className,
|
|
256
|
+
role: "alertdialog",
|
|
257
|
+
"aria-label": question.title,
|
|
258
|
+
"data-testid": "agent-ui-question",
|
|
259
|
+
children: [
|
|
260
|
+
/* @__PURE__ */ jsx("p", { className: "break-words font-semibold leading-snug", children: question.title }),
|
|
261
|
+
question.body ? /* @__PURE__ */ jsx("p", { className: "break-words pt-1 text-muted-foreground leading-snug", children: question.body }) : null,
|
|
262
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-2.5", children: [
|
|
263
|
+
/* @__PURE__ */ jsx(
|
|
264
|
+
"button",
|
|
265
|
+
{
|
|
266
|
+
type: "button",
|
|
267
|
+
onClick: () => stage.answer(true),
|
|
268
|
+
className: "rounded-md bg-primary px-2.5 py-1 font-medium text-primary-foreground text-xs transition-colors hover:bg-primary/90",
|
|
269
|
+
children: question.allowLabel
|
|
270
|
+
}
|
|
271
|
+
),
|
|
272
|
+
/* @__PURE__ */ jsx(
|
|
273
|
+
"button",
|
|
274
|
+
{
|
|
275
|
+
type: "button",
|
|
276
|
+
onClick: () => stage.answer(false),
|
|
277
|
+
className: "rounded-md border border-border px-2.5 py-1 font-medium text-xs transition-colors hover:bg-muted",
|
|
278
|
+
children: question.declineLabel
|
|
279
|
+
}
|
|
280
|
+
)
|
|
281
|
+
] })
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
function AgentUiLayer({ agentUi, classNames }) {
|
|
287
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
288
|
+
const { stage } = agentUi;
|
|
289
|
+
const state = useSyncExternalStore(stage.subscribe, stage.getState, stage.getState);
|
|
290
|
+
const ownerDocument = useOwnerDocument();
|
|
291
|
+
const portalContainer = usePortalContainer();
|
|
292
|
+
const themeStyle = useAthenaThemeStyle();
|
|
293
|
+
const tourElement = ((_a = state.tour) == null ? void 0 : _a.element) ?? null;
|
|
294
|
+
const markedElement = state.tour ? tourElement : ((_b = state.mark) == null ? void 0 : _b.element) ?? null;
|
|
295
|
+
const markBox = useElementBox(markedElement);
|
|
296
|
+
const cursorBox = useElementBox(((_c = state.cursor) == null ? void 0 : _c.element) ?? null);
|
|
297
|
+
const questionBox = useElementBox(((_d = state.question) == null ? void 0 : _d.element) ?? null);
|
|
298
|
+
const showing = state.tour !== null || state.mark !== null;
|
|
299
|
+
useEffect(() => {
|
|
300
|
+
if (!showing || !ownerDocument) return;
|
|
301
|
+
const onKeyDown = (event) => {
|
|
302
|
+
if (event.key !== "Escape") return;
|
|
303
|
+
stage.endTour();
|
|
304
|
+
stage.clearMark();
|
|
305
|
+
};
|
|
306
|
+
ownerDocument.addEventListener("keydown", onKeyDown);
|
|
307
|
+
return () => ownerDocument.removeEventListener("keydown", onKeyDown);
|
|
308
|
+
}, [ownerDocument, showing, stage]);
|
|
309
|
+
const container = portalContainer ?? (ownerDocument == null ? void 0 : ownerDocument.body);
|
|
310
|
+
if (!container) return null;
|
|
311
|
+
const idle = !state.mark && !state.cursor && !state.tour && !state.question;
|
|
312
|
+
if (idle) return null;
|
|
313
|
+
const viewport = viewportOf(ownerDocument);
|
|
314
|
+
const caption = state.tour ? null : ((_e = state.mark) == null ? void 0 : _e.caption) ?? null;
|
|
315
|
+
return createPortal(
|
|
316
|
+
/* @__PURE__ */ jsxs(
|
|
317
|
+
"div",
|
|
318
|
+
{
|
|
319
|
+
...{ [AGENT_ATTR.layer]: "" },
|
|
320
|
+
className: "athena-sdk athena-sdk-chrome pointer-events-none fixed inset-0 z-[2147483000]",
|
|
321
|
+
style: themeStyle,
|
|
322
|
+
children: [
|
|
323
|
+
markBox && (state.tour || state.mark) ? /* @__PURE__ */ jsx(
|
|
324
|
+
Mark,
|
|
325
|
+
{
|
|
326
|
+
box: markBox,
|
|
327
|
+
tone: state.tour ? "point" : ((_f = state.mark) == null ? void 0 : _f.tone) ?? "point",
|
|
328
|
+
spotlight: state.tour ? false : ((_g = state.mark) == null ? void 0 : _g.spotlight) ?? false,
|
|
329
|
+
leaving: state.tour ? false : ((_h = state.mark) == null ? void 0 : _h.leaving) ?? false,
|
|
330
|
+
className: classNames == null ? void 0 : classNames.mark
|
|
331
|
+
}
|
|
332
|
+
) : null,
|
|
333
|
+
/* @__PURE__ */ jsx("div", { "aria-live": "polite", children: caption && markBox && !state.question ? /* @__PURE__ */ jsx(
|
|
334
|
+
Card,
|
|
335
|
+
{
|
|
336
|
+
box: markBox,
|
|
337
|
+
viewport,
|
|
338
|
+
fit: true,
|
|
339
|
+
className: cn(
|
|
340
|
+
"pointer-events-none transition-opacity duration-500 motion-reduce:transition-none",
|
|
341
|
+
((_i = state.mark) == null ? void 0 : _i.leaving) ? "opacity-0" : "opacity-100",
|
|
342
|
+
classNames == null ? void 0 : classNames.caption
|
|
343
|
+
),
|
|
344
|
+
"data-testid": "agent-ui-caption",
|
|
345
|
+
children: /* @__PURE__ */ jsx("p", { className: "break-words leading-snug", children: caption })
|
|
346
|
+
}
|
|
347
|
+
) : null }),
|
|
348
|
+
state.tour ? /* @__PURE__ */ jsx(
|
|
349
|
+
TourCard,
|
|
350
|
+
{
|
|
351
|
+
tour: state.tour,
|
|
352
|
+
stage,
|
|
353
|
+
box: markBox,
|
|
354
|
+
viewport,
|
|
355
|
+
className: classNames == null ? void 0 : classNames.tour
|
|
356
|
+
}
|
|
357
|
+
) : null,
|
|
358
|
+
state.question ? /* @__PURE__ */ jsx(
|
|
359
|
+
QuestionCard,
|
|
360
|
+
{
|
|
361
|
+
question: state.question,
|
|
362
|
+
stage,
|
|
363
|
+
box: questionBox,
|
|
364
|
+
viewport,
|
|
365
|
+
className: classNames == null ? void 0 : classNames.question
|
|
366
|
+
}
|
|
367
|
+
) : null,
|
|
368
|
+
state.cursor && cursorBox ? /* @__PURE__ */ jsx(
|
|
369
|
+
Cursor,
|
|
370
|
+
{
|
|
371
|
+
box: cursorBox,
|
|
372
|
+
presses: state.cursor.presses,
|
|
373
|
+
glideMs: agentUi.pace().cursorGlideMs,
|
|
374
|
+
className: classNames == null ? void 0 : classNames.cursor
|
|
375
|
+
}
|
|
376
|
+
) : null
|
|
377
|
+
]
|
|
378
|
+
}
|
|
379
|
+
),
|
|
380
|
+
container
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
export {
|
|
384
|
+
AgentUiLayer,
|
|
385
|
+
cardPosition
|
|
386
|
+
};
|
|
387
|
+
//# sourceMappingURL=AgentUiLayer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AgentUiLayer.js","sources":["../../src/agent-ui/AgentUiLayer.tsx"],"sourcesContent":["'use client';\n\nimport {\n ChevronLeftIcon,\n ChevronRightIcon,\n MousePointer2Icon,\n PauseIcon,\n PlayIcon,\n XIcon,\n} from 'lucide-react';\nimport {\n type CSSProperties,\n type ReactNode,\n useEffect,\n useRef,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { useOwnerDocument, usePortalContainer } from '../lib/owner-document';\nimport { cn } from '../lib/utils';\nimport { useAthenaThemeStyle } from '../provider/theme-style-context';\nimport { AGENT_ATTR } from './contract';\nimport type { AgentUiMarkTone, AgentUiQuestion, AgentUiStage, AgentUiTour } from './stage';\nimport type { AgentUi } from './tools';\n\n/*\n * The overlay: what the person sees of the agent on their page. A box around\n * the element it means, a note beside it, a cursor where it clicks, the\n * walkthrough's card and the question it asks before acting.\n *\n * It draws the stage and nothing else. Every piece is restyled through\n * `classNames`; a host that wants something else entirely reads\n * `agentUi.stage` and draws its own.\n */\n\nexport interface AgentUiLayerClassNames {\n /** The box around the element. */\n mark?: string;\n /** The note beside a marked element. */\n caption?: string;\n /** The agent's cursor. */\n cursor?: string;\n /** The walkthrough's card. */\n tour?: string;\n /** The question asked before acting. */\n question?: string;\n}\n\nexport interface AgentUiLayerProps {\n agentUi: AgentUi;\n classNames?: AgentUiLayerClassNames;\n}\n\ninterface Box {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\n/** Space between an element and the box drawn around it. */\nconst MARK_INSET_PX = 4;\n/** Space between the box and a card anchored to it. */\nconst CARD_GAP_PX = 10;\n/** A card's width, and the room a card needs below an element to sit there. */\nconst CARD_WIDTH_PX = 320;\nconst CARD_ROOM_PX = 150;\nconst EDGE_PX = 8;\n\n/** Follow an element's box frame by frame while it is shown: it rides scrolling and layout changes. */\nfunction useElementBox(element: Element | null): Box | null {\n const [box, setBox] = useState<Box | null>(null);\n useEffect(() => {\n const view = element?.ownerDocument.defaultView;\n if (!element || !view) {\n setBox(null);\n return;\n }\n let frame = 0;\n let last = '';\n const read = () => {\n const rect = element.isConnected ? element.getBoundingClientRect() : null;\n const next = rect ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height } : null;\n const signature = next\n ? [next.top, next.left, next.width, next.height].map(Math.round).join(':')\n : 'gone';\n if (signature !== last) {\n last = signature;\n setBox(next);\n }\n frame = view.requestAnimationFrame(read);\n };\n read();\n return () => view.cancelAnimationFrame(frame);\n }, [element]);\n return box;\n}\n\ninterface Viewport {\n width: number;\n height: number;\n}\n\nfunction viewportOf(doc: Document | null): Viewport {\n const view = doc?.defaultView;\n return { width: view?.innerWidth ?? 1024, height: view?.innerHeight ?? 768 };\n}\n\n/** Where a card anchored to an element sits: under it when there is room, else over it; at the bottom of the page without one. */\nexport function cardPosition({ box, viewport }: { box: Box | null; viewport: Viewport }): CSSProperties {\n const width = Math.min(CARD_WIDTH_PX, viewport.width - EDGE_PX * 2);\n if (!box) {\n return { left: Math.max(EDGE_PX, (viewport.width - width) / 2), bottom: 24, width };\n }\n const left = Math.min(Math.max(box.left, EDGE_PX), Math.max(EDGE_PX, viewport.width - width - EDGE_PX));\n const below = box.top + box.height + MARK_INSET_PX + CARD_GAP_PX;\n if (below + CARD_ROOM_PX <= viewport.height || box.top < CARD_ROOM_PX) {\n return { left, top: Math.min(below, Math.max(EDGE_PX, viewport.height - CARD_ROOM_PX)), width };\n }\n return { left, bottom: viewport.height - box.top + MARK_INSET_PX + CARD_GAP_PX, width };\n}\n\nconst TONE_CLASS: Record<AgentUiMarkTone, string> = {\n point: 'border-primary ring-primary/25',\n act: 'border-primary ring-primary/25 border-dashed',\n ask: 'border-amber-500 ring-amber-500/25',\n};\n\nfunction Mark({\n box,\n tone,\n spotlight,\n leaving,\n className,\n}: {\n box: Box;\n tone: AgentUiMarkTone;\n spotlight: boolean;\n leaving: boolean;\n className?: string;\n}) {\n return (\n <div\n data-testid=\"agent-ui-mark\"\n data-tone={tone}\n className={cn(\n 'pointer-events-none fixed rounded-lg border-2 ring-4 transition-opacity duration-500 motion-reduce:transition-none',\n TONE_CLASS[tone],\n spotlight && 'shadow-[0_0_0_100vmax_rgb(0_0_0/0.45)]',\n leaving ? 'opacity-0' : 'opacity-100',\n className,\n )}\n style={{\n top: box.top - MARK_INSET_PX,\n left: box.left - MARK_INSET_PX,\n width: box.width + MARK_INSET_PX * 2,\n height: box.height + MARK_INSET_PX * 2,\n }}\n />\n );\n}\n\nfunction Card({\n box,\n viewport,\n fit = false,\n className,\n children,\n ...props\n}: {\n box: Box | null;\n viewport: Viewport;\n /** As wide as its words need, up to a card's width (a note); otherwise a card's full width. */\n fit?: boolean;\n className?: string;\n children: ReactNode;\n role?: string;\n 'aria-label'?: string;\n 'data-testid'?: string;\n}) {\n const { width, ...position } = cardPosition({ box, viewport });\n return (\n <div\n {...props}\n className={cn(\n 'pointer-events-auto fixed rounded-xl border border-border bg-popover px-3.5 py-3 text-popover-foreground text-sm shadow-lg',\n className,\n )}\n style={fit ? { ...position, maxWidth: width } : { ...position, width }}\n >\n {children}\n </div>\n );\n}\n\nfunction Cursor({\n box,\n presses,\n glideMs,\n className,\n}: {\n box: Box;\n presses: number;\n glideMs: number;\n className?: string;\n}) {\n const x = box.left + box.width / 2;\n const y = box.top + box.height / 2;\n // The cursor starts a little off the element and glides onto it.\n const [arrived, setArrived] = useState(false);\n useEffect(() => {\n const frame = requestAnimationFrame(() => setArrived(true));\n return () => cancelAnimationFrame(frame);\n }, []);\n return (\n <div\n data-testid=\"agent-ui-cursor\"\n className={cn('pointer-events-none fixed top-0 left-0 motion-reduce:transition-none', className)}\n style={{\n transform: `translate(${arrived ? x : x + 48}px, ${arrived ? y : y + 48}px)`,\n transitionProperty: 'transform',\n transitionTimingFunction: 'ease-out',\n transitionDuration: `${glideMs}ms`,\n }}\n >\n <MousePointer2Icon className=\"size-5 fill-primary text-primary-foreground drop-shadow\" />\n {presses > 0 ? (\n <span\n key={presses}\n data-testid=\"agent-ui-press\"\n className=\"-top-2 -left-2 absolute size-6 animate-ping rounded-full bg-primary/50 [animation-iteration-count:1]\"\n />\n ) : null}\n </div>\n );\n}\n\nfunction TourCard({\n tour,\n stage,\n box,\n viewport,\n className,\n}: {\n tour: AgentUiTour;\n stage: AgentUiStage;\n box: Box | null;\n viewport: Viewport;\n className?: string;\n}) {\n const step = tour.steps[tour.index];\n const last = tour.index >= tour.steps.length - 1;\n const progressRef = useRef<HTMLDivElement>(null);\n const advancing = tour.playing && !tour.searching;\n const durationMs = step?.durationMs ?? 0;\n // The bar fills over the step's time, so the person sees it will move on by itself.\n // `tour.index` restarts it for a step of the same length.\n // biome-ignore lint/correctness/useExhaustiveDependencies: a new step restarts the bar\n useEffect(() => {\n const bar = progressRef.current;\n if (!bar || typeof bar.animate !== 'function') return;\n if (!advancing) return;\n const animation = bar.animate([{ width: '0%' }, { width: '100%' }], {\n duration: durationMs,\n fill: 'forwards',\n easing: 'linear',\n });\n return () => animation.cancel();\n }, [advancing, durationMs, tour.index]);\n if (!step) return null;\n return (\n <Card\n box={box}\n viewport={viewport}\n className={className}\n role=\"dialog\"\n aria-label={tour.title ?? 'Walkthrough'}\n data-testid=\"agent-ui-tour\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <p className=\"min-w-0 truncate text-muted-foreground text-xs\">\n {tour.title ? `${tour.title} · ` : ''}\n Step {tour.index + 1} of {tour.steps.length}\n </p>\n <button\n type=\"button\"\n aria-label=\"Close the walkthrough\"\n onClick={() => stage.endTour()}\n className=\"shrink-0 rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n <XIcon className=\"size-3.5\" />\n </button>\n </div>\n <p className=\"break-words pt-1.5 leading-snug\" data-testid=\"agent-ui-tour-text\">\n {step.text}\n </p>\n {!tour.searching && !tour.element ? (\n <p className=\"pt-1 text-muted-foreground text-xs\">This one is not on the page you have open.</p>\n ) : null}\n <div className=\"mt-2.5 h-0.5 overflow-hidden rounded-full bg-muted\">\n <div ref={progressRef} className=\"h-full w-0 bg-primary\" />\n </div>\n <div className=\"flex items-center gap-1 pt-2\">\n <button\n type=\"button\"\n aria-label=\"Previous step\"\n disabled={tour.index === 0}\n onClick={() => stage.tourBack()}\n className=\"rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40\"\n >\n <ChevronLeftIcon className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n aria-label={tour.playing ? 'Pause the walkthrough' : 'Play the walkthrough'}\n onClick={() => stage.tourTogglePlaying()}\n className=\"rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n {tour.playing ? <PauseIcon className=\"size-4\" /> : <PlayIcon className=\"size-4\" />}\n </button>\n <button\n type=\"button\"\n onClick={() => stage.tourNext()}\n className=\"ml-auto flex items-center gap-1 rounded-md bg-primary px-2.5 py-1 font-medium text-primary-foreground text-xs transition-colors hover:bg-primary/90\"\n >\n {last ? 'Done' : 'Next'}\n {last ? null : <ChevronRightIcon className=\"size-3.5\" />}\n </button>\n </div>\n </Card>\n );\n}\n\nfunction QuestionCard({\n question,\n stage,\n box,\n viewport,\n className,\n}: {\n question: AgentUiQuestion;\n stage: AgentUiStage;\n box: Box | null;\n viewport: Viewport;\n className?: string;\n}) {\n return (\n <Card\n box={box}\n viewport={viewport}\n className={className}\n role=\"alertdialog\"\n aria-label={question.title}\n data-testid=\"agent-ui-question\"\n >\n <p className=\"break-words font-semibold leading-snug\">{question.title}</p>\n {question.body ? (\n <p className=\"break-words pt-1 text-muted-foreground leading-snug\">{question.body}</p>\n ) : null}\n <div className=\"flex flex-wrap items-center gap-2 pt-2.5\">\n <button\n type=\"button\"\n onClick={() => stage.answer(true)}\n className=\"rounded-md bg-primary px-2.5 py-1 font-medium text-primary-foreground text-xs transition-colors hover:bg-primary/90\"\n >\n {question.allowLabel}\n </button>\n <button\n type=\"button\"\n onClick={() => stage.answer(false)}\n className=\"rounded-md border border-border px-2.5 py-1 font-medium text-xs transition-colors hover:bg-muted\"\n >\n {question.declineLabel}\n </button>\n </div>\n </Card>\n );\n}\n\n/**\n * Draws the agent's stage over the page. Mount one per `AgentUi`, anywhere\n * under the provider; it portals to the document's body and takes no clicks\n * except on its own cards.\n */\nexport function AgentUiLayer({ agentUi, classNames }: AgentUiLayerProps) {\n const { stage } = agentUi;\n const state = useSyncExternalStore(stage.subscribe, stage.getState, stage.getState);\n const ownerDocument = useOwnerDocument();\n const portalContainer = usePortalContainer();\n const themeStyle = useAthenaThemeStyle();\n\n const tourElement = state.tour?.element ?? null;\n const markedElement = state.tour ? tourElement : (state.mark?.element ?? null);\n const markBox = useElementBox(markedElement);\n const cursorBox = useElementBox(state.cursor?.element ?? null);\n const questionBox = useElementBox(state.question?.element ?? null);\n\n // Escape closes what the agent is showing: the person's page is theirs.\n const showing = state.tour !== null || state.mark !== null;\n useEffect(() => {\n if (!showing || !ownerDocument) return;\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return;\n stage.endTour();\n stage.clearMark();\n };\n ownerDocument.addEventListener('keydown', onKeyDown);\n return () => ownerDocument.removeEventListener('keydown', onKeyDown);\n }, [ownerDocument, showing, stage]);\n\n const container = portalContainer ?? ownerDocument?.body;\n if (!container) return null;\n const idle = !state.mark && !state.cursor && !state.tour && !state.question;\n if (idle) return null;\n const viewport = viewportOf(ownerDocument);\n const caption = state.tour ? null : (state.mark?.caption ?? null);\n\n return createPortal(\n <div\n {...{ [AGENT_ATTR.layer]: '' }}\n className=\"athena-sdk athena-sdk-chrome pointer-events-none fixed inset-0 z-[2147483000]\"\n style={themeStyle as CSSProperties | undefined}\n >\n {markBox && (state.tour || state.mark) ? (\n <Mark\n box={markBox}\n tone={state.tour ? 'point' : (state.mark?.tone ?? 'point')}\n spotlight={state.tour ? false : (state.mark?.spotlight ?? false)}\n leaving={state.tour ? false : (state.mark?.leaving ?? false)}\n className={classNames?.mark}\n />\n ) : null}\n {/* What the agent is doing, said aloud for a screen reader too. */}\n <div aria-live=\"polite\">\n {caption && markBox && !state.question ? (\n <Card\n box={markBox}\n viewport={viewport}\n fit\n className={cn(\n 'pointer-events-none transition-opacity duration-500 motion-reduce:transition-none',\n state.mark?.leaving ? 'opacity-0' : 'opacity-100',\n classNames?.caption,\n )}\n data-testid=\"agent-ui-caption\"\n >\n <p className=\"break-words leading-snug\">{caption}</p>\n </Card>\n ) : null}\n </div>\n {state.tour ? (\n <TourCard\n tour={state.tour}\n stage={stage}\n box={markBox}\n viewport={viewport}\n className={classNames?.tour}\n />\n ) : null}\n {state.question ? (\n <QuestionCard\n question={state.question}\n stage={stage}\n box={questionBox}\n viewport={viewport}\n className={classNames?.question}\n />\n ) : null}\n {state.cursor && cursorBox ? (\n <Cursor\n box={cursorBox}\n presses={state.cursor.presses}\n glideMs={agentUi.pace().cursorGlideMs}\n className={classNames?.cursor}\n />\n ) : null}\n </div>,\n container,\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const AGENT_TARGET_ACTIONS = ["click", "type", "select", "toggle", "clear"];
|
|
4
|
+
function isAgentTargetAction(value) {
|
|
5
|
+
return AGENT_TARGET_ACTIONS.some((action) => action === value);
|
|
6
|
+
}
|
|
7
|
+
const AGENT_ATTR = {
|
|
8
|
+
/** The target's id, stable across renders (`investors.search`). */
|
|
9
|
+
target: "data-agent-target",
|
|
10
|
+
/** Which one of several targets sharing an id (a row's key). */
|
|
11
|
+
key: "data-agent-key",
|
|
12
|
+
/** The name the person would call it. Falls back to the element's own accessible name. */
|
|
13
|
+
label: "data-agent-label",
|
|
14
|
+
/** One line for the agent on what it is for. */
|
|
15
|
+
description: "data-agent-description",
|
|
16
|
+
/** Space-separated actions the agent may take, or `none`. Derived from the element when absent. */
|
|
17
|
+
actions: "data-agent-actions",
|
|
18
|
+
/** Present: acting on it changes or destroys something, so the person confirms every time. */
|
|
19
|
+
danger: "data-agent-danger",
|
|
20
|
+
/** Present: its value is never read and never typed. */
|
|
21
|
+
sensitive: "data-agent-sensitive",
|
|
22
|
+
/** On a container: a named part of the screen its targets belong to. */
|
|
23
|
+
region: "data-agent-region",
|
|
24
|
+
/** Present on the overlay's own root: never a target, and input inside it is the overlay's. */
|
|
25
|
+
layer: "data-agent-ui-layer"
|
|
26
|
+
};
|
|
27
|
+
const TARGET_ID_PATTERN = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;
|
|
28
|
+
const NO_PROPS = {};
|
|
29
|
+
function isAgentTargetId(id) {
|
|
30
|
+
return id.length <= 80 && TARGET_ID_PATTERN.test(id);
|
|
31
|
+
}
|
|
32
|
+
function agentTarget(options) {
|
|
33
|
+
if (!isAgentTargetId(options.id)) {
|
|
34
|
+
console.error(
|
|
35
|
+
`agentTarget: "${options.id}" is not a target id, so the element is not a target. Use lowercase words joined by ".", "-" or "_" (e.g. "investors.search").`
|
|
36
|
+
);
|
|
37
|
+
return NO_PROPS;
|
|
38
|
+
}
|
|
39
|
+
const props = { [AGENT_ATTR.target]: options.id };
|
|
40
|
+
if (options.key !== void 0 && options.key !== "") props[AGENT_ATTR.key] = options.key;
|
|
41
|
+
if (options.label) props[AGENT_ATTR.label] = options.label;
|
|
42
|
+
if (options.description) props[AGENT_ATTR.description] = options.description;
|
|
43
|
+
if (options.actions) {
|
|
44
|
+
props[AGENT_ATTR.actions] = options.actions.length > 0 ? options.actions.join(" ") : "none";
|
|
45
|
+
}
|
|
46
|
+
if (options.danger) props[AGENT_ATTR.danger] = "";
|
|
47
|
+
if (options.sensitive) props[AGENT_ATTR.sensitive] = "";
|
|
48
|
+
return props;
|
|
49
|
+
}
|
|
50
|
+
function agentRegion({ id, label }) {
|
|
51
|
+
if (!isAgentTargetId(id)) {
|
|
52
|
+
console.error(`agentRegion: "${id}" is not a region id (e.g. "filters"), so the element names no region.`);
|
|
53
|
+
return NO_PROPS;
|
|
54
|
+
}
|
|
55
|
+
return label ? { [AGENT_ATTR.region]: id, [AGENT_ATTR.label]: label } : { [AGENT_ATTR.region]: id };
|
|
56
|
+
}
|
|
57
|
+
function agentTargetRef({ id, key }) {
|
|
58
|
+
return key ? `${id}[${key}]` : id;
|
|
59
|
+
}
|
|
60
|
+
function parseAgentTargetRef(ref) {
|
|
61
|
+
const text = ref.trim();
|
|
62
|
+
const match = /^([^[\]]+)(?:\[(.+)\])?$/.exec(text);
|
|
63
|
+
if (!(match == null ? void 0 : match[1])) return null;
|
|
64
|
+
return { id: match[1].trim(), key: match[2] ?? null };
|
|
65
|
+
}
|
|
66
|
+
const HANDLER_REGISTRY_KEY = Symbol.for("athena.agentUi.targetHandlers");
|
|
67
|
+
function handlerRegistry() {
|
|
68
|
+
const holder = globalThis;
|
|
69
|
+
let registry = holder[HANDLER_REGISTRY_KEY];
|
|
70
|
+
if (!registry) {
|
|
71
|
+
registry = /* @__PURE__ */ new Map();
|
|
72
|
+
holder[HANDLER_REGISTRY_KEY] = registry;
|
|
73
|
+
}
|
|
74
|
+
return registry;
|
|
75
|
+
}
|
|
76
|
+
function registerAgentTargetHandlers({
|
|
77
|
+
ref,
|
|
78
|
+
handlers
|
|
79
|
+
}) {
|
|
80
|
+
const registry = handlerRegistry();
|
|
81
|
+
if (registry.has(ref)) {
|
|
82
|
+
console.error(
|
|
83
|
+
`useAgentTarget: two mounted targets share "${ref}" and both register handlers. Give each its own key.`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
registry.set(ref, handlers);
|
|
87
|
+
return () => {
|
|
88
|
+
if (registry.get(ref) === handlers) registry.delete(ref);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function agentTargetHandlers(ref) {
|
|
92
|
+
return handlerRegistry().get(ref);
|
|
93
|
+
}
|
|
94
|
+
exports.AGENT_ATTR = AGENT_ATTR;
|
|
95
|
+
exports.AGENT_TARGET_ACTIONS = AGENT_TARGET_ACTIONS;
|
|
96
|
+
exports.agentRegion = agentRegion;
|
|
97
|
+
exports.agentTarget = agentTarget;
|
|
98
|
+
exports.agentTargetHandlers = agentTargetHandlers;
|
|
99
|
+
exports.agentTargetRef = agentTargetRef;
|
|
100
|
+
exports.isAgentTargetAction = isAgentTargetAction;
|
|
101
|
+
exports.isAgentTargetId = isAgentTargetId;
|
|
102
|
+
exports.parseAgentTargetRef = parseAgentTargetRef;
|
|
103
|
+
exports.registerAgentTargetHandlers = registerAgentTargetHandlers;
|
|
104
|
+
//# sourceMappingURL=contract.cjs.map
|