diff --git a/app/ui/app/codegen/gotypes.gen.ts b/app/ui/app/codegen/gotypes.gen.ts index 51928e685..b82cc10c6 100644 --- a/app/ui/app/codegen/gotypes.gen.ts +++ b/app/ui/app/codegen/gotypes.gen.ts @@ -550,14 +550,12 @@ export class Error { } } export class ModelUpstreamResponse { - digest?: string; - pushTime: number; + stale: boolean; error?: string; constructor(source: any = {}) { if ('string' === typeof source) source = JSON.parse(source); - this.digest = source["digest"]; - this.pushTime = source["pushTime"]; + this.stale = source["stale"]; this.error = source["error"]; } } diff --git a/app/ui/app/src/api.ts b/app/ui/app/src/api.ts index 062f5bdd2..ae2f13182 100644 --- a/app/ui/app/src/api.ts +++ b/app/ui/app/src/api.ts @@ -161,7 +161,7 @@ export async function getModels(query?: string): Promise { // Add query if it's in the registry and not already in the list if (!exactMatch) { const result = await getModelUpstreamInfo(new Model({ model: query })); - const existsUpstream = !!result.digest && !result.error; + const existsUpstream = result.exists; if (existsUpstream) { filteredModels.push(new Model({ model: query })); } @@ -339,7 +339,7 @@ export async function deleteChat(chatId: string): Promise { // Get upstream information for model staleness checking export async function getModelUpstreamInfo( model: Model, -): Promise<{ digest?: string; pushTime: number; error?: string }> { +): Promise<{ stale: boolean; exists: boolean; error?: string }> { try { const response = await fetch(`${API_BASE}/api/v1/model/upstream`, { method: "POST", @@ -353,22 +353,22 @@ export async function getModelUpstreamInfo( if (!response.ok) { console.warn( - `Failed to check upstream digest for ${model.model}: ${response.status}`, + `Failed to check upstream for ${model.model}: ${response.status}`, ); - return { pushTime: 0 }; + return { stale: false, exists: false }; } const data = await response.json(); if (data.error) { - console.warn(`Upstream digest check: ${data.error}`); - return { error: data.error, pushTime: 0 }; + console.warn(`Upstream check: ${data.error}`); + return { stale: false, exists: false, error: data.error }; } - return { digest: data.digest, pushTime: data.pushTime || 0 }; + return { stale: !!data.stale, exists: true }; } catch (error) { console.warn(`Error checking model staleness:`, error); - return { pushTime: 0 }; + return { stale: false, exists: false }; } } diff --git a/app/ui/app/src/components/ModelPicker.tsx b/app/ui/app/src/components/ModelPicker.tsx index 6908c29bf..2dba77d8c 100644 --- a/app/ui/app/src/components/ModelPicker.tsx +++ b/app/ui/app/src/components/ModelPicker.tsx @@ -61,24 +61,7 @@ export const ModelPicker = forwardRef< try { const upstreamInfo = await getModelUpstreamInfo(model); - // Compare local digest with upstream digest - let isStale = - model.digest && - upstreamInfo.digest && - model.digest !== upstreamInfo.digest; - - // If the model has a modified time and upstream has a push time, - // check if the model was modified after the push time - if so, it's not stale - if (isStale && model.modified_at && upstreamInfo.pushTime > 0) { - const modifiedAtTime = - new Date(model.modified_at as string | number | Date).getTime() / - 1000; - if (modifiedAtTime > upstreamInfo.pushTime) { - isStale = false; - } - } - - if (isStale) { + if (upstreamInfo.stale) { const currentStaleModels = queryClient.getQueryData>(["staleModels"]) || new Map(); diff --git a/app/ui/responses/types.go b/app/ui/responses/types.go index 0293ca4e5..1a382599e 100644 --- a/app/ui/responses/types.go +++ b/app/ui/responses/types.go @@ -133,9 +133,8 @@ type Error struct { } type ModelUpstreamResponse struct { - Digest string `json:"digest,omitempty"` - PushTime int64 `json:"pushTime"` - Error string `json:"error,omitempty"` + Stale bool `json:"stale"` + Error string `json:"error,omitempty"` } // Serializable data for the browser state diff --git a/app/ui/ui.go b/app/ui/ui.go index 5920192ba..c37cd9b25 100644 --- a/app/ui/ui.go +++ b/app/ui/ui.go @@ -32,6 +32,7 @@ import ( "github.com/ollama/ollama/app/version" ollamaAuth "github.com/ollama/ollama/auth" "github.com/ollama/ollama/envconfig" + "github.com/ollama/ollama/manifest" "github.com/ollama/ollama/types/model" _ "github.com/tkrajina/typescriptify-golang-structs/typescriptify" ) @@ -193,7 +194,7 @@ func (s *Server) Handler() http.Handler { if CORS() { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") - w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, User-Agent, Accept, X-Requested-With") w.Header().Set("Access-Control-Allow-Credentials", "true") // Handle preflight requests @@ -318,7 +319,7 @@ func (s *Server) handleError(w http.ResponseWriter, e error) { if CORS() { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") - w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, User-Agent, Accept, X-Requested-With") w.Header().Set("Access-Control-Allow-Credentials", "true") } @@ -1572,9 +1573,18 @@ func (s *Server) modelUpstream(w http.ResponseWriter, r *http.Request) error { return json.NewEncoder(w).Encode(response) } + n := model.ParseName(req.Model) + stale := true + if m, err := manifest.ParseNamedManifest(n); err == nil { + if m.Digest() == digest { + stale = false + } else if pushTime > 0 && m.FileInfo().ModTime().Unix() >= pushTime { + stale = false + } + } + response := responses.ModelUpstreamResponse{ - Digest: digest, - PushTime: pushTime, + Stale: stale, } w.Header().Set("Content-Type", "application/json")