initial commit
This commit is contained in:
@@ -1,22 +1,20 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import '../DoormileAI.css';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import dayjs from 'dayjs';
|
||||
import { X as CloseOutlined, MoreHorizontal as MoreOutlined } from 'lucide-react';
|
||||
import { ChevronDown as LuChevronDown, PanelRightOpen as LuPanelRightOpen, PanelRightClose as LuPanelRightClose, History as LuHistory, Trash2 as LuTrash2, MessageSquare as LuMessageSquare } from 'lucide-react';
|
||||
import { AiOutlineClose as CloseOutlined, AiOutlineMore as MoreOutlined } from 'react-icons/ai';
|
||||
import { LuChevronDown, LuPanelRightOpen, LuPanelRightClose, LuHistory, LuTrash2, LuMessageSquare } from 'react-icons/lu';
|
||||
|
||||
import { HStack } from './shim';
|
||||
import { VStack } from './shim';
|
||||
import { Text } from './shim';
|
||||
import { IconButton } from './shim';
|
||||
import { DropdownMenu } from './shim';
|
||||
import { HStack } from '@astryxdesign/core/HStack';
|
||||
import { VStack } from '@astryxdesign/core/VStack';
|
||||
import { Text } from '@astryxdesign/core/Text';
|
||||
import { IconButton } from '@astryxdesign/core/IconButton';
|
||||
import { DropdownMenu } from '@astryxdesign/core/DropdownMenu';
|
||||
|
||||
import { toast } from '@/components/ui/use-toast';
|
||||
const OpenToast = ({message, tone}) => toast({description: message, variant: tone === 'error' ? 'destructive' : 'default'});
|
||||
const STATUS = { error: 'error', success: 'success', warning: 'warning', info: 'info', muted: 'muted' };
|
||||
import { OpenToast } from 'components/third-party/OpenToast';
|
||||
import { STATUS } from 'themes/dt/tokens';
|
||||
import { answerQuestion, FOLLOW_UP_SUGGESTIONS } from '@/lib/assistant/intents';
|
||||
import { executeCreateCustomer, buildCustomerPayload } from '@/lib/assistant/actions';
|
||||
import { executeCreateOrder, buildOrderPayload, validateOrderDraft } from '@/lib/assistant/orderActions';
|
||||
@@ -228,17 +226,8 @@ const AIPanel = ({ isOpen, onClose }) => {
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setIsMounted(true);
|
||||
// One frame between mounting and showing, so the slide-in has a
|
||||
// from-state to animate out of. rAF is the right clock for that, but a
|
||||
// hidden or heavily throttled document never runs the callback at all —
|
||||
// and a panel that refuses to open is worse than one that opens without
|
||||
// its animation, so a timer backs it up.
|
||||
const raf = requestAnimationFrame(() => setIsShown(true));
|
||||
const fallback = setTimeout(() => setIsShown(true), 50);
|
||||
return () => {
|
||||
cancelAnimationFrame(raf);
|
||||
clearTimeout(fallback);
|
||||
};
|
||||
return () => cancelAnimationFrame(raf);
|
||||
}
|
||||
setIsShown(false);
|
||||
const timer = setTimeout(() => setIsMounted(false), ANIMATION_MS);
|
||||
@@ -275,14 +264,15 @@ const AIPanel = ({ isOpen, onClose }) => {
|
||||
}, [isWide]);
|
||||
|
||||
// ---- anchor the dock to the real header height ----
|
||||
// The dock starts where the top nav ends. Measured rather than assumed, and
|
||||
// re-measured on resize, so the seam stays closed if the header ever changes
|
||||
// height — a hard-coded number leaves a sliver of page visible above the
|
||||
// panel the moment the bar grows.
|
||||
// The dock starts where the top nav ends. `--appshell-header-height` looks
|
||||
// like the right token for that and Dispatch.css already reads it, but
|
||||
// Astryx does not actually publish it — verified in the browser, where it
|
||||
// resolves to nothing and the fallback wins. The header is 57px, not the 64
|
||||
// a fallback would guess, so trusting it left a 7px sliver of page visible
|
||||
// above the panel.
|
||||
//
|
||||
// AdminLayout marks its bar `data-app-header`; that attribute exists purely
|
||||
// as this hook. The selector used to be `.astryx-layout-header`, a class this
|
||||
// app does not render at all, so the measurement silently never ran.
|
||||
// Measured instead, and re-measured on resize, so the seam stays closed if
|
||||
// the header ever changes height.
|
||||
useEffect(() => {
|
||||
if (!isMounted) return undefined;
|
||||
const header = document.querySelector('[data-app-header]');
|
||||
@@ -1394,25 +1384,22 @@ const AIPanel = ({ isOpen, onClose }) => {
|
||||
// shrink and scroll. See the [data-compact] rule in DoormileAI.css.
|
||||
const isCompactStrip = hasThread;
|
||||
|
||||
if (!isMounted || !isShown) return null;
|
||||
|
||||
return createPortal(
|
||||
<>
|
||||
<div
|
||||
className="dai-scrim"
|
||||
data-open={isShown}
|
||||
onClick={onClose}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<HStack className="dai-root dai-scrim" data-open={isShown} padding={0} gap={0} onClick={onClose} aria-hidden="true" />
|
||||
|
||||
<VStack
|
||||
as="aside"
|
||||
ref={panelRef}
|
||||
className="dai-root dai-panel"
|
||||
data-open={isShown}
|
||||
data-state={isShown ? 'open' : 'closed'}
|
||||
gap={0}
|
||||
padding={0}
|
||||
/* `complementary`, not `dialog`. A dialog promises modality — focus is
|
||||
trapped, the rest of the page is inert, and screen readers announce
|
||||
it as blocking. None of that is true any more: the page beside it
|
||||
stays live and operable, so announcing it as a dialog would be a
|
||||
lie to exactly the users who cannot see that it is docked. */
|
||||
role="complementary"
|
||||
aria-label="Doormile AI — Operations Copilot"
|
||||
tabIndex={-1}
|
||||
|
||||
Reference in New Issue
Block a user