mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
feat: tools frontend and endpoints refactor
This commit is contained in:
@@ -18,6 +18,11 @@ const endpoints = {
|
||||
FEEDBACK_ANALYTICS: '/api/get_feedback_analytics',
|
||||
LOGS: `/api/get_user_logs`,
|
||||
MANAGE_SYNC: '/api/manage_sync',
|
||||
GET_AVAILABLE_TOOLS: '/api/available_tools',
|
||||
GET_USER_TOOLS: '/api/get_tools',
|
||||
CREATE_TOOL: '/api/create_tool',
|
||||
UPDATE_TOOL_STATUS: '/api/update_tool_status',
|
||||
UPDATE_TOOL: '/api/update_tool',
|
||||
},
|
||||
CONVERSATION: {
|
||||
ANSWER: '/api/answer',
|
||||
|
||||
@@ -35,6 +35,16 @@ const userService = {
|
||||
apiClient.post(endpoints.USER.LOGS, data),
|
||||
manageSync: (data: any): Promise<any> =>
|
||||
apiClient.post(endpoints.USER.MANAGE_SYNC, data),
|
||||
getAvailableTools: (): Promise<any> =>
|
||||
apiClient.get(endpoints.USER.GET_AVAILABLE_TOOLS),
|
||||
getUserTools: (): Promise<any> =>
|
||||
apiClient.get(endpoints.USER.GET_USER_TOOLS),
|
||||
createTool: (data: any): Promise<any> =>
|
||||
apiClient.post(endpoints.USER.CREATE_TOOL, data),
|
||||
updateToolStatus: (data: any): Promise<any> =>
|
||||
apiClient.post(endpoints.USER.UPDATE_TOOL_STATUS, data),
|
||||
updateTool: (data: any): Promise<any> =>
|
||||
apiClient.post(endpoints.USER.UPDATE_TOOL, data),
|
||||
};
|
||||
|
||||
export default userService;
|
||||
|
||||
3
frontend/src/assets/cogwheel.svg
Normal file
3
frontend/src/assets/cogwheel.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.00182 11.2502C7.23838 11.2502 6.50621 10.9552 5.96637 10.4301C5.42654 9.90499 5.12327 9.1928 5.12327 8.4502C5.12327 7.70759 5.42654 6.9954 5.96637 6.4703C6.50621 5.9452 7.23838 5.6502 8.00182 5.6502C8.76525 5.6502 9.49743 5.9452 10.0373 6.4703C10.5771 6.9954 10.8804 7.70759 10.8804 8.4502C10.8804 9.1928 10.5771 9.90499 10.0373 10.4301C9.49743 10.9552 8.76525 11.2502 8.00182 11.2502ZM14.1126 9.2262C14.1455 8.9702 14.1701 8.7142 14.1701 8.4502C14.1701 8.1862 14.1455 7.9222 14.1126 7.6502L15.8479 6.3462C16.0042 6.2262 16.0453 6.0102 15.9466 5.8342L14.3017 3.0662C14.203 2.8902 13.981 2.8182 13.8 2.8902L11.7522 3.6902C11.3245 3.3782 10.8804 3.1062 10.3622 2.9062L10.0579 0.786197C10.0412 0.69197 9.99076 0.606538 9.91549 0.545038C9.84022 0.483538 9.745 0.449939 9.6467 0.450197H6.35693C6.15132 0.450197 5.97861 0.594197 5.94571 0.786197L5.64141 2.9062C5.12327 3.1062 4.67915 3.3782 4.25148 3.6902L2.2036 2.8902C2.02266 2.8182 1.8006 2.8902 1.70191 3.0662L0.0570212 5.8342C-0.0498963 6.0102 -0.00054964 6.2262 0.155714 6.3462L1.89107 7.6502C1.85817 7.9222 1.8335 8.1862 1.8335 8.4502C1.8335 8.7142 1.85817 8.9702 1.89107 9.2262L0.155714 10.5542C-0.00054964 10.6742 -0.0498963 10.8902 0.0570212 11.0662L1.70191 13.8342C1.8006 14.0102 2.02266 14.0742 2.2036 14.0102L4.25148 13.2022C4.67915 13.5222 5.12327 13.7942 5.64141 13.9942L5.94571 16.1142C5.97861 16.3062 6.15132 16.4502 6.35693 16.4502H9.6467C9.85231 16.4502 10.025 16.3062 10.0579 16.1142L10.3622 13.9942C10.8804 13.7862 11.3245 13.5222 11.7522 13.2022L13.8 14.0102C13.981 14.0742 14.203 14.0102 14.3017 13.8342L15.9466 11.0662C16.0453 10.8902 16.0042 10.6742 15.8479 10.5542L14.1126 9.2262Z" fill="#747474"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -13,6 +13,7 @@ const useTabs = () => {
|
||||
t('settings.apiKeys.label'),
|
||||
t('settings.analytics.label'),
|
||||
t('settings.logs.label'),
|
||||
t('settings.tools.label'),
|
||||
];
|
||||
return tabs;
|
||||
};
|
||||
|
||||
@@ -68,6 +68,15 @@ body.dark {
|
||||
.table-default td:last-child {
|
||||
@apply border-r-0; /* Ensure no right border on the last column */
|
||||
}
|
||||
|
||||
.table-default th,
|
||||
.table-default td {
|
||||
min-width: 150px;
|
||||
max-width: 320px;
|
||||
overflow: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: grey transparent;
|
||||
}
|
||||
}
|
||||
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
@@ -73,6 +73,9 @@
|
||||
},
|
||||
"logs": {
|
||||
"label": "Logs"
|
||||
},
|
||||
"tools": {
|
||||
"label": "Tools"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
|
||||
136
frontend/src/modals/AddToolModal.tsx
Normal file
136
frontend/src/modals/AddToolModal.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import React from 'react';
|
||||
|
||||
import userService from '../api/services/userService';
|
||||
import Exit from '../assets/exit.svg';
|
||||
import { ActiveState } from '../models/misc';
|
||||
import { AvailableTool } from './types';
|
||||
import ConfigToolModal from './ConfigToolModal';
|
||||
|
||||
export default function AddToolModal({
|
||||
message,
|
||||
modalState,
|
||||
setModalState,
|
||||
getUserTools,
|
||||
}: {
|
||||
message: string;
|
||||
modalState: ActiveState;
|
||||
setModalState: (state: ActiveState) => void;
|
||||
getUserTools: () => void;
|
||||
}) {
|
||||
const [availableTools, setAvailableTools] = React.useState<AvailableTool[]>(
|
||||
[],
|
||||
);
|
||||
const [selectedTool, setSelectedTool] = React.useState<AvailableTool | null>(
|
||||
null,
|
||||
);
|
||||
const [configModalState, setConfigModalState] =
|
||||
React.useState<ActiveState>('INACTIVE');
|
||||
|
||||
const getAvailableTools = () => {
|
||||
userService
|
||||
.getAvailableTools()
|
||||
.then((res) => {
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
setAvailableTools(data.data);
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddTool = (tool: AvailableTool) => {
|
||||
if (Object.keys(tool.configRequirements).length === 0) {
|
||||
userService
|
||||
.createTool({
|
||||
name: tool.name,
|
||||
displayName: tool.displayName,
|
||||
description: tool.description,
|
||||
config: {},
|
||||
actions: tool.actions,
|
||||
status: true,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
getUserTools();
|
||||
setModalState('INACTIVE');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setModalState('INACTIVE');
|
||||
setConfigModalState('ACTIVE');
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (modalState === 'ACTIVE') getAvailableTools();
|
||||
}, [modalState]);
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`${
|
||||
modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha flex items-center justify-center`}
|
||||
>
|
||||
<article className="flex h-[85vh] w-[90vw] md:w-[75vw] flex-col gap-4 rounded-2xl bg-[#FBFBFB] shadow-lg dark:bg-[#26272E]">
|
||||
<div className="relative">
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3"
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
</button>
|
||||
<div className="p-6">
|
||||
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
|
||||
Select a tool to set up
|
||||
</h2>
|
||||
<div className="mt-5 grid grid-cols-3 gap-4 h-[73vh] overflow-auto px-3 py-px">
|
||||
{availableTools.map((tool, index) => (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
key={index}
|
||||
className="h-52 w-full p-6 border rounded-2xl border-silver dark:border-[#4D4E58] flex flex-col justify-between dark:bg-[#32333B] cursor-pointer"
|
||||
onClick={() => {
|
||||
setSelectedTool(tool);
|
||||
handleAddTool(tool);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
setSelectedTool(tool);
|
||||
handleAddTool(tool);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="w-full">
|
||||
<div className="px-1 w-full flex items-center justify-between">
|
||||
<img
|
||||
src={`/toolIcons/tool_${tool.name}.svg`}
|
||||
className="h-8 w-8"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-[9px]">
|
||||
<p className="px-1 text-sm font-semibold text-eerie-black dark:text-white leading-relaxed capitalize">
|
||||
{tool.displayName}
|
||||
</p>
|
||||
<p className="mt-1 px-1 h-24 overflow-auto text-sm text-gray-600 dark:text-[#8a8a8c] leading-relaxed">
|
||||
{tool.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<ConfigToolModal
|
||||
modalState={configModalState}
|
||||
setModalState={setConfigModalState}
|
||||
tool={selectedTool}
|
||||
getUserTools={getUserTools}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
95
frontend/src/modals/ConfigToolModal.tsx
Normal file
95
frontend/src/modals/ConfigToolModal.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React from 'react';
|
||||
|
||||
import Exit from '../assets/exit.svg';
|
||||
import Input from '../components/Input';
|
||||
import { ActiveState } from '../models/misc';
|
||||
import { AvailableTool } from './types';
|
||||
import userService from '../api/services/userService';
|
||||
|
||||
export default function ConfigToolModal({
|
||||
modalState,
|
||||
setModalState,
|
||||
tool,
|
||||
getUserTools,
|
||||
}: {
|
||||
modalState: ActiveState;
|
||||
setModalState: (state: ActiveState) => void;
|
||||
tool: AvailableTool | null;
|
||||
getUserTools: () => void;
|
||||
}) {
|
||||
const [authKey, setAuthKey] = React.useState<string>('');
|
||||
|
||||
const handleAddTool = (tool: AvailableTool) => {
|
||||
userService
|
||||
.createTool({
|
||||
name: tool.name,
|
||||
displayName: tool.displayName,
|
||||
description: tool.description,
|
||||
config: { token: authKey },
|
||||
actions: tool.actions,
|
||||
status: true,
|
||||
})
|
||||
.then(() => {
|
||||
setModalState('INACTIVE');
|
||||
getUserTools();
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha flex items-center justify-center`}
|
||||
>
|
||||
<article className="flex w-11/12 sm:w-[512px] flex-col gap-4 rounded-2xl bg-white shadow-lg dark:bg-[#26272E]">
|
||||
<div className="relative">
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3"
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
</button>
|
||||
<div className="p-6">
|
||||
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
|
||||
Tool Config
|
||||
</h2>
|
||||
<p className="mt-5 text-sm text-gray-600 dark:text-gray-400 px-3">
|
||||
Type: <span className="font-semibold">{tool?.name} </span>
|
||||
</p>
|
||||
<div className="mt-6 relative px-3">
|
||||
<span className="absolute left-5 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-[#26272E] dark:text-silver">
|
||||
API Key / Oauth
|
||||
</span>
|
||||
<Input
|
||||
type="text"
|
||||
value={authKey}
|
||||
onChange={(e) => setAuthKey(e.target.value)}
|
||||
borderVariant="thin"
|
||||
placeholder="Enter API Key / Oauth"
|
||||
></Input>
|
||||
</div>
|
||||
<div className="mt-8 flex flex-row-reverse gap-1 px-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
handleAddTool(tool as AvailableTool);
|
||||
}}
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
|
||||
>
|
||||
Add Tool
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
className="cursor-pointer rounded-3xl px-5 py-2 text-sm font-medium hover:bg-gray-100 dark:bg-transparent dark:text-light-gray dark:hover:bg-[#767183]/50"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
11
frontend/src/modals/types/index.ts
Normal file
11
frontend/src/modals/types/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export type AvailableTool = {
|
||||
name: string;
|
||||
displayName: string;
|
||||
description: string;
|
||||
configRequirements: object;
|
||||
actions: {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: object;
|
||||
}[];
|
||||
};
|
||||
@@ -215,18 +215,22 @@ const Documents: React.FC<DocumentsProps> = ({
|
||||
{document.type === 'remote' ? 'Pre-loaded' : 'Private'}
|
||||
</td>
|
||||
<td>
|
||||
<div className="min-w-[70px] flex flex-row items-end justify-end ml-auto">
|
||||
<div className="w-full flex flex-row-reverse items-center justify-items-end ml-auto gap-2">
|
||||
{document.type !== 'remote' && (
|
||||
<img
|
||||
src={Trash}
|
||||
alt="Delete"
|
||||
className="h-4 w-4 cursor-pointer opacity-60 hover:opacity-100"
|
||||
id={`img-${index}`}
|
||||
<button
|
||||
className="h-8 w-8 border border-[#747474] rounded-full p-2 opacity-60 hover:opacity-100"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
handleDeleteDocument(index, document);
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<img
|
||||
id={`img-${index}`}
|
||||
src={Trash}
|
||||
alt="Delete"
|
||||
className="h-full w-full cursor-pointer"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
{document.syncFrequency && (
|
||||
<div className="ml-2">
|
||||
|
||||
293
frontend/src/settings/ToolConfig.tsx
Normal file
293
frontend/src/settings/ToolConfig.tsx
Normal file
@@ -0,0 +1,293 @@
|
||||
import React from 'react';
|
||||
|
||||
import userService from '../api/services/userService';
|
||||
import ArrowLeft from '../assets/arrow-left.svg';
|
||||
import Input from '../components/Input';
|
||||
import { UserTool } from './types';
|
||||
|
||||
export default function ToolConfig({
|
||||
tool,
|
||||
setTool,
|
||||
handleGoBack,
|
||||
}: {
|
||||
tool: UserTool;
|
||||
setTool: (tool: UserTool) => void;
|
||||
handleGoBack: () => void;
|
||||
}) {
|
||||
const [authKey, setAuthKey] = React.useState<string>(
|
||||
tool.config?.token || '',
|
||||
);
|
||||
|
||||
const handleCheckboxChange = (actionIndex: number, property: string) => {
|
||||
setTool({
|
||||
...tool,
|
||||
actions: tool.actions.map((action, index) => {
|
||||
if (index === actionIndex) {
|
||||
return {
|
||||
...action,
|
||||
parameters: {
|
||||
...action.parameters,
|
||||
properties: {
|
||||
...action.parameters.properties,
|
||||
[property]: {
|
||||
...action.parameters.properties[property],
|
||||
filled_by_llm:
|
||||
!action.parameters.properties[property].filled_by_llm,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return action;
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
const handleSaveChanges = () => {
|
||||
userService
|
||||
.updateTool({
|
||||
id: tool.id,
|
||||
name: tool.name,
|
||||
displayName: tool.displayName,
|
||||
description: tool.description,
|
||||
config: { token: authKey },
|
||||
actions: tool.actions,
|
||||
status: tool.status,
|
||||
})
|
||||
.then(() => {
|
||||
handleGoBack();
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className="mt-8 flex flex-col gap-4">
|
||||
<div className="mb-4 flex items-center gap-3 text-eerie-black dark:text-bright-gray text-sm">
|
||||
<button
|
||||
className="text-sm text-gray-400 dark:text-gray-500 border dark:border-0 dark:bg-[#28292D] dark:hover:bg-[#2E2F34] p-3 rounded-full"
|
||||
onClick={handleGoBack}
|
||||
>
|
||||
<img src={ArrowLeft} alt="left-arrow" className="w-3 h-3" />
|
||||
</button>
|
||||
<p className="mt-px">Back to all tools</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-eerie-black dark:text-bright-gray">
|
||||
Type
|
||||
</p>
|
||||
<p className="mt-1 text-base font-normal text-eerie-black dark:text-bright-gray font-sans">
|
||||
{tool.name}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-1">
|
||||
{Object.keys(tool?.config).length !== 0 && (
|
||||
<p className="text-sm font-semibold text-eerie-black dark:text-bright-gray">
|
||||
Authentication
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 flex items-center gap-2">
|
||||
{Object.keys(tool?.config).length !== 0 && (
|
||||
<div className="relative w-96">
|
||||
<span className="absolute left-5 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-[#26272E] dark:text-silver">
|
||||
API Key / Oauth
|
||||
</span>
|
||||
<Input
|
||||
type="text"
|
||||
value={authKey}
|
||||
onChange={(e) => setAuthKey(e.target.value)}
|
||||
borderVariant="thin"
|
||||
placeholder="Enter API Key / Oauth"
|
||||
></Input>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
className="rounded-full h-10 w-36 bg-purple-30 text-white hover:bg-[#6F3FD1] text-nowrap text-sm"
|
||||
onClick={handleSaveChanges}
|
||||
>
|
||||
Save changes
|
||||
</button>
|
||||
</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>
|
||||
<p className="text-base font-semibold text-eerie-black dark:text-bright-gray">
|
||||
Actions
|
||||
</p>
|
||||
<div className="flex flex-col gap-10">
|
||||
{tool.actions.map((action, actionIndex) => {
|
||||
return (
|
||||
<div
|
||||
key={actionIndex}
|
||||
className="w-full border border-silver dark:border-silver/40 rounded-xl"
|
||||
>
|
||||
<div className="h-10 bg-[#F9F9F9] dark:bg-[#28292D] rounded-t-xl border-b border-silver dark:border-silver/40 flex items-center justify-between px-5">
|
||||
<p className="font-semibold text-eerie-black dark:text-bright-gray">
|
||||
{action.name}
|
||||
</p>
|
||||
<label
|
||||
htmlFor={`actionToggle-${actionIndex}`}
|
||||
className="relative inline-block h-6 w-10 cursor-pointer rounded-full bg-gray-300 dark:bg-[#D2D5DA33]/20 transition [-webkit-tap-highlight-color:_transparent] has-[:checked]:bg-[#0C9D35CC] has-[:checked]:dark:bg-[#0C9D35CC]"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`actionToggle-${actionIndex}`}
|
||||
className="peer sr-only"
|
||||
checked={action.active}
|
||||
onChange={() => {
|
||||
setTool({
|
||||
...tool,
|
||||
actions: tool.actions.map((act, index) => {
|
||||
if (index === actionIndex) {
|
||||
return { ...act, active: !act.active };
|
||||
}
|
||||
return act;
|
||||
}),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<span className="absolute inset-y-0 start-0 m-[3px] size-[18px] rounded-full bg-white transition-all peer-checked:start-4"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="mt-5 relative px-5 w-96">
|
||||
<Input
|
||||
type="text"
|
||||
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"
|
||||
></Input>
|
||||
</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">
|
||||
​
|
||||
<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="bg-transparent border border-silver dark:border-silver/40 outline-none px-2 py-1 rounded-lg text-sm"
|
||||
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={`bg-transparent border border-silver dark:border-silver/40 outline-none px-2 py-1 rounded-lg text-sm ${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>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
157
frontend/src/settings/Tools.tsx
Normal file
157
frontend/src/settings/Tools.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import React from 'react';
|
||||
|
||||
import userService from '../api/services/userService';
|
||||
import CogwheelIcon from '../assets/cogwheel.svg';
|
||||
import Input from '../components/Input';
|
||||
import AddToolModal from '../modals/AddToolModal';
|
||||
import { ActiveState } from '../models/misc';
|
||||
import { UserTool } from './types';
|
||||
import ToolConfig from './ToolConfig';
|
||||
|
||||
export default function Tools() {
|
||||
const [searchTerm, setSearchTerm] = React.useState('');
|
||||
const [addToolModalState, setAddToolModalState] =
|
||||
React.useState<ActiveState>('INACTIVE');
|
||||
const [userTools, setUserTools] = React.useState<UserTool[]>([]);
|
||||
const [selectedTool, setSelectedTool] = React.useState<UserTool | null>(null);
|
||||
|
||||
const getUserTools = () => {
|
||||
userService
|
||||
.getUserTools()
|
||||
.then((res) => {
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
setUserTools(data.tools);
|
||||
});
|
||||
};
|
||||
|
||||
const updateToolStatus = (toolId: string, newStatus: boolean) => {
|
||||
userService
|
||||
.updateToolStatus({ id: toolId, status: newStatus })
|
||||
.then(() => {
|
||||
setUserTools((prevTools) =>
|
||||
prevTools.map((tool) =>
|
||||
tool.id === toolId ? { ...tool, status: newStatus } : tool,
|
||||
),
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to update tool status:', error);
|
||||
});
|
||||
};
|
||||
|
||||
const handleSettingsClick = (tool: UserTool) => {
|
||||
setSelectedTool(tool);
|
||||
};
|
||||
|
||||
const handleGoBack = () => {
|
||||
setSelectedTool(null);
|
||||
getUserTools();
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
getUserTools();
|
||||
}, []);
|
||||
return (
|
||||
<div>
|
||||
{selectedTool ? (
|
||||
<ToolConfig
|
||||
tool={selectedTool}
|
||||
setTool={setSelectedTool}
|
||||
handleGoBack={handleGoBack}
|
||||
/>
|
||||
) : (
|
||||
<div className="mt-8">
|
||||
<div className="flex flex-col relative">
|
||||
<div className="my-3 flex justify-between items-center gap-1">
|
||||
<div className="p-1">
|
||||
<Input
|
||||
maxLength={256}
|
||||
placeholder="Search..."
|
||||
name="Document-search-input"
|
||||
type="text"
|
||||
id="document-search-input"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="rounded-full w-40 bg-purple-30 px-4 py-3 text-white hover:bg-[#6F3FD1] text-nowrap"
|
||||
onClick={() => {
|
||||
setAddToolModalState('ACTIVE');
|
||||
}}
|
||||
>
|
||||
Add Tool
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{userTools
|
||||
.filter((tool) =>
|
||||
tool.displayName
|
||||
.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase()),
|
||||
)
|
||||
.map((tool, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="relative h-56 w-full p-6 border rounded-2xl border-silver dark:border-silver/40 flex flex-col justify-between"
|
||||
>
|
||||
<div className="w-full">
|
||||
<div className="w-full flex items-center justify-between">
|
||||
<img
|
||||
src={`/toolIcons/tool_${tool.name}.svg`}
|
||||
className="h-8 w-8"
|
||||
/>
|
||||
<button
|
||||
className="absolute top-3 right-3 cursor-pointer"
|
||||
onClick={() => handleSettingsClick(tool)}
|
||||
>
|
||||
<img
|
||||
src={CogwheelIcon}
|
||||
alt="settings"
|
||||
className="h-[19px] w-[19px]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-[9px]">
|
||||
<p className="text-sm font-semibold text-eerie-black dark:text-[#EEEEEE] leading-relaxed">
|
||||
{tool.displayName}
|
||||
</p>
|
||||
<p className="mt-1 h-16 overflow-auto text-[13px] text-gray-600 dark:text-gray-400 leading-relaxed pr-1">
|
||||
{tool.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-3 right-3">
|
||||
<label
|
||||
htmlFor={`toolToggle-${index}`}
|
||||
className="relative inline-block h-6 w-10 cursor-pointer rounded-full bg-gray-300 dark:bg-[#D2D5DA33]/20 transition [-webkit-tap-highlight-color:_transparent] has-[:checked]:bg-[#0C9D35CC] has-[:checked]:dark:bg-[#0C9D35CC]"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`toolToggle-${index}`}
|
||||
className="peer sr-only"
|
||||
checked={tool.status}
|
||||
onChange={() =>
|
||||
updateToolStatus(tool.id, !tool.status)
|
||||
}
|
||||
/>
|
||||
<span className="absolute inset-y-0 start-0 m-[3px] size-[18px] rounded-full bg-white transition-all peer-checked:start-4"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<AddToolModal
|
||||
message="Select a tool to set up"
|
||||
modalState={addToolModalState}
|
||||
setModalState={setAddToolModalState}
|
||||
getUserTools={getUserTools}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import SettingsBar from '../components/SettingsBar';
|
||||
import i18n from '../locale/i18n';
|
||||
import { Doc } from '../models/misc';
|
||||
import {
|
||||
selectSourceDocs,
|
||||
selectPaginatedDocuments,
|
||||
selectSourceDocs,
|
||||
setPaginatedDocuments,
|
||||
setSourceDocs,
|
||||
} from '../preferences/preferenceSlice';
|
||||
@@ -17,6 +17,7 @@ import APIKeys from './APIKeys';
|
||||
import Documents from './Documents';
|
||||
import General from './General';
|
||||
import Logs from './Logs';
|
||||
import Tools from './Tools';
|
||||
import Widgets from './Widgets';
|
||||
|
||||
export default function Settings() {
|
||||
@@ -100,6 +101,8 @@ export default function Settings() {
|
||||
return <Analytics />;
|
||||
case t('settings.logs.label'):
|
||||
return <Logs />;
|
||||
case t('settings.tools.label'):
|
||||
return <Tools />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -18,3 +18,32 @@ export type LogData = {
|
||||
retriever_params: Record<string, any>;
|
||||
timestamp: string;
|
||||
};
|
||||
|
||||
export type UserTool = {
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
description: string;
|
||||
status: boolean;
|
||||
config: {
|
||||
[key: string]: string;
|
||||
};
|
||||
actions: {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: {
|
||||
properties: {
|
||||
[key: string]: {
|
||||
type: string;
|
||||
description: string;
|
||||
filled_by_llm: boolean;
|
||||
value: string;
|
||||
};
|
||||
};
|
||||
additionalProperties: boolean;
|
||||
required: string[];
|
||||
type: string;
|
||||
};
|
||||
active: boolean;
|
||||
}[];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user