* Refactor inputbar system with configurable scope-based architecture - **Implement scope-based configuration** for chat, agent sessions, and mini-window with feature toggles - **Add tool registry system** with dependency injection for modular inputbar tools - **Create shared state management** via InputbarToolsProvider for consistent state handling - **Migrate existing tools** to registry-based definitions with proper scope filtering The changes introduce a flexible inputbar architecture that supports different use cases through scope-based configuration while maintaining feature parity and improving code organization. * Remove unused import and refactor tool rendering - Delete obsolete '@renderer/pages/home/Inputbar/tools' import from Inputbar.tsx - Extract ToolButton component to render tools outside useMemo dependency cycle - Store tool definitions in config for deferred rendering with current context - Fix potential stale closure issues in tool rendering by rebuilding context on each render * Wrap ToolButton in React.memo and optimize quick panel menu updates - Memoize ToolButton component to prevent unnecessary re-renders when tool key remains unchanged - Replace direct menu state updates with version-based triggering to batch registry changes - Add useEffect to consolidate menu updates and reduce redundant flat operations * chore style * refactor(InputbarToolsProvider): simplify quick panel menu update logic * Improve QuickPanel behavior and input handling - Default select first item when panel symbol changes to enhance user experience - Add Tab key support for selecting template variables in input field - Refactor QuickPanel trigger logic with better symbol tracking and boundary checks - Fix typo in translation key for model selection menu item * Refactor import statements to use type-only imports - Convert inline type imports to explicit type imports in Inputbar.tsx and types.ts - Replace combined type/value imports with separate type imports in InputbarToolsProvider and tools - Remove unnecessary menu version state and effect in InputbarToolsProvider * Refactor InputbarTools context to separate state and dispatch concerns - Split single context into separate state and dispatch contexts to optimize re-renders - Introduce derived state for `couldMentionNotVisionModel` based on file types - Encapsulate Quick Panel API in stable object with memoized functions - Add internal dispatch context for Inputbar-specific state setters * Refactor Inputbar to use split context hooks and optimize QuickPanel - Replace monolithic `useInputbarTools` with separate state, dispatch, and internal dispatch hooks - Move text state from context to local component state in InputbarInner - Optimize QuickPanel trigger registration to use ref pattern, avoiding frequent re-registrations * Refactor QuickPanel API to separate concerns between tools and inputbar - Split QuickPanel API into `toolsRegistry` for tool registration and `triggers` for inputbar triggering - Remove unused QuickPanel state variables and clean up dependencies - Update tool context to use new API structure with proper type safety * Optimize the state management of QuickPanel and Inputbar, add text update functionality, and improve the tool registration logic. * chore * Add reusable React hooks and InputbarCore component for chat input - Create `useInputText`, `useKeyboardHandler`, and `useTextareaResize` hooks for text management, keyboard shortcuts, and auto-resizing - Implement `InputbarCore` component with modular toolbar sections, drag-drop support, and textarea customization - Add `useFileDragDrop` and `usePasteHandler` hooks for file uploads and paste handling with type filtering * Refactor Inputbar to use custom hooks for text and textarea management - Replace manual text state with useInputText hook for text management and empty state - Replace textarea resize logic with useTextareaResize hook for automatic height adjustment - Add comprehensive refactoring documentation with usage examples and guidelines * Refactor inputbar drag-drop and paste handling into custom hooks - Extract paste handling logic into usePasteHandler hook - Extract drag-drop file handling into useFileDragDrop hook - Remove inline drag-drop state and handlers, use hook interfaces - Clean up dependencies and callback optimizations * Refactor Inputbar component to use InputbarCore composition - Extract complex UI logic into InputbarCore component for better separation of concerns - Remove intermediate wrapper component and action ref forwarding pattern - Consolidate focus/blur handlers and simplify component structure * Refactor Inputbar to expose actions via ref for external control - Extract action handlers into ProviderActionHandlers interface and expose via ref - Split component into Inputbar wrapper and InputbarInner implementation - Update useEffect to sync inner component actions with ref for external access * feat: inputbar core * refactor: Update QuickPanel integration across various tools * refactor: migrate to antd * chore: format * fix: clean code * clean code * fix i18n * fix: i18n * relative path * model type * 🤖 Weekly Automated Update: Nov 09, 2025 (#11209) feat(bot): Weekly automated script run Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com> Co-authored-by: SuYao <sy20010504@gmail.com> * format * fix * fix: format * use ripgrep * update with input * add common filters * fix build issue * format * fix error * smooth change * adjust * support listing dir * keep list files when focus and blur * support draft save * Optimize the rendering logic of session messages and input bars, and simplify conditional judgments. * Upgrade to agentId * format * 🐛 fix: force quick triggers for agent sessions * revert * fix migrate * fix: filter * fix: trigger * chore packages * feat: 添加过滤和排序功能,支持自定义函数 * fix cursor bug * fix format --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: beyondkmp <beyondkmp@gmail.com> Co-authored-by: kangfenmao <kangfenmao@qq.com>
235 lines
8.0 KiB
TypeScript
235 lines
8.0 KiB
TypeScript
import { BaiduOutlined, GoogleOutlined } from '@ant-design/icons'
|
|
import { loggerService } from '@logger'
|
|
import { BingLogo, BochaLogo, ExaLogo, SearXNGLogo, TavilyLogo, ZhipuLogo } from '@renderer/components/Icons'
|
|
import type { QuickPanelListItem } from '@renderer/components/QuickPanel'
|
|
import { QuickPanelReservedSymbol } from '@renderer/components/QuickPanel'
|
|
import {
|
|
isGeminiModel,
|
|
isGPT5SeriesReasoningModel,
|
|
isOpenAIWebSearchModel,
|
|
isWebSearchModel
|
|
} from '@renderer/config/models'
|
|
import { isGeminiWebSearchProvider } from '@renderer/config/providers'
|
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
|
import { useTimer } from '@renderer/hooks/useTimer'
|
|
import { useWebSearchProviders } from '@renderer/hooks/useWebSearchProviders'
|
|
import type { ToolQuickPanelController, ToolRenderContext } from '@renderer/pages/home/Inputbar/types'
|
|
import { getProviderByModel } from '@renderer/services/AssistantService'
|
|
import WebSearchService from '@renderer/services/WebSearchService'
|
|
import type { WebSearchProvider, WebSearchProviderId } from '@renderer/types'
|
|
import { hasObjectKey } from '@renderer/utils'
|
|
import { isToolUseModeFunction } from '@renderer/utils/assistant'
|
|
import { Globe } from 'lucide-react'
|
|
import { useCallback, useEffect, useMemo } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
|
|
const logger = loggerService.withContext('WebSearchQuickPanel')
|
|
|
|
export const WebSearchProviderIcon = ({
|
|
pid,
|
|
size = 18,
|
|
color
|
|
}: {
|
|
pid?: WebSearchProviderId
|
|
size?: number
|
|
color?: string
|
|
}) => {
|
|
switch (pid) {
|
|
case 'bocha':
|
|
return <BochaLogo className="icon" width={size} height={size} color={color} />
|
|
case 'exa':
|
|
return <ExaLogo className="icon" width={size - 2} height={size} color={color} />
|
|
case 'tavily':
|
|
return <TavilyLogo className="icon" width={size} height={size} color={color} />
|
|
case 'zhipu':
|
|
return <ZhipuLogo className="icon" width={size} height={size} color={color} />
|
|
case 'searxng':
|
|
return <SearXNGLogo className="icon" width={size} height={size} color={color} />
|
|
case 'local-baidu':
|
|
return <BaiduOutlined size={size} style={{ color, fontSize: size }} />
|
|
case 'local-bing':
|
|
return <BingLogo className="icon" width={size} height={size} color={color} />
|
|
case 'local-google':
|
|
return <GoogleOutlined size={size} style={{ color, fontSize: size }} />
|
|
default:
|
|
return <Globe className="icon" size={size} style={{ color, fontSize: size }} />
|
|
}
|
|
}
|
|
|
|
export const useWebSearchPanelController = (assistantId: string, quickPanelController: ToolQuickPanelController) => {
|
|
const { t } = useTranslation()
|
|
const { assistant, updateAssistant } = useAssistant(assistantId)
|
|
const { providers } = useWebSearchProviders()
|
|
const { setTimeoutTimer } = useTimer()
|
|
|
|
const enableWebSearch = assistant?.webSearchProviderId || assistant.enableWebSearch
|
|
|
|
const updateWebSearchProvider = useCallback(
|
|
async (providerId?: WebSearchProvider['id']) => {
|
|
setTimeoutTimer('updateWebSearchProvider', () => {
|
|
updateAssistant({
|
|
...assistant,
|
|
webSearchProviderId: providerId,
|
|
enableWebSearch: false
|
|
})
|
|
})
|
|
},
|
|
[assistant, setTimeoutTimer, updateAssistant]
|
|
)
|
|
|
|
const updateQuickPanelItem = useCallback(
|
|
async (providerId?: WebSearchProvider['id']) => {
|
|
if (providerId === assistant.webSearchProviderId) {
|
|
updateWebSearchProvider(undefined)
|
|
} else {
|
|
updateWebSearchProvider(providerId)
|
|
}
|
|
},
|
|
[assistant.webSearchProviderId, updateWebSearchProvider]
|
|
)
|
|
|
|
const updateToModelBuiltinWebSearch = useCallback(async () => {
|
|
const update = {
|
|
...assistant,
|
|
webSearchProviderId: undefined,
|
|
enableWebSearch: !assistant.enableWebSearch
|
|
}
|
|
const model = assistant.model
|
|
const provider = getProviderByModel(model)
|
|
if (!model) {
|
|
logger.error('Model does not exist.')
|
|
window.toast.error(t('error.model.not_exists'))
|
|
return
|
|
}
|
|
if (
|
|
isGeminiWebSearchProvider(provider) &&
|
|
isGeminiModel(model) &&
|
|
isToolUseModeFunction(assistant) &&
|
|
update.enableWebSearch &&
|
|
assistant.mcpServers &&
|
|
assistant.mcpServers.length > 0
|
|
) {
|
|
update.enableWebSearch = false
|
|
window.toast.warning(t('chat.mcp.warning.gemini_web_search'))
|
|
}
|
|
if (
|
|
isOpenAIWebSearchModel(model) &&
|
|
isGPT5SeriesReasoningModel(model) &&
|
|
update.enableWebSearch &&
|
|
assistant.settings?.reasoning_effort === 'minimal'
|
|
) {
|
|
update.enableWebSearch = false
|
|
window.toast.warning(t('chat.web_search.warning.openai'))
|
|
}
|
|
setTimeoutTimer('updateSelectedWebSearchBuiltin', () => updateAssistant(update), 200)
|
|
}, [assistant, setTimeoutTimer, t, updateAssistant])
|
|
|
|
const providerItems = useMemo<QuickPanelListItem[]>(() => {
|
|
const isWebSearchModelEnabled = assistant.model && isWebSearchModel(assistant.model)
|
|
const items: QuickPanelListItem[] = providers
|
|
.map((p) => ({
|
|
label: p.name,
|
|
description: WebSearchService.isWebSearchEnabled(p.id)
|
|
? hasObjectKey(p, 'apiKey')
|
|
? t('settings.tool.websearch.apikey')
|
|
: t('settings.tool.websearch.free')
|
|
: t('chat.input.web_search.enable_content'),
|
|
icon: <WebSearchProviderIcon size={13} pid={p.id} />,
|
|
isSelected: p.id === assistant?.webSearchProviderId,
|
|
disabled: !WebSearchService.isWebSearchEnabled(p.id),
|
|
action: () => updateQuickPanelItem(p.id)
|
|
}))
|
|
.filter((item) => !item.disabled)
|
|
|
|
if (isWebSearchModelEnabled) {
|
|
items.unshift({
|
|
label: t('chat.input.web_search.builtin.label'),
|
|
description: isWebSearchModelEnabled
|
|
? t('chat.input.web_search.builtin.enabled_content')
|
|
: t('chat.input.web_search.builtin.disabled_content'),
|
|
icon: <Globe />,
|
|
isSelected: assistant.enableWebSearch,
|
|
disabled: !isWebSearchModelEnabled,
|
|
action: () => updateToModelBuiltinWebSearch()
|
|
})
|
|
}
|
|
|
|
return items
|
|
}, [
|
|
assistant.enableWebSearch,
|
|
assistant.model,
|
|
assistant?.webSearchProviderId,
|
|
providers,
|
|
t,
|
|
updateQuickPanelItem,
|
|
updateToModelBuiltinWebSearch
|
|
])
|
|
|
|
const openQuickPanel = useCallback(() => {
|
|
quickPanelController.open({
|
|
title: t('chat.input.web_search.label'),
|
|
list: providerItems,
|
|
symbol: QuickPanelReservedSymbol.WebSearch,
|
|
pageSize: 9
|
|
})
|
|
}, [providerItems, quickPanelController, t])
|
|
|
|
const toggleQuickPanel = useCallback(() => {
|
|
if (quickPanelController.isVisible && quickPanelController.symbol === QuickPanelReservedSymbol.WebSearch) {
|
|
quickPanelController.close()
|
|
} else {
|
|
openQuickPanel()
|
|
}
|
|
}, [openQuickPanel, quickPanelController])
|
|
|
|
return {
|
|
enableWebSearch,
|
|
providerItems,
|
|
openQuickPanel,
|
|
toggleQuickPanel,
|
|
updateWebSearchProvider,
|
|
updateToModelBuiltinWebSearch,
|
|
selectedProviderId: assistant.webSearchProviderId
|
|
}
|
|
}
|
|
|
|
interface ManagerProps {
|
|
context: ToolRenderContext<any, any>
|
|
}
|
|
|
|
const WebSearchQuickPanelManager = ({ context }: ManagerProps) => {
|
|
const { assistant, quickPanel, quickPanelController, t } = context
|
|
const { providerItems, openQuickPanel } = useWebSearchPanelController(assistant.id, quickPanelController)
|
|
const { registerRootMenu, registerTrigger } = quickPanel
|
|
const { updateList, isVisible, symbol } = quickPanelController
|
|
|
|
useEffect(() => {
|
|
if (isVisible && symbol === QuickPanelReservedSymbol.WebSearch) {
|
|
updateList(providerItems)
|
|
}
|
|
}, [isVisible, providerItems, symbol, updateList])
|
|
|
|
useEffect(() => {
|
|
const disposeMenu = registerRootMenu([
|
|
{
|
|
label: t('chat.input.web_search.label'),
|
|
description: '',
|
|
icon: <Globe size={18} />,
|
|
isMenu: true,
|
|
action: () => openQuickPanel()
|
|
}
|
|
])
|
|
|
|
const disposeTrigger = registerTrigger(QuickPanelReservedSymbol.WebSearch, () => openQuickPanel())
|
|
|
|
return () => {
|
|
disposeMenu()
|
|
disposeTrigger()
|
|
}
|
|
}, [openQuickPanel, registerRootMenu, registerTrigger, t])
|
|
|
|
return null
|
|
}
|
|
|
|
export default WebSearchQuickPanelManager
|