Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion core/config/model_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,10 @@ func (c *ModelConfig) GuessUsecases(u ModelConfigUsecase) bool {
if c.Backend != "whisper" {
return false
}
// whisper models with vad_only option are VAD, not transcription
if slices.Contains(c.Options, "vad_only") {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder here we should start to move things to constants, easier to map out across the code

return false
}
}
if (u & FLAG_TTS) == FLAG_TTS {
ttsBackends := []string{"piper", "transformers-musicgen", "kokoro"}
Expand Down Expand Up @@ -689,7 +693,7 @@ func (c *ModelConfig) GuessUsecases(u ModelConfigUsecase) bool {
}

if (u & FLAG_VAD) == FLAG_VAD {
if c.Backend != "silero-vad" {
if c.Backend != "silero-vad" && !(c.Backend == "whisper" && slices.Contains(c.Options, "vad_only")) {
return false
}
}
Expand Down
186 changes: 185 additions & 1 deletion core/http/react-ui/bun.lock

Large diffs are not rendered by default.

56 changes: 36 additions & 20 deletions core/http/react-ui/src/components/SearchableModelSelect.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ export default function SearchableModelSelect({ value, onChange, capability, pla
m.id.toLowerCase().includes(query.toLowerCase())
)

// Which item Enter will select — matches SearchableSelect behavior
const enterTargetIndex = focusIndex >= 0 ? focusIndex
: filtered.length > 0 ? 0
: -1

const commit = useCallback((val) => {
setQuery(val)
onChange(val)
Expand All @@ -39,6 +44,11 @@ export default function SearchableModelSelect({ value, onChange, capability, pla
setOpen(true)
return
}
if (!open && e.key === 'Enter') {
e.preventDefault()
commit(query)
return
}
if (!open) return

if (e.key === 'ArrowDown') {
Expand All @@ -49,8 +59,8 @@ export default function SearchableModelSelect({ value, onChange, capability, pla
setFocusIndex(i => Math.max(i - 1, 0))
} else if (e.key === 'Enter') {
e.preventDefault()
if (focusIndex >= 0 && focusIndex < filtered.length) {
commit(filtered[focusIndex].id)
if (enterTargetIndex >= 0) {
commit(filtered[enterTargetIndex].id)
} else {
commit(query)
}
Expand Down Expand Up @@ -97,9 +107,9 @@ export default function SearchableModelSelect({ value, onChange, capability, pla
padding: 6px 10px;
font-size: 0.8125rem;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: flex;
align-items: center;
gap: 6px;
}
.sms-item:hover, .sms-item.sms-focused {
background: var(--color-bg-tertiary);
Expand Down Expand Up @@ -137,21 +147,27 @@ export default function SearchableModelSelect({ value, onChange, capability, pla
{query ? 'No matching models — value will be used as-is' : 'No models available'}
</div>
) : (
filtered.map((m, i) => (
<div
key={m.id}
role="option"
aria-selected={m.id === value}
className={`sms-item${i === focusIndex ? ' sms-focused' : ''}${m.id === value ? ' sms-active' : ''}`}
onMouseEnter={() => setFocusIndex(i)}
onMouseDown={(e) => {
e.preventDefault()
commit(m.id)
}}
>
{m.id}
</div>
))
filtered.map((m, i) => {
const isEnterTarget = i === enterTargetIndex
return (
<div
key={m.id}
role="option"
aria-selected={m.id === value}
className={`sms-item${i === focusIndex || isEnterTarget ? ' sms-focused' : ''}${m.id === value ? ' sms-active' : ''}`}
onMouseEnter={() => setFocusIndex(i)}
onMouseDown={(e) => {
e.preventDefault()
commit(m.id)
}}
>
<span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.id}</span>
{isEnterTarget && (
<span style={{ color: 'var(--color-text-muted)', fontSize: '0.75rem', flexShrink: 0 }}>↵</span>
)}
</div>
)
})
)}
</div>
)}
Expand Down
9 changes: 5 additions & 4 deletions core/http/react-ui/src/pages/AgentCreate.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState, useEffect, useMemo } from 'react'
import { useParams, useNavigate, useLocation, useOutletContext, useSearchParams } from 'react-router-dom'
import { agentsApi } from '../utils/api'
import SearchableModelSelect from '../components/SearchableModelSelect'
import { CAP_CHAT, CAP_TRANSCRIPT, CAP_TTS } from '../utils/capabilities'
import Toggle from '../components/Toggle'
import SettingRow from '../components/SettingRow'

Expand Down Expand Up @@ -115,10 +116,10 @@ function FormField({ field, value, onChange, disabled }) {
const isModelField = /^(model|multimodal_model|transcription_model|tts_model|embedding_model)$/.test(field.name)
if (isModelField && !disabled && !field.disabled) {
const capabilityMap = {
model: 'FLAG_CHAT',
multimodal_model: 'FLAG_CHAT',
transcription_model: 'FLAG_TRANSCRIPT',
tts_model: 'FLAG_TTS',
model: CAP_CHAT,
multimodal_model: CAP_CHAT,
transcription_model: CAP_TRANSCRIPT,
tts_model: CAP_TTS,
embedding_model: undefined,
}
return (
Expand Down
3 changes: 2 additions & 1 deletion core/http/react-ui/src/pages/AgentTaskDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useParams, useNavigate, useOutletContext, useLocation } from 'react-rou
import { agentJobsApi } from '../utils/api'
import { basePath } from '../utils/basePath'
import ModelSelector from '../components/ModelSelector'
import { CAP_CHAT } from '../utils/capabilities'
import LoadingSpinner from '../components/LoadingSpinner'

export default function AgentTaskDetails() {
Expand Down Expand Up @@ -322,7 +323,7 @@ export default function AgentTaskDetails() {
</div>
<div className="form-group">
<label className="form-label">Model</label>
<ModelSelector value={task.model} onChange={(model) => updateField('model', model)} capability="FLAG_CHAT" />
<ModelSelector value={task.model} onChange={(model) => updateField('model', model)} capability={CAP_CHAT} />
</div>
</div>
<div className="form-group">
Expand Down
3 changes: 2 additions & 1 deletion core/http/react-ui/src/pages/Chat.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { renderMarkdown, highlightAll } from '../utils/markdown'
import { extractCodeArtifacts, renderMarkdownWithArtifacts } from '../utils/artifacts'
import CanvasPanel from '../components/CanvasPanel'
import { fileToBase64, modelsApi, mcpApi } from '../utils/api'
import { CAP_CHAT } from '../utils/capabilities'
import { useMCPClient } from '../hooks/useMCPClient'
import MCPAppFrame from '../components/MCPAppFrame'
import UnifiedMCPDropdown from '../components/UnifiedMCPDropdown'
Expand Down Expand Up @@ -898,7 +899,7 @@ export default function Chat() {
<ModelSelector
value={activeChat.model}
onChange={(model) => updateChatSettings(activeChat.id, { model })}
capability="FLAG_CHAT"
capability={CAP_CHAT}
style={{ flex: '1 1 0', minWidth: 120 }}
/>
<div className="chat-header-actions">
Expand Down
3 changes: 2 additions & 1 deletion core/http/react-ui/src/pages/Home.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useNavigate, useOutletContext } from 'react-router-dom'
import { apiUrl } from '../utils/basePath'
import { useAuth } from '../context/AuthContext'
import ModelSelector from '../components/ModelSelector'
import { CAP_CHAT } from '../utils/capabilities'
import UnifiedMCPDropdown from '../components/UnifiedMCPDropdown'
import ConfirmDialog from '../components/ConfirmDialog'
import { useResources } from '../hooks/useResources'
Expand Down Expand Up @@ -237,7 +238,7 @@ export default function Home() {
<form onSubmit={handleSubmit}>
{/* Model selector + MCP toggle */}
<div className="home-model-row">
<ModelSelector value={selectedModel} onChange={setSelectedModel} capability="FLAG_CHAT" />
<ModelSelector value={selectedModel} onChange={setSelectedModel} capability={CAP_CHAT} />
<UnifiedMCPDropdown
serverMCPAvailable={mcpAvailable}
mcpServerList={mcpServerList}
Expand Down
3 changes: 2 additions & 1 deletion core/http/react-ui/src/pages/ImageGen.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState, useRef } from 'react'
import { useParams, useOutletContext } from 'react-router-dom'
import ModelSelector from '../components/ModelSelector'
import { CAP_IMAGE } from '../utils/capabilities'
import LoadingSpinner from '../components/LoadingSpinner'
import { imageApi, fileToBase64 } from '../utils/api'

Expand Down Expand Up @@ -73,7 +74,7 @@ export default function ImageGen() {
<form onSubmit={handleGenerate}>
<div className="form-group">
<label className="form-label">Model</label>
<ModelSelector value={model} onChange={setModel} capability="FLAG_IMAGE" />
<ModelSelector value={model} onChange={setModel} capability={CAP_IMAGE} />
</div>
<div className="form-group">
<label className="form-label">Prompt</label>
Expand Down
5 changes: 4 additions & 1 deletion core/http/react-ui/src/pages/ImportModel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,10 @@ export default function ImportModel() {
{isAdvancedMode ? 'Configure your model settings using YAML' : 'Import a model from URI with preferences'}
</p>
</div>
<div style={{ display: 'flex', gap: 'var(--spacing-sm)' }}>
<div style={{ display: 'flex', gap: 'var(--spacing-sm)', flexWrap: 'wrap' }}>
<button className="btn btn-secondary" onClick={() => navigate('/app/pipeline-editor')}>
<i className="fas fa-diagram-project" /> Create Pipeline Model
</button>
<button className="btn btn-secondary" onClick={() => setIsAdvancedMode(!isAdvancedMode)}>
<i className={`fas ${isAdvancedMode ? 'fa-magic' : 'fa-code'}`} />
{isAdvancedMode ? ' Simple Mode' : ' Advanced Mode'}
Expand Down
Loading
Loading