@neo4j-ndl/react
Version:
React implementation of Neo4j Design System
165 lines • 9.89 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 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