UNPKG

@websolutespa/payload-plugin-bowl-llm

Version:

LLM plugin for Bowl PayloadCms plugin

566 lines (565 loc) 28.8 kB
'use client'; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { MinimalTemplate } from '@payloadcms/next/templates'; import { Button, Form, Modal, SelectInput, toast, useConfig, useModal, useTranslation, XIcon } from '@payloadcms/ui'; import React, { useCallback, useRef, useState } from 'react'; import { LlmKnowledgeBaseCreationMode } from '../../api/consts'; import './GenerateKbModal.scss'; const baseClass = 'generate-kb-options'; export const BtnGenerateKbVectorDb = (props)=>{ const { config } = useConfig(); const modalKey = 'generate-kb-modal'; const bulkUploadModalKey = 'bulk-upload-modal'; const { openModal, isModalOpen, closeAllModals } = useModal(); const [loading, setLoading] = React.useState(false); // tools const [toolOptions, setToolOptions] = React.useState([]); const [selectedTool, setSelectedTool] = React.useState(''); // generation mode const [generationModeOptions, setGenerationModeOptions] = React.useState([]); const [selectedGenerationMode, setSelectedGenerationMode] = React.useState(''); // llm apps const [llmApps, setllmApps] = useState([]); const [selectedLlmApp, setSelectedLlmApp] = React.useState(''); // bulk upload state const [bulkLlmApps, setBulkLlmApps] = useState([]); const [bulkSelectedApp, setBulkSelectedApp] = useState(''); const [bulkToolOptions, setBulkToolOptions] = useState([]); const [bulkSelectedTool, setBulkSelectedTool] = useState(''); const [bulkFiles, setBulkFiles] = useState([]); const [bulkUploading, setBulkUploading] = useState(false); const fileInputRef = useRef(null); const { t } = useTranslation(); const intervalRef = useRef(null); const { routes: { api: apiRoute } = {} } = config; React.useEffect(()=>{ const fetchOptions = async ()=>{ try { if (!selectedLlmApp) { return; } const url = `${process.env.PAYLOAD_PUBLIC_BASE_PATH || ''}/api/llm/appTools?llmApp=${selectedLlmApp}&onlyActive`; const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); if (response.ok) { // tools select options const toolsOpts = await response.json(); setToolOptions(toolsOpts); if (toolsOpts.length > 0) { setSelectedTool(''); } // generation mode select options const generationModeOpts = [ { label: t('general:generateKbAppOnly'), value: LlmKnowledgeBaseCreationMode.appOnly } ]; if (toolsOpts.length > 0) { generationModeOpts.unshift({ label: t('general:generateKbAppAndTools'), value: LlmKnowledgeBaseCreationMode.appAndTools }); generationModeOpts.push({ label: t('general:generateKbToolsOnly'), value: LlmKnowledgeBaseCreationMode.toolsOnly }); generationModeOpts.push({ label: t('general:generateKbSpecificTool'), value: LlmKnowledgeBaseCreationMode.specificTool }); } setGenerationModeOptions(generationModeOpts); setSelectedGenerationMode(generationModeOpts[0].value); } else { setToolOptions([ { label: (await response.json()).message, value: '' } ]); } } catch (error) { console.error('Error fetching data:', error); } }; fetchOptions(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [ selectedLlmApp ]); // Fetch tool options for bulk upload when app changes React.useEffect(()=>{ const fetchBulkToolOptions = async ()=>{ try { if (!bulkSelectedApp) { setBulkToolOptions([]); return; } const url = `${process.env.PAYLOAD_PUBLIC_BASE_PATH || ''}/api/llm/appTools?llmApp=${bulkSelectedApp}&onlyActive`; const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); if (response.ok) { const toolsOpts = await response.json(); setBulkToolOptions([ { label: t('general:kbFileNoToolOption'), value: '' }, ...toolsOpts ]); } } catch (error) { console.error('Error fetching bulk tool options:', error); } }; fetchBulkToolOptions(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [ bulkSelectedApp ]); const pollKbGeneration = useCallback(async (traceId)=>{ try { console.log('polling job ...', traceId); const httpResponse = await fetch(`${apiRoute}/llm/kb/${traceId}/poll`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); if (httpResponse.ok) { const response = await httpResponse.json(); console.log(response); if (!response.processing) { if (response.success) { toast.success(t('general:generateKbSuccess')); } else { const errors = response.errors || []; const toastMsg = /*#__PURE__*/ _jsxs("div", { children: [ /*#__PURE__*/ _jsx("div", { children: t('general:generateKbError') }), /*#__PURE__*/ _jsx("ul", { children: errors.map((error, idx)=>/*#__PURE__*/ _jsx("li", { children: /*#__PURE__*/ _jsxs("div", { children: [ error.job, ": ", error.taskStatus, " - ", error.error ] }) }, idx)) }) ] }); toast.error(toastMsg, {}); // { autoClose: false }); } setLoading(false); clearInterval(intervalRef.current); } } else { throw new Error(`Response status: ${httpResponse.status}`); } } catch (error) { clearInterval(intervalRef.current); console.error(`${t('general:generateKbError')}: ${error}`); toast.error(`${t('general:generateKbError')}: ${error}`, {}); // { autoClose: false }); setLoading(false); } }, [ apiRoute, t ]); const generateKb = async ()=>{ setLoading(true); try { const httpResponse = await fetch(`${apiRoute}/llm/kb`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, referrer: '', body: JSON.stringify({ appId: selectedLlmApp, mode: selectedGenerationMode, toolFunctionId: selectedTool }) }); if (httpResponse.ok) { const response = await httpResponse.json(); const intervalId = setInterval(()=>{ pollKbGeneration(response.traceId); }, 2000); intervalRef.current = intervalId; } else { throw new Error(`Response status: ${httpResponse.status}`); } } catch (error) { console.error(`${t('general:generateKbError')}: ${error}`); toast.error(`${t('general:generateKbError')}: ${error}`, {}); // { autoClose: false }); setLoading(false); } }; const onSubmit = async ()=>{ try { // generate kb with the selected options generateKb(); closeAllModals(); } catch (error) { console.error(t('general:generateKbError') + ': ', error); toast.error(t('general:generateKbError')); } }; const handleClick = async ()=>{ try { // get user apps for which it's possible to generate kb const httpResponse = await fetch(`${apiRoute}/llm/apps`, { method: 'GET' }); const response = await httpResponse.json(); if (httpResponse.ok) { const apps = response; setllmApps(apps.map((app)=>({ value: app.id, label: app.name }))); openModal(modalKey); } else { toast.error(response.message); return; } } catch (error) { console.error(t('general:generateKbError') + ': ', error); toast.error(t('general:generateKbError')); } }; // --- Bulk Upload handlers --- const handleBulkUploadClick = async ()=>{ try { const httpResponse = await fetch(`${apiRoute}/llm/apps`, { method: 'GET' }); const response = await httpResponse.json(); if (httpResponse.ok) { const apps = response; setBulkLlmApps(apps.map((app)=>({ value: app.id, label: app.name }))); setBulkSelectedApp(''); setBulkSelectedTool(''); setBulkFiles([]); openModal(bulkUploadModalKey); } else { toast.error(response.message); } } catch (error) { console.error(`${t('general:bulkUploadError')}: ${error}`); toast.error(t('general:bulkUploadError')); } }; const handleFileSelect = (e)=>{ if (e.target.files) { const newFiles = Array.from(e.target.files); setBulkFiles((prev)=>[ ...prev, ...newFiles ]); } // reset input so the same file can be selected again if (fileInputRef.current) { fileInputRef.current.value = ''; } }; const removeFile = (index)=>{ setBulkFiles((prev)=>prev.filter((_, i)=>i !== index)); }; const handleBulkUploadSubmit = async ()=>{ if (!bulkSelectedApp || bulkFiles.length === 0) return; setBulkUploading(true); try { const formData = new FormData(); formData.append('llmApp', bulkSelectedApp); if (bulkSelectedTool) { formData.append('appTool', bulkSelectedTool); } bulkFiles.forEach((file)=>{ formData.append('files', file, file.name); }); const httpResponse = await fetch(`${apiRoute}/llm/kb/bulk-upload`, { method: 'POST', body: formData }); if (httpResponse.ok || httpResponse.status === 207) { const result = await httpResponse.json(); if (result.errorCount === 0) { toast.success(`${t('general:bulkUploadSuccess')} (${result.successCount} file)`); } else { const toastMsg = /*#__PURE__*/ _jsxs("div", { children: [ /*#__PURE__*/ _jsx("div", { children: t('general:bulkUploadPartial') }), /*#__PURE__*/ _jsxs("div", { children: [ "✓ ", result.successCount, " | ✗ ", result.errorCount ] }), /*#__PURE__*/ _jsx("ul", { children: result.results.filter((r)=>!r.success).map((r, i)=>/*#__PURE__*/ _jsxs("li", { children: [ r.filename, ": ", r.error ] }, i)) }) ] }); toast.warning(toastMsg, {}); // { autoClose: false }); } closeAllModals(); // reload the page to show new files window.location.reload(); } else { throw new Error(`Response status: ${httpResponse.status}`); } } catch (error) { console.error(`${t('general:bulkUploadError')}: ${error}`); toast.error(`${t('general:bulkUploadError')}: ${error}`, {}); // { autoClose: false }); } finally{ setBulkUploading(false); } }; const handleSelectChange = (setter)=>{ return (e)=>setter(e.value); }; return /*#__PURE__*/ _jsxs("div", { children: [ /*#__PURE__*/ _jsx("div", { className: `${baseClass}__bulk-upload-header`, children: /*#__PURE__*/ _jsx(Button, { onClick: handleBulkUploadClick, buttonStyle: "secondary", disabled: bulkUploading, children: bulkUploading ? t('general:bulkUploadUploading') : t('general:bulkUpload') }) }), /*#__PURE__*/ _jsx(Button, { onClick: handleClick, disabled: loading, children: loading ? t('general:generatingKb') : t('general:generateKb') }), /*#__PURE__*/ _jsx(Modal, { slug: modalKey, className: `${baseClass}__modal`, children: isModalOpen(modalKey) && /*#__PURE__*/ _jsxs(MinimalTemplate, { children: [ /*#__PURE__*/ _jsxs("header", { className: `${baseClass}__header`, children: [ /*#__PURE__*/ _jsx("h3", { children: t('general:generateKbModalTitle') }), /*#__PURE__*/ _jsx(Button, { buttonStyle: "none", onClick: closeAllModals, children: /*#__PURE__*/ _jsx(XIcon, {}) }) ] }), /*#__PURE__*/ _jsx("main", { className: `${baseClass}__main`, children: /*#__PURE__*/ _jsxs(Form, { onSubmit: onSubmit, children: [ /*#__PURE__*/ _jsxs("div", { className: "render-fields", children: [ /*#__PURE__*/ _jsx(SelectInput, { label: t('general:selectLlmApp'), name: "llmApp", path: 'llmApp', options: llmApps, value: selectedLlmApp, onChange: (option)=>setSelectedLlmApp(String(Array.isArray(option) ? option.map((x)=>x.value).join(', ') : option.value)) }), /*#__PURE__*/ _jsx(SelectInput, { label: t('general:selectKbGenerationMode'), name: "generationMode", path: 'generationMode', options: generationModeOptions, value: selectedGenerationMode, onChange: (option)=>setSelectedGenerationMode(String(Array.isArray(option) ? option.map((x)=>x.value).join(', ') : option.value)) }), selectedGenerationMode === LlmKnowledgeBaseCreationMode.specificTool && /*#__PURE__*/ _jsx(SelectInput, { label: t('general:selectTool'), name: "tool", path: 'tool', options: toolOptions, value: selectedTool, onChange: (option)=>setSelectedTool(String(Array.isArray(option) ? option.map((x)=>x.value).join(', ') : option.value)) }) ] }), /*#__PURE__*/ _jsx("div", { className: `${baseClass}__foot`, children: /*#__PURE__*/ _jsx(Button, { type: "submit", children: t('general:submit') }) }) ] }) }) ] }) }), /*#__PURE__*/ _jsx(Modal, { slug: bulkUploadModalKey, className: `${baseClass}__modal`, children: isModalOpen(bulkUploadModalKey) && /*#__PURE__*/ _jsxs(MinimalTemplate, { children: [ /*#__PURE__*/ _jsxs("header", { className: `${baseClass}__header`, children: [ /*#__PURE__*/ _jsx("h3", { children: t('general:bulkUploadTitle') }), /*#__PURE__*/ _jsx(Button, { buttonStyle: "none", onClick: closeAllModals, children: /*#__PURE__*/ _jsx(XIcon, {}) }) ] }), /*#__PURE__*/ _jsxs("main", { className: `${baseClass}__main`, children: [ /*#__PURE__*/ _jsxs("div", { className: "render-fields", children: [ /*#__PURE__*/ _jsx(SelectInput, { label: t('general:selectLlmApp'), name: "bulkLlmApp", path: 'bulkLlmApp', options: bulkLlmApps, value: bulkSelectedApp, onChange: (option)=>setBulkSelectedApp(String(Array.isArray(option) ? option.map((x)=>x.value).join(', ') : option.value)) }), bulkToolOptions.length > 0 && /*#__PURE__*/ _jsx(SelectInput, { label: t('general:selectTool'), name: "bulkTool", path: 'bulkTool', options: bulkToolOptions, value: bulkSelectedTool, onChange: (option)=>setBulkSelectedTool(String(Array.isArray(option) ? option.map((x)=>x.value).join(', ') : option.value)) }), /*#__PURE__*/ _jsxs("div", { className: `${baseClass}__dropzone`, children: [ /*#__PURE__*/ _jsx("label", { className: `${baseClass}__dropzone-label`, children: t('general:selectFiles') }), /*#__PURE__*/ _jsxs("div", { className: `${baseClass}__dropzone-area`, onClick: ()=>fileInputRef.current?.click(), onDragOver: (e)=>{ e.preventDefault(); e.currentTarget.classList.add(`${baseClass}__dropzone-area--active`); }, onDragLeave: (e)=>{ e.currentTarget.classList.remove(`${baseClass}__dropzone-area--active`); }, onDrop: (e)=>{ e.preventDefault(); e.currentTarget.classList.remove(`${baseClass}__dropzone-area--active`); if (e.dataTransfer.files) { const newFiles = Array.from(e.dataTransfer.files); setBulkFiles((prev)=>[ ...prev, ...newFiles ]); } }, children: [ /*#__PURE__*/ _jsx("span", { className: `${baseClass}__dropzone-icon`, children: "📁" }), /*#__PURE__*/ _jsx("span", { className: `${baseClass}__dropzone-text`, children: bulkFiles.length === 0 ? t('general:noFilesSelected') : `${bulkFiles.length} ${t('general:filesSelected')}` }) ] }), /*#__PURE__*/ _jsx("input", { ref: fileInputRef, type: "file", multiple: true, style: { display: 'none' }, onChange: handleFileSelect }) ] }), bulkFiles.length > 0 && /*#__PURE__*/ _jsx("div", { className: `${baseClass}__file-list`, children: bulkFiles.map((file, index)=>/*#__PURE__*/ _jsxs("div", { className: `${baseClass}__file-item`, children: [ /*#__PURE__*/ _jsx("span", { className: `${baseClass}__file-name`, children: file.name }), /*#__PURE__*/ _jsxs("span", { className: `${baseClass}__file-size`, children: [ (file.size / 1024).toFixed(1), " KB" ] }), /*#__PURE__*/ _jsx(Button, { buttonStyle: "none", className: `${baseClass}__file-remove`, onClick: ()=>removeFile(index), children: /*#__PURE__*/ _jsx(XIcon, {}) }) ] }, `${file.name}-${index}`)) }) ] }), /*#__PURE__*/ _jsx("div", { className: `${baseClass}__foot`, children: /*#__PURE__*/ _jsx(Button, { onClick: handleBulkUploadSubmit, disabled: !bulkSelectedApp || bulkFiles.length === 0 || bulkUploading, children: bulkUploading ? t('general:bulkUploadUploading') : `${t('general:bulkUpload')} (${bulkFiles.length})` }) }) ] }) ] }) }) ] }); }; //# sourceMappingURL=BtnGenerateKbVectorDb.js.map