feat: Adding proactive button as experimental feature
This commit is contained in:
parent
2561fcd78a
commit
c773abb688
@ -10,6 +10,7 @@ import {
|
|||||||
IconAtom,
|
IconAtom,
|
||||||
IconWorld,
|
IconWorld,
|
||||||
IconCodeCircle2,
|
IconCodeCircle2,
|
||||||
|
IconSparkles,
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
import { Fragment } from 'react/jsx-runtime'
|
import { Fragment } from 'react/jsx-runtime'
|
||||||
|
|
||||||
@ -29,6 +30,8 @@ const Capabilities = ({ capabilities }: CapabilitiesProps) => {
|
|||||||
icon = <IconEye className="size-4" />
|
icon = <IconEye className="size-4" />
|
||||||
} else if (capability === 'tools') {
|
} else if (capability === 'tools') {
|
||||||
icon = <IconTool className="size-3.5" />
|
icon = <IconTool className="size-3.5" />
|
||||||
|
} else if (capability === 'proactive') {
|
||||||
|
icon = <IconSparkles className="size-3.5" />
|
||||||
} else if (capability === 'reasoning') {
|
} else if (capability === 'reasoning') {
|
||||||
icon = <IconAtom className="size-3.5" />
|
icon = <IconAtom className="size-3.5" />
|
||||||
} else if (capability === 'embeddings') {
|
} else if (capability === 'embeddings') {
|
||||||
@ -54,7 +57,11 @@ const Capabilities = ({ capabilities }: CapabilitiesProps) => {
|
|||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>
|
<p>
|
||||||
{capability === 'web_search' ? 'Web Search' : capability}
|
{capability === 'web_search'
|
||||||
|
? 'Web Search'
|
||||||
|
: capability === 'proactive'
|
||||||
|
? 'Proactive'
|
||||||
|
: capability}
|
||||||
</p>
|
</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@ -108,6 +108,7 @@ const ChatInput = ({
|
|||||||
const [connectedServers, setConnectedServers] = useState<string[]>([])
|
const [connectedServers, setConnectedServers] = useState<string[]>([])
|
||||||
const [isDragOver, setIsDragOver] = useState(false)
|
const [isDragOver, setIsDragOver] = useState(false)
|
||||||
const [hasMmproj, setHasMmproj] = useState(false)
|
const [hasMmproj, setHasMmproj] = useState(false)
|
||||||
|
const [hasProactive, setHasProactive] = useState(false)
|
||||||
const [hasActiveModels, setHasActiveModels] = useState(false)
|
const [hasActiveModels, setHasActiveModels] = useState(false)
|
||||||
const attachmentsEnabled = useAttachments((s) => s.enabled)
|
const attachmentsEnabled = useAttachments((s) => s.enabled)
|
||||||
// Determine whether to show the Attach documents button (simple gating)
|
// Determine whether to show the Attach documents button (simple gating)
|
||||||
@ -206,6 +207,29 @@ const ChatInput = ({
|
|||||||
checkMmprojSupport()
|
checkMmprojSupport()
|
||||||
}, [selectedModel, selectedModel?.capabilities, selectedProvider, serviceHub])
|
}, [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
|
// Check if there are active MCP servers
|
||||||
const hasActiveMCPServers = connectedServers.length > 0 || tools.length > 0
|
const hasActiveMCPServers = connectedServers.length > 0 || tools.length > 0
|
||||||
|
|
||||||
|
|||||||
@ -152,12 +152,19 @@ export const ModelInfoHoverCard = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Features Section */}
|
{/* 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">
|
<div className="border-t border-main-view-fg/10 pt-3">
|
||||||
<h5 className="text-xs font-medium text-main-view-fg/70 mb-2">
|
<h5 className="text-xs font-medium text-main-view-fg/70 mb-2">
|
||||||
Features
|
Features
|
||||||
</h5>
|
</h5>
|
||||||
<div className="flex flex-wrap gap-2">
|
<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 && (
|
{model.num_mmproj > 0 && (
|
||||||
<div className="flex items-center gap-1.5 px-2 py-1 bg-main-view-fg/10 rounded-md">
|
<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">
|
<span className="text-xs text-main-view-fg font-medium">
|
||||||
@ -165,10 +172,10 @@ export const ModelInfoHoverCard = ({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</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">
|
<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">
|
<span className="text-xs text-main-view-fg font-medium">
|
||||||
Tools
|
Proactive
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -82,6 +82,7 @@ vi.mock('@tabler/icons-react', () => ({
|
|||||||
IconEye: () => <div data-testid="eye-icon" />,
|
IconEye: () => <div data-testid="eye-icon" />,
|
||||||
IconTool: () => <div data-testid="tool-icon" />,
|
IconTool: () => <div data-testid="tool-icon" />,
|
||||||
IconLoader2: () => <div data-testid="loader-icon" />,
|
IconLoader2: () => <div data-testid="loader-icon" />,
|
||||||
|
IconSparkles: () => <div data-testid="sparkles-icon" />,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe('DialogEditModel - Basic Component Tests', () => {
|
describe('DialogEditModel - Basic Component Tests', () => {
|
||||||
@ -189,7 +190,7 @@ describe('DialogEditModel - Basic Component Tests', () => {
|
|||||||
{
|
{
|
||||||
id: 'test-model.gguf',
|
id: 'test-model.gguf',
|
||||||
displayName: 'Test Model',
|
displayName: 'Test Model',
|
||||||
capabilities: ['vision', 'tools'],
|
capabilities: ['vision', 'tools', 'proactive'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
settings: [],
|
settings: [],
|
||||||
@ -226,7 +227,7 @@ describe('DialogEditModel - Basic Component Tests', () => {
|
|||||||
{
|
{
|
||||||
id: 'test-model.gguf',
|
id: 'test-model.gguf',
|
||||||
displayName: 'Test Model',
|
displayName: 'Test Model',
|
||||||
capabilities: ['vision', 'tools', 'completion', 'embeddings', 'web_search', 'reasoning'],
|
capabilities: ['vision', 'tools', 'proactive', 'completion', 'embeddings', 'web_search', 'reasoning'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
settings: [],
|
settings: [],
|
||||||
@ -240,7 +241,7 @@ describe('DialogEditModel - Basic Component Tests', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Component should render without errors even with extra capabilities
|
// 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()
|
expect(container).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
IconTool,
|
IconTool,
|
||||||
IconAlertTriangle,
|
IconAlertTriangle,
|
||||||
IconLoader2,
|
IconLoader2,
|
||||||
|
IconSparkles,
|
||||||
} from '@tabler/icons-react'
|
} from '@tabler/icons-react'
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { useTranslation } from '@/i18n/react-i18next-compat'
|
import { useTranslation } from '@/i18n/react-i18next-compat'
|
||||||
@ -45,6 +46,7 @@ export const DialogEditModel = ({
|
|||||||
const [capabilities, setCapabilities] = useState<Record<string, boolean>>({
|
const [capabilities, setCapabilities] = useState<Record<string, boolean>>({
|
||||||
vision: false,
|
vision: false,
|
||||||
tools: false,
|
tools: false,
|
||||||
|
proactive: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Initialize with the provided model ID or the first model if available
|
// Initialize with the provided model ID or the first model if available
|
||||||
@ -67,6 +69,7 @@ export const DialogEditModel = ({
|
|||||||
const capabilitiesToObject = (capabilitiesList: string[]) => ({
|
const capabilitiesToObject = (capabilitiesList: string[]) => ({
|
||||||
vision: capabilitiesList.includes('vision'),
|
vision: capabilitiesList.includes('vision'),
|
||||||
tools: capabilitiesList.includes('tools'),
|
tools: capabilitiesList.includes('tools'),
|
||||||
|
proactive: capabilitiesList.includes('proactive'),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Initialize capabilities and display name from selected model
|
// Initialize capabilities and display name from selected model
|
||||||
@ -268,6 +271,23 @@ export const DialogEditModel = ({
|
|||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -61,6 +61,7 @@
|
|||||||
"capabilities": "Fähigkeiten",
|
"capabilities": "Fähigkeiten",
|
||||||
"tools": "Werkzeuge",
|
"tools": "Werkzeuge",
|
||||||
"vision": "Vision",
|
"vision": "Vision",
|
||||||
|
"proactive": "Proaktiv (Experimentell)",
|
||||||
"embeddings": "Einbettungen",
|
"embeddings": "Einbettungen",
|
||||||
"notAvailable": "Noch nicht verfügbar",
|
"notAvailable": "Noch nicht verfügbar",
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -61,6 +61,7 @@
|
|||||||
"capabilities": "Capabilities",
|
"capabilities": "Capabilities",
|
||||||
"tools": "Tools",
|
"tools": "Tools",
|
||||||
"vision": "Vision",
|
"vision": "Vision",
|
||||||
|
"proactive": "Proactive (Experimental)",
|
||||||
"embeddings": "Embeddings",
|
"embeddings": "Embeddings",
|
||||||
"notAvailable": "Not available yet",
|
"notAvailable": "Not available yet",
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -61,6 +61,7 @@
|
|||||||
"capabilities": "Kemampuan",
|
"capabilities": "Kemampuan",
|
||||||
"tools": "Alat",
|
"tools": "Alat",
|
||||||
"vision": "Visi",
|
"vision": "Visi",
|
||||||
|
"proactive": "Proaktif (Eksperimental)",
|
||||||
"embeddings": "Embedding",
|
"embeddings": "Embedding",
|
||||||
"notAvailable": "Belum tersedia",
|
"notAvailable": "Belum tersedia",
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -61,6 +61,7 @@
|
|||||||
"capabilities": "Możliwości",
|
"capabilities": "Możliwości",
|
||||||
"tools": "Narzędzia",
|
"tools": "Narzędzia",
|
||||||
"vision": "Wizja",
|
"vision": "Wizja",
|
||||||
|
"proactive": "Proaktywny (Eksperymentalny)",
|
||||||
"embeddings": "Osadzenia",
|
"embeddings": "Osadzenia",
|
||||||
"notAvailable": "Jeszcze niedostępne",
|
"notAvailable": "Jeszcze niedostępne",
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -61,6 +61,7 @@
|
|||||||
"capabilities": "Khả năng",
|
"capabilities": "Khả năng",
|
||||||
"tools": "Công cụ",
|
"tools": "Công cụ",
|
||||||
"vision": "Thị giác",
|
"vision": "Thị giác",
|
||||||
|
"proactive": "Chủ động (Thử nghiệm)",
|
||||||
"embeddings": "Nhúng",
|
"embeddings": "Nhúng",
|
||||||
"notAvailable": "Chưa có",
|
"notAvailable": "Chưa có",
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -61,6 +61,7 @@
|
|||||||
"capabilities": "功能",
|
"capabilities": "功能",
|
||||||
"tools": "工具",
|
"tools": "工具",
|
||||||
"vision": "视觉",
|
"vision": "视觉",
|
||||||
|
"proactive": "主动模式(实验性)",
|
||||||
"embeddings": "嵌入",
|
"embeddings": "嵌入",
|
||||||
"notAvailable": "尚不可用",
|
"notAvailable": "尚不可用",
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
@ -61,6 +61,7 @@
|
|||||||
"capabilities": "功能",
|
"capabilities": "功能",
|
||||||
"tools": "工具",
|
"tools": "工具",
|
||||||
"vision": "視覺",
|
"vision": "視覺",
|
||||||
|
"proactive": "主動模式(實驗性)",
|
||||||
"embeddings": "嵌入",
|
"embeddings": "嵌入",
|
||||||
"notAvailable": "尚不可用",
|
"notAvailable": "尚不可用",
|
||||||
"warning": {
|
"warning": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user