mirror of
https://github.com/cheikh2shift/godex.git
synced 2026-10-19 01:29:53 -05:00
add vision support indicator
This commit is contained in:
1 parent
7f23125d0a
commit
9196435ccd
3 files changed
+149
-1
No files matched your search
+15
-1
@@ -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}
|
||||
}()
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Reference in new issue
Block a user