fix(deploy): remove hardcoded docker env defaults and dynamic model list

This commit is contained in:
mrbeandev 2026-02-16 10:04:36 +05:30
parent 79497a13c1
commit 55c2a0f9d1
3 changed files with 41 additions and 64 deletions

View file

@ -34,10 +34,8 @@ RUN /usr/local/bin/picoclaw onboard
COPY entrypoint-coolify.sh /usr/local/bin/entrypoint-coolify.sh COPY entrypoint-coolify.sh /usr/local/bin/entrypoint-coolify.sh
RUN chmod +x /usr/local/bin/entrypoint-coolify.sh RUN chmod +x /usr/local/bin/entrypoint-coolify.sh
# Default env vars (overridden by Coolify) # Default env vars (can be overridden by Coolify)
ENV PICOCLAW_AGENTS_DEFAULTS_PROVIDER="gemini" ENV TZ=UTC
ENV PICOCLAW_AGENTS_DEFAULTS_MODEL="gemini-2.5-flash-lite"
ENV PICOCLAW_PROVIDERS_GEMINI_API_KEY=""
ENTRYPOINT ["/usr/local/bin/entrypoint-coolify.sh"] ENTRYPOINT ["/usr/local/bin/entrypoint-coolify.sh"]
CMD ["gateway"] CMD ["gateway"]

View file

