@neo4j-ndl/react
Version:
React implementation of Neo4j Design System
181 lines • 11.9 kB
JavaScript
;
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 classnames_1 = __importDefault(require("classnames"));
const react_2 = require("react");
const ArrowSend_1 = __importDefault(require("../../icons/generated/custom/ArrowSend"));
const ArrowSmallUpIconOutline_1 = require("../../icons/generated/heroIcons/ArrowSmallUpIconOutline");
const StopCircleIconOutline_1 = require("../../icons/generated/heroIcons/StopCircleIconOutline");
const XMarkIconOutline_1 = require("../../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 = (0, classnames_1.default)('ndl-ai-prompt', className, {
'ndl-disabled': isDisabled,
});
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 + 12}px`;
}
else {
ta.style.overflowY = 'auto';
ta.style.height = `${maxHeight + 12}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.jsxs)("div", { className: "ndl-ai-prompt-wrapper", children: [Boolean(headerContent) && ((0, jsx_runtime_1.jsx)("div", { className: "ndl-ai-prompt-header", children: headerContent })), (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, 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)), (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.jsxs)("div", { className: "ndl-ai-prompt-textarea-below-trailing", children: [bottomTrailingContent, (0, jsx_runtime_1.jsx)(SubmitPromptButton, { 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 SubmitPromptButton = ({ onSubmit, onCancel, isDisabled, isRunningPrompt, }) => {
return ((0, jsx_runtime_1.jsx)(react_1.CleanIconButton, { className: "ndl-ai-prompt-submit-button", description: isRunningPrompt === true ? 'Stop' : 'Send', size: "small", isDisabled: isDisabled, onClick: isRunningPrompt === true ? onCancel : onSubmit, children: isRunningPrompt === true ? ((0, jsx_runtime_1.jsx)(StopCircleIconOutline_1.StopCircleIconOutline, {})) : ((0, jsx_runtime_1.jsx)(ArrowSmallUpIconOutline_1.ArrowSmallUpIconOutline, {})) }));
};
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: options.map((option) => ((0, jsx_runtime_1.jsx)(react_1.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 = (0, react_2.useRef)(null);
const [isTooltipOpen, setIsTooltipOpen] = (0, react_2.useState)(false);
return ((0, jsx_runtime_1.jsxs)(react_1.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: [(0, jsx_runtime_1.jsx)(react_1.Tooltip.Trigger, { hasButtonWrapper: true, children: (0, jsx_runtime_1.jsxs)("div", { className: "ndl-ai-prompt-context-tag", children: [(0, jsx_runtime_1.jsx)(ArrowSend_1.default, { className: "ndl-ai-prompt-context-tag-icon" }), (0, jsx_runtime_1.jsx)(react_1.Typography, { variant: "body-medium", className: "ndl-ai-prompt-context-tag-text", ref: textRef, children: children }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onClose, "aria-label": `Remove context: ${children}`, className: "ndl-ai-prompt-context-tag-close-button", children: (0, jsx_runtime_1.jsx)(XMarkIconOutline_1.XMarkIconOutline, { className: "ndl-ai-prompt-context-tag-icon" }) })] }) }), (0, jsx_runtime_1.jsx)(react_1.Tooltip.Content, { children: children })] }));
};
const Prompt = Object.assign(PromptComponent, {
ContextTag: ContextTag,
Select: AgentSelect,
});
exports.Prompt = Prompt;
//# sourceMappingURL=Prompt.js.map