<repo><f p="cmd\bot\main.go"><![CDATA[package main
import (
 "context"
 "os"
 "os/signal"
 "github.com/go-telegram/bot"
 "github.com/go-telegram/bot/models"
 "github.com/rakett1337/tg_assistant_bot/internal/config"
 "github.com/rakett1337/tg_assistant_bot/internal/handler"
 "github.com/rakett1337/tg_assistant_bot/internal/llm"
 "github.com/rakett1337/tg_assistant_bot/pkg/logger"
)
func init() {
 logger.Init()
 config.LoadConfig()
}
func main() {
 ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
 defer cancel()
 llmProcessor := llm.NewProcessor(config.AppConfig.GoogleAPIKey)
 defaultHandler := func(ctx context.Context, b *bot.Bot, update *models.Update) {
 handler.DefaultHandler(ctx, b, update, llmProcessor)
 }
 opts := []bot.Option{
 bot.WithDefaultHandler(defaultHandler),
 }
 b, err := bot.New(config.AppConfig.TelegramAPIKey, opts...)
 if err != nil {
 logger.Log.Fatalf("Failed to create bot: %v", err)
 }
 b.Start(ctx)
}]]></f><f p="internal\audio\audio.go"><![CDATA[package audio
import (
 "fmt"
 "os/exec"
 "github.com/rakett1337/tg_assistant_bot/pkg/logger"
)
func ConvertAudio(inputPath, outputPath string) error {
 cmd := exec.Command("ffmpeg", "-i", inputPath, outputPath)
 output, err := cmd.CombinedOutput()
 if err != nil {
 logger.Log.Errorf("FFmpeg conversion error: %v\nOutput: %s", err, string(output))
 return fmt.Errorf("FFmpeg conversion error: %v\nOutput: %s", err, string(output))
 }
 logger.Log.Infof("Audio converted from %s to %s", inputPath, outputPath)
 return nil
}]]></f><f p="internal\config\config.go"><![CDATA[package config
import (
 "os"
 "github.com/joho/godotenv"
 "github.com/kelseyhightower/envconfig"
 "github.com/rakett1337/tg_assistant_bot/pkg/logger"
)
type Config struct {
 TelegramAPIKey string `envconfig:"TELEGRAM_API_KEY" required:"true"`
 GoogleAPIKey string `envconfig:"GOOGLE_API_KEY" required:"true"`
}
var AppConfig Config
func LoadConfig() {
 if err := godotenv.Load(); err != nil && !os.IsNotExist(err) {
 logger.Log.Fatalf("Error loading .env file: %v", err)
 }
 err := envconfig.Process("", &AppConfig)
 if err != nil {
 logger.Log.Fatalf("Error loading config: %v", err)
 }
 logger.Log.Info("Configuration loaded successfully")
}]]></f><f p="internal\handler\handler.go"><![CDATA[package handler
import (
 "context"
 "io"
 "net/http"
 "os"
 "strings"
 "github.com/go-telegram/bot"
 tgmodels "github.com/go-telegram/bot/models"
 "github.com/rakett1337/tg_assistant_bot/internal/audio"
 "github.com/rakett1337/tg_assistant_bot/internal/llm"
 "github.com/rakett1337/tg_assistant_bot/pkg/logger"
)
func DefaultHandler(ctx context.Context, b *bot.Bot, update *tgmodels.Update, processor *llm.Processor) {
 if update.Message == nil {
 return
 }
 if update.Message.Voice != nil {
 err := handleVoiceMessage(ctx, b, update, processor)
 if err != nil {
 b.SendMessage(ctx, &bot.SendMessageParams{
 ChatID: update.Message.Chat.ID,
 Text: "Can't process voice message request",
 ParseMode: tgmodels.ParseModeHTML,
 })
 }
 return
 }
 if update.Message.Audio != nil {
 }
}
func handleVoiceMessage(ctx context.Context, b *bot.Bot, update *tgmodels.Update, processor *llm.Processor) error {
 logger.Log.Infof("Handling voice message from user %s", update.Message.From.Username)
 file, err := b.GetFile(ctx, &bot.GetFileParams{
 FileID: update.Message.Voice.FileID,
 })
 if err != nil {
 logger.Log.Errorf("Error getting file: %v", err)
 return err
 }
 downloadURL := b.FileDownloadLink(file)
 resp, err := http.Get(downloadURL)
 if err != nil {
 logger.Log.Errorf("Error downloading file: %v", err)
 return err
 }
 defer resp.Body.Close()
 fileData, err := io.ReadAll(resp.Body)
 if err != nil {
 logger.Log.Errorf("Error reading file data: %v", err)
 return err
 }
 inputFileName := file.FileUniqueID + ".oga"
 err = os.WriteFile(inputFileName, fileData, 0644)
 if err != nil {
 logger.Log.Errorf("Error saving file: %v", err)
 return err
 }
 defer os.Remove(inputFileName)
 outputFileName := strings.TrimSuffix(inputFileName, ".oga") + ".wav"
 err = audio.ConvertAudio(inputFileName, outputFileName)
 if err != nil {
 logger.Log.Errorf("Error converting audio: %v", err)
 return err
 }
 defer os.Remove(outputFileName)
 finalFile, err := os.ReadFile(outputFileName)
 if err != nil {
 logger.Log.Errorf("Error reading converted audio file: %v", err)
 return err
 }
 response, err := processor.ProcessAudio(ctx, finalFile)
 if err != nil {
 logger.Log.Errorf("Error processing audio with LLM: %v", err)
 return err
 }
 _, err = b.SendMessage(ctx, &bot.SendMessageParams{
 ChatID: update.Message.Chat.ID,
 Text: response,
 ParseMode: tgmodels.ParseModeHTML,
 })
 if err != nil {
 logger.Log.Errorf("Error sending message: %v", err)
 return err
 }
 return nil
}]]></f><f p="internal\llm\processor.go"><![CDATA[package llm
import (
 "context"
 "encoding/json"
 "log"
 "github.com/rakett1337/tg_assistant_bot/pkg/logger"
 "github.com/tmc/langchaingo/llms"
 "github.com/tmc/langchaingo/llms/googleai"
)
type Processor struct {
 apiKey string
}
func NewProcessor(apiKey string) *Processor {
 return &Processor{
 apiKey: apiKey,
 }
}
func (p *Processor) ProcessAudio(ctx context.Context, audioData []byte) (string, error) {
 llm, err := googleai.New(ctx, googleai.WithAPIKey(p.apiKey))
 if err != nil {
 logger.Log.Errorf("Error initializing LLM: %v", err)
 return "", err
 }
 answer, err := llm.GenerateContent(
 ctx,
 []llms.MessageContent{
 {
 Role: llms.ChatMessageTypeHuman,
 Parts: []llms.ContentPart{
 llms.TextPart("Find appropriate function to call and return"),
 llms.BinaryPart("audio/wav", audioData),
 },
 },
 },
 llms.WithMaxTokens(4000),
 llms.WithTemperature(0.1),
 llms.WithModel("gemini-1.5-pro"),
 llms.WithTools(availableTools),
 )
 if err != nil {
 logger.Log.Errorf("Error generating content: %v", err)
 return "", err
 }
 logger.Log.Printf("Answer: %v", answer.Choices[0].ToolCalls[0].FunctionCall.Name)
 logger.Log.Printf("Answer: %v", answer.Choices[0].ToolCalls[0].FunctionCall.Arguments)
 var todo ToDo
 if err := json.Unmarshal([]byte(answer.Choices[0].ToolCalls[0].FunctionCall.Arguments), &todo); err != nil {
 log.Fatal(err)
 }
 logger.Log.Infof("LLM response received")
 return answer.Choices[0].Content, nil
}]]></f><f p="internal\llm\tool.go"><![CDATA[package llm
import (
 "errors"
 "github.com/tmc/langchaingo/llms"
)
type ToDo struct {
 Name string `json:"name"`
 Items []string `json:"todos"`
}
func createNewToDo(name string, items []string) (ToDo, error) {
 if name == "" {
 return ToDo{}, errors.New("name cannot be empty")
 }
 return ToDo{
 Name: name,
 Items: items,
 }, nil
}
var availableTools = []llms.Tool{
 {
 Type: "function",
 Function: &llms.FunctionDefinition{
 Name: "createNewToDo",
 Description: "Create a new To-Do List.",
 Parameters: map[string]any{
 "type": "object",
 "properties": map[string]any{
 "name": map[string]any{
 "type": "string",
 "description": "Name of the To-Do",
 },
 "todos": map[string]any{
 "type": "string",
 "description": "Array of todos to add to To-Do list",
 "items": map[string]any{
 "type": "string",
 },
 },
 },
 "required": []string{"name", "todos"},
 },
 },
 },
}]]></f><f p="pkg\logger\logger.go"><![CDATA[package logger
import (
 "io"
 "os"
 "github.com/sirupsen/logrus"
)
var Log *logrus.Logger
func Init() {
 Log = logrus.New()
 Log.SetOutput(os.Stdout)
 Log.SetLevel(logrus.InfoLevel)
 Log.SetFormatter(&logrus.TextFormatter{
 FullTimestamp: true,
 })
}
func SetOutput(w io.Writer) {
 Log.SetOutput(w)
}]]></f></repo>

Provide a way to have configuration option to switch between AI providers (lerts go with googleai and openai for now), including models and such. Also add api key config options. 
also analyze the code to make it more modular etc