feat: allow users to enable and disable tools

This commit is contained in:
Louis 2025-04-23 20:22:07 +07:00
parent 5e80587138
commit 1630e2eb77
No known key found for this signature in database
GPG Key ID: 44FA9F4D33C37DE2
4 changed files with 59 additions and 18 deletions

View File

@ -63,7 +63,8 @@ export function useTollCallPromiseModal() {
<div className="mt-4 flex justify-end gap-x-2"> <div className="mt-4 flex justify-end gap-x-2">
<ModalClose asChild> <ModalClose asChild>
<Button <Button
theme="ghost" variant="outline" theme="ghost"
variant="outline"
onClick={() => { onClick={() => {
setApprovedToolsAtom((prev) => { setApprovedToolsAtom((prev) => {
const newState = { ...prev } const newState = { ...prev }
@ -87,7 +88,12 @@ export function useTollCallPromiseModal() {
</Button> </Button>
</ModalClose> </ModalClose>
<ModalClose asChild> <ModalClose asChild>
<Button theme="ghost" variant="outline" onClick={handleConfirm} autoFocus> <Button
theme="ghost"
variant="outline"
onClick={handleConfirm}
autoFocus
>
Allow once Allow once
</Button> </Button>
</ModalClose> </ModalClose>

View File

@ -24,6 +24,7 @@ enum ThreadStorageAtomKeys {
ThreadStates = 'threadStates', ThreadStates = 'threadStates',
ThreadList = 'threadList', ThreadList = 'threadList',
ThreadListReady = 'threadListReady', ThreadListReady = 'threadListReady',
DisabledTools = 'disabledTools',
} }
//// Threads Atom //// Threads Atom
@ -73,10 +74,18 @@ export const threadDataReadyAtom = atomWithStorage<boolean>(
export const threadModelParamsAtom = atom<Record<string, ModelParams>>({}) export const threadModelParamsAtom = atom<Record<string, ModelParams>>({})
/** /**
* Store the tool call approval thread id * Store the tool call approval for thread id
*/ */
export const approvedThreadToolsAtom = atom<Record<string, string[]>>({}) export const approvedThreadToolsAtom = atom<Record<string, string[]>>({})
/**
* Store the tool call disabled for thread id
*/
export const disabledThreadToolsAtom = atomWithStorage<string[]>(
ThreadStorageAtomKeys.DisabledTools,
[]
)
//// End Thread Atom //// End Thread Atom
/// Active Thread Atom /// Active Thread Atom

View File

@ -56,6 +56,7 @@ import { selectedModelAtom } from '@/helpers/atoms/Model.atom'
import { import {
activeThreadAtom, activeThreadAtom,
approvedThreadToolsAtom, approvedThreadToolsAtom,
disabledThreadToolsAtom,
engineParamsUpdateAtom, engineParamsUpdateAtom,
getActiveThreadModelParamsAtom, getActiveThreadModelParamsAtom,
isGeneratingResponseAtom, isGeneratingResponseAtom,
@ -78,6 +79,7 @@ export default function useSendChatMessage(
const deleteMessage = useSetAtom(deleteMessageAtom) const deleteMessage = useSetAtom(deleteMessageAtom)
const setEditPrompt = useSetAtom(editPromptAtom) const setEditPrompt = useSetAtom(editPromptAtom)
const approvedTools = useAtomValue(approvedThreadToolsAtom) const approvedTools = useAtomValue(approvedThreadToolsAtom)
const disabledTools = useAtomValue(disabledThreadToolsAtom)
const currentMessages = useAtomValue(getCurrentChatMessagesAtom) const currentMessages = useAtomValue(getCurrentChatMessagesAtom)
const selectedModel = useAtomValue(selectedModelAtom) const selectedModel = useAtomValue(selectedModelAtom)
@ -196,15 +198,17 @@ export default function useSendChatMessage(
}, },
activeThread, activeThread,
messages ?? currentMessages, messages ?? currentMessages,
(await window.core.api.getTools())?.map((tool: ModelTool) => ({ (await window.core.api.getTools())
type: 'function' as const, ?.filter((tool: ModelTool) => !disabledTools.includes(tool.name))
function: { .map((tool: ModelTool) => ({
name: tool.name, type: 'function' as const,
description: tool.description?.slice(0, 1024), function: {
parameters: tool.inputSchema, name: tool.name,
strict: false, description: tool.description?.slice(0, 1024),
}, parameters: tool.inputSchema,
})) strict: false,
},
}))
).addSystemMessage(activeAssistant.instructions) ).addSystemMessage(activeAssistant.instructions)
requestBuilder.pushMessage(prompt, base64Blob, fileUpload) requestBuilder.pushMessage(prompt, base64Blob, fileUpload)

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import { useContext, useEffect, useRef, useState } from 'react' import { useContext, useEffect, useMemo, useRef, useState } from 'react'
import { InferenceEngine } from '@janhq/core' import { InferenceEngine } from '@janhq/core'
@ -11,6 +11,7 @@ import {
badgeVariants, badgeVariants,
Badge, Badge,
Modal, Modal,
Switch,
} from '@janhq/joi' } from '@janhq/joi'
import { useAtom, useAtomValue } from 'jotai' import { useAtom, useAtomValue } from 'jotai'
import { import {
@ -51,6 +52,7 @@ import { spellCheckAtom } from '@/helpers/atoms/Setting.atom'
import { import {
activeSettingInputBoxAtom, activeSettingInputBoxAtom,
activeThreadAtom, activeThreadAtom,
disabledThreadToolsAtom,
getActiveThreadIdAtom, getActiveThreadIdAtom,
isBlockingSendAtom, isBlockingSendAtom,
} from '@/helpers/atoms/Thread.atom' } from '@/helpers/atoms/Thread.atom'
@ -69,6 +71,7 @@ const ChatInput = () => {
const { showApprovalModal } = useContext(ChatContext) const { showApprovalModal } = useContext(ChatContext)
const { sendChatMessage } = useSendChatMessage(showApprovalModal) const { sendChatMessage } = useSendChatMessage(showApprovalModal)
const selectedModel = useAtomValue(selectedModelAtom) const selectedModel = useAtomValue(selectedModelAtom)
const [disabledTools, setDisableTools] = useAtom(disabledThreadToolsAtom)
const activeThreadId = useAtomValue(getActiveThreadIdAtom) const activeThreadId = useAtomValue(getActiveThreadIdAtom)
const [fileUpload, setFileUpload] = useAtom(fileUploadAtom) const [fileUpload, setFileUpload] = useAtom(fileUploadAtom)
@ -89,6 +92,10 @@ const ChatInput = () => {
activeTabThreadRightPanelAtom activeTabThreadRightPanelAtom
) )
const availableTools = useMemo(() => {
return tools.filter((tool: ModelTool) => !disabledTools.includes(tool.name))
}, [tools, disabledTools])
const refAttachmentMenus = useClickOutside(() => const refAttachmentMenus = useClickOutside(() =>
setShowAttachmentMenus(false) setShowAttachmentMenus(false)
) )
@ -420,7 +427,7 @@ const ChatInput = () => {
size={16} size={16}
className="flex-shrink-0 cursor-pointer text-[hsla(var(--text-secondary))]" className="flex-shrink-0 cursor-pointer text-[hsla(var(--text-secondary))]"
/> />
<span className="text-xs">{tools.length}</span> <span className="text-xs">{availableTools.length}</span>
</Badge> </Badge>
<Modal <Modal
@ -449,11 +456,26 @@ const ChatInput = () => {
size={16} size={16}
className="flex-shrink-0 text-[hsla(var(--text-secondary))]" className="flex-shrink-0 text-[hsla(var(--text-secondary))]"
/> />
<div> <div className="flex w-full flex-1 flex-row justify-between">
<div className="font-medium">{tool.name}</div> <div>
<div className="text-sm text-[hsla(var(--text-secondary))]"> <div className="font-medium">{tool.name}</div>
{tool.description} <div className="text-sm text-[hsla(var(--text-secondary))]">
{tool.description}
</div>
</div> </div>
<Switch
checked={!disabledTools.includes(tool.name)}
onChange={(e) => {
if (e.target.checked) {
setDisableTools((prev) =>
prev.filter((t) => t !== tool.name)
)
} else {
setDisableTools([...disabledTools, tool.name])
}
}}
className="flex-shrink-0"
/>
</div> </div>
</div> </div>
))} ))}