feat: Adding proactive button as experimental feature

This commit is contained in:
Vanalite 2025-10-27 18:18:23 +07:00
parent 2561fcd78a
commit c773abb688
12 changed files with 73 additions and 7 deletions

View File

@ -10,6 +10,7 @@ import {
IconAtom,
IconWorld,
IconCodeCircle2,
IconSparkles,
} from '@tabler/icons-react'
import { Fragment } from 'react/jsx-runtime'
@ -29,6 +30,8 @@ const Capabilities = ({ capabilities }: CapabilitiesProps) => {
icon = <IconEye className="size-4" />
} else if (capability === 'tools') {
icon = <IconTool className="size-3.5" />
} else if (capability === 'proactive') {
icon = <IconSparkles className="size-3.5" />
} else if (capability === 'reasoning') {
icon = <IconAtom className="size-3.5" />
} else if (capability === 'embeddings') {
@ -54,7 +57,11 @@ const Capabilities = ({ capabilities }: CapabilitiesProps) => {
</TooltipTrigger>
<TooltipContent>
<p>
{capability === 'web_search' ? 'Web Search' : capability}
{capability === 'web_search'
? 'Web Search'
: capability === 'proactive'
? 'Proactive'
: capability}
</p>
</TooltipContent>
</Tooltip>

View File

@ -108,6 +108,7 @@ const ChatInput = ({
const [connectedServers, setConnectedServers] = useState<string[]>([])
const [isDragOver, setIsDragOver] = useState(false)
const [hasMmproj, setHasMmproj] = useState(false)
const [hasProactive, setHasProactive] = useState(false)
const [hasActiveModels, setHasActiveModels] = useState(false)
const attachmentsEnabled = useAttachments((s) => s.enabled)
// Determine whether to show the Attach documents button (simple gating)
@ -206,6 +207,29 @@ const ChatInput = ({
checkMmprojSupport()
}, [selectedModel, selectedModel?.capabilities, selectedProvider, serviceHub])
// Check for proactive capability when model changes
useEffect(() => {
const checkProactiveSupport = () => {
if (selectedModel && selectedModel?.id) {
// Proactive mode requires both tools and vision capabilities
const hasTools = selectedModel?.capabilities?.includes('tools')
const hasVision = selectedModel?.capabilities?.includes('vision')
const hasProactiveCapability = selectedModel?.capabilities?.includes('proactive')
if (hasTools && hasVision && hasProactiveCapability) {
setHasProactive(true)
// TODO: Implement proactive mode template insertion
// This is where we'll add the proactive mode prompt/template
// when sending messages with models that have proactive capability enabled
} else {
setHasProactive(false)
}
}
}
checkProactiveSupport()
}, [selectedModel, selectedModel?.capabilities])
// Check if there are active MCP servers
const hasActiveMCPServers = connectedServers.length > 0 || tools.length > 0

View File

@ -152,12 +152,19 @@ export const ModelInfoHoverCard = ({
</div>
{/* Features Section */}
{(model.num_mmproj > 0 || model.tools) && (
{(model.num_mmproj > 0 || model.tools || (model.num_mmproj > 0 && model.tools)) && (
<div className="border-t border-main-view-fg/10 pt-3">
<h5 className="text-xs font-medium text-main-view-fg/70 mb-2">
Features
</h5>
<div className="flex flex-wrap gap-2">
{model.tools && (
<div className="flex items-center gap-1.5 px-2 py-1 bg-main-view-fg/10 rounded-md">
<span className="text-xs text-main-view-fg font-medium">
Tools
</span>
</div>
)}
{model.num_mmproj > 0 && (
<div className="flex items-center gap-1.5 px-2 py-1 bg-main-view-fg/10 rounded-md">
<span className="text-xs text-main-view-fg font-medium">
@ -165,10 +172,10 @@ export const ModelInfoHoverCard = ({
</span>
</div>
)}
{model.tools && (
{model.num_mmproj > 0 && model.tools && (
<div className="flex items-center gap-1.5 px-2 py-1 bg-main-view-fg/10 rounded-md">
<span className="text-xs text-main-view-fg font-medium">
Tools
Proactive
</span>
</div>
)}

View File

@ -82,6 +82,7 @@ vi.mock('@tabler/icons-react', () => ({
IconEye: () => <div data-testid="eye-icon" />,
IconTool: () => <div data-testid="tool-icon" />,
IconLoader2: () => <div data-testid="loader-icon" />,
IconSparkles: () => <div data-testid="sparkles-icon" />,
}))
describe('DialogEditModel - Basic Component Tests', () => {
@ -189,7 +190,7 @@ describe('DialogEditModel - Basic Component Tests', () => {
{
id: 'test-model.gguf',
displayName: 'Test Model',
capabilities: ['vision', 'tools'],
capabilities: ['vision', 'tools', 'proactive'],
},
],
settings: [],
@ -226,7 +227,7 @@ describe('DialogEditModel - Basic Component Tests', () => {
{
id: 'test-model.gguf',
displayName: 'Test Model',
capabilities: ['vision', 'tools', 'completion', 'embeddings', 'web_search', 'reasoning'],
capabilities: ['vision', 'tools', 'proactive', 'completion', 'embeddings', 'web_search', 'reasoning'],
},
],
settings: [],
@ -240,7 +241,7 @@ describe('DialogEditModel - Basic Component Tests', () => {
)
// Component should render without errors even with extra capabilities
// The capabilities helper should only extract vision and tools
// The capabilities helper should only extract vision, tools, and proactive
expect(container).toBeInTheDocument()
})
})

View File

@ -17,6 +17,7 @@ import {
IconTool,
IconAlertTriangle,
IconLoader2,
IconSparkles,
} from '@tabler/icons-react'
import { useState, useEffect } from 'react'
import { useTranslation } from '@/i18n/react-i18next-compat'
@ -45,6 +46,7 @@ export const DialogEditModel = ({
const [capabilities, setCapabilities] = useState<Record<string, boolean>>({
vision: false,
tools: false,
proactive: false,
})
// Initialize with the provided model ID or the first model if available
@ -67,6 +69,7 @@ export const DialogEditModel = ({
const capabilitiesToObject = (capabilitiesList: string[]) => ({
vision: capabilitiesList.includes('vision'),
tools: capabilitiesList.includes('tools'),
proactive: capabilitiesList.includes('proactive'),
})
// Initialize capabilities and display name from selected model
@ -268,6 +271,23 @@ export const DialogEditModel = ({
disabled={isLoading}
/>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<IconSparkles className="size-4 text-main-view-fg/70" />
<span className="text-sm">
{t('providers:editModel.proactive')}
</span>
</div>
<Switch
id="proactive-capability"
checked={capabilities.proactive}
onCheckedChange={(checked) =>
handleCapabilityChange('proactive', checked)
}
disabled={isLoading || !(capabilities.tools && capabilities.vision)}
/>
</div>
</div>
</div>

View File

@ -61,6 +61,7 @@
"capabilities": "Fähigkeiten",
"tools": "Werkzeuge",
"vision": "Vision",
"proactive": "Proaktiv (Experimentell)",
"embeddings": "Einbettungen",
"notAvailable": "Noch nicht verfügbar",
"warning": {

View File

@ -61,6 +61,7 @@
"capabilities": "Capabilities",
"tools": "Tools",
"vision": "Vision",
"proactive": "Proactive (Experimental)",
"embeddings": "Embeddings",
"notAvailable": "Not available yet",
"warning": {

View File

@ -61,6 +61,7 @@
"capabilities": "Kemampuan",
"tools": "Alat",
"vision": "Visi",
"proactive": "Proaktif (Eksperimental)",
"embeddings": "Embedding",
"notAvailable": "Belum tersedia",
"warning": {

View File

@ -61,6 +61,7 @@
"capabilities": "Możliwości",
"tools": "Narzędzia",
"vision": "Wizja",
"proactive": "Proaktywny (Eksperymentalny)",
"embeddings": "Osadzenia",
"notAvailable": "Jeszcze niedostępne",
"warning": {

View File

@ -61,6 +61,7 @@
"capabilities": "Khả năng",
"tools": "Công cụ",
"vision": "Thị giác",
"proactive": "Chủ động (Thử nghiệm)",
"embeddings": "Nhúng",
"notAvailable": "Chưa có",
"warning": {

View File

@ -61,6 +61,7 @@
"capabilities": "功能",
"tools": "工具",
"vision": "视觉",
"proactive": "主动模式(实验性)",
"embeddings": "嵌入",
"notAvailable": "尚不可用",
"warning": {

View File

@ -61,6 +61,7 @@
"capabilities": "功能",
"tools": "工具",
"vision": "視覺",
"proactive": "主動模式(實驗性)",
"embeddings": "嵌入",
"notAvailable": "尚不可用",
"warning": {