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
|
api
|
||||||
.get('v1/models/hub?author=cortexso&tag=cortex.cpp')
|
.get('v1/models/hub?author=cortexso&tag=cortex.cpp')
|
||||||
.json<Data<string>>()
|
.json<Data<string>>()
|
||||||
.then((e) => {
|
.then(async (e) => {
|
||||||
e.data?.forEach((model) => {
|
await Promise.all(
|
||||||
if (
|
e.data?.map((model) => {
|
||||||
!models.some(
|
if (
|
||||||
(e) => 'modelSource' in e && e.modelSource === model
|
!models.some(
|
||||||
|
(e) => 'modelSource' in e && e.modelSource === model
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
return this.addSource(model).catch((e) => console.debug(e))
|
||||||
this.addSource(model).catch((e) => console.debug(e))
|
})
|
||||||
})
|
)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.catch((e) => console.debug(e))
|
.catch((e) => console.debug(e))
|
||||||
|
|||||||
@ -8,13 +8,12 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu'
|
} from '@/components/ui/dropdown-menu'
|
||||||
import { Switch } from '@/components/ui/switch'
|
import { Switch } from '@/components/ui/switch'
|
||||||
import { getTools } from '@/services/mcp'
|
|
||||||
import { MCPTool } from '@/types/completion'
|
|
||||||
|
|
||||||
import { useThreads } from '@/hooks/useThreads'
|
import { useThreads } from '@/hooks/useThreads'
|
||||||
import { useToolAvailable } from '@/hooks/useToolAvailable'
|
import { useToolAvailable } from '@/hooks/useToolAvailable'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useAppState } from '@/hooks/useAppState'
|
||||||
|
|
||||||
interface DropdownToolsAvailableProps {
|
interface DropdownToolsAvailableProps {
|
||||||
children: (isOpen: boolean, toolsCount: number) => React.ReactNode
|
children: (isOpen: boolean, toolsCount: number) => React.ReactNode
|
||||||
@ -25,45 +24,20 @@ export default function DropdownToolsAvailable({
|
|||||||
children,
|
children,
|
||||||
initialMessage = false,
|
initialMessage = false,
|
||||||
}: DropdownToolsAvailableProps) {
|
}: DropdownToolsAvailableProps) {
|
||||||
const [tools, setTools] = useState<MCPTool[]>([])
|
const { tools } = useAppState()
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
const { getCurrentThread } = useThreads()
|
const { getCurrentThread } = useThreads()
|
||||||
const {
|
const {
|
||||||
isToolAvailable,
|
isToolDisabled,
|
||||||
setToolAvailableForThread,
|
setToolDisabledForThread,
|
||||||
setDefaultAvailableTools,
|
setDefaultDisabledTools,
|
||||||
initializeThreadTools,
|
initializeThreadTools,
|
||||||
getAvailableToolsForThread,
|
getDisabledToolsForThread,
|
||||||
getDefaultAvailableTools,
|
getDefaultDisabledTools,
|
||||||
} = useToolAvailable()
|
} = useToolAvailable()
|
||||||
|
|
||||||
const currentThread = getCurrentThread()
|
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
|
// Separate effect for thread initialization - only when we have tools and a new thread
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tools.length > 0 && currentThread?.id) {
|
if (tools.length > 0 && currentThread?.id) {
|
||||||
@ -74,40 +48,38 @@ export default function DropdownToolsAvailable({
|
|||||||
const handleToolToggle = (toolName: string, checked: boolean) => {
|
const handleToolToggle = (toolName: string, checked: boolean) => {
|
||||||
if (initialMessage) {
|
if (initialMessage) {
|
||||||
// Update default tools for new threads/index page
|
// Update default tools for new threads/index page
|
||||||
const currentDefaults = getDefaultAvailableTools()
|
const currentDefaults = getDefaultDisabledTools()
|
||||||
if (checked) {
|
if (checked) {
|
||||||
if (!currentDefaults.includes(toolName)) {
|
setDefaultDisabledTools(
|
||||||
setDefaultAvailableTools([...currentDefaults, toolName])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setDefaultAvailableTools(
|
|
||||||
currentDefaults.filter((name) => name !== toolName)
|
currentDefaults.filter((name) => name !== toolName)
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
setDefaultDisabledTools([...currentDefaults, toolName])
|
||||||
}
|
}
|
||||||
} else if (currentThread?.id) {
|
} else if (currentThread?.id) {
|
||||||
// Update tools for specific thread
|
// Update tools for specific thread
|
||||||
setToolAvailableForThread(currentThread.id, toolName, checked)
|
setToolDisabledForThread(currentThread.id, toolName, checked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isToolChecked = (toolName: string): boolean => {
|
const isToolChecked = (toolName: string): boolean => {
|
||||||
if (initialMessage) {
|
if (initialMessage) {
|
||||||
// Use default tools for index page
|
// Use default tools for index page
|
||||||
return getDefaultAvailableTools().includes(toolName)
|
return !getDefaultDisabledTools().includes(toolName)
|
||||||
} else if (currentThread?.id) {
|
} else if (currentThread?.id) {
|
||||||
// Use thread-specific tools
|
// Use thread-specific tools
|
||||||
return isToolAvailable(currentThread.id, toolName)
|
return !isToolDisabled(currentThread.id, toolName)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const getEnabledToolsCount = (): number => {
|
const getEnabledToolsCount = (): number => {
|
||||||
if (initialMessage) {
|
const disabledTools = initialMessage
|
||||||
return getDefaultAvailableTools().length
|
? getDefaultDisabledTools()
|
||||||
} else if (currentThread?.id) {
|
: currentThread?.id
|
||||||
return getAvailableToolsForThread(currentThread.id).length
|
? getDisabledToolsForThread(currentThread.id)
|
||||||
}
|
: []
|
||||||
return 0
|
return tools.filter((tool) => !disabledTools.includes(tool.name)).length
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderTrigger = () => children(isOpen, getEnabledToolsCount())
|
const renderTrigger = () => children(isOpen, getEnabledToolsCount())
|
||||||
|
|||||||
@ -44,12 +44,16 @@ export const useChat = () => {
|
|||||||
|
|
||||||
const { approvedTools, showApprovalModal, allowAllMCPPermissions } =
|
const { approvedTools, showApprovalModal, allowAllMCPPermissions } =
|
||||||
useToolApproval()
|
useToolApproval()
|
||||||
const { getAvailableToolsForThread } = useToolAvailable()
|
const { getDisabledToolsForThread } = useToolAvailable()
|
||||||
|
|
||||||
const { getProviderByName, selectedModel, selectedProvider } =
|
const { getProviderByName, selectedModel, selectedProvider } =
|
||||||
useModelProvider()
|
useModelProvider()
|
||||||
|
|
||||||
const { getCurrentThread: retrieveThread, createThread, updateThreadTimestamp } = useThreads()
|
const {
|
||||||
|
getCurrentThread: retrieveThread,
|
||||||
|
createThread,
|
||||||
|
updateThreadTimestamp,
|
||||||
|
} = useThreads()
|
||||||
const { getMessages, addMessage } = useMessages()
|
const { getMessages, addMessage } = useMessages()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@ -65,7 +69,7 @@ export const useChat = () => {
|
|||||||
}
|
}
|
||||||
setTools()
|
setTools()
|
||||||
|
|
||||||
let unsubscribe = () => { }
|
let unsubscribe = () => {}
|
||||||
listen(SystemEvent.MCP_UPDATE, setTools).then((unsub) => {
|
listen(SystemEvent.MCP_UPDATE, setTools).then((unsub) => {
|
||||||
// Unsubscribe from the event when the component unmounts
|
// Unsubscribe from the event when the component unmounts
|
||||||
unsubscribe = unsub
|
unsubscribe = unsub
|
||||||
@ -134,11 +138,9 @@ export const useChat = () => {
|
|||||||
// Filter tools based on model capabilities and available tools for this thread
|
// Filter tools based on model capabilities and available tools for this thread
|
||||||
let availableTools = selectedModel?.capabilities?.includes('tools')
|
let availableTools = selectedModel?.capabilities?.includes('tools')
|
||||||
? tools.filter((tool) => {
|
? tools.filter((tool) => {
|
||||||
const availableToolNames = getAvailableToolsForThread(
|
const disabledTools = getDisabledToolsForThread(activeThread.id)
|
||||||
activeThread.id
|
return !disabledTools.includes(tool.name)
|
||||||
)
|
})
|
||||||
return availableToolNames.includes(tool.name)
|
|
||||||
})
|
|
||||||
: []
|
: []
|
||||||
|
|
||||||
// TODO: Later replaced by Agent setup?
|
// TODO: Later replaced by Agent setup?
|
||||||
@ -247,7 +249,7 @@ export const useChat = () => {
|
|||||||
updateTokenSpeed,
|
updateTokenSpeed,
|
||||||
approvedTools,
|
approvedTools,
|
||||||
showApprovalModal,
|
showApprovalModal,
|
||||||
getAvailableToolsForThread,
|
getDisabledToolsForThread,
|
||||||
allowAllMCPPermissions,
|
allowAllMCPPermissions,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@ -3,102 +3,100 @@ import { persist, createJSONStorage } from 'zustand/middleware'
|
|||||||
import { localStorageKey } from '@/constants/localStorage'
|
import { localStorageKey } from '@/constants/localStorage'
|
||||||
import { MCPTool } from '@/types/completion'
|
import { MCPTool } from '@/types/completion'
|
||||||
|
|
||||||
type ToolAvailableState = {
|
type ToolDisabledState = {
|
||||||
// Track available tools per thread
|
// Track disabled tools per thread
|
||||||
availableTools: Record<string, string[]> // threadId -> toolNames[]
|
disabledTools: Record<string, string[]> // threadId -> toolNames[]
|
||||||
// Global default available tools (for new threads/index page)
|
// Global default disabled tools (for new threads/index page)
|
||||||
defaultAvailableTools: string[]
|
defaultDisabledTools: string[]
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
setToolAvailableForThread: (
|
setToolDisabledForThread: (
|
||||||
threadId: string,
|
threadId: string,
|
||||||
toolName: string,
|
toolName: string,
|
||||||
available: boolean
|
available: boolean
|
||||||
) => void
|
) => void
|
||||||
isToolAvailable: (threadId: string, toolName: string) => boolean
|
isToolDisabled: (threadId: string, toolName: string) => boolean
|
||||||
getAvailableToolsForThread: (threadId: string) => string[]
|
getDisabledToolsForThread: (threadId: string) => string[]
|
||||||
setDefaultAvailableTools: (toolNames: string[]) => void
|
setDefaultDisabledTools: (toolNames: string[]) => void
|
||||||
getDefaultAvailableTools: () => string[]
|
getDefaultDisabledTools: () => string[]
|
||||||
// Initialize thread tools from default or existing thread settings
|
// Initialize thread tools from default or existing thread settings
|
||||||
initializeThreadTools: (threadId: string, allTools: MCPTool[]) => void
|
initializeThreadTools: (threadId: string, allTools: MCPTool[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useToolAvailable = create<ToolAvailableState>()(
|
export const useToolAvailable = create<ToolDisabledState>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
availableTools: {},
|
disabledTools: {},
|
||||||
defaultAvailableTools: [],
|
defaultDisabledTools: [],
|
||||||
|
|
||||||
setToolAvailableForThread: (
|
setToolDisabledForThread: (
|
||||||
threadId: string,
|
threadId: string,
|
||||||
toolName: string,
|
toolName: string,
|
||||||
available: boolean
|
available: boolean
|
||||||
) => {
|
) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const currentTools = state.availableTools[threadId] || []
|
const currentTools = state.disabledTools[threadId] || []
|
||||||
let updatedTools: string[]
|
let updatedTools: string[]
|
||||||
|
|
||||||
if (available) {
|
if (available) {
|
||||||
// Add tool if not already present
|
// Remove disabled tool
|
||||||
updatedTools = currentTools.includes(toolName)
|
updatedTools = [...currentTools.filter((tool) => tool !== toolName)]
|
||||||
? currentTools
|
|
||||||
: [...currentTools, toolName]
|
|
||||||
} else {
|
} else {
|
||||||
// Remove tool
|
// Disable tool
|
||||||
updatedTools = currentTools.filter((tool) => tool !== toolName)
|
updatedTools = [...currentTools, toolName]
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
availableTools: {
|
disabledTools: {
|
||||||
...state.availableTools,
|
...state.disabledTools,
|
||||||
[threadId]: updatedTools,
|
[threadId]: updatedTools,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
isToolAvailable: (threadId: string, toolName: string) => {
|
isToolDisabled: (threadId: string, toolName: string) => {
|
||||||
const state = get()
|
const state = get()
|
||||||
// If no thread-specific settings, use default
|
// If no thread-specific settings, use default
|
||||||
if (!state.availableTools[threadId]) {
|
if (!state.disabledTools[threadId]) {
|
||||||
return state.defaultAvailableTools.includes(toolName)
|
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()
|
const state = get()
|
||||||
// If no thread-specific settings, use default
|
// If no thread-specific settings, use default
|
||||||
if (!state.availableTools[threadId]) {
|
if (!state.disabledTools[threadId]) {
|
||||||
return state.defaultAvailableTools
|
return state.defaultDisabledTools
|
||||||
}
|
}
|
||||||
return state.availableTools[threadId] || []
|
return state.disabledTools[threadId] || []
|
||||||
},
|
},
|
||||||
|
|
||||||
setDefaultAvailableTools: (toolNames: string[]) => {
|
setDefaultDisabledTools: (toolNames: string[]) => {
|
||||||
set({ defaultAvailableTools: toolNames })
|
set({ defaultDisabledTools: toolNames })
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultAvailableTools: () => {
|
getDefaultDisabledTools: () => {
|
||||||
return get().defaultAvailableTools
|
return get().defaultDisabledTools
|
||||||
},
|
},
|
||||||
|
|
||||||
initializeThreadTools: (threadId: string, allTools: MCPTool[]) => {
|
initializeThreadTools: (threadId: string, allTools: MCPTool[]) => {
|
||||||
const state = get()
|
const state = get()
|
||||||
// If thread already has settings, don't override
|
// If thread already has settings, don't override
|
||||||
if (state.availableTools[threadId]) {
|
if (state.disabledTools[threadId]) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize with default tools only
|
// Initialize with default tools only
|
||||||
// Don't auto-enable all tools if defaults are explicitly empty
|
// 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)
|
allTools.some((tool) => tool.name === toolName)
|
||||||
)
|
)
|
||||||
|
|
||||||
set((currentState) => ({
|
set((currentState) => ({
|
||||||
availableTools: {
|
disabledTools: {
|
||||||
...currentState.availableTools,
|
...currentState.disabledTools,
|
||||||
[threadId]: initialTools,
|
[threadId]: initialTools,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
@ -109,8 +107,8 @@ export const useToolAvailable = create<ToolAvailableState>()(
|
|||||||
storage: createJSONStorage(() => localStorage),
|
storage: createJSONStorage(() => localStorage),
|
||||||
// Persist all state
|
// Persist all state
|
||||||
partialize: (state) => ({
|
partialize: (state) => ({
|
||||||
availableTools: state.availableTools,
|
disabledTools: state.disabledTools,
|
||||||
defaultAvailableTools: state.defaultAvailableTools,
|
defaultDisabledTools: state.defaultDisabledTools,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -29,7 +29,7 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu'
|
} from '@/components/ui/dropdown-menu'
|
||||||
import { addModelSource, downloadModel } from '@/services/models'
|
import { addModelSource, downloadModel, fetchModelHub } from '@/services/models'
|
||||||
import { useDownloadStore } from '@/hooks/useDownloadStore'
|
import { useDownloadStore } from '@/hooks/useDownloadStore'
|
||||||
import { Progress } from '@/components/ui/progress'
|
import { Progress } from '@/components/ui/progress'
|
||||||
import HeaderPage from '@/containers/HeaderPage'
|
import HeaderPage from '@/containers/HeaderPage'
|
||||||
@ -82,6 +82,9 @@ function Hub() {
|
|||||||
[modelId]: !prev[modelId],
|
[modelId]: !prev[modelId],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
fetchModelHub().then(fetchSources)
|
||||||
|
}, [fetchSources])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (search.repo) {
|
if (search.repo) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user