@ -12,6 +12,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -217,65 +218,43 @@ func (al *AgentLoop) listModelsResponse() string {
sb.WriteString(fmt.Sprintf("**Active model:** `%s`\n", al.model)) sb.WriteString(fmt.Sprintf("**Active model:** `%s`\n", al.model))
sb.WriteString(fmt.Sprintf("**Active provider:** `%s`\n\n", al.cfg.Agents.Defaults.Provider)) sb.WriteString(fmt.Sprintf("**Active provider:** `%s`\n\n", al.cfg.Agents.Defaults.Provider))
// List all configured providers sb.WriteString("**Configured providers:**\n")
type providerEntry struct {
Name string // Use reflection to iterate over Config.Providers fields
APIBase string v := reflect.ValueOf(al.cfg.Providers)
} t := v.Type()
providersList := []providerEntry{
{"gemini", al.cfg.Providers.Gemini.APIBase},
{"openrouter", al.cfg.Providers.OpenRouter.APIBase},
{"openai", al.cfg.Providers.OpenAI.APIBase},
{"anthropic", al.cfg.Providers.Anthropic.APIBase},
{"vllm", al.cfg.Providers.VLLM.APIBase},
{"groq", al.cfg.Providers.Groq.APIBase},
{"deepseek", al.cfg.Providers.DeepSeek.APIBase},
{"nvidia", al.cfg.Providers.Nvidia.APIBase},
{"moonshot", al.cfg.Providers.Moonshot.APIBase},
{"zhipu", al.cfg.Providers.Zhipu.APIBase},
}
hasConfigured := false hasConfigured := false
for _, p := range providersList { for i := 0; i < v.NumField(); i++ {
// Show providers that have either an API key or API base configured field := v.Field(i)
hasKey := false fieldName := strings.ToLower(t.Field(i).Name)
switch p.Name {
case "gemini": // Provider name from json tag if available
hasKey = al.cfg.Providers.Gemini.APIKey != "" jsonTag := t.Field(i).Tag.Get("json")
case "openrouter": if jsonTag != "" {
hasKey = al.cfg.Providers.OpenRouter.APIKey != "" fieldName = strings.Split(jsonTag, ",")[0]
case "openai":
hasKey = al.cfg.Providers.OpenAI.APIKey != ""
case "anthropic":
hasKey = al.cfg.Providers.Anthropic.APIKey != ""
case "vllm":
hasKey = al.cfg.Providers.VLLM.APIKey != "" || al.cfg.Providers.VLLM.APIBase != ""
case "groq":
hasKey = al.cfg.Providers.Groq.APIKey != ""
case "deepseek":
hasKey = al.cfg.Providers.DeepSeek.APIKey != ""
case "nvidia":
hasKey = al.cfg.Providers.Nvidia.APIKey != ""
case "moonshot":
hasKey = al.cfg.Providers.Moonshot.APIKey != ""
case "zhipu":
hasKey = al.cfg.Providers.Zhipu.APIKey != ""
}
if hasKey {
if !hasConfigured {
sb.WriteString("**Configured providers:**\n")
hasConfigured = true
} }
// Check if provider has API key or Base URL
apiKey := field.FieldByName("APIKey").String()
apiBase := field.FieldByName("APIBase").String()
if apiKey != "" || apiBase != "" {
active := "" active := ""
if p.Name == al.cfg.Agents.Defaults.Provider { if fieldName == al.cfg.Agents.Defaults.Provider {
active = " ✅" active = " ✅"
} }
if p.APIBase != "" { if apiBase != "" {
sb.WriteString(fmt.Sprintf("- `%s` → %s%s\n", p.Name, p.APIBase, active)) sb.WriteString(fmt.Sprintf("- `%s` → %s%s\n", fieldName, apiBase, active))
} else { } else {
sb.WriteString(fmt.Sprintf("- `%s`%s\n", p.Name, active)) sb.WriteString(fmt.Sprintf("- `%s`%s\n", fieldName, active))
}
hasConfigured = true
} }
} }
if !hasConfigured {
sb.WriteString("_None configured_\n")
} }
sb.WriteString("\n_Usage: `/model <name>` or `/model <provider>/<model>`_") sb.WriteString("\n_Usage: `/model <name>` or `/model <provider>/<model>`_")

View file

@ -31,14 +31,14 @@ type TelegramChannel struct {
config *config.Config config *config.Config
chatIDs map[string]int64 chatIDs map[string]int64
transcriber *voice.GroqTranscriber transcriber *voice.GroqTranscriber
stopThinking sync.Map // chatID -> typingCancel typingTasks sync.Map // chatID -> *typingTask
} }
type thinkingCancel struct { type typingTask struct {
fn context.CancelFunc fn context.CancelFunc
} }
func (c *thinkingCancel) Cancel() { func (c *typingTask) Cancel() {
if c != nil && c.fn != nil { if c != nil && c.fn != nil {
c.fn() c.fn()
} }
@ -74,7 +74,7 @@ func NewTelegramChannel(cfg *config.Config, bus *bus.MessageBus) (*TelegramChann
config: cfg, config: cfg,
chatIDs: make(map[string]int64), chatIDs: make(map[string]int64),
transcriber: nil, transcriber: nil,
stopThinking: sync.Map{}, typingTasks: sync.Map{},
}, nil }, nil
} }
@ -149,11 +149,11 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
} }
// Stop typing indicator goroutine // Stop typing indicator goroutine
if stop, ok := c.stopThinking.Load(msg.ChatID); ok { if stop, ok := c.typingTasks.Load(msg.ChatID); ok {
if cf, ok := stop.(*thinkingCancel); ok && cf != nil { if cf, ok := stop.(*typingTask); ok && cf != nil {
cf.Cancel() cf.Cancel()
} }
c.stopThinking.Delete(msg.ChatID) c.typingTasks.Delete(msg.ChatID)
} }
htmlContent := markdownToTelegramHTML(msg.Content) htmlContent := markdownToTelegramHTML(msg.Content)
@ -312,13 +312,13 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Mes
// so we re-send every 4s until the response arrives) // so we re-send every 4s until the response arrives)
chatIDStr := fmt.Sprintf("%d", chatID) chatIDStr := fmt.Sprintf("%d", chatID)
// Cancel any previous typing goroutine for this chat // Cancel any previous typing goroutine for this chat
if prevStop, ok := c.stopThinking.Load(chatIDStr); ok { if prevStop, ok := c.typingTasks.Load(chatIDStr); ok {
if cf, ok := prevStop.(*thinkingCancel); ok && cf != nil { if cf, ok := prevStop.(*typingTask); ok && cf != nil {
cf.Cancel() cf.Cancel()
} }
} }
typingCtx, typingCancel := context.WithCancel(ctx) typingCtx, typingCancel := context.WithCancel(ctx)
c.stopThinking.Store(chatIDStr, &thinkingCancel{fn: typingCancel}) c.typingTasks.Store(chatIDStr, &typingTask{fn: typingCancel})
go func() { go func() {
ticker := time.NewTicker(4 * time.Second) ticker := time.NewTicker(4 * time.Second)
defer ticker.Stop() defer ticker.Stop()