add switch mode tool

This commit is contained in:
duanfuxiang
2025-03-17 09:12:49 +08:00
parent 9a5e5f3880
commit 4aa321dffc
14 changed files with 264 additions and 84 deletions

View File

@@ -14,6 +14,7 @@ import {
} from 'react'
import { v4 as uuidv4 } from 'uuid'
import { ModeSelect } from './chat-input/ModeSelect'
import { ApplyViewState } from '../../ApplyView'
import { APPLY_VIEW_TYPE } from '../../constants'
import { useApp } from '../../contexts/AppContext'
@@ -90,7 +91,7 @@ export type ChatProps = {
const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
const app = useApp()
const { settings } = useSettings()
const { settings, setSettings } = useSettings()
const { getRAGEngine } = useRAG()
const {
@@ -565,6 +566,25 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
mentionables: [],
}
}
} else if (toolArgs.type === 'switch_mode') {
setSettings({
...settings,
mode: toolArgs.mode,
})
const formattedContent = `[switch_mode to ${toolArgs.mode}] Result: successfully switched to ${toolArgs.mode}\n`
return {
type: 'switch_mode',
applyMsgId,
applyStatus: ApplyStatus.Applied,
returnMsg: {
role: 'user',
applyStatus: ApplyStatus.Idle,
content: null,
promptContent: formattedContent,
id: uuidv4(),
mentionables: [],
}
}
}
} catch (error) {
console.error('Failed to apply changes', error)
@@ -745,7 +765,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
return (
<div className="infio-chat-container">
<div className="infio-chat-header">
<h1 className="infio-chat-header-title"> CHAT </h1>
<ModeSelect />
<div className="infio-chat-header-buttons">
<button
onClick={() => handleNewChat()}

View File

@@ -0,0 +1,82 @@
import { Check, Loader2, Settings2, X } from 'lucide-react'
import { PropsWithChildren, useState } from 'react'
import { useDarkModeContext } from '../../contexts/DarkModeContext'
import { ApplyStatus, ToolArgs } from '../../types/apply'
import { MemoizedSyntaxHighlighterWrapper } from './SyntaxHighlighterWrapper'
export default function MarkdownSwitchModeBlock({
mode,
applyStatus,
onApply,
reason,
finish,
}: PropsWithChildren<{
mode: string
applyStatus: ApplyStatus
onApply: (args: ToolArgs) => void
reason: string
finish: boolean
}>) {
const [applying, setApplying] = useState(false)
const { isDarkMode } = useDarkModeContext()
const handleApply = async () => {
if (applyStatus !== ApplyStatus.Idle) {
return
}
setApplying(true)
onApply({
type: 'switch_mode',
mode: mode,
reason: reason,
finish: finish,
})
}
return (
<div className={`infio-chat-code-block has-filename`}>
<div className={'infio-chat-code-block-header'}>
<div className={'infio-chat-code-block-header-filename'}>
<Settings2 size={10} className="infio-chat-code-block-header-icon" />
Switch to &quot;{mode.charAt(0).toUpperCase() + mode.slice(1)}&quot; mode
</div>
<div className={'infio-chat-code-block-header-button'}>
<button
onClick={handleApply}
style={{ color: '#008000' }}
disabled={applyStatus !== ApplyStatus.Idle || applying}
>
{applyStatus === ApplyStatus.Idle ? (
applying ? (
<>
<Loader2 className="spinner" size={14} /> Allowing...
</>
) : (
'Allow'
)
) : applyStatus === ApplyStatus.Applied ? (
<>
<Check size={14} /> Success
</>
) : (
<>
<X size={14} /> Failed
</>
)}
</button>
</div>
</div>
<MemoizedSyntaxHighlighterWrapper
isDarkMode={isDarkMode}
language="markdown"
hasFilename={true}
wrapLines={true}
isOpen={true}
>
{reason}
</MemoizedSyntaxHighlighterWrapper>
</div>
)
}

View File

@@ -16,6 +16,7 @@ import MarkdownRegexSearchFilesBlock from './MarkdownRegexSearchFilesBlock'
import MarkdownSearchAndReplace from './MarkdownSearchAndReplace'
import MarkdownSearchWebBlock from './MarkdownSearchWebBlock'
import MarkdownSemanticSearchFilesBlock from './MarkdownSemanticSearchFilesBlock'
import MarkdownSwitchModeBlock from './MarkdownSwitchModeBlock'
import MarkdownWithIcons from './MarkdownWithIcon'
function ReactMarkdown({
applyStatus,
@@ -132,6 +133,15 @@ function ReactMarkdown({
markdownContent={
`<icon name='ask_followup_question' size={14} className="infio-markdown-icon" />
${block.question && block.question.trimStart()}`} />
) : block.type === 'switch_mode' ? (
<MarkdownSwitchModeBlock
key={"switch-mode-" + index}
applyStatus={applyStatus}
onApply={onApply}
mode={block.mode}
reason={block.reason}
finish={block.finish}
/>
) : block.type === 'search_web' ? (
<MarkdownSearchWebBlock
key={"search-web-" + index}

View File

@@ -0,0 +1,53 @@
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import { ChevronDown, ChevronUp } from 'lucide-react'
import { useEffect, useState } from 'react'
import { useSettings } from '../../../contexts/SettingsContext'
import { modes } from '../../../utils/modes'
export function ModeSelect() {
const { settings, setSettings } = useSettings()
const [isOpen, setIsOpen] = useState(false)
const [mode, setMode] = useState(settings.mode)
useEffect(() => {
setMode(settings.mode)
}, [settings.mode])
return (
<DropdownMenu.Root open={isOpen} onOpenChange={setIsOpen}>
<DropdownMenu.Trigger className="infio-chat-input-model-select">
<div className="infio-chat-input-model-select__icon">
{isOpen ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
</div>
<div className="infio-chat-input-model-select__model-name">
{modes.find((m) => m.slug === mode)?.name}
</div>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="infio-popover">
<ul>
{modes.map((mode) => (
<DropdownMenu.Item
key={mode.slug}
onSelect={() => {
setMode(mode.slug)
setSettings({
...settings,
mode: mode.slug,
})
}}
asChild
>
<li>{mode.name}</li>
</DropdownMenu.Item>
))}
</ul>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
)
}

View File

@@ -18,15 +18,16 @@ export function ModelSelect() {
try {
const models = await GetProviderModelIds(settings.chatModelProvider)
setProviderModels(models)
setChatModelId(settings.chatModelId)
} catch (error) {
console.error('Failed to fetch provider models:', error)
} finally {
setIsLoading(false)
}
}
fetchModels()
}, [settings.chatModelProvider])
}, [settings])
return (
<DropdownMenu.Root open={isOpen} onOpenChange={setIsOpen}>

View File

@@ -30,6 +30,7 @@ import { ImageUploadButton } from './ImageUploadButton'
import LexicalContentEditable from './LexicalContentEditable'
import MentionableBadge from './MentionableBadge'
import { ModelSelect } from './ModelSelect'
import { ModeSelect } from './ModeSelect'
import { MentionNode } from './plugins/mention/MentionNode'
import { NodeMutations } from './plugins/on-mutation/OnMutationPlugin'
import { SubmitButton } from './SubmitButton'