package config import "reflect" type FieldChange struct { Path string Hot bool } func Diff(previous, current *Config) []FieldChange { if previous == nil || current == nil { return nil } changes := make([]FieldChange, 0) addChange := func(path string, hot bool) { changes = append(changes, FieldChange{Path: path, Hot: hot}) } if !reflect.DeepEqual(previous.Server, current.Server) { addChange("server", false) } if previous.Database != current.Database { addChange("database", false) } if previous.MonitoringDatabase != current.MonitoringDatabase { addChange("monitoring_database", false) } if previous.RabbitMQ != current.RabbitMQ { addChange("rabbitmq", false) } if previous.Scheduler.HTTPHost != current.Scheduler.HTTPHost || previous.Scheduler.HTTPPort != current.Scheduler.HTTPPort { addChange("scheduler.http", false) } if previous.Redis != current.Redis { addChange("redis", false) } if previous.Cache != current.Cache { addChange("cache", false) } if previous.Log != current.Log { addChange("log", false) } if previous.Scheduler.InternalMetricsToken != current.Scheduler.InternalMetricsToken { addChange("scheduler.internal_metrics_token", true) } if previous.Scheduler.DispatchInterval != current.Scheduler.DispatchInterval || previous.Scheduler.DispatchTimeout != current.Scheduler.DispatchTimeout || previous.Scheduler.DispatchBatchSize != current.Scheduler.DispatchBatchSize || previous.Scheduler.DispatchConcurrency != current.Scheduler.DispatchConcurrency || previous.Scheduler.GenerationQueueBackpressureLimit != current.Scheduler.GenerationQueueBackpressureLimit { addChange("scheduler.dispatch", true) } if previous.MonitoringWorkers != current.MonitoringWorkers { addChange("monitoring_workers", true) } if previous.MonitoringDispatch != current.MonitoringDispatch { addChange("monitoring_dispatch", true) } if !reflect.DeepEqual(previous.Membership, current.Membership) { addChange("membership", true) } if previous.BrandLibrary != current.BrandLibrary { addChange("brand_library", true) } if previous.Qdrant != current.Qdrant { addChange("qdrant", true) } if previous.ObjectStorage != current.ObjectStorage { addChange("object_storage", true) } if previous.JWT != current.JWT { addChange("jwt", true) } if previous.LLM != current.LLM { addChange("llm", true) } if previous.Retrieval != current.Retrieval { addChange("retrieval", true) } if previous.BrowserFetch.QueueSize != current.BrowserFetch.QueueSize || previous.BrowserFetch.WorkerConcurrency != current.BrowserFetch.WorkerConcurrency { addChange("browser_fetch.worker", false) } else if !reflect.DeepEqual(previous.BrowserFetch, current.BrowserFetch) { addChange("browser_fetch", true) } if previous.Generation.QueueSize != current.Generation.QueueSize || previous.Generation.WorkerConcurrency != current.Generation.WorkerConcurrency { addChange("generation.worker", false) } if previous.Generation.StreamEnabled != current.Generation.StreamEnabled || previous.Generation.ArticleTimeout != current.Generation.ArticleTimeout || previous.Generation.TaskLeaseTTL != current.Generation.TaskLeaseTTL || previous.Generation.TaskRecoveryInterval != current.Generation.TaskRecoveryInterval || previous.Generation.TaskRecoveryTimeout != current.Generation.TaskRecoveryTimeout || previous.Generation.TaskRecoveryBatchSize != current.Generation.TaskRecoveryBatchSize || previous.Generation.TaskQueuedStaleAfter != current.Generation.TaskQueuedStaleAfter || previous.Generation.TaskMaxAttempts != current.Generation.TaskMaxAttempts { addChange("generation", true) } return changes } func RestartRequired(changes []FieldChange) bool { for _, change := range changes { if !change.Hot { return true } } return false } func ChangePaths(changes []FieldChange, hot bool) []string { paths := make([]string, 0, len(changes)) for _, change := range changes { if change.Hot == hot { paths = append(paths, change.Path) } } return paths }