UNPKG

@tabula/ui-ai-chat

Version:

Allows to embed UI for conversation with AI assistant

813 lines (765 loc) 27.3 kB
// src/UiAiChat/UiAiChat.tsx import { forwardRef as forwardRef5, useRef as useRef5 } from "react"; import clsx8 from "clsx"; // src/UiAiChat/UiAiChat.css.ts var conversation = "tabula_ui_ai_chat__dshu581"; var root = "tabula_ui_ai_chat__dshu580"; var suggestions = "tabula_ui_ai_chat__dshu582"; // src/Conversation/Conversation.tsx import { forwardRef as forwardRef2 } from "react"; import { clsx as clsx4 } from "clsx/lite"; // src/Conversation/Conversation.css.ts var placeholder = "tabula_ui_ai_chat__m5jrp61"; var requests = "tabula_ui_ai_chat__m5jrp62"; var root2 = "tabula_ui_ai_chat__m5jrp60"; // src/RequestView/assets/ai.svg?svgr import * as React from "react"; import { memo } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; var SvgAi = (props) => /* @__PURE__ */ jsxs("svg", { width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", ...props, children: [ /* @__PURE__ */ jsx("rect", { width: 24, height: 24, rx: 12, fill: "currentColor" }), /* @__PURE__ */ jsx("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M11.236 8.569A.333.333 0 0 0 11 8a1.667 1.667 0 0 1-1.667-1.667.333.333 0 0 0-.666 0A1.667 1.667 0 0 1 7 8a.333.333 0 1 0 0 .667 1.667 1.667 0 0 1 1.667 1.666.333.333 0 1 0 .666 0A1.667 1.667 0 0 1 11 8.667a.333.333 0 0 0 .236-.098Zm5.916 4.869a.619.619 0 0 0-.438-1.057 3.095 3.095 0 0 1-3.095-3.095.619.619 0 1 0-1.238 0 3.095 3.095 0 0 1-3.095 3.095.619.619 0 1 0 0 1.238 3.095 3.095 0 0 1 3.095 3.095.619.619 0 1 0 1.238 0 3.095 3.095 0 0 1 3.095-3.095.62.62 0 0 0 .438-.181Z", fill: "#fff" }) ] }); var Memo = memo(SvgAi); // src/RequestView/RequestView.css.ts var answer = "tabula_ui_ai_chat__1tiibt92"; var answerBody = "tabula_ui_ai_chat__1tiibt93"; var answerIcon = "tabula_ui_ai_chat__1tiibt94"; var prompt = "tabula_ui_ai_chat__1tiibt91"; var root3 = "tabula_ui_ai_chat__1tiibt90"; // src/AnswerView/AnswerView.tsx import { useEffect, useMemo } from "react"; import { clsx } from "clsx/lite"; // src/AnswerView/AnswerView.css.ts var action = "tabula_ui_ai_chat__125216z4"; var actions = "tabula_ui_ai_chat__125216z3"; var placeholder2 = "tabula_ui_ai_chat__125216z0"; var tableContainer = "tabula_ui_ai_chat__125216z2"; var tableScroll = "tabula_ui_ai_chat__125216z1"; // src/AnswerView/AnswerView.helpers.ts import DOMPurify from "dompurify"; import { xxHash32 } from "js-xxhash"; import { Marked } from "marked"; window.UI_AI_CHAT_TABLE_ACTIONS = window.UI_AI_CHAT_TABLE_ACTIONS || /* @__PURE__ */ new Map(); function hashOf(table) { return xxHash32(JSON.stringify(table)).toString(16); } function registerActions(id, table, actions2) { const prefix = `${id}__${hashOf(table)}`; const vtable = []; for (const [idx, { label, action: action2 }] of actions2.entries()) { const key = `${prefix}__${idx}`; const call = `window.UI_AI_CHAT_TABLE_ACTIONS.get('${key}')?.()`; window.UI_AI_CHAT_TABLE_ACTIONS.set(key, () => { action2(table); }); vtable.push([label, call]); } return vtable; } function unregisterActions(id) { for (const [key] of window.UI_AI_CHAT_TABLE_ACTIONS.entries()) { if (!key.startsWith(`${id}__`)) { return; } window.UI_AI_CHAT_TABLE_ACTIONS.delete(key); } } function renderActions(id, table, actions2) { const vtable = registerActions(id, table, actions2); const items2 = vtable.map( ([label, call]) => ` <button class="${action}" onclick="${call}" type="button">${label}</button> ` ).join(""); return `<div class="${actions}">${items2}</div>`; } function renderHead(data) { const cells = data.map((it) => `<th>${it}</th>`).join(""); return `<thead><tr>${cells}</tr></thead>`; } function renderBody(data) { const rows = data.map((row) => { const cells = row.map((it) => `<td>${it}</td>`).join(""); return `<tr>${cells}</tr>`; }).join(""); return `<tbody>${rows}</tbody>`; } function renderTable(table) { return ` <div class="${tableScroll}"> <div class="${tableContainer}"> <table> ${renderHead(table.header)} ${renderBody(table.rows)} </table> </div> </div> `; } function render(id, input4, tableActions) { const parser = new Marked({ async: false, renderer: { table({ header, rows }) { const table = { header: header.map((h) => h.text), rows: rows.map((row) => row.map((v) => v.text)) }; return `${renderTable(table)}${renderActions(id, table, tableActions)}`; } } }); const output = parser.parse(input4, { async: false }); return DOMPurify.sanitize(output); } var allowedOnClickContent = /^window\.UI_AI_CHAT_TABLE_ACTIONS\.get\('\d+__\w+__\d+'\)\?\.\(\)$/; DOMPurify.addHook("uponSanitizeAttribute", (_, data) => { if (data.attrName === "onclick" && allowedOnClickContent.test(data.attrValue)) { data.forceKeepAttr = true; } }); // src/AnswerView/AnswerView.tsx import { jsx as jsx2 } from "react/jsx-runtime"; function AnswerView({ className, pendingPlaceholder = "Analyzing...", request, tableActions }) { const content = useMemo(() => { if (request.id == null) { return null; } return render(request.id, request.answer, tableActions); }, [request.id, request.answer, tableActions]); useEffect( () => () => { if (request.id == null) { return; } unregisterActions(request.id); }, [request.id] ); if (content == null) { return /* @__PURE__ */ jsx2("div", { className: clsx(placeholder2, className), children: pendingPlaceholder }); } return ( // eslint-disable-next-line react/no-danger /* @__PURE__ */ jsx2("div", { className, dangerouslySetInnerHTML: { __html: content } }) ); } if (import.meta.env.DEV) { AnswerView.displayName = "ui-ai-chat(AnswerView)"; } // src/PromptView/PromptView.tsx import { useCallback as useCallback2, useState } from "react"; import { clsx as clsx3 } from "clsx/lite"; // src/PromptView/PromptView.css.ts var controls = "tabula_ui_ai_chat__xlpk9h5"; var edit = "tabula_ui_ai_chat__xlpk9h3"; var input = "tabula_ui_ai_chat__xlpk9h4"; var root4 = "tabula_ui_ai_chat__xlpk9h0"; var startEdit = "tabula_ui_ai_chat__xlpk9h2"; var view = "tabula_ui_ai_chat__xlpk9h1"; // src/PromptView/PromptView.Edit.tsx import { useEffect as useEffect2, useRef } from "react"; import { UiButton24 } from "@tabula/ui-button"; // src/PromptView/assets/checked.svg?svgr import * as React2 from "react"; import { memo as memo2 } from "react"; import { jsx as jsx3 } from "react/jsx-runtime"; var SvgChecked = (props) => /* @__PURE__ */ jsx3("svg", { width: 16, height: 16, viewBox: "0 0 16 16", xmlns: "http://www.w3.org/2000/svg", fill: "none", ...props, children: /* @__PURE__ */ jsx3("path", { clipRule: "evenodd", fill: "currentColor", fillRule: "evenodd", d: "M12.48 4.424a.75.75 0 0 1 .096 1.056l-5 6a.75.75 0 0 1-1.106.05l-3-3a.75.75 0 0 1 1.06-1.06l2.42 2.419 4.474-5.37a.75.75 0 0 1 1.056-.095Z" }) }); var Memo2 = memo2(SvgChecked); // src/TextArea/TextArea.tsx import { forwardRef, useCallback } from "react"; import clsx2 from "clsx"; import BaseTextArea from "react-textarea-autosize"; // src/TextArea/TextArea.css.ts var root5 = "tabula_ui_ai_chat__ctqa1m0"; // src/TextArea/TextArea.tsx import { jsx as jsx4 } from "react/jsx-runtime"; var MIN_VISIBLE_ROWS_COUNT = 1; var MAX_VISIBLE_ROWS_COUNT = 10; var TextArea = forwardRef( ({ className, maxLength, onChange, onEnter, onEscape, placeholder: placeholder3, value }, ref) => { const handleKeyDown = useCallback( (event) => { if (event.shiftKey) { return; } if (event.key === "Enter" && onEnter != null) { event.preventDefault(); onEnter(); } else if (event.key === "Escape" && onEscape != null) { event.preventDefault(); onEscape(); } }, [onEnter, onEscape] ); const handleChange = useCallback( (event) => { onChange(event.target.value); }, [onChange] ); return /* @__PURE__ */ jsx4( BaseTextArea, { className: clsx2(root5, className, "input-root"), maxLength, maxRows: MAX_VISIBLE_ROWS_COUNT, minRows: MIN_VISIBLE_ROWS_COUNT, onChange: handleChange, onKeyDown: handleKeyDown, placeholder: placeholder3, ref, value } ); } ); if (import.meta.env.DEV) { TextArea.displayName = "ui-ai-chat(TextArea)"; } // src/PromptView/PromptView.Edit.tsx import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime"; function Edit({ onApply, maxLength, onChange, onCancel, value }) { const ref = useRef(null); useEffect2(() => { ref.current?.select(); }, []); return /* @__PURE__ */ jsxs2("div", { className: edit, children: [ /* @__PURE__ */ jsx5( TextArea, { className: input, maxLength, onChange, onEnter: onApply, onEscape: onCancel, ref, value } ), /* @__PURE__ */ jsxs2("div", { className: controls, children: [ /* @__PURE__ */ jsx5(UiButton24, { onClick: onCancel, variant: "cancel", children: "Cancel" }), /* @__PURE__ */ jsx5(UiButton24, { icon: Memo2, onClick: onApply, variant: "primary", children: "Apply" }) ] }) ] }); } if (import.meta.env.DEV) { Edit.displayName = "ui-ai-chat(PromptView.Edit)"; } // src/PromptView/assets/edit.svg?svgr import * as React3 from "react"; import { memo as memo3 } from "react"; import { jsx as jsx6 } from "react/jsx-runtime"; var SvgEdit = (props) => /* @__PURE__ */ jsx6("svg", { width: 16, height: 16, fill: "none", xmlns: "http://www.w3.org/2000/svg", ...props, children: /* @__PURE__ */ jsx6("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M7.646 4.353 8 4l.354-.354 1.439-1.439a1 1 0 0 1 1.414 0l2.586 2.586a1 1 0 0 1 0 1.414l-1.44 1.44L12 8l-.354.353-5.353 5.35a1 1 0 0 1-.707.293H3a1 1 0 0 1-1-1v-2.582a1 1 0 0 1 .293-.707l5.353-5.354Zm4 2.586 1.44-1.44L10.5 2.915l-1.44 1.44 2.586 2.585ZM8.354 5.061l2.585 2.585-5.353 5.35H3v-2.582l5.354-5.353Z", fill: "currentColor" }) }); var Memo3 = memo3(SvgEdit); // src/PromptView/PromptView.Read.tsx import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime"; function Read({ isEditable, onStartEdit, prompt: prompt2 }) { return /* @__PURE__ */ jsxs3("div", { className: view, children: [ isEditable && /* @__PURE__ */ jsx7("button", { className: startEdit, onClick: onStartEdit, title: "Edit", type: "button", children: /* @__PURE__ */ jsx7(Memo3, {}) }), prompt2 ] }); } if (import.meta.env.DEV) { Read.displayName = "ui-ai-chat(PromptView.Read)"; } // src/PromptView/PromptView.tsx import { jsx as jsx8 } from "react/jsx-runtime"; function PromptView({ className, id, isEditable, maxLength, onResend, prompt: prompt2 }) { const [isEditing, setIsEditing] = useState(false); const [editInput, setEditInput] = useState(prompt2); const handleCancel = useCallback2(() => { setIsEditing(false); setEditInput(prompt2); }, [prompt2]); const handleApply = useCallback2(() => { setIsEditing(false); if (id != null) { onResend(id, editInput); } }, [editInput, id, onResend]); const handleStartEdit = useCallback2(() => { setIsEditing(true); }, []); return /* @__PURE__ */ jsx8("div", { className: clsx3(root4, className), children: isEditing ? /* @__PURE__ */ jsx8( Edit, { maxLength, onApply: handleApply, onCancel: handleCancel, onChange: setEditInput, value: editInput } ) : /* @__PURE__ */ jsx8(Read, { isEditable: isEditable && id != null, onStartEdit: handleStartEdit, prompt: prompt2 }) }); } if (import.meta.env.DEV) { PromptView.displayName = "ui-ai-chat(PromptView)"; } // src/RequestView/RequestView.tsx import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime"; function RequestView({ editDisabled, maxPromptLength, onResend, pendingPlaceholder, request, tableActions }) { return /* @__PURE__ */ jsxs4("div", { className: root3, children: [ /* @__PURE__ */ jsx9( PromptView, { className: prompt, id: request.id, isEditable: !editDisabled, maxLength: maxPromptLength, onResend, prompt: request.prompt } ), /* @__PURE__ */ jsxs4("div", { className: answer, children: [ /* @__PURE__ */ jsx9(Memo, { className: answerIcon }), /* @__PURE__ */ jsx9( AnswerView, { className: answerBody, pendingPlaceholder, request, tableActions } ) ] }) ] }); } if (import.meta.env.DEV) { RequestView.displayName = "ui-ai-chat(RequestView)"; } // src/Conversation/Conversation.hooks.ts import { useImperativeHandle, useRef as useRef2 } from "react"; function useController(ref) { const conversationRef = useRef2(null); useImperativeHandle( ref, () => ({ get hasTextArea() { const { current: conversation2 } = conversationRef; if (conversation2 == null) { return false; } return conversation2.querySelector("textarea") != null; }, scrollToTop: (behavior) => { const { current: conversation2 } = conversationRef; if (conversation2 == null) { return; } conversation2.scrollTo({ left: 0, top: 0, behavior }); }, scrollToBottom: (behavior) => { const { current: conversation2 } = conversationRef; if (conversation2 == null) { return; } conversation2.scrollTo({ left: 0, top: conversation2.scrollHeight, behavior }); } }), [] ); return conversationRef; } // src/Conversation/Conversation.tsx import { jsx as jsx10 } from "react/jsx-runtime"; var Conversation = forwardRef2( ({ className, conversation: conversation2, empty, isPending, maxPromptLength, onResend, pendingPlaceholder, tableActions }, ref) => { const conversationRef = useController(ref); const isEmpty = conversation2.length === 0; return /* @__PURE__ */ jsx10("div", { className: clsx4(root2, className), ref: conversationRef, children: /* @__PURE__ */ jsx10("div", { className: isEmpty ? placeholder : requests, children: isEmpty ? empty?.() : conversation2.map((request) => /* @__PURE__ */ jsx10( RequestView, { editDisabled: isPending, maxPromptLength, onResend, pendingPlaceholder, request, tableActions }, request.id ?? "pending-request" )) }) }); } ); if (import.meta.env.DEV) { Conversation.displayName = "ui-ai-chat(Conversation)"; } // src/Prompt/Prompt.tsx import { forwardRef as forwardRef4 } from "react"; // src/Prompt/Prompt.css.ts var context = "tabula_ui_ai_chat__7nto7o1"; var input2 = "tabula_ui_ai_chat__7nto7o2"; var root6 = "tabula_ui_ai_chat__7nto7o0"; // src/PromptContext/PromptContext.tsx import { clsx as clsx5 } from "clsx/lite"; // src/PromptContext/assets/ref.svg?svgr import * as React4 from "react"; import { memo as memo4 } from "react"; import { jsx as jsx11 } from "react/jsx-runtime"; var SvgRef = (props) => /* @__PURE__ */ jsx11("svg", { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", ...props, children: /* @__PURE__ */ jsx11("path", { d: "M3 4v1a3 3 0 0 0 3 3h8m0 0-4-4m4 4-4 4", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round" }) }); var Memo4 = memo4(SvgRef); // src/PromptContext/assets/reset.svg?svgr import * as React5 from "react"; import { memo as memo5 } from "react"; import { jsx as jsx12 } from "react/jsx-runtime"; var SvgReset = (props) => /* @__PURE__ */ jsx12("svg", { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", ...props, children: /* @__PURE__ */ jsx12("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M3.146 3.146a.5.5 0 0 1 .708 0L8 7.293l4.146-4.147a.5.5 0 0 1 .708.708L8.707 8l4.147 4.146a.5.5 0 0 1-.708.708L8 8.707l-4.146 4.147a.5.5 0 0 1-.708-.708L7.293 8 3.146 3.854a.5.5 0 0 1 0-.708Z", fill: "currentColor" }) }); var Memo5 = memo5(SvgReset); // src/PromptContext/PromptContext.css.ts var context2 = "tabula_ui_ai_chat__kbspkq3"; var icon = "tabula_ui_ai_chat__kbspkq1"; var reset = "tabula_ui_ai_chat__kbspkq2"; var root7 = "tabula_ui_ai_chat__kbspkq0"; // src/PromptContext/PromptContext.tsx import { jsx as jsx13, jsxs as jsxs5 } from "react/jsx-runtime"; function PromptContext({ className, onClear, value }) { return /* @__PURE__ */ jsxs5("div", { className: clsx5(root7, className), children: [ /* @__PURE__ */ jsx13(Memo4, { className: icon }), /* @__PURE__ */ jsx13("div", { className: context2, children: value }), onClear != null && /* @__PURE__ */ jsx13("button", { className: reset, onClick: onClear, title: "Clear context", type: "button", children: /* @__PURE__ */ jsx13(Memo5, {}) }) ] }); } if (import.meta.env.DEV) { PromptContext.displayName = "ui-ai-chat(PromptContext)"; } // src/PromptInput/PromptInput.tsx import { forwardRef as forwardRef3 } from "react"; import clsx6 from "clsx"; // src/PromptInput/assets/send.svg?svgr import * as React6 from "react"; import { memo as memo6 } from "react"; import { jsx as jsx14 } from "react/jsx-runtime"; var SvgSend = (props) => /* @__PURE__ */ jsx14("svg", { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", ...props, children: /* @__PURE__ */ jsx14("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M7.826 1.71a1 1 0 0 1 1.223 0l4.063 3.142a1 1 0 1 1-1.224 1.582l-2.45-1.896V13.5a1 1 0 1 1-2 0V4.538L4.987 6.434a1 1 0 0 1-1.224-1.582l4.063-3.143Z", fill: "currentColor" }) }); var Memo6 = memo6(SvgSend); // src/PromptInput/assets/sending.svg?svgr import * as React7 from "react"; import { memo as memo7 } from "react"; import { jsx as jsx15 } from "react/jsx-runtime"; var SvgSending = (props) => /* @__PURE__ */ jsx15("svg", { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", ...props, children: /* @__PURE__ */ jsx15("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M8 14a6 6 0 1 1 6-6h1a7 7 0 1 0-7 7v-1Z", fill: "currentColor", children: /* @__PURE__ */ jsx15("animateTransform", { attributeName: "transform", attributeType: "XML", type: "rotate", from: "0 8 8", to: "360 8 8", dur: "700ms", repeatCount: "indefinite" }) }) }); var Memo7 = memo7(SvgSending); // src/PromptInput/PromptInput.css.ts var input3 = "tabula_ui_ai_chat__99e4gi2"; var isSending = "tabula_ui_ai_chat__99e4gi1"; var root8 = "tabula_ui_ai_chat__99e4gi0"; var send = "tabula_ui_ai_chat__99e4gi3"; // src/PromptInput/PromptInput.hooks.ts import { useImperativeHandle as useImperativeHandle2, useRef as useRef3 } from "react"; function useController2(ref) { const inputRef = useRef3(null); useImperativeHandle2( ref, () => ({ focus: () => { inputRef.current?.focus(); }, blur: () => { inputRef.current?.blur(); }, select: () => { inputRef.current?.select(); } }), [] ); return inputRef; } // src/PromptInput/PromptInput.tsx import { jsx as jsx16, jsxs as jsxs6 } from "react/jsx-runtime"; var PromptInput = forwardRef3( ({ className, isSendable, isSending: isSending2, maxLength, onChange, onSend, placeholder: placeholder3, value }, ref) => { const inputRef = useController2(ref); const isAllowToSend = isSendable && !isSending2; return /* @__PURE__ */ jsxs6("div", { className: clsx6(root8, isSending2 && isSending, className), children: [ /* @__PURE__ */ jsx16( TextArea, { className: input3, maxLength, onChange, onEnter: onSend, placeholder: placeholder3, ref: inputRef, value } ), /* @__PURE__ */ jsx16( "button", { className: send, disabled: !isAllowToSend, onClick: onSend, title: "Send", type: "button", children: isSending2 ? /* @__PURE__ */ jsx16(Memo7, {}) : /* @__PURE__ */ jsx16(Memo6, {}) } ) ] }); } ); if (import.meta.env.DEV) { PromptInput.displayName = "ui-ai-chat(PromptInput)"; } // src/Prompt/Prompt.tsx import { jsx as jsx17, jsxs as jsxs7 } from "react/jsx-runtime"; var Prompt = forwardRef4( ({ context: context3, isSendable, isSending: isSending2, maxLength, onChangePrompt, onClearContext, onSend, placeholder: placeholder3, prompt: prompt2 }, ref) => /* @__PURE__ */ jsxs7("div", { className: root6, children: [ context3 != null && context3.trim().length > 0 && /* @__PURE__ */ jsx17(PromptContext, { className: context, onClear: onClearContext, value: context3 }), /* @__PURE__ */ jsx17( PromptInput, { className: input2, isSendable, isSending: isSending2, maxLength, onChange: onChangePrompt, onSend, placeholder: placeholder3, ref, value: prompt2 } ) ] }) ); if (import.meta.env.DEV) { Prompt.displayName = "ui-ai-chat(Prompt)"; } // src/Suggestions/Suggestions.tsx import { clsx as clsx7 } from "clsx/lite"; // src/Suggestions/Suggestions.css.ts var item = "tabula_ui_ai_chat__y8z1ve2"; var items = "tabula_ui_ai_chat__y8z1ve1"; var root9 = "tabula_ui_ai_chat__y8z1ve0"; // src/Suggestions/Suggestions.Item.tsx import { useCallback as useCallback3 } from "react"; import { jsx as jsx18 } from "react/jsx-runtime"; function Item({ onSuggest, suggestion }) { const handleClick = useCallback3(() => { onSuggest(suggestion); }, [onSuggest, suggestion]); return /* @__PURE__ */ jsx18("button", { className: item, onClick: handleClick, type: "button", children: suggestion }); } if (import.meta.env.DEV) { Item.displayName = "ui-ai-chat(Suggestions.Item)"; } // src/Suggestions/Suggestions.tsx import { jsx as jsx19 } from "react/jsx-runtime"; function Suggestions({ className, suggestions: suggestions2, onSuggest }) { return /* @__PURE__ */ jsx19("div", { className: clsx7(root9, className), children: /* @__PURE__ */ jsx19("div", { className: items, children: suggestions2.map((it, idx) => ( // eslint-disable-next-line react/no-array-index-key /* @__PURE__ */ jsx19(Item, { onSuggest, suggestion: it }, idx) )) }) }); } if (import.meta.env.DEV) { Suggestions.displayName = "ui-ai-chat(Suggestions)"; } // src/UiAiChat/hooks/useAutoScroll.ts import { useEffect as useEffect3, useRef as useRef4 } from "react"; function shouldToScroll(previous, current) { if (current == null) { return false; } if (previous == null) { return true; } return previous.id !== current.id || previous.prompt !== current.prompt || previous.answer !== current.answer; } function useAutoScroll(conversation2, conversationRef) { const lastRef = useRef4(null); useEffect3(() => { if (conversationRef.current == null) { return; } const { current: previous } = lastRef; const current = conversation2.at(-1) ?? null; if (shouldToScroll(previous, current) && !conversationRef.current.hasTextArea) { conversationRef.current.scrollToBottom(); } lastRef.current = current; }, [conversation2, conversationRef]); useEffect3(() => { if (conversation2.length > 0) { conversationRef.current?.scrollToBottom(); } lastRef.current = conversation2.at(-1) ?? null; }, []); } // src/UiAiChat/hooks/useController.ts import { useImperativeHandle as useImperativeHandle3 } from "react"; function useController3({ ref, conversationRef, promptInputRef }) { useImperativeHandle3( ref, () => ({ prompt: { focus: () => { promptInputRef.current?.focus(); }, blur: () => { promptInputRef.current?.blur(); }, select: () => { promptInputRef.current?.select(); } }, conversation: { scrollToTop: (behavior) => { conversationRef.current?.scrollToTop(behavior); }, scrollToBottom: (behavior) => { conversationRef.current?.scrollToBottom(behavior); } } }), [conversationRef, promptInputRef] ); } // src/UiAiChat/hooks/usePrompt.ts import { useCallback as useCallback4, useState as useState2 } from "react"; function usePrompt({ conversation: conversation2, promptInputRef, onSend }) { const [prompt2, setPrompt] = useState2(""); const isPending = conversation2.some((it) => it.id == null); const isSendable = !isPending && prompt2.trim().length > 0; const handleSend = useCallback4(() => { if (isPending || !isSendable) { return; } onSend(prompt2); setPrompt(""); promptInputRef.current?.focus(); }, [isPending, isSendable, onSend, prompt2, promptInputRef]); const handleSuggest = useCallback4( (suggestion) => { setPrompt(suggestion); promptInputRef.current?.focus(); }, [promptInputRef] ); return { onSend: handleSend, isPending, isSendable, prompt: prompt2, onChangePrompt: setPrompt, onSuggest: handleSuggest }; } // src/UiAiChat/UiAiChat.tsx import { jsx as jsx20, jsxs as jsxs8 } from "react/jsx-runtime"; var UiAiChat = forwardRef5( ({ className, context: context3, conversation: conversation2, empty, maxPromptLength, onClearContext, onResend, onSend, pendingPlaceholder, placeholder: placeholder3, suggestions: suggestions2 = [], tableActions = [] }, ref) => { const conversationRef = useRef5(null); const promptInputRef = useRef5(null); useController3({ ref, conversationRef, promptInputRef }); useAutoScroll(conversation2, conversationRef); const { isPending, isSendable, onChangePrompt, onSend: handleSend, onSuggest, prompt: prompt2 } = usePrompt({ conversation: conversation2, onSend, promptInputRef }); return /* @__PURE__ */ jsxs8("div", { className: clsx8(root, className), children: [ /* @__PURE__ */ jsx20( Conversation, { className: conversation, conversation: conversation2, empty, isPending, maxPromptLength, onResend, pendingPlaceholder, ref: conversationRef, tableActions } ), suggestions2.length > 0 && /* @__PURE__ */ jsx20( Suggestions, { className: suggestions, onSuggest, suggestions: suggestions2 } ), /* @__PURE__ */ jsx20( Prompt, { context: context3, isSendable, isSending: isPending, maxLength: maxPromptLength, onChangePrompt, onClearContext, onSend: handleSend, placeholder: placeholder3 ?? "Ask GPT", prompt: prompt2, ref: promptInputRef } ) ] }); } ); if (import.meta.env.DEV) { UiAiChat.displayName = "ui-ai-chat(UiAiChat)"; } export { UiAiChat }; // post-build: auto import bundled styles import "./index.css"; //# sourceMappingURL=index.js.map