add vision support indicator

This commit is contained in:
Cheikh Seck committed 2026-04-05 23:11:02 +00:00
1 parent 7f23125d0a
commit 9196435ccd
3 files changed
+149 -1

No files matched your search

+15 -1
View File
@@ -427,6 +427,20 @@ promptLoop:
// log.Println("[DEBUG] Context limit:", contextLimit, "Input tokens:", inputTokens, "Output tokens:", outputTokens)
modelLabel := provider.Model
if llmProvider != nil {
if vs, ok := llmProvider.(interface {
SupportsVision(context.Context) (bool, error)
}); ok {
visionCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
supported, err := vs.SupportsVision(visionCtx)
cancel()
if err == nil && supported {
modelLabel = modelLabel + " [V]"
}
}
}
var delegateCh <-chan hive.HiveStats
if hiveMgr != nil {
delegateCh = hiveMgr.Stats()
@@ -440,7 +454,7 @@ promptLoop:
promptResultCh := make(chan promptResult, 1)
go func() {
input, err := readPrompt(promptStr, history, provider.Model, inputTokens+outputTokens, contextLimit, historyDB, wd, statusCh, delegateCh, promptCancelCh)
input, err := readPrompt(promptStr, history, modelLabel, inputTokens+outputTokens, contextLimit, historyDB, wd, statusCh, delegateCh, promptCancelCh)
promptResultCh <- promptResult{input: input, err: err}
}()
+64
View File
@@ -41,6 +41,8 @@ type ollamaProvider struct {
promptTokens int
completionTokens int
statusCh chan<- string
visionChecked bool
visionSupported bool
}
func init() {
@@ -499,6 +501,8 @@ func (o *ollamaProvider) Reset() error {
o.messages = []map[string]string{}
o.promptTokens = 0
o.completionTokens = 0
o.visionChecked = false
o.visionSupported = false
return nil
}
@@ -563,5 +567,65 @@ func (o *ollamaProvider) SetModel(model string, contextLimit int) error {
defer o.mu.Unlock()
o.model = model
o.contextLimit = contextLimit
o.visionChecked = false
o.visionSupported = false
return nil
}
func (o *ollamaProvider) SupportsVision(ctx context.Context) (bool, error) {
o.mu.Lock()
if o.visionChecked {
supported := o.visionSupported
o.mu.Unlock()
return supported, nil
}
o.mu.Unlock()
supported, err := o.detectVisionSupport(ctx)
o.mu.Lock()
o.visionChecked = true
o.visionSupported = supported
o.mu.Unlock()
return supported, err
}
func (o *ollamaProvider) detectVisionSupport(ctx context.Context) (bool, error) {
endpoint := o.baseURL
if endpoint == "" {
endpoint = defaultOllamaBase
}
endpoint = strings.TrimRight(endpoint, "/") + "/api/show"
reqBody := map[string]string{
"name": o.model,
}
payload, err := json.Marshal(reqBody)
if err != nil {
return false, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewReader(payload))
if err != nil {
return false, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := o.client.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Errorf("failed to fetch model info: HTTP %d", resp.StatusCode)
}
var result struct {
Capabilities []string `json:"capabilities"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return false, err
}
for _, cap := range result.Capabilities {
if strings.EqualFold(cap, "vision") {
return true, nil
}
}
return false, nil
}
+70
View File
@@ -32,8 +32,12 @@ func getMaxHistoryMessages() int {
type openRouterModelInfo struct {
ID string `json:"id"`
CanonicalSlug string `json:"canonical_slug"`
Name string `json:"name"`
ContextLength float64 `json:"context_length"`
Architecture struct {
InputModalities []string `json:"input_modalities"`
} `json:"architecture"`
}
type openRouterModelsResponse struct {
@@ -78,6 +82,8 @@ type openRouterProvider struct {
contextLimit int
promptTokens int
completionTokens int
visionChecked bool
visionSupported bool
OnThink func(string)
mu sync.Mutex
statusCh chan<- string
@@ -489,6 +495,8 @@ func (p *openRouterProvider) Reset() error {
p.pendingToolCalls = make(map[string]string)
p.promptTokens = 0
p.completionTokens = 0
p.visionChecked = false
p.visionSupported = false
return nil
}
@@ -762,5 +770,67 @@ func (p *openRouterProvider) SetModel(model string, contextLimit int) error {
defer p.mu.Unlock()
p.model = model
p.contextLimit = contextLimit
p.visionChecked = false
p.visionSupported = false
return nil
}
func (p *openRouterProvider) SupportsVision(ctx context.Context) (bool, error) {
p.mu.Lock()
if p.visionChecked {
supported := p.visionSupported
p.mu.Unlock()
return supported, nil
}
p.mu.Unlock()
supported, err := openRouterModelSupportsVision(ctx, p.baseURL, p.model, p.apiKey)
p.mu.Lock()
p.visionChecked = true
p.visionSupported = supported
p.mu.Unlock()
return supported, err
}
func openRouterModelSupportsVision(ctx context.Context, baseURL, model, apiKey string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, strings.TrimRight(baseURL, "/")+"/models", nil)
if err != nil {
return false, err
}
req.Header.Set("Accept", "application/json")
if strings.TrimSpace(apiKey) != "" {
req.Header.Set("Authorization", "Bearer "+strings.TrimSpace(apiKey))
}
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Errorf("failed to fetch models: status %d", resp.StatusCode)
}
var result openRouterModelsResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return false, err
}
modelLower := strings.ToLower(model)
for _, m := range result.Data {
if strings.ToLower(m.ID) == modelLower ||
strings.ToLower(m.CanonicalSlug) == modelLower ||
strings.ToLower(m.Name) == modelLower {
for _, modality := range m.Architecture.InputModalities {
if strings.EqualFold(modality, "image") {
return true, nil
}
}
return false, nil
}
}
return false, fmt.Errorf("model not found: %s", model)
}