@tabula/ui-ai-chat
Version:
Allows to embed UI for conversation with AI assistant
813 lines (765 loc) • 27.3 kB
JavaScript
// 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