Merge pull request #1820 from ManishMadan2882/main

Chore:  Frontend Refinements
This commit is contained in:
Alex
2025-05-29 00:53:59 +01:00
committed by GitHub
12 changed files with 603 additions and 322 deletions

View File

@@ -30,7 +30,17 @@ export default function ContextMenu({
offset = { x: 0, y: 8 },
}: ContextMenuProps) {
const menuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (isOpen && menuRef.current) {
const positionStyle = getMenuPosition();
if (menuRef.current) {
Object.assign(menuRef.current.style, {
top: positionStyle.top,
left: positionStyle.left,
});
}
}
}, [isOpen]);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
@@ -61,20 +71,45 @@ export default function ContextMenu({
let top = rect.bottom + scrollY + offset.y;
let left = rect.right + scrollX + offset.x;
// Get menu dimensions (need ref to be available)
const menuWidth = menuRef.current?.offsetWidth || 144; // Default min-width
const menuHeight = menuRef.current?.offsetHeight || 0;
// Get viewport dimensions
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// Adjust position based on specified position
switch (position) {
case 'bottom-left':
left = rect.left + scrollX - offset.x;
break;
case 'top-right':
top = rect.top + scrollY - offset.y;
top = rect.top + scrollY - offset.y - menuHeight;
break;
case 'top-left':
top = rect.top + scrollY - offset.y;
top = rect.top + scrollY - offset.y - menuHeight;
left = rect.left + scrollX - offset.x;
break;
// bottom-right is default
}
if (left + menuWidth > viewportWidth) {
left = Math.max(5, viewportWidth - menuWidth - 5);
}
if (left < 5) {
left = 5;
}
if (top + menuHeight > viewportHeight + scrollY) {
top = rect.top + scrollY - menuHeight - offset.y;
}
if (top < scrollY + 5) {
top = rect.bottom + scrollY + offset.y;
}
return {
position: 'fixed',
top: `${top}px`,
@@ -90,7 +125,7 @@ export default function ContextMenu({
onClick={(e) => e.stopPropagation()}
>
<div
className="flex w-32 flex-col rounded-xl bg-lotion text-sm shadow-xl dark:bg-charleston-green-2 md:w-36"
className="flex flex-col rounded-xl bg-lotion text-sm shadow-xl dark:bg-charleston-green-2"
style={{ minWidth: '144px' }}
>
{options.map((option, index) => (
@@ -109,7 +144,7 @@ export default function ContextMenu({
} `}
>
{option.icon && (
<div className="flex w-4 justify-center">
<div className="flex w-4 min-w-4 flex-shrink-0 justify-center">
<img
width={option.iconWidth || 16}
height={option.iconHeight || 16}
@@ -119,7 +154,7 @@ export default function ContextMenu({
/>
</div>
)}
<span>{option.label}</span>
<span className="hyphens-auto break-words">{option.label}</span>
</button>
))}
</div>

View File

@@ -60,7 +60,11 @@ const Input = ({
{placeholder && (
<label
htmlFor={id}
className={`absolute select-none ${hasValue ? '-top-2.5 left-3 text-xs' : ''} px-2 transition-all peer-placeholder-shown:left-3 peer-placeholder-shown:top-2.5 peer-placeholder-shown:${textSizeStyles[textSize]} pointer-events-none cursor-none text-gray-4000 peer-focus:-top-2.5 peer-focus:left-3 peer-focus:text-xs dark:text-gray-400 ${labelBgClassName}`}
className={`absolute select-none ${
hasValue ? '-top-2.5 left-3 text-xs' : ''
} px-2 transition-all peer-placeholder-shown:left-3 peer-placeholder-shown:top-2.5 peer-placeholder-shown:${
textSizeStyles[textSize]
} pointer-events-none cursor-none text-gray-4000 peer-focus:-top-2.5 peer-focus:left-3 peer-focus:text-xs dark:text-gray-400 ${labelBgClassName} max-w-[calc(100%-24px)] overflow-hidden text-ellipsis whitespace-nowrap`}
>
{placeholder}
{required && (

View File

@@ -35,21 +35,21 @@ export function useOutsideAlerter<T extends HTMLElement>(
export function useMediaQuery() {
const mobileQuery = '(max-width: 768px)';
const darkModeQuery = '(prefers-color-scheme: dark)'; // Detect dark mode
const desktopQuery = '(min-width: 960px)';
const tabletQuery = '(max-width: 1024px)'; // Tablet breakpoint at 1024px
const desktopQuery = '(min-width: 1025px)'; // Desktop starts after tablet
const [isMobile, setIsMobile] = useState(false);
const [isTablet, setIsTablet] = useState(false);
const [isDesktop, setIsDesktop] = useState(false);
const [isDarkMode, setIsDarkMode] = useState(false);
useEffect(() => {
const mobileMedia = window.matchMedia(mobileQuery);
const tabletMedia = window.matchMedia(tabletQuery);
const desktopMedia = window.matchMedia(desktopQuery);
const darkModeMedia = window.matchMedia(darkModeQuery);
const updateMediaQueries = () => {
setIsMobile(mobileMedia.matches);
setIsTablet(tabletMedia.matches && !mobileMedia.matches); // Tablet but not mobile
setIsDesktop(desktopMedia.matches);
setIsDarkMode(darkModeMedia.matches);
};
updateMediaQueries();
@@ -60,9 +60,9 @@ export function useMediaQuery() {
return () => {
window.removeEventListener('resize', listener);
};
}, [mobileQuery, desktopQuery, darkModeQuery]);
}, [mobileQuery, tabletQuery, desktopQuery]);
return { isMobile, isDesktop, isDarkMode };
return { isMobile, isTablet, isDesktop };
}
export function useDarkTheme() {

View File

@@ -11,6 +11,7 @@
"help": "Help",
"emailUs": "Email us",
"documentation": "Documentation",
"manageAgents": "Manage Agents",
"demo": [
{
"header": "Learn about DocsGPT",
@@ -72,7 +73,11 @@
},
"actions": "Actions",
"view": "View",
"deleteWarning": "Are you sure you want to delete \"{{name}}\"?"
"deleteWarning": "Are you sure you want to delete \"{{name}}\"?",
"backToAll": "Back to all documents",
"chunks": "Chunks",
"noChunks": "No chunks found",
"noChunksAlt": "No chunks found"
},
"apiKeys": {
"label": "Chatbots",
@@ -125,7 +130,31 @@
"deleteWarning": "Are you sure you want to delete the tool \"{{toolName}}\" ?",
"unsavedChanges": "You have unsaved changes that will be lost if you leave without saving.",
"leaveWithoutSaving": "Leave without Saving",
"saveAndLeave": "Save and Leave"
"saveAndLeave": "Save and Leave",
"customName": "Custom Name",
"customNamePlaceholder": "Enter a custom name (optional)",
"authentication": "Authentication",
"actions": "Actions",
"addAction": "Add action",
"noActionsFound": "No actions found",
"url": "URL",
"urlPlaceholder": "Enter url",
"method": "Method",
"description": "Description",
"descriptionPlaceholder": "Enter description",
"headers": "Headers",
"queryParameters": "Query Parameters",
"body": "Body",
"deleteActionWarning": "Are you sure you want to delete the action \"{{name}}\"?",
"backToTools": "Back to Tools",
"save": "Save",
"name": "Name",
"type": "Type",
"filledByLLM": "Filled by LLM",
"value": "Value",
"addProperty": "Add property",
"propertyName": "Property name",
"noProperties": "No properties"
}
},
"modals": {

View File

@@ -71,7 +71,11 @@
},
"actions": "Acciones",
"view": "Ver",
"deleteWarning": "¿Estás seguro de que deseas eliminar \"{{name}}\"?"
"deleteWarning": "¿Estás seguro de que deseas eliminar \"{{name}}\"?",
"backToAll": "Volver a todos los documentos",
"chunks": "Fragmentos",
"noChunks": "No se encontraron fragmentos",
"noChunksAlt": "No se encontraron fragmentos"
},
"apiKeys": {
"label": "Chatbots",
@@ -116,8 +120,39 @@
"noToolsFound": "No se encontraron herramientas",
"selectToolSetup": "Seleccione una herramienta para configurar",
"settingsIconAlt": "Icono de configuración",
"configureToolAria": "Configurar {toolName}",
"toggleToolAria": "Alternar {toolName}"
"configureToolAria": "Configurar {{toolName}}",
"toggleToolAria": "Alternar {{toolName}}",
"manageTools": "Ir a Herramientas",
"edit": "Editar",
"delete": "Eliminar",
"deleteWarning": "¿Estás seguro de que deseas eliminar la herramienta \"{{toolName}}\"?",
"unsavedChanges": "Tienes cambios sin guardar que se perderán si sales sin guardar.",
"leaveWithoutSaving": "Salir sin Guardar",
"saveAndLeave": "Guardar y Salir",
"customName": "Nombre Personalizado",
"customNamePlaceholder": "Ingresa un nombre personalizado (opcional)",
"authentication": "Autenticación",
"actions": "Acciones",
"addAction": "Agregar acción",
"noActionsFound": "No se encontraron acciones",
"url": "URL",
"urlPlaceholder": "Ingresa url",
"method": "Método",
"description": "Descripción",
"descriptionPlaceholder": "Ingresa descripción",
"headers": "Encabezados",
"queryParameters": "Parámetros de Consulta",
"body": "Cuerpo",
"deleteActionWarning": "¿Estás seguro de que deseas eliminar la acción \"{{name}}\"?",
"backToTools": "Volver a Herramientas",
"save": "Guardar",
"name": "Nombre",
"type": "Tipo",
"filledByLLM": "Completado por LLM",
"value": "Valor",
"addProperty": "Agregar propiedad",
"propertyName": "Nombre de propiedad",
"noProperties": "Sin propiedades"
}
},
"modals": {

View File

@@ -52,6 +52,7 @@
"add": "追加"
},
"documents": {
"title": "この表には、利用可能なすべてのドキュメントとアップロードしたドキュメントが含まれています",
"label": "ドキュメント",
"name": "ドキュメント名",
"date": "ベクトル日付",
@@ -71,7 +72,11 @@
},
"actions": "アクション",
"view": "表示",
"deleteWarning": "\"{{name}}\"を削除してもよろしいですか?"
"deleteWarning": "\"{{name}}\"を削除してもよろしいですか?",
"backToAll": "すべてのドキュメントに戻る",
"chunks": "チャンク",
"noChunks": "チャンクが見つかりません",
"noChunksAlt": "チャンクが見つかりません"
},
"apiKeys": {
"label": "チャットボット",
@@ -117,8 +122,39 @@
"noToolsFound": "ツールが見つかりません",
"selectToolSetup": "設定するツールを選択してください",
"settingsIconAlt": "設定アイコン",
"configureToolAria": "{toolName} を設定",
"toggleToolAria": "{toolName} を切り替え"
"configureToolAria": "{{toolName}}を設定",
"toggleToolAria": "{{toolName}}を切り替え",
"manageTools": "ツールへ移動",
"edit": "編集",
"delete": "削除",
"deleteWarning": "ツール \"{{toolName}}\" を削除してもよろしいですか?",
"unsavedChanges": "保存されていない変更があります。保存せずに離れると失われます。",
"leaveWithoutSaving": "保存せずに離れる",
"saveAndLeave": "保存して離れる",
"customName": "カスタム名",
"customNamePlaceholder": "カスタム名を入力(任意)",
"authentication": "認証",
"actions": "アクション",
"addAction": "アクションを追加",
"noActionsFound": "アクションが見つかりません",
"url": "URL",
"urlPlaceholder": "URLを入力",
"method": "メソッド",
"description": "説明",
"descriptionPlaceholder": "説明を入力",
"headers": "ヘッダー",
"queryParameters": "クエリパラメータ",
"body": "ボディ",
"deleteActionWarning": "アクション \"{{name}}\" を削除してもよろしいですか?",
"backToTools": "ツールに戻る",
"save": "保存",
"name": "名前",
"type": "タイプ",
"filledByLLM": "LLMによる入力",
"value": "値",
"addProperty": "プロパティを追加",
"propertyName": "プロパティ名",
"noProperties": "プロパティなし"
}
},
"modals": {

View File

@@ -72,7 +72,11 @@
},
"actions": "Действия",
"view": "Просмотр",
"deleteWarning": "Вы уверены, что хотите удалить \"{{name}}\"?"
"deleteWarning": "Вы уверены, что хотите удалить \"{{name}}\"?",
"backToAll": "Вернуться ко всем документам",
"chunks": "Фрагменты",
"noChunks": "Фрагменты не найдены",
"noChunksAlt": "Фрагменты не найдены"
},
"apiKeys": {
"label": "API ключи",
@@ -116,9 +120,40 @@
"addTool": "Добавить инструмент",
"noToolsFound": "Инструменты не найдены",
"selectToolSetup": "Выберите инструмент для настройки",
"settingsIconAlt": "Иконка настроек",
"configureToolAria": "Настроить {toolName}",
"toggleToolAria": "Переключить {toolName}"
"settingsIconAlt": "Значок настроек",
"configureToolAria": "Настроить {{toolName}}",
"toggleToolAria": "Переключить {{toolName}}",
"manageTools": "Перейти к инструментам",
"edit": "Редактировать",
"delete": "Удалить",
"deleteWarning": "Вы уверены, что хотите удалить инструмент \"{{toolName}}\"?",
"unsavedChanges": "У вас есть несохраненные изменения, которые будут потеряны, если вы уйдете без сохранения.",
"leaveWithoutSaving": "Уйти без сохранения",
"saveAndLeave": "Сохранить и уйти",
"customName": "Пользовательское имя",
"customNamePlaceholder": "Введите пользовательское имя (необязательно)",
"authentication": "Аутентификация",
"actions": "Действия",
"addAction": "Добавить действие",
"noActionsFound": "Действия не найдены",
"url": "URL",
"urlPlaceholder": "Введите url",
"method": "Метод",
"description": "Описание",
"descriptionPlaceholder": "Введите описание",
"headers": "Заголовки",
"queryParameters": "Параметры запроса",
"body": "Тело",
"deleteActionWarning": "Вы уверены, что хотите удалить действие \"{{name}}\"?",
"backToTools": "Вернуться к инструментам",
"save": "Сохранить",
"name": "Имя",
"type": "Тип",
"filledByLLM": "Заполнено LLM",
"value": "Значение",
"addProperty": "Добавить свойство",
"propertyName": "Имя свойства",
"noProperties": "Нет свойств"
}
},
"modals": {

View File

@@ -72,7 +72,11 @@
},
"actions": "操作",
"view": "查看",
"deleteWarning": "您確定要刪除 \"{{name}}\" 嗎?"
"deleteWarning": "您確定要刪除 \"{{name}}\" 嗎?",
"backToAll": "返回所有文件",
"chunks": "文本塊",
"noChunks": "未找到文本塊",
"noChunksAlt": "未找到文本塊"
},
"apiKeys": {
"label": "聊天機器人",
@@ -112,13 +116,44 @@
},
"tools": {
"label": "工具",
"searchPlaceholder": "搜尋...",
"searchPlaceholder": "搜尋工具...",
"addTool": "新增工具",
"noToolsFound": "找不到工具",
"selectToolSetup": "選擇要設定的工具",
"settingsIconAlt": "設定圖",
"configureToolAria": "配置 {toolName}",
"toggleToolAria": "切換 {toolName}"
"settingsIconAlt": "設定圖",
"configureToolAria": "設定 {{toolName}}",
"toggleToolAria": "切換 {{toolName}}",
"manageTools": "前往工具",
"edit": "編輯",
"delete": "刪除",
"deleteWarning": "您確定要刪除工具 \"{{toolName}}\" 嗎?",
"unsavedChanges": "您有未儲存的變更,如果不儲存就離開將會遺失。",
"leaveWithoutSaving": "不儲存離開",
"saveAndLeave": "儲存並離開",
"customName": "自訂名稱",
"customNamePlaceholder": "輸入自訂名稱(選填)",
"authentication": "認證",
"actions": "操作",
"addAction": "新增操作",
"noActionsFound": "找不到操作",
"url": "URL",
"urlPlaceholder": "輸入url",
"method": "方法",
"description": "描述",
"descriptionPlaceholder": "輸入描述",
"headers": "標頭",
"queryParameters": "查詢參數",
"body": "主體",
"deleteActionWarning": "您確定要刪除操作 \"{{name}}\" 嗎?",
"backToTools": "返回工具",
"save": "儲存",
"name": "名稱",
"type": "類型",
"filledByLLM": "由LLM填寫",
"value": "值",
"addProperty": "新增屬性",
"propertyName": "屬性名稱",
"noProperties": "無屬性"
}
},
"modals": {

View File

@@ -112,13 +112,44 @@
},
"tools": {
"label": "工具",
"searchPlaceholder": "搜索...",
"searchPlaceholder": "搜索工具...",
"addTool": "添加工具",
"noToolsFound": "未找到工具",
"selectToolSetup": "选择要设置的工具",
"settingsIconAlt": "设置图标",
"configureToolAria": "配置 {toolName}",
"toggleToolAria": "切换 {toolName}"
"configureToolAria": "配置 {{toolName}}",
"toggleToolAria": "切换 {{toolName}}",
"manageTools": "前往工具",
"edit": "编辑",
"delete": "删除",
"deleteWarning": "您确定要删除工具 \"{{toolName}}\" 吗?",
"unsavedChanges": "您有未保存的更改,如果不保存就离开将会丢失。",
"leaveWithoutSaving": "不保存离开",
"saveAndLeave": "保存并离开",
"customName": "自定义名称",
"customNamePlaceholder": "输入自定义名称(可选)",
"authentication": "认证",
"actions": "操作",
"addAction": "添加操作",
"noActionsFound": "未找到操作",
"url": "URL",
"urlPlaceholder": "输入url",
"method": "方法",
"description": "描述",
"descriptionPlaceholder": "输入描述",
"headers": "请求头",
"queryParameters": "查询参数",
"body": "请求体",
"deleteActionWarning": "您确定要删除操作 \"{{name}}\" 吗?",
"backToTools": "返回工具",
"save": "保存",
"name": "名称",
"type": "类型",
"filledByLLM": "由LLM填充",
"value": "值",
"addProperty": "添加属性",
"propertyName": "属性名称",
"noProperties": "无属性"
}
},
"modals": {

View File

@@ -312,7 +312,7 @@ export default function Documents({
/>
</div>
<button
className="flex h-[32px] w-[108px] items-center justify-center rounded-full bg-purple-30 text-sm text-white hover:bg-violets-are-blue"
className="flex h-[32px] min-w-[108px] items-center justify-center whitespace-normal rounded-full bg-purple-30 px-4 text-sm text-white hover:bg-violets-are-blue"
title={t('settings.documents.addNew')}
onClick={() => {
setIsOnboarding(false);
@@ -641,11 +641,11 @@ function DocumentChunks({
>
<img src={ArrowLeft} alt="left-arrow" className="h-3 w-3" />
</button>
<p className="mt-px">Back to all documents</p>
<p className="mt-px">{t('settings.documents.backToAll')}</p>
</div>
<div className="my-3 flex items-center justify-between gap-1">
<div className="flex w-full items-center gap-2 text-eerie-black dark:text-bright-gray sm:w-auto">
<p className="hidden text-2xl font-semibold sm:flex">{`${totalChunks} Chunks`}</p>
<p className="hidden text-2xl font-semibold sm:flex">{`${totalChunks} ${t('settings.documents.chunks')}`}</p>
<label htmlFor="chunk-search-input" className="sr-only">
{t('settings.documents.searchPlaceholder')}
</label>
@@ -663,7 +663,7 @@ function DocumentChunks({
/>
</div>
<button
className="flex h-[32px] w-[108px] items-center justify-center rounded-full bg-purple-30 text-sm text-white hover:bg-violets-are-blue"
className="flex h-[32px] min-w-[108px] items-center justify-center whitespace-normal rounded-full bg-purple-30 px-4 text-sm text-white hover:bg-violets-are-blue"
title={t('settings.documents.addNew')}
onClick={() => setAddModal('ACTIVE')}
>
@@ -687,10 +687,10 @@ function DocumentChunks({
<div className="col-span-2 mt-24 text-center text-gray-500 dark:text-gray-400 lg:col-span-3">
<img
src={isDarkTheme ? NoFilesDarkIcon : NoFilesIcon}
alt="No tools found"
alt={t('settings.documents.noChunksAlt')}
className="mx-auto mb-2 h-24 w-24"
/>
No chunks found
{t('settings.documents.noChunks')}
</div>
) : (
paginatedChunks

View File

@@ -16,6 +16,9 @@ import { selectToken } from '../preferences/preferenceSlice';
import { APIActionType, APIToolType, UserToolType } from './types';
import { useTranslation } from 'react-i18next';
import { areObjectsEqual } from '../utils/objectUtils';
import { useDarkTheme } from '../hooks';
import NoFilesIcon from '../assets/no-files.svg';
import NoFilesDarkIcon from '../assets/no-files-dark.svg';
export default function ToolConfig({
tool,
@@ -44,6 +47,7 @@ export default function ToolConfig({
const [hasUnsavedChanges, setHasUnsavedChanges] = React.useState(false);
const [showUnsavedModal, setShowUnsavedModal] = React.useState(false);
const { t } = useTranslation();
const [isDarkTheme] = useDarkTheme();
const handleBackClick = () => {
if (hasUnsavedChanges) {
@@ -177,7 +181,7 @@ export default function ToolConfig({
{/* Custom name section */}
<div className="mt-1">
<p className="text-sm font-semibold text-eerie-black dark:text-bright-gray">
Custom Name
{t('settings.tools.customName')}
</p>
<div className="relative mt-4 w-full max-w-96">
<Input
@@ -185,14 +189,14 @@ export default function ToolConfig({
value={customName}
onChange={(e) => setCustomName(e.target.value)}
borderVariant="thin"
placeholder="Enter a custom name (optional)"
placeholder={t('settings.tools.customNamePlaceholder')}
/>
</div>
</div>
<div className="mt-1">
{Object.keys(tool?.config).length !== 0 && tool.name !== 'api_tool' && (
<p className="text-sm font-semibold text-eerie-black dark:text-bright-gray">
Authentication
{t('settings.tools.authentication')}
</p>
)}
<div className="mt-4 flex flex-col items-start gap-2 sm:flex-row sm:items-center">
@@ -211,201 +215,227 @@ export default function ToolConfig({
</div>
</div>
<div className="flex flex-col gap-4">
<div className="mx-1 my-2 h-[0.8px] w-full rounded-full bg-[#C4C4C4]/40 lg:w-[95%]"></div>
<div className="mx-0 my-2 h-[0.8px] w-full rounded-full bg-[#C4C4C4]/40"></div>
<div className="flex w-full flex-row items-center justify-between gap-2">
<p className="text-base font-semibold text-eerie-black dark:text-bright-gray">
Actions
{t('settings.tools.actions')}
</p>
</div>
{tool.name === 'api_tool' ? (
<>
<APIToolConfig tool={tool as APIToolType} setTool={setTool} />
<div className="mt-4 flex justify-end">
{tool.name === 'api_tool' &&
(!tool.config.actions ||
Object.keys(tool.config.actions).length === 0) && (
<button
onClick={() => setActionModalState('ACTIVE')}
className="rounded-full border border-solid border-violets-are-blue px-5 py-1 text-sm text-violets-are-blue transition-colors hover:bg-violets-are-blue hover:text-white"
>
Add action
{t('settings.tools.addAction')}
</button>
</div>
)}
</div>
{tool.name === 'api_tool' ? (
<>
{tool.config.actions &&
Object.keys(tool.config.actions).length > 0 ? (
<APIToolConfig tool={tool as APIToolType} setTool={setTool} />
) : (
<div className="flex flex-col items-center justify-center py-8">
<img
src={isDarkTheme ? NoFilesDarkIcon : NoFilesIcon}
alt="No actions found"
className="mx-auto mb-4 h-24 w-24"
/>
<p className="text-center text-gray-500 dark:text-gray-400">
{t('settings.tools.noActionsFound')}
</p>
</div>
)}
</>
) : (
<div className="flex flex-col gap-12">
{'actions' in tool &&
tool.actions.map((action, actionIndex) => {
return (
<div
key={actionIndex}
className="w-full rounded-xl border border-silver dark:border-silver/40"
>
<div className="flex h-10 flex-wrap items-center justify-between rounded-t-xl border-b border-silver bg-[#F9F9F9] px-5 dark:border-silver/40 dark:bg-[#28292D]">
<p className="font-semibold text-eerie-black dark:text-bright-gray">
{action.name}
</p>
<ToggleSwitch
checked={action.active}
onChange={(checked) => {
setTool({
...tool,
actions: tool.actions.map((act, index) => {
if (index === actionIndex) {
return { ...act, active: checked };
}
return act;
}),
});
}}
size="small"
id={`actionToggle-${actionIndex}`}
/>
</div>
<div className="relative mt-5 w-full px-5">
<Input
type="text"
className="w-full"
placeholder="Enter description"
value={action.description}
onChange={(e) => {
setTool({
...tool,
actions: tool.actions.map((act, index) => {
if (index === actionIndex) {
return {
...act,
description: e.target.value,
};
}
return act;
}),
});
}}
borderVariant="thin"
/>
</div>
<div className="px-5 py-4">
<table className="table-default">
<thead>
<tr>
<th>Field Name</th>
<th>Field Type</th>
<th>Filled by LLM</th>
<th>FIeld description</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{Object.entries(action.parameters?.properties).map(
(param, index) => {
const uniqueKey = `${actionIndex}-${param[0]}`;
return (
<tr
key={index}
className="text-nowrap font-normal"
>
<td>{param[0]}</td>
<td>{param[1].type}</td>
<td>
<label
htmlFor={uniqueKey}
className="ml-[10px] flex cursor-pointer items-start gap-4"
>
<div className="flex items-center">
&#8203;
<input
checked={param[1].filled_by_llm}
id={uniqueKey}
type="checkbox"
className="size-4 rounded border-gray-300 bg-transparent"
onChange={() =>
handleCheckboxChange(
actionIndex,
param[0],
)
}
/>
</div>
</label>
</td>
<td className="w-10">
<input
key={uniqueKey}
value={param[1].description}
className="rounded-lg border border-silver bg-transparent px-2 py-1 text-sm outline-none dark:border-silver/40"
onChange={(e) => {
setTool({
...tool,
actions: tool.actions.map(
(act, index) => {
if (index === actionIndex) {
return {
...act,
parameters: {
...act.parameters,
properties: {
...act.parameters
.properties,
[param[0]]: {
...act.parameters
.properties[param[0]],
description:
e.target.value,
},
},
},
};
}
return act;
},
),
});
}}
></input>
</td>
<td>
<input
value={param[1].value}
key={uniqueKey}
disabled={param[1].filled_by_llm}
className={`rounded-lg border border-silver bg-transparent px-2 py-1 text-sm outline-none dark:border-silver/40 ${param[1].filled_by_llm ? 'opacity-50' : ''}`}
onChange={(e) => {
setTool({
...tool,
actions: tool.actions.map(
(act, index) => {
if (index === actionIndex) {
return {
...act,
parameters: {
...act.parameters,
properties: {
...act.parameters
.properties,
[param[0]]: {
...act.parameters
.properties[param[0]],
value: e.target.value,
},
},
},
};
}
return act;
},
),
});
}}
></input>
</td>
</tr>
);
},
)}
</tbody>
</table>
</div>
{'actions' in tool && tool.actions && tool.actions.length > 0 ? (
tool.actions.map((action, actionIndex) => (
<div
key={actionIndex}
className="w-full rounded-xl border border-silver dark:border-silver/40"
>
<div className="flex h-10 flex-wrap items-center justify-between rounded-t-xl border-b border-silver bg-[#F9F9F9] px-5 dark:border-silver/40 dark:bg-[#28292D]">
<p className="font-semibold text-eerie-black dark:text-bright-gray">
{action.name}
</p>
<ToggleSwitch
checked={action.active}
onChange={(checked) => {
setTool({
...tool,
actions: tool.actions.map((act, index) => {
if (index === actionIndex) {
return { ...act, active: checked };
}
return act;
}),
});
}}
size="small"
id={`actionToggle-${actionIndex}`}
/>
</div>
);
})}
<div className="relative mt-5 w-full px-5">
<Input
type="text"
className="w-full"
placeholder="Enter description"
value={action.description}
onChange={(e) => {
setTool({
...tool,
actions: tool.actions.map((act, index) => {
if (index === actionIndex) {
return {
...act,
description: e.target.value,
};
}
return act;
}),
});
}}
borderVariant="thin"
/>
</div>
<div className="px-5 py-4">
<table className="table-default">
<thead>
<tr>
<th>Field Name</th>
<th>Field Type</th>
<th>Filled by LLM</th>
<th>FIeld description</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{Object.entries(action.parameters?.properties).map(
(param, index) => {
const uniqueKey = `${actionIndex}-${param[0]}`;
return (
<tr
key={index}
className="text-nowrap font-normal"
>
<td>{param[0]}</td>
<td>{param[1].type}</td>
<td>
<label
htmlFor={uniqueKey}
className="ml-[10px] flex cursor-pointer items-start gap-4"
>
<div className="flex items-center">
&#8203;
<input
checked={param[1].filled_by_llm}
id={uniqueKey}
type="checkbox"
className="size-4 rounded border-gray-300 bg-transparent"
onChange={() =>
handleCheckboxChange(
actionIndex,
param[0],
)
}
/>
</div>
</label>
</td>
<td className="w-10">
<input
key={uniqueKey}
value={param[1].description}
className="rounded-lg border border-silver bg-transparent px-2 py-1 text-sm outline-none dark:border-silver/40"
onChange={(e) => {
setTool({
...tool,
actions: tool.actions.map(
(act, index) => {
if (index === actionIndex) {
return {
...act,
parameters: {
...act.parameters,
properties: {
...act.parameters
.properties,
[param[0]]: {
...act.parameters
.properties[param[0]],
description:
e.target.value,
},
},
},
};
}
return act;
},
),
});
}}
></input>
</td>
<td>
<input
value={param[1].value}
key={uniqueKey}
disabled={param[1].filled_by_llm}
className={`rounded-lg border border-silver bg-transparent px-2 py-1 text-sm outline-none dark:border-silver/40 ${param[1].filled_by_llm ? 'opacity-50' : ''}`}
onChange={(e) => {
setTool({
...tool,
actions: tool.actions.map(
(act, index) => {
if (index === actionIndex) {
return {
...act,
parameters: {
...act.parameters,
properties: {
...act.parameters
.properties,
[param[0]]: {
...act.parameters
.properties[param[0]],
value: e.target.value,
},
},
},
};
}
return act;
},
),
});
}}
></input>
</td>
</tr>
);
},
)}
</tbody>
</table>
</div>
</div>
))
) : (
<div className="flex flex-col items-center justify-center py-8">
<img
src={isDarkTheme ? NoFilesDarkIcon : NoFilesIcon}
alt="No actions found"
className="mx-auto mb-4 h-24 w-24"
/>
<p className="text-center text-gray-500 dark:text-gray-400">
{t('settings.tools.noActionsFound')}
</p>
</div>
)}
</div>
)}
<AddActionModal
@@ -415,15 +445,10 @@ export default function ToolConfig({
/>
{showUnsavedModal && (
<ConfirmationModal
message={t('settings.tools.unsavedChanges', {
defaultValue:
'You have unsaved changes that will be lost if you leave without saving.',
})}
message={t('settings.tools.unsavedChanges')}
modalState="ACTIVE"
setModalState={(state) => setShowUnsavedModal(state === 'ACTIVE')}
submitLabel={t('settings.tools.saveAndLeave', {
defaultValue: 'Save and Leave',
})}
submitLabel={t('settings.tools.saveAndLeave')}
handleSubmit={() => {
userService
.updateTool(
@@ -447,9 +472,7 @@ export default function ToolConfig({
handleGoBack();
});
}}
cancelLabel={t('settings.tools.leaveWithoutSaving', {
defaultValue: 'Leave without Saving',
})}
cancelLabel={t('settings.tools.leaveWithoutSaving')}
handleCancel={() => {
setShowUnsavedModal(false);
handleGoBack();
@@ -567,36 +590,31 @@ function APIToolConfig({
</div>
</div>
<div className="mt-8 px-5">
<div className="relative w-full">
<span className="absolute -top-2 left-5 z-10 bg-white px-2 text-xs text-gray-4000 dark:bg-raisin-black dark:text-silver">
URL
</span>
<Input
type="text"
value={action.url}
onChange={(e) => {
setApiTool((prevApiTool) => {
const updatedActions = {
...prevApiTool.config.actions,
};
const updatedAction = {
...updatedActions[actionName],
};
updatedAction.url = e.target.value;
updatedActions[actionName] = updatedAction;
return {
...prevApiTool,
config: {
...prevApiTool.config,
actions: updatedActions,
},
};
});
}}
borderVariant="thin"
placeholder="Enter url"
></Input>
</div>
<Input
type="text"
value={action.url}
onChange={(e) => {
setApiTool((prevApiTool) => {
const updatedActions = {
...prevApiTool.config.actions,
};
const updatedAction = {
...updatedActions[actionName],
};
updatedAction.url = e.target.value;
updatedActions[actionName] = updatedAction;
return {
...prevApiTool,
config: {
...prevApiTool.config,
actions: updatedActions,
},
};
});
}}
borderVariant="thin"
placeholder={t('settings.tools.urlPlaceholder')}
/>
</div>
<div className="mt-4 px-5 py-2">
<div className="relative w-full">
@@ -636,36 +654,31 @@ function APIToolConfig({
</div>
</div>
<div className="mt-4 px-5 py-2">
<div className="relative w-full">
<span className="absolute -top-2 left-5 z-10 bg-white px-2 text-xs text-gray-4000 dark:bg-raisin-black dark:text-silver">
Description
</span>
<Input
type="text"
value={action.description}
onChange={(e) => {
setApiTool((prevApiTool) => {
const updatedActions = {
...prevApiTool.config.actions,
};
const updatedAction = {
...updatedActions[actionName],
};
updatedAction.description = e.target.value;
updatedActions[actionName] = updatedAction;
return {
...prevApiTool,
config: {
...prevApiTool.config,
actions: updatedActions,
},
};
});
}}
borderVariant="thin"
placeholder="Enter description"
></Input>
</div>
<Input
type="text"
value={action.description}
onChange={(e) => {
setApiTool((prevApiTool) => {
const updatedActions = {
...prevApiTool.config.actions,
};
const updatedAction = {
...updatedActions[actionName],
};
updatedAction.description = e.target.value;
updatedActions[actionName] = updatedAction;
return {
...prevApiTool,
config: {
...prevApiTool.config,
actions: updatedActions,
},
};
});
}}
borderVariant="thin"
placeholder={t('settings.tools.descriptionPlaceholder')}
/>
</div>
<div className="mt-4 px-5 py-2">
<APIActionTable
@@ -682,7 +695,6 @@ function APIToolConfig({
<ConfirmationModal
message={t('settings.tools.deleteActionWarning', {
name: actionToDelete,
defaultValue: `Are you sure you want to delete the action "${actionToDelete}"?`,
})}
modalState={deleteModalState}
setModalState={setDeleteModalState}
@@ -709,6 +721,8 @@ function APIActionTable({
updatedAction: APIActionType,
) => void;
}) {
const { t } = useTranslation();
const [action, setAction] = React.useState<APIActionType>(apiAction);
const [newPropertyKey, setNewPropertyKey] = React.useState('');
const [addingPropertySection, setAddingPropertySection] = React.useState<
@@ -1026,16 +1040,26 @@ function APIActionTable({
<div className="scrollbar-thin flex flex-col gap-6">
<div>
<h3 className="mb-1 text-base font-normal text-eerie-black dark:text-bright-gray">
Headers
{t('settings.tools.headers')}
</h3>
<table className="table-default">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Filled by LLM</th>
<th>Description</th>
<th>Value</th>
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
{t('settings.tools.name')}
</th>
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
{t('settings.tools.type')}
</th>
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
{t('settings.tools.filledByLLM')}
</th>
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
{t('settings.tools.description')}
</th>
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
{t('settings.tools.value')}
</th>
<th
style={{
width: '50px',
@@ -1051,16 +1075,26 @@ function APIActionTable({
</div>
<div>
<h3 className="mb-1 text-base font-normal text-eerie-black dark:text-bright-gray">
Query Parameters
{t('settings.tools.queryParameters')}
</h3>
<table className="table-default">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Filled by LLM</th>
<th>Description</th>
<th>Value</th>
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
{t('settings.tools.name')}
</th>
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
{t('settings.tools.type')}
</th>
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
{t('settings.tools.filledByLLM')}
</th>
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
{t('settings.tools.description')}
</th>
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
{t('settings.tools.value')}
</th>
<th
style={{
width: '50px',
@@ -1076,16 +1110,26 @@ function APIActionTable({
</div>
<div className="mb-6">
<h3 className="mb-1 text-base font-normal text-eerie-black dark:text-bright-gray">
Body
{t('settings.tools.body')}
</h3>
<table className="table-default">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Filled by LLM</th>
<th>Description</th>
<th>Value</th>
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
{t('settings.tools.name')}
</th>
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
{t('settings.tools.type')}
</th>
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
{t('settings.tools.filledByLLM')}
</th>
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
{t('settings.tools.description')}
</th>
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
{t('settings.tools.value')}
</th>
<th
style={{
width: '50px',

View File

@@ -157,11 +157,8 @@ export default function Tools() {
) : (
<div className="mt-8">
<div className="relative flex flex-col">
<div className="my-3 flex items-center justify-between gap-1">
<div className="p-1">
<label htmlFor="tool-search-input" className="sr-only">
{t('settings.tools.searchPlaceholder')}
</label>
<div className="my-3 flex flex-col items-start gap-3 sm:flex-row sm:items-center sm:justify-between">
<div className="w-full sm:w-auto">
<Input
maxLength={256}
placeholder={t('settings.tools.searchPlaceholder')}
@@ -174,7 +171,7 @@ export default function Tools() {
/>
</div>
<button
className="flex h-[30px] w-[108px] items-center justify-center rounded-full bg-purple-30 text-sm text-white hover:bg-violets-are-blue"
className="flex h-[32px] min-w-[108px] items-center justify-center whitespace-normal rounded-full bg-purple-30 px-4 text-sm text-white hover:bg-violets-are-blue"
onClick={() => {
setAddToolModalState('ACTIVE');
}}
@@ -236,7 +233,7 @@ export default function Tools() {
}}
options={getMenuOptions(tool)}
anchorRef={menuRefs.current[tool.id]}
position="top-right"
position="bottom-right"
offset={{ x: 0, y: 0 }}
/>
</div>