fix: handle tool availability states (#5183)
* fix: handle tool availability states * Update web-app/src/hooks/useToolAvailable.ts Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * Update web-app/src/hooks/useToolAvailable.ts Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * Update web-app/src/hooks/useToolAvailable.ts Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> * fix: hub refresh --------- Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
This commit is contained in:
parent
9c825956e8
commit
171b1e8c60
@ -401,15 +401,17 @@ export default class JanModelExtension extends ModelExtension {
|
||||
api
|
||||
.get('v1/models/hub?author=cortexso&tag=cortex.cpp')
|
||||
.json<Data<string>>()
|
||||
.then((e) => {
|
||||
e.data?.forEach((model) => {
|
||||
.then(async (e) => {
|
||||
await Promise.all(
|
||||
e.data?.map((model) => {
|
||||
if (
|
||||
!models.some(
|
||||
(e) => 'modelSource' in e && e.modelSource === model
|
||||
)
|
||||
)
|
||||
this.addSource(model).catch((e) => console.debug(e))
|
||||
return this.addSource(model).catch((e) => console.debug(e))
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
.catch((e) => console.debug(e))
|
||||
|
||||
@ -8,13 +8,12 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { getTools } from '@/services/mcp'
|
||||
import { MCPTool } from '@/types/completion'
|
||||
|
||||
import { useThreads } from '@/hooks/useThreads'
|
||||
import { useToolAvailable } from '@/hooks/useToolAvailable'
|
||||
|
||||
import React from 'react'
|
||||
import { useAppState } from '@/hooks/useAppState'
|
||||
|
||||
interface DropdownToolsAvailableProps {
|
||||
children: (isOpen: boolean, toolsCount: number) => React.ReactNode
|
||||
@ -25,45 +24,20 @@ export default function DropdownToolsAvailable({
|
||||
children,
|
||||
initialMessage = false,
|
||||
}: DropdownToolsAvailableProps) {
|
||||
const [tools, setTools] = useState<MCPTool[]>([])
|
||||
const { tools } = useAppState()
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const { getCurrentThread } = useThreads()
|
||||
const {
|
||||
isToolAvailable,
|
||||
setToolAvailableForThread,
|
||||
setDefaultAvailableTools,
|
||||
isToolDisabled,
|
||||
setToolDisabledForThread,
|
||||
setDefaultDisabledTools,
|
||||
initializeThreadTools,
|
||||
getAvailableToolsForThread,
|
||||
getDefaultAvailableTools,
|
||||
getDisabledToolsForThread,
|
||||
getDefaultDisabledTools,
|
||||
} = useToolAvailable()
|
||||
|
||||
const currentThread = getCurrentThread()
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTools = async () => {
|
||||
try {
|
||||
const availableTools = await getTools()
|
||||
setTools(availableTools)
|
||||
|
||||
// If this is for the initial message (index page) and no defaults are set,
|
||||
// initialize with all tools as default
|
||||
if (
|
||||
initialMessage &&
|
||||
getDefaultAvailableTools().length === 0 &&
|
||||
availableTools.length > 0
|
||||
) {
|
||||
setDefaultAvailableTools(availableTools.map((tool) => tool.name))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch tools:', error)
|
||||
setTools([])
|
||||
}
|
||||
}
|
||||
|
||||
// Only fetch tools once when component mounts
|
||||
fetchTools()
|
||||
}, [initialMessage, setDefaultAvailableTools, getDefaultAvailableTools])
|
||||
|
||||
// Separate effect for thread initialization - only when we have tools and a new thread
|
||||
useEffect(() => {
|
||||
if (tools.length > 0 && currentThread?.id) {
|
||||
@ -74,40 +48,38 @@ export default function DropdownToolsAvailable({
|
||||
const handleToolToggle = (toolName: string, checked: boolean) => {
|
||||
if (initialMessage) {
|
||||
// Update default tools for new threads/index page
|
||||
const currentDefaults = getDefaultAvailableTools()
|
||||
const currentDefaults = getDefaultDisabledTools()
|
||||
if (checked) {
|
||||
if (!currentDefaults.includes(toolName)) {
|
||||
setDefaultAvailableTools([...currentDefaults, toolName])
|
||||
}
|
||||
} else {
|
||||
setDefaultAvailableTools(
|
||||
setDefaultDisabledTools(
|
||||
currentDefaults.filter((name) => name !== toolName)
|
||||
)
|
||||
} else {
|
||||
setDefaultDisabledTools([...currentDefaults, toolName])
|
||||
}
|
||||
} else if (currentThread?.id) {
|
||||
// Update tools for specific thread
|
||||
setToolAvailableForThread(currentThread.id, toolName, checked)
|
||||
setToolDisabledForThread(currentThread.id, toolName, checked)
|
||||
}
|
||||
}
|
||||
|
||||
const isToolChecked = (toolName: string): boolean => {
|
||||
if (initialMessage) {
|
||||
// Use default tools for index page
|
||||
return getDefaultAvailableTools().includes(toolName)
|
||||
return !getDefaultDisabledTools().includes(toolName)
|
||||
} else if (currentThread?.id) {
|
||||
// Use thread-specific tools
|
||||
return isToolAvailable(currentThread.id, toolName)
|
||||
return !isToolDisabled(currentThread.id, toolName)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const getEnabledToolsCount = (): number => {
|
||||
if (initialMessage) {
|
||||
return getDefaultAvailableTools().length
|
||||
} else if (currentThread?.id) {
|
||||
return getAvailableToolsForThread(currentThread.id).length
|
||||
}
|
||||
return 0
|
||||
const disabledTools = initialMessage
|
||||
? getDefaultDisabledTools()
|
||||
: currentThread?.id
|
||||
? getDisabledToolsForThread(currentThread.id)
|
||||
: []
|
||||
return tools.filter((tool) => !disabledTools.includes(tool.name)).length
|
||||
}
|
||||
|
||||
const renderTrigger = () => children(isOpen, getEnabledToolsCount())
|
||||
|
||||
@ -44,12 +44,16 @@ export const useChat = () => {
|
||||
|
||||
const { approvedTools, showApprovalModal, allowAllMCPPermissions } =
|
||||
useToolApproval()
|
||||
const { getAvailableToolsForThread } = useToolAvailable()
|
||||
const { getDisabledToolsForThread } = useToolAvailable()
|
||||
|
||||
const { getProviderByName, selectedModel, selectedProvider } =
|
||||
useModelProvider()
|
||||
|
||||
const { getCurrentThread: retrieveThread, createThread, updateThreadTimestamp } = useThreads()
|
||||
const {
|
||||
getCurrentThread: retrieveThread,
|
||||
createThread,
|
||||
updateThreadTimestamp,
|
||||
} = useThreads()
|
||||
const { getMessages, addMessage } = useMessages()
|
||||
const router = useRouter()
|
||||
|
||||
@ -134,10 +138,8 @@ export const useChat = () => {
|
||||
// Filter tools based on model capabilities and available tools for this thread
|
||||
let availableTools = selectedModel?.capabilities?.includes('tools')
|
||||
? tools.filter((tool) => {
|
||||
const availableToolNames = getAvailableToolsForThread(
|
||||
activeThread.id
|
||||
)
|
||||
return availableToolNames.includes(tool.name)
|
||||
const disabledTools = getDisabledToolsForThread(activeThread.id)
|
||||
return !disabledTools.includes(tool.name)
|
||||
})
|
||||
: []
|
||||
|
||||
@ -247,7 +249,7 @@ export const useChat = () => {
|
||||
updateTokenSpeed,
|
||||
approvedTools,
|
||||
showApprovalModal,
|
||||
getAvailableToolsForThread,
|
||||
getDisabledToolsForThread,
|
||||
allowAllMCPPermissions,
|
||||
]
|
||||
)
|
||||
|
||||
@ -3,102 +3,100 @@ import { persist, createJSONStorage } from 'zustand/middleware'
|
||||
import { localStorageKey } from '@/constants/localStorage'
|
||||
import { MCPTool } from '@/types/completion'
|
||||
|
||||
type ToolAvailableState = {
|
||||
// Track available tools per thread
|
||||
availableTools: Record<string, string[]> // threadId -> toolNames[]
|
||||
// Global default available tools (for new threads/index page)
|
||||
defaultAvailableTools: string[]
|
||||
type ToolDisabledState = {
|
||||
// Track disabled tools per thread
|
||||
disabledTools: Record<string, string[]> // threadId -> toolNames[]
|
||||
// Global default disabled tools (for new threads/index page)
|
||||
defaultDisabledTools: string[]
|
||||
|
||||
// Actions
|
||||
setToolAvailableForThread: (
|
||||
setToolDisabledForThread: (
|
||||
threadId: string,
|
||||
toolName: string,
|
||||
available: boolean
|
||||
) => void
|
||||
isToolAvailable: (threadId: string, toolName: string) => boolean
|
||||
getAvailableToolsForThread: (threadId: string) => string[]
|
||||
setDefaultAvailableTools: (toolNames: string[]) => void
|
||||
getDefaultAvailableTools: () => string[]
|
||||
isToolDisabled: (threadId: string, toolName: string) => boolean
|
||||
getDisabledToolsForThread: (threadId: string) => string[]
|
||||
setDefaultDisabledTools: (toolNames: string[]) => void
|
||||
getDefaultDisabledTools: () => string[]
|
||||
// Initialize thread tools from default or existing thread settings
|
||||
initializeThreadTools: (threadId: string, allTools: MCPTool[]) => void
|
||||
}
|
||||
|
||||
export const useToolAvailable = create<ToolAvailableState>()(
|
||||
export const useToolAvailable = create<ToolDisabledState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
availableTools: {},
|
||||
defaultAvailableTools: [],
|
||||
disabledTools: {},
|
||||
defaultDisabledTools: [],
|
||||
|
||||
setToolAvailableForThread: (
|
||||
setToolDisabledForThread: (
|
||||
threadId: string,
|
||||
toolName: string,
|
||||
available: boolean
|
||||
) => {
|
||||
set((state) => {
|
||||
const currentTools = state.availableTools[threadId] || []
|
||||
const currentTools = state.disabledTools[threadId] || []
|
||||
let updatedTools: string[]
|
||||
|
||||
if (available) {
|
||||
// Add tool if not already present
|
||||
updatedTools = currentTools.includes(toolName)
|
||||
? currentTools
|
||||
: [...currentTools, toolName]
|
||||
// Remove disabled tool
|
||||
updatedTools = [...currentTools.filter((tool) => tool !== toolName)]
|
||||
} else {
|
||||
// Remove tool
|
||||
updatedTools = currentTools.filter((tool) => tool !== toolName)
|
||||
// Disable tool
|
||||
updatedTools = [...currentTools, toolName]
|
||||
}
|
||||
|
||||
return {
|
||||
availableTools: {
|
||||
...state.availableTools,
|
||||
disabledTools: {
|
||||
...state.disabledTools,
|
||||
[threadId]: updatedTools,
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
isToolAvailable: (threadId: string, toolName: string) => {
|
||||
isToolDisabled: (threadId: string, toolName: string) => {
|
||||
const state = get()
|
||||
// If no thread-specific settings, use default
|
||||
if (!state.availableTools[threadId]) {
|
||||
return state.defaultAvailableTools.includes(toolName)
|
||||
if (!state.disabledTools[threadId]) {
|
||||
return state.defaultDisabledTools.includes(toolName)
|
||||
}
|
||||
return state.availableTools[threadId]?.includes(toolName) || false
|
||||
return state.disabledTools[threadId]?.includes(toolName) || false
|
||||
},
|
||||
|
||||
getAvailableToolsForThread: (threadId: string) => {
|
||||
getDisabledToolsForThread: (threadId: string) => {
|
||||
const state = get()
|
||||
// If no thread-specific settings, use default
|
||||
if (!state.availableTools[threadId]) {
|
||||
return state.defaultAvailableTools
|
||||
if (!state.disabledTools[threadId]) {
|
||||
return state.defaultDisabledTools
|
||||
}
|
||||
return state.availableTools[threadId] || []
|
||||
return state.disabledTools[threadId] || []
|
||||
},
|
||||
|
||||
setDefaultAvailableTools: (toolNames: string[]) => {
|
||||
set({ defaultAvailableTools: toolNames })
|
||||
setDefaultDisabledTools: (toolNames: string[]) => {
|
||||
set({ defaultDisabledTools: toolNames })
|
||||
},
|
||||
|
||||
getDefaultAvailableTools: () => {
|
||||
return get().defaultAvailableTools
|
||||
getDefaultDisabledTools: () => {
|
||||
return get().defaultDisabledTools
|
||||
},
|
||||
|
||||
initializeThreadTools: (threadId: string, allTools: MCPTool[]) => {
|
||||
const state = get()
|
||||
// If thread already has settings, don't override
|
||||
if (state.availableTools[threadId]) {
|
||||
if (state.disabledTools[threadId]) {
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize with default tools only
|
||||
// Don't auto-enable all tools if defaults are explicitly empty
|
||||
const initialTools = state.defaultAvailableTools.filter((toolName) =>
|
||||
const initialTools = state.defaultDisabledTools.filter((toolName) =>
|
||||
allTools.some((tool) => tool.name === toolName)
|
||||
)
|
||||
|
||||
set((currentState) => ({
|
||||
availableTools: {
|
||||
...currentState.availableTools,
|
||||
disabledTools: {
|
||||
...currentState.disabledTools,
|
||||
[threadId]: initialTools,
|
||||
},
|
||||
}))
|
||||
@ -109,8 +107,8 @@ export const useToolAvailable = create<ToolAvailableState>()(
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
// Persist all state
|
||||
partialize: (state) => ({
|
||||
availableTools: state.availableTools,
|
||||
defaultAvailableTools: state.defaultAvailableTools,
|
||||
disabledTools: state.disabledTools,
|
||||
defaultDisabledTools: state.defaultDisabledTools,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
@ -29,7 +29,7 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { addModelSource, downloadModel } from '@/services/models'
|
||||
import { addModelSource, downloadModel, fetchModelHub } from '@/services/models'
|
||||
import { useDownloadStore } from '@/hooks/useDownloadStore'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import HeaderPage from '@/containers/HeaderPage'
|
||||
@ -82,6 +82,9 @@ function Hub() {
|
||||
[modelId]: !prev[modelId],
|
||||
}))
|
||||
}
|
||||
useEffect(() => {
|
||||
fetchModelHub().then(fetchSources)
|
||||
}, [fetchSources])
|
||||
|
||||
useEffect(() => {
|
||||
if (search.repo) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user