204 lines
7.0 KiB
Go
Executable File
204 lines
7.0 KiB
Go
Executable File
// config.go – Konfiguration, Clients und gemeinsame Verbindungen
|
||
package config
|
||
|
||
import (
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
|
||
openai "github.com/sashabaranov/go-openai"
|
||
"google.golang.org/grpc"
|
||
"google.golang.org/grpc/credentials/insecure"
|
||
"gopkg.in/yaml.v3"
|
||
)
|
||
|
||
// ArchiveFolder beschreibt einen IMAP-Archivordner mit optionaler Aufbewahrungsdauer.
|
||
type ArchiveFolder struct {
|
||
Name string `yaml:"name"` // Anzeigename (z.B. "5Jahre")
|
||
IMAPFolder string `yaml:"imap_folder"` // Echter IMAP-Ordnername (z.B. "5Jahre")
|
||
RetentionDays int `yaml:"retention_days"` // 0 = dauerhaft behalten
|
||
}
|
||
|
||
// EmailAccount beschreibt einen einzelnen IMAP-Account.
|
||
type EmailAccount struct {
|
||
Name string `yaml:"name"`
|
||
Host string `yaml:"host"`
|
||
Port int `yaml:"port"`
|
||
User string `yaml:"user"`
|
||
Password string `yaml:"password"`
|
||
TLS bool `yaml:"tls"`
|
||
StartTLS bool `yaml:"starttls"`
|
||
Folder string `yaml:"folder"`
|
||
ProcessedFolder string `yaml:"processed_folder"`
|
||
Model string `yaml:"model"`
|
||
ArchiveFolders []ArchiveFolder `yaml:"archive_folders"`
|
||
TriageImportantFolder string `yaml:"triage_important_folder"` // Ordner für wichtige Emails (leer = in INBOX lassen)
|
||
TriageUnimportantFolder string `yaml:"triage_unimportant_folder"` // Ordner für unwichtige Emails (leer = kein Triage)
|
||
}
|
||
|
||
type Config struct {
|
||
Qdrant struct {
|
||
Host string `yaml:"host"`
|
||
Port string `yaml:"port"`
|
||
APIKey string `yaml:"api_key"`
|
||
Collection string `yaml:"collection"`
|
||
} `yaml:"qdrant"`
|
||
|
||
Embedding struct {
|
||
URL string `yaml:"url"`
|
||
Model string `yaml:"model"`
|
||
Dimensions uint64 `yaml:"dimensions"`
|
||
} `yaml:"embedding"`
|
||
|
||
Chat struct {
|
||
URL string `yaml:"url"`
|
||
Model string `yaml:"model"`
|
||
} `yaml:"chat"`
|
||
|
||
Discord struct {
|
||
Token string `yaml:"token"`
|
||
GuildID string `yaml:"guild_id"`
|
||
AllowedUsers []string `yaml:"allowed_users"` // Wenn gesetzt, dürfen nur diese User-IDs den Bot nutzen
|
||
} `yaml:"discord"`
|
||
|
||
// Email ist der Legacy-Block für einen einzelnen Account.
|
||
Email struct {
|
||
Host string `yaml:"host"`
|
||
Port int `yaml:"port"`
|
||
User string `yaml:"user"`
|
||
Password string `yaml:"password"`
|
||
TLS bool `yaml:"tls"`
|
||
StartTLS bool `yaml:"starttls"`
|
||
Folder string `yaml:"folder"`
|
||
ProcessedFolder string `yaml:"processed_folder"`
|
||
Model string `yaml:"model"`
|
||
ArchiveFolders []ArchiveFolder `yaml:"archive_folders"`
|
||
TriageImportantFolder string `yaml:"triage_important_folder"`
|
||
TriageUnimportantFolder string `yaml:"triage_unimportant_folder"`
|
||
} `yaml:"email"`
|
||
|
||
// EmailAccounts ermöglicht mehrere IMAP-Accounts. Hat Vorrang vor email:.
|
||
EmailAccounts []EmailAccount `yaml:"email_accounts"`
|
||
|
||
Tasks struct {
|
||
StorePath string `yaml:"store_path"`
|
||
} `yaml:"tasks"`
|
||
|
||
Daemon struct {
|
||
ChannelID string `yaml:"channel_id"`
|
||
EmailIntervalMin int `yaml:"email_interval_min"`
|
||
TaskReminderHour int `yaml:"task_reminder_hour"`
|
||
CleanupHour int `yaml:"cleanup_hour"` // Uhrzeit für tägliches Archiv-Aufräumen (Standard: 2)
|
||
IngestHour int `yaml:"ingest_hour"` // Uhrzeit für nächtlichen Email-Ingest (Standard: 23, 0 = deaktiviert)
|
||
} `yaml:"daemon"`
|
||
|
||
BrainRoot string `yaml:"brain_root"`
|
||
TopK uint64 `yaml:"top_k"`
|
||
ScoreThreshold float32 `yaml:"score_threshold"`
|
||
|
||
// RSSFeeds definiert RSS-Feeds die automatisch überwacht werden.
|
||
RSSFeeds []RSSFeed `yaml:"rss_feeds"`
|
||
}
|
||
|
||
// RSSFeed beschreibt einen RSS-Feed mit Polling-Intervall.
|
||
type RSSFeed struct {
|
||
URL string `yaml:"url"`
|
||
IntervalHours int `yaml:"interval_hours"` // 0 = Standard 24h
|
||
}
|
||
|
||
var Cfg Config
|
||
|
||
// AllEmailAccounts gibt alle konfigurierten Email-Accounts zurück.
|
||
// Wenn email_accounts konfiguriert ist, hat das Vorrang vor dem Legacy-email:-Block.
|
||
func AllEmailAccounts() []EmailAccount {
|
||
if len(Cfg.EmailAccounts) > 0 {
|
||
return Cfg.EmailAccounts
|
||
}
|
||
if Cfg.Email.Host == "" {
|
||
return nil
|
||
}
|
||
return []EmailAccount{{
|
||
Name: "Email",
|
||
Host: Cfg.Email.Host,
|
||
Port: Cfg.Email.Port,
|
||
User: Cfg.Email.User,
|
||
Password: Cfg.Email.Password,
|
||
TLS: Cfg.Email.TLS,
|
||
StartTLS: Cfg.Email.StartTLS,
|
||
Folder: Cfg.Email.Folder,
|
||
ProcessedFolder: Cfg.Email.ProcessedFolder,
|
||
Model: Cfg.Email.Model,
|
||
ArchiveFolders: Cfg.Email.ArchiveFolders,
|
||
TriageImportantFolder: Cfg.Email.TriageImportantFolder,
|
||
TriageUnimportantFolder: Cfg.Email.TriageUnimportantFolder,
|
||
}}
|
||
}
|
||
|
||
// NewQdrantConn öffnet eine gRPC-Verbindung zur Qdrant-Instanz.
|
||
// Der Aufrufer ist verantwortlich für conn.Close().
|
||
func NewQdrantConn() *grpc.ClientConn {
|
||
conn, err := grpc.Dial(
|
||
fmt.Sprintf("%s:%s", Cfg.Qdrant.Host, Cfg.Qdrant.Port),
|
||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||
)
|
||
if err != nil {
|
||
log.Fatalf("❌ Qdrant Verbindung fehlgeschlagen: %v", err)
|
||
}
|
||
return conn
|
||
}
|
||
|
||
// NewEmbeddingClient erstellt einen Client für LocalAI (Embeddings).
|
||
func NewEmbeddingClient() *openai.Client {
|
||
c := openai.DefaultConfig("localai")
|
||
c.BaseURL = Cfg.Embedding.URL
|
||
return openai.NewClientWithConfig(c)
|
||
}
|
||
|
||
// NewChatClient erstellt einen Client für Chat-Completion (LocalAI).
|
||
func NewChatClient() *openai.Client {
|
||
c := openai.DefaultConfig("localai")
|
||
c.BaseURL = Cfg.Chat.URL
|
||
return openai.NewClientWithConfig(c)
|
||
}
|
||
|
||
// LoadConfig liest config.yml aus dem aktuellen Verzeichnis und validiert Pflichtfelder.
|
||
func LoadConfig() {
|
||
data, err := os.ReadFile("config.yml")
|
||
if err != nil {
|
||
log.Fatalf("❌ config.yml nicht gefunden: %v\n Lege config.yml im selben Verzeichnis an.", err)
|
||
}
|
||
if err := yaml.Unmarshal(data, &Cfg); err != nil {
|
||
log.Fatalf("❌ config.yml ungültig: %v", err)
|
||
}
|
||
validateConfig()
|
||
}
|
||
|
||
// validateConfig prüft Pflichtfelder und gibt früh eine klare Fehlermeldung.
|
||
func validateConfig() {
|
||
var errs []string
|
||
if Cfg.Qdrant.Host == "" {
|
||
errs = append(errs, "qdrant.host fehlt")
|
||
}
|
||
if Cfg.Qdrant.Port == "" {
|
||
errs = append(errs, "qdrant.port fehlt")
|
||
}
|
||
if Cfg.Embedding.URL == "" {
|
||
errs = append(errs, "embedding.url fehlt")
|
||
}
|
||
if Cfg.Embedding.Model == "" {
|
||
errs = append(errs, "embedding.model fehlt")
|
||
}
|
||
if Cfg.Chat.URL == "" {
|
||
errs = append(errs, "chat.url fehlt")
|
||
}
|
||
if Cfg.Chat.Model == "" {
|
||
errs = append(errs, "chat.model fehlt")
|
||
}
|
||
if len(errs) > 0 {
|
||
for _, e := range errs {
|
||
log.Printf("❌ config.yml: %s", e)
|
||
}
|
||
log.Fatal("❌ Konfiguration unvollständig – Bot wird nicht gestartet.")
|
||
}
|
||
}
|