mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-26 12:35:35 +02:00
feat(desktop): add React hooks for Hub, Tools, and Skills state
- Create use-hub.ts for Hub connection state and agent info - Create use-tools.ts for tools list and toggle functionality - Create use-skills.ts for skills list and management Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
210
apps/desktop/src/hooks/use-hub.ts
Normal file
210
apps/desktop/src/hooks/use-hub.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
|
||||
// ============================================================================
|
||||
// Types matching the IPC response from main process
|
||||
// ============================================================================
|
||||
|
||||
export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting'
|
||||
|
||||
export interface HubInfo {
|
||||
hubId: string
|
||||
url: string
|
||||
connectionState: ConnectionState
|
||||
agentCount: number
|
||||
}
|
||||
|
||||
export interface AgentInfo {
|
||||
id: string
|
||||
closed: boolean
|
||||
}
|
||||
|
||||
export interface UseHubReturn {
|
||||
/** Hub information */
|
||||
hubInfo: HubInfo | null
|
||||
/** List of agents */
|
||||
agents: AgentInfo[]
|
||||
/** Loading state */
|
||||
loading: boolean
|
||||
/** Error state */
|
||||
error: string | null
|
||||
|
||||
/** Initialize the Hub (called automatically on mount) */
|
||||
initHub: () => Promise<void>
|
||||
/** Refresh Hub info and agents list */
|
||||
refresh: () => Promise<void>
|
||||
/** Reconnect to a different Gateway URL */
|
||||
reconnect: (url: string) => Promise<void>
|
||||
/** Create a new agent */
|
||||
createAgent: (id?: string) => Promise<AgentInfo | null>
|
||||
/** Close an agent */
|
||||
closeAgent: (id: string) => Promise<boolean>
|
||||
/** Send a message to an agent */
|
||||
sendMessage: (agentId: string, content: string) => Promise<boolean>
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for managing Hub connection and agents via IPC.
|
||||
*
|
||||
* This hook communicates with the Electron main process to:
|
||||
* - Initialize and manage the Hub singleton
|
||||
* - Create, list, and close agents
|
||||
* - Send messages to agents
|
||||
*/
|
||||
export function useHub(): UseHubReturn {
|
||||
const [hubInfo, setHubInfo] = useState<HubInfo | null>(null)
|
||||
const [agents, setAgents] = useState<AgentInfo[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
// Initialize Hub and fetch info
|
||||
const initHub = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
// Initialize Hub (use new electronAPI if available)
|
||||
if (window.electronAPI) {
|
||||
await window.electronAPI.hub.init()
|
||||
const info = await window.electronAPI.hub.info()
|
||||
setHubInfo(info as HubInfo)
|
||||
const agentList = await window.electronAPI.hub.listAgents()
|
||||
setAgents(agentList as AgentInfo[])
|
||||
} else {
|
||||
await window.ipcRenderer.invoke('hub:init')
|
||||
const info = await window.ipcRenderer.invoke('hub:info')
|
||||
setHubInfo(info)
|
||||
const agentList = await window.ipcRenderer.invoke('hub:listAgents')
|
||||
setAgents(agentList)
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to initialize Hub')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Initial load
|
||||
useEffect(() => {
|
||||
initHub()
|
||||
}, [initHub])
|
||||
|
||||
// Refresh Hub info and agents
|
||||
const refresh = useCallback(async () => {
|
||||
try {
|
||||
setError(null)
|
||||
|
||||
if (window.electronAPI) {
|
||||
const info = await window.electronAPI.hub.info()
|
||||
setHubInfo(info as HubInfo)
|
||||
const agentList = await window.electronAPI.hub.listAgents()
|
||||
setAgents(agentList as AgentInfo[])
|
||||
} else {
|
||||
const info = await window.ipcRenderer.invoke('hub:info')
|
||||
setHubInfo(info)
|
||||
const agentList = await window.ipcRenderer.invoke('hub:listAgents')
|
||||
setAgents(agentList)
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to refresh Hub info')
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Reconnect to different Gateway
|
||||
const reconnect = useCallback(async (url: string) => {
|
||||
try {
|
||||
setError(null)
|
||||
if (window.electronAPI) {
|
||||
await window.electronAPI.hub.reconnect(url)
|
||||
} else {
|
||||
await window.ipcRenderer.invoke('hub:reconnect', url)
|
||||
}
|
||||
await refresh()
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to reconnect')
|
||||
}
|
||||
}, [refresh])
|
||||
|
||||
// Create a new agent
|
||||
const createAgent = useCallback(async (id?: string): Promise<AgentInfo | null> => {
|
||||
try {
|
||||
setError(null)
|
||||
const result = window.electronAPI
|
||||
? await window.electronAPI.hub.createAgent(id)
|
||||
: await window.ipcRenderer.invoke('hub:createAgent', id)
|
||||
|
||||
const typedResult = result as { error?: string; id?: string; closed?: boolean }
|
||||
if (typedResult.error) {
|
||||
setError(typedResult.error)
|
||||
return null
|
||||
}
|
||||
|
||||
// Refresh agents list
|
||||
await refresh()
|
||||
|
||||
return result as AgentInfo
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to create agent')
|
||||
return null
|
||||
}
|
||||
}, [refresh])
|
||||
|
||||
// Close an agent
|
||||
const closeAgent = useCallback(async (id: string): Promise<boolean> => {
|
||||
try {
|
||||
setError(null)
|
||||
const result = window.electronAPI
|
||||
? await window.electronAPI.hub.closeAgent(id)
|
||||
: await window.ipcRenderer.invoke('hub:closeAgent', id)
|
||||
|
||||
const typedResult = result as { ok?: boolean }
|
||||
if (!typedResult.ok) {
|
||||
setError(`Failed to close agent: ${id}`)
|
||||
return false
|
||||
}
|
||||
|
||||
// Refresh agents list
|
||||
await refresh()
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to close agent')
|
||||
return false
|
||||
}
|
||||
}, [refresh])
|
||||
|
||||
// Send message to agent
|
||||
const sendMessage = useCallback(async (agentId: string, content: string): Promise<boolean> => {
|
||||
try {
|
||||
setError(null)
|
||||
const result = window.electronAPI
|
||||
? await window.electronAPI.hub.sendMessage(agentId, content)
|
||||
: await window.ipcRenderer.invoke('hub:sendMessage', agentId, content)
|
||||
|
||||
const typedResult = result as { error?: string }
|
||||
if (typedResult.error) {
|
||||
setError(typedResult.error)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to send message')
|
||||
return false
|
||||
}
|
||||
}, [])
|
||||
|
||||
return {
|
||||
hubInfo,
|
||||
agents,
|
||||
loading,
|
||||
error,
|
||||
initHub,
|
||||
refresh,
|
||||
reconnect,
|
||||
createAgent,
|
||||
closeAgent,
|
||||
sendMessage,
|
||||
}
|
||||
}
|
||||
|
||||
export default useHub
|
||||
264
apps/desktop/src/hooks/use-skills.ts
Normal file
264
apps/desktop/src/hooks/use-skills.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react'
|
||||
|
||||
// ============================================================================
|
||||
// Types matching the IPC response from main process
|
||||
// ============================================================================
|
||||
|
||||
export type SkillSource = 'bundled' | 'global' | 'profile'
|
||||
|
||||
export interface SkillInfo {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
version: string
|
||||
enabled: boolean
|
||||
source: SkillSource
|
||||
triggers: string[]
|
||||
}
|
||||
|
||||
export interface SkillGroup {
|
||||
source: SkillSource
|
||||
name: string
|
||||
skills: SkillInfo[]
|
||||
}
|
||||
|
||||
// Source display names
|
||||
const SOURCE_NAMES: Record<string, string> = {
|
||||
bundled: 'Built-in Skills',
|
||||
global: 'Global Skills',
|
||||
profile: 'Profile Skills',
|
||||
}
|
||||
|
||||
export interface UseSkillsReturn {
|
||||
/** List of all skills */
|
||||
skills: SkillInfo[]
|
||||
/** Skills grouped by source */
|
||||
groups: SkillGroup[]
|
||||
/** Loading state */
|
||||
loading: boolean
|
||||
/** Error state */
|
||||
error: string | null
|
||||
|
||||
/** Toggle a skill on/off */
|
||||
toggleSkill: (skillId: string) => Promise<void>
|
||||
/** Enable a skill */
|
||||
enableSkill: (skillId: string) => Promise<void>
|
||||
/** Disable a skill */
|
||||
disableSkill: (skillId: string) => Promise<void>
|
||||
|
||||
/** Refresh skills list */
|
||||
refresh: () => Promise<void>
|
||||
|
||||
/** Get skill by ID */
|
||||
getSkill: (id: string) => SkillInfo | undefined
|
||||
|
||||
/** Filter skills by search query */
|
||||
filterSkills: (query: string) => SkillInfo[]
|
||||
|
||||
/** Check if a skill is enabled */
|
||||
isSkillEnabled: (skillId: string) => boolean
|
||||
|
||||
/** Stats */
|
||||
stats: {
|
||||
total: number
|
||||
enabled: number
|
||||
disabled: number
|
||||
bundled: number
|
||||
global: number
|
||||
profile: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for managing Agent skills configuration via IPC.
|
||||
*
|
||||
* This hook communicates with the Electron main process to:
|
||||
* - Fetch the list of all skills (bundled, global, profile)
|
||||
* - Toggle skills on/off
|
||||
* - Match the CLI `multica skills list` output
|
||||
*/
|
||||
export function useSkills(): UseSkillsReturn {
|
||||
const [skills, setSkills] = useState<SkillInfo[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
// Fetch skills from main process
|
||||
const fetchSkills = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
// Use new electronAPI if available, fallback to ipcRenderer
|
||||
const result = window.electronAPI
|
||||
? await window.electronAPI.skills.list()
|
||||
: await window.ipcRenderer.invoke('skills:list')
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
setSkills(result)
|
||||
} else {
|
||||
setError('Invalid response from skills:list')
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch skills')
|
||||
setSkills([])
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Initial fetch
|
||||
useEffect(() => {
|
||||
fetchSkills()
|
||||
}, [fetchSkills])
|
||||
|
||||
// Group skills by source
|
||||
const groups = useMemo<SkillGroup[]>(() => {
|
||||
const sourceOrder: SkillSource[] = ['bundled', 'global', 'profile']
|
||||
const groupMap = new Map<SkillSource, SkillInfo[]>()
|
||||
|
||||
for (const skill of skills) {
|
||||
const sourceSkills = groupMap.get(skill.source) || []
|
||||
sourceSkills.push(skill)
|
||||
groupMap.set(skill.source, sourceSkills)
|
||||
}
|
||||
|
||||
return sourceOrder
|
||||
.filter((source) => groupMap.has(source))
|
||||
.map((source) => ({
|
||||
source,
|
||||
name: SOURCE_NAMES[source] || source,
|
||||
skills: groupMap.get(source) || [],
|
||||
}))
|
||||
}, [skills])
|
||||
|
||||
// Stats
|
||||
const stats = useMemo(() => ({
|
||||
total: skills.length,
|
||||
enabled: skills.filter((s) => s.enabled).length,
|
||||
disabled: skills.filter((s) => !s.enabled).length,
|
||||
bundled: skills.filter((s) => s.source === 'bundled').length,
|
||||
global: skills.filter((s) => s.source === 'global').length,
|
||||
profile: skills.filter((s) => s.source === 'profile').length,
|
||||
}), [skills])
|
||||
|
||||
// Toggle skill via IPC
|
||||
const toggleSkill = useCallback(async (skillId: string) => {
|
||||
try {
|
||||
const result = window.electronAPI
|
||||
? await window.electronAPI.skills.toggle(skillId)
|
||||
: await window.ipcRenderer.invoke('skills:toggle', skillId)
|
||||
|
||||
const typedResult = result as { error?: string; enabled?: boolean }
|
||||
if (typedResult.error) {
|
||||
setError(typedResult.error)
|
||||
return
|
||||
}
|
||||
|
||||
// Update local state
|
||||
setSkills((prev) =>
|
||||
prev.map((skill) =>
|
||||
skill.id === skillId ? { ...skill, enabled: typedResult.enabled ?? !skill.enabled } : skill
|
||||
)
|
||||
)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to toggle skill')
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Enable skill via IPC
|
||||
const enableSkill = useCallback(async (skillId: string) => {
|
||||
try {
|
||||
const result = window.electronAPI
|
||||
? await window.electronAPI.skills.setStatus(skillId, true)
|
||||
: await window.ipcRenderer.invoke('skills:setStatus', skillId, true)
|
||||
|
||||
const typedResult = result as { error?: string }
|
||||
if (typedResult.error) {
|
||||
setError(typedResult.error)
|
||||
return
|
||||
}
|
||||
|
||||
setSkills((prev) =>
|
||||
prev.map((skill) =>
|
||||
skill.id === skillId ? { ...skill, enabled: true } : skill
|
||||
)
|
||||
)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to enable skill')
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Disable skill via IPC
|
||||
const disableSkill = useCallback(async (skillId: string) => {
|
||||
try {
|
||||
const result = window.electronAPI
|
||||
? await window.electronAPI.skills.setStatus(skillId, false)
|
||||
: await window.ipcRenderer.invoke('skills:setStatus', skillId, false)
|
||||
|
||||
const typedResult = result as { error?: string }
|
||||
if (typedResult.error) {
|
||||
setError(typedResult.error)
|
||||
return
|
||||
}
|
||||
|
||||
setSkills((prev) =>
|
||||
prev.map((skill) =>
|
||||
skill.id === skillId ? { ...skill, enabled: false } : skill
|
||||
)
|
||||
)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to disable skill')
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Get skill by ID
|
||||
const getSkill = useCallback(
|
||||
(id: string): SkillInfo | undefined => {
|
||||
return skills.find((s) => s.id === id)
|
||||
},
|
||||
[skills]
|
||||
)
|
||||
|
||||
// Filter skills by search query
|
||||
const filterSkills = useCallback(
|
||||
(query: string): SkillInfo[] => {
|
||||
if (!query.trim()) return skills
|
||||
|
||||
const lowerQuery = query.toLowerCase()
|
||||
return skills.filter(
|
||||
(skill) =>
|
||||
skill.name.toLowerCase().includes(lowerQuery) ||
|
||||
skill.id.toLowerCase().includes(lowerQuery) ||
|
||||
skill.description.toLowerCase().includes(lowerQuery) ||
|
||||
skill.triggers.some((t) => t.toLowerCase().includes(lowerQuery))
|
||||
)
|
||||
},
|
||||
[skills]
|
||||
)
|
||||
|
||||
// Check if skill is enabled
|
||||
const isSkillEnabled = useCallback(
|
||||
(skillId: string): boolean => {
|
||||
const skill = skills.find((s) => s.id === skillId)
|
||||
return skill?.enabled ?? false
|
||||
},
|
||||
[skills]
|
||||
)
|
||||
|
||||
return {
|
||||
skills,
|
||||
groups,
|
||||
loading,
|
||||
error,
|
||||
toggleSkill,
|
||||
enableSkill,
|
||||
disableSkill,
|
||||
refresh: fetchSkills,
|
||||
getSkill,
|
||||
filterSkills,
|
||||
isSkillEnabled,
|
||||
stats,
|
||||
}
|
||||
}
|
||||
|
||||
export default useSkills
|
||||
232
apps/desktop/src/hooks/use-tools.ts
Normal file
232
apps/desktop/src/hooks/use-tools.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react'
|
||||
|
||||
// ============================================================================
|
||||
// Types matching the IPC response from main process
|
||||
// ============================================================================
|
||||
|
||||
export interface ToolInfo {
|
||||
name: string
|
||||
description?: string
|
||||
group: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface ToolGroup {
|
||||
id: string
|
||||
name: string
|
||||
tools: string[]
|
||||
}
|
||||
|
||||
// Tool descriptions (for UI display)
|
||||
const TOOL_DESCRIPTIONS: Record<string, string> = {
|
||||
read: 'Read file contents',
|
||||
write: 'Write content to file',
|
||||
edit: 'Edit file with search/replace',
|
||||
glob: 'Find files by pattern',
|
||||
exec: 'Execute shell commands',
|
||||
process: 'Manage background processes',
|
||||
web_fetch: 'Fetch content from URLs',
|
||||
web_search: 'Search the web (requires API key)',
|
||||
memory_get: 'Get stored memory value',
|
||||
memory_set: 'Store a memory value',
|
||||
memory_delete: 'Delete a memory value',
|
||||
memory_list: 'List all memory keys',
|
||||
}
|
||||
|
||||
// Group display names
|
||||
const GROUP_NAMES: Record<string, string> = {
|
||||
fs: 'File System',
|
||||
runtime: 'Runtime',
|
||||
web: 'Web',
|
||||
memory: 'Memory',
|
||||
other: 'Other',
|
||||
}
|
||||
|
||||
export interface UseToolsReturn {
|
||||
/** List of all tools with their status */
|
||||
tools: ToolInfo[]
|
||||
/** List of tool groups */
|
||||
groups: ToolGroup[]
|
||||
/** Loading state */
|
||||
loading: boolean
|
||||
/** Error state */
|
||||
error: string | null
|
||||
|
||||
/** Toggle a specific tool on/off */
|
||||
toggleTool: (toolName: string) => Promise<void>
|
||||
/** Enable a tool */
|
||||
enableTool: (toolName: string) => Promise<void>
|
||||
/** Disable a tool */
|
||||
disableTool: (toolName: string) => Promise<void>
|
||||
|
||||
/** Refresh tools list from main process */
|
||||
refresh: () => Promise<void>
|
||||
|
||||
/** Check if a tool is enabled */
|
||||
isToolEnabled: (toolName: string) => boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for managing Agent tools configuration via IPC.
|
||||
*
|
||||
* This hook communicates with the Electron main process to:
|
||||
* - Fetch the list of available tools and their status
|
||||
* - Toggle tools on/off (persisted to credentials.json5)
|
||||
* - Trigger agent.reloadTools() to apply changes immediately
|
||||
*/
|
||||
export function useTools(): UseToolsReturn {
|
||||
const [tools, setTools] = useState<ToolInfo[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
// Fetch tools from main process
|
||||
const fetchTools = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
// Use new electronAPI if available, fallback to ipcRenderer
|
||||
const result = window.electronAPI
|
||||
? await window.electronAPI.tools.list()
|
||||
: await window.ipcRenderer.invoke('tools:list')
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
// Add descriptions to tools
|
||||
const toolsWithDesc = result.map((tool: { name: string; enabled: boolean; group: string }) => ({
|
||||
...tool,
|
||||
description: TOOL_DESCRIPTIONS[tool.name],
|
||||
}))
|
||||
setTools(toolsWithDesc)
|
||||
} else {
|
||||
setError('Invalid response from tools:list')
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch tools')
|
||||
// Fallback to empty list
|
||||
setTools([])
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Initial fetch
|
||||
useEffect(() => {
|
||||
fetchTools()
|
||||
}, [fetchTools])
|
||||
|
||||
// Build groups list from tools
|
||||
const groups = useMemo<ToolGroup[]>(() => {
|
||||
const groupMap = new Map<string, string[]>()
|
||||
|
||||
for (const tool of tools) {
|
||||
const groupTools = groupMap.get(tool.group) || []
|
||||
groupTools.push(tool.name)
|
||||
groupMap.set(tool.group, groupTools)
|
||||
}
|
||||
|
||||
return Array.from(groupMap.entries()).map(([id, toolNames]) => ({
|
||||
id,
|
||||
name: GROUP_NAMES[id] || id,
|
||||
tools: toolNames,
|
||||
}))
|
||||
}, [tools])
|
||||
|
||||
// Toggle tool via IPC
|
||||
const toggleTool = useCallback(async (toolName: string) => {
|
||||
console.log('[useTools] toggleTool called:', toolName)
|
||||
try {
|
||||
const result = window.electronAPI
|
||||
? await window.electronAPI.tools.toggle(toolName)
|
||||
: await window.ipcRenderer.invoke('tools:toggle', toolName)
|
||||
|
||||
console.log('[useTools] toggleTool result:', result)
|
||||
|
||||
const typedResult = result as { error?: string; enabled?: boolean }
|
||||
if (typedResult.error) {
|
||||
console.error('[useTools] toggleTool error:', typedResult.error)
|
||||
setError(typedResult.error)
|
||||
return
|
||||
}
|
||||
|
||||
// Update local state
|
||||
console.log('[useTools] Updating tool state:', toolName, 'enabled:', typedResult.enabled)
|
||||
setTools((prev) =>
|
||||
prev.map((tool) =>
|
||||
tool.name === toolName ? { ...tool, enabled: typedResult.enabled ?? !tool.enabled } : tool
|
||||
)
|
||||
)
|
||||
} catch (err) {
|
||||
console.error('[useTools] toggleTool exception:', err)
|
||||
setError(err instanceof Error ? err.message : 'Failed to toggle tool')
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Enable tool via IPC
|
||||
const enableTool = useCallback(async (toolName: string) => {
|
||||
try {
|
||||
const result = window.electronAPI
|
||||
? await window.electronAPI.tools.setStatus(toolName, true)
|
||||
: await window.ipcRenderer.invoke('tools:setStatus', toolName, true)
|
||||
|
||||
const typedResult = result as { error?: string }
|
||||
if (typedResult.error) {
|
||||
setError(typedResult.error)
|
||||
return
|
||||
}
|
||||
|
||||
setTools((prev) =>
|
||||
prev.map((tool) =>
|
||||
tool.name === toolName ? { ...tool, enabled: true } : tool
|
||||
)
|
||||
)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to enable tool')
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Disable tool via IPC
|
||||
const disableTool = useCallback(async (toolName: string) => {
|
||||
try {
|
||||
const result = window.electronAPI
|
||||
? await window.electronAPI.tools.setStatus(toolName, false)
|
||||
: await window.ipcRenderer.invoke('tools:setStatus', toolName, false)
|
||||
|
||||
const typedResult = result as { error?: string }
|
||||
if (typedResult.error) {
|
||||
setError(typedResult.error)
|
||||
return
|
||||
}
|
||||
|
||||
setTools((prev) =>
|
||||
prev.map((tool) =>
|
||||
tool.name === toolName ? { ...tool, enabled: false } : tool
|
||||
)
|
||||
)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to disable tool')
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Check if tool is enabled
|
||||
const isToolEnabled = useCallback(
|
||||
(toolName: string): boolean => {
|
||||
const tool = tools.find((t) => t.name === toolName)
|
||||
return tool?.enabled ?? false
|
||||
},
|
||||
[tools]
|
||||
)
|
||||
|
||||
return {
|
||||
tools,
|
||||
groups,
|
||||
loading,
|
||||
error,
|
||||
toggleTool,
|
||||
enableTool,
|
||||
disableTool,
|
||||
refresh: fetchTools,
|
||||
isToolEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
export default useTools
|
||||
Reference in New Issue
Block a user