UNPKG

@neo4j-ndl/react

Version:

React implementation of Neo4j Design System

175 lines 10.9 kB
var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; /** * * Copyright (c) "Neo4j" * Neo4j Sweden AB [http://neo4j.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ import { CleanIconButton, DropdownButton, Menu, Tooltip, Typography, } from '@neo4j-ndl/react'; import classNames from 'classnames'; import { useCallback, useEffect, useRef, useState } from 'react'; import ArrowSendIcon from '../../icons/generated/custom/ArrowSend'; import { ArrowSmallUpIconOutline } from '../../icons/generated/heroIcons/ArrowSmallUpIconOutline'; import { StopCircleIconOutline } from '../../icons/generated/heroIcons/StopCircleIconOutline'; import { XMarkIconOutline } from '../../icons/generated/heroIcons/XMarkIconOutline'; /** * The component is used to display the prompt input for an LLM. * It includes a textarea for the user to enter their prompt and a submit button. * It can also display content above and below the textarea, like uploaded files or a select for changing agents. * * @alpha - Changes to this component may be breaking. */ const PromptComponent = (_a) => { var { value, onChange, maxRows = 5, isDisabled = false, isRunningPrompt = false, onSubmitPrompt, onCancelPrompt, isSubmitDisabled = false, headerContent, topContent, bottomContent, bottomTrailingContent, disclaimer = 'All information should be verified.', className, style, htmlAttributes, ref, textareaProps } = _a, restProps = __rest(_a, ["value", "onChange", "maxRows", "isDisabled", "isRunningPrompt", "onSubmitPrompt", "onCancelPrompt", "isSubmitDisabled", "headerContent", "topContent", "bottomContent", "bottomTrailingContent", "disclaimer", "className", "style", "htmlAttributes", "ref", "textareaProps"]); const classes = classNames('ndl-ai-prompt', className, { 'ndl-disabled': isDisabled, }); const textareaRef = useRef(null); const textareaAboveRef = useRef(null); const [lineHeight, setLineHeight] = useState(0); /** File overflow stuff */ useEffect(() => { let rafId = null; const checkOverflow = () => { if (textareaAboveRef.current !== null) { const element = textareaAboveRef.current; const maxHeight = parseFloat(getComputedStyle(element).maxHeight.replace('px', '')); const scrollHeight = element.scrollHeight; if (scrollHeight > maxHeight) { element.classList.add('ndl-can-scroll'); } else { element.classList.remove('ndl-can-scroll'); } } }; const debouncedCheck = () => { if (rafId !== null) { cancelAnimationFrame(rafId); } rafId = requestAnimationFrame(checkOverflow); }; // Initial check checkOverflow(); const resizeObserver = new ResizeObserver(debouncedCheck); const mutationObserver = new MutationObserver(debouncedCheck); if (textareaAboveRef.current !== null) { resizeObserver.observe(textareaAboveRef.current); mutationObserver.observe(textareaAboveRef.current, { childList: true, subtree: true, }); } return () => { if (rafId !== null) { cancelAnimationFrame(rafId); } resizeObserver.disconnect(); mutationObserver.disconnect(); }; }, []); /** Textarea stuff */ useEffect(() => { if (textareaRef.current !== null) { const computed = window.getComputedStyle(textareaRef.current); setLineHeight(parseInt(computed.lineHeight)); } }, []); const adjustTextareaHeight = useCallback(() => { const ta = textareaRef.current; if (ta !== null && lineHeight > 0) { // Reset height so scrollHeight measures correctly ta.style.height = 'auto'; const maxHeight = lineHeight * maxRows; if (ta.scrollHeight <= maxHeight) { ta.style.overflowY = 'hidden'; ta.style.height = `${ta.scrollHeight + 12}px`; } else { ta.style.overflowY = 'auto'; ta.style.height = `${maxHeight + 12}px`; } } }, [lineHeight, maxRows]); // Adjust height when prompt value changes (including when cleared) useEffect(() => { adjustTextareaHeight(); }, [value, adjustTextareaHeight]); const handleInput = (e) => { adjustTextareaHeight(); onChange === null || onChange === void 0 ? void 0 : onChange(e); }; return (_jsxs("div", Object.assign({ ref: ref, className: classes, style: style }, restProps, htmlAttributes, { children: [_jsxs("div", { className: "ndl-ai-prompt-wrapper", children: [Boolean(headerContent) && (_jsx("div", { className: "ndl-ai-prompt-header", children: headerContent })), _jsxs("div", { className: "ndl-ai-prompt-wrapper-inner", children: [Boolean(topContent) && (_jsx("div", { className: "ndl-ai-prompt-textarea-above", ref: textareaAboveRef, children: topContent })), _jsx("textarea", Object.assign({ placeholder: "Ask anything", className: "ndl-ai-prompt-textarea", rows: 1, onChange: handleInput, value: value, ref: textareaRef, disabled: isDisabled, onKeyDown: (e) => { if (e.key === 'Enter' && !e.shiftKey && isSubmitDisabled !== true) { e.preventDefault(); if (isRunningPrompt !== true) { onSubmitPrompt === null || onSubmitPrompt === void 0 ? void 0 : onSubmitPrompt(e); } } } }, textareaProps)), _jsxs("div", { className: "ndl-ai-prompt-textarea-below", children: [Boolean(bottomContent) && (_jsx("div", { className: "ndl-ai-prompt-textarea-below-leading", children: bottomContent })), _jsxs("div", { className: "ndl-ai-prompt-textarea-below-trailing", children: [bottomTrailingContent, _jsx(SubmitPromptButton, { isDisabled: isSubmitDisabled, isRunningPrompt: isRunningPrompt, onSubmit: onSubmitPrompt, onCancel: onCancelPrompt })] })] })] })] }), Boolean(disclaimer) && (_jsx(Typography, { variant: "body-small", className: "ndl-ai-prompt-footer", children: disclaimer }))] }))); }; const SubmitPromptButton = ({ onSubmit, onCancel, isDisabled, isRunningPrompt, }) => { return (_jsx(CleanIconButton, { className: "ndl-ai-prompt-submit-button", description: isRunningPrompt === true ? 'Stop' : 'Send', size: "small", isDisabled: isDisabled, onClick: isRunningPrompt === true ? onCancel : onSubmit, children: isRunningPrompt === true ? (_jsx(StopCircleIconOutline, {})) : (_jsx(ArrowSmallUpIconOutline, {})) })); }; const AgentSelect = ({ value, options = [], onChange }) => { var _a, _b, _c; const anchorRef = useRef(null); const [isMenuOpen, setIsMenuOpen] = useState(false); const displayLabel = (_c = (_a = value === null || value === void 0 ? void 0 : value.label) !== null && _a !== void 0 ? _a : (_b = options[0]) === null || _b === void 0 ? void 0 : _b.label) !== null && _c !== void 0 ? _c : ''; const handleItemClick = (option) => { onChange === null || onChange === void 0 ? void 0 : onChange(option); setIsMenuOpen(false); }; return (_jsxs(_Fragment, { children: [_jsx(DropdownButton, { size: "small", className: "ndl-ai-prompt-dropdown-button", onClick: () => setIsMenuOpen((old) => !old), isOpen: isMenuOpen, ref: anchorRef, children: displayLabel }), _jsx(Menu, { isOpen: isMenuOpen, onClose: () => setIsMenuOpen(false), anchorRef: anchorRef, placement: "top-start-bottom-start", children: options.map((option) => (_jsx(Menu.RadioItem, { title: option.label, isChecked: (value === null || value === void 0 ? void 0 : value.value) === option.value, onClick: () => handleItemClick(option) }, option.value))) })] })); }; const ContextTag = ({ children, onClose }) => { const textRef = useRef(null); const [isTooltipOpen, setIsTooltipOpen] = useState(false); return (_jsxs(Tooltip, { type: "simple", isOpen: isTooltipOpen, onOpenChange: (isOpen) => { var _a, _b; if (isOpen) { const isTruncated = ((_a = textRef.current) === null || _a === void 0 ? void 0 : _a.scrollWidth) && ((_b = textRef.current) === null || _b === void 0 ? void 0 : _b.clientWidth) && textRef.current.scrollWidth > textRef.current.clientWidth; if (isTruncated) { setIsTooltipOpen(true); } } else { setIsTooltipOpen(false); } }, hoverDelay: { close: 0, open: 500 }, children: [_jsx(Tooltip.Trigger, { hasButtonWrapper: true, children: _jsxs("div", { className: "ndl-ai-prompt-context-tag", children: [_jsx(ArrowSendIcon, { className: "ndl-ai-prompt-context-tag-icon" }), _jsx(Typography, { variant: "body-medium", className: "ndl-ai-prompt-context-tag-text", ref: textRef, children: children }), _jsx("button", { type: "button", onClick: onClose, "aria-label": `Remove context: ${children}`, className: "ndl-ai-prompt-context-tag-close-button", children: _jsx(XMarkIconOutline, { className: "ndl-ai-prompt-context-tag-icon" }) })] }) }), _jsx(Tooltip.Content, { children: children })] })); }; const Prompt = Object.assign(PromptComponent, { ContextTag: ContextTag, Select: AgentSelect, }); export { Prompt }; //# sourceMappingURL=Prompt.js.map