UNPKG

@neo4j-ndl/react

Version:

React implementation of Neo4j Design System

165 lines 9.89 kB
"use strict"; 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; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Prompt = void 0; const jsx_runtime_1 = require("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/>. */ const react_1 = require("@neo4j-ndl/react"); const icons_1 = require("@neo4j-ndl/react/icons"); const classnames_1 = __importDefault(require("classnames")); const react_2 = require("react"); /** * 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, isRunningPrompt = false, onSubmitPrompt, onCancelPrompt, isSubmitDisabled = false, topContent, bottomContent, disclaimer = 'All information should be verified.', className, style, htmlAttributes, ref, textareaProps } = _a, restProps = __rest(_a, ["value", "onChange", "maxRows", "isRunningPrompt", "onSubmitPrompt", "onCancelPrompt", "isSubmitDisabled", "topContent", "bottomContent", "disclaimer", "className", "style", "htmlAttributes", "ref", "textareaProps"]); const classes = (0, classnames_1.default)('ndl-ai-prompt', className); const textareaRef = (0, react_2.useRef)(null); const textareaAboveRef = (0, react_2.useRef)(null); const [lineHeight, setLineHeight] = (0, react_2.useState)(0); /** File overflow stuff */ (0, react_2.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 */ (0, react_2.useEffect)(() => { if (textareaRef.current !== null) { const computed = window.getComputedStyle(textareaRef.current); setLineHeight(parseInt(computed.lineHeight)); } }, []); const adjustTextareaHeight = (0, react_2.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}px`; } else { ta.style.overflowY = 'auto'; ta.style.height = `${maxHeight}px`; } } }, [lineHeight, maxRows]); // Adjust height when prompt value changes (including when cleared) (0, react_2.useEffect)(() => { adjustTextareaHeight(); }, [value, adjustTextareaHeight]); const handleInput = (e) => { adjustTextareaHeight(); onChange === null || onChange === void 0 ? void 0 : onChange(e); }; return ((0, jsx_runtime_1.jsxs)("div", Object.assign({ ref: ref, className: classes, style: style }, restProps, htmlAttributes, { children: [(0, jsx_runtime_1.jsx)("div", { className: "ndl-ai-prompt-wrapper", children: (0, jsx_runtime_1.jsxs)("div", { className: "ndl-ai-prompt-wrapper-inner", children: [Boolean(topContent) && ((0, jsx_runtime_1.jsx)("div", { className: "ndl-ai-prompt-textarea-above", ref: textareaAboveRef, children: topContent })), (0, jsx_runtime_1.jsx)("textarea", Object.assign({ placeholder: "Ask anything", className: "ndl-ai-prompt-textarea", rows: 1, onChange: handleInput, value: value, ref: textareaRef, 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)), (0, jsx_runtime_1.jsxs)("div", { className: "ndl-ai-prompt-textarea-below", children: [Boolean(bottomContent) && ((0, jsx_runtime_1.jsx)("div", { className: "ndl-ai-prompt-textarea-below-leading", children: bottomContent })), (0, jsx_runtime_1.jsx)(SumbitPromptButton, { isDisabled: isSubmitDisabled, isRunningPrompt: isRunningPrompt, onSubmit: onSubmitPrompt, onCancel: onCancelPrompt })] })] }) }), Boolean(disclaimer) && ((0, jsx_runtime_1.jsx)(react_1.Typography, { variant: "body-small", className: "ndl-ai-prompt-footer", children: disclaimer }))] }))); }; const SumbitPromptButton = ({ onSubmit, onCancel, isDisabled, isRunningPrompt, }) => { const description = isRunningPrompt === true ? 'Cancel' : 'Send'; return ((0, jsx_runtime_1.jsxs)(react_1.CleanIconButton, { className: "ndl-ai-prompt-submit-button", description: description, size: "medium", onClick: isRunningPrompt === true ? onCancel : onSubmit, isDisabled: isDisabled, children: [(0, jsx_runtime_1.jsx)(icons_1.ArrowSmallUpIconOutline, { style: { opacity: isRunningPrompt === true ? 0 : 1, position: isRunningPrompt === true ? 'absolute' : 'relative', transform: isRunningPrompt === true ? 'translateY(-8px)' : 'translateY(0)', } }), (0, jsx_runtime_1.jsx)(icons_1.StopCircleIconOutline, { style: { opacity: isRunningPrompt === true ? 1 : 0, position: isRunningPrompt === true ? 'relative' : 'absolute', } })] })); }; const AgentSelect = ({ value, options = [], onChange }) => { var _a, _b, _c; const anchorRef = (0, react_2.useRef)(null); const [isMenuOpen, setIsMenuOpen] = (0, react_2.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 ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(react_1.DropdownButton, { size: "small", className: "ndl-ai-prompt-dropdown-button", onClick: () => setIsMenuOpen((old) => !old), isOpen: isMenuOpen, ref: anchorRef, children: displayLabel }), (0, jsx_runtime_1.jsx)(react_1.Menu, { isOpen: isMenuOpen, onClose: () => setIsMenuOpen(false), anchorRef: anchorRef, placement: "top-start-bottom-start", children: (0, jsx_runtime_1.jsx)(react_1.Menu.Items, { children: options.map((option) => ((0, jsx_runtime_1.jsx)(react_1.Menu.Item, { title: option.label, onClick: () => handleItemClick(option), leadingVisual: (value === null || value === void 0 ? void 0 : value.value) === option.value ? (0, jsx_runtime_1.jsx)(icons_1.CheckIconOutline, {}) : null }, option.value))) }) })] })); }; const Prompt = Object.assign(PromptComponent, { Select: AgentSelect, }); exports.Prompt = Prompt; //# sourceMappingURL=Prompt.js.map