package main
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/alecthomas/chroma/v2/quick"
"github.com/google/generative-ai-go/genai"
"github.com/lrstanley/girc"
"github.com/pelletier/go-toml/v2"
openai "github.com/sashabaranov/go-openai"
"google.golang.org/api/option"
)
type TomlConfig struct {
IrcServer string
IrcPort int
IrcNick string
IrcSaslUser string
IrcSaslPass string
IrcChannel string
OllamaEndpoint string
Temp float64
OllamaSystem string
RequestTimeout int
MillaReconnectDelay int
EnableSasl bool
Model string
ChromaStyle string
ChromaFormatter string
Provider string
Apikey string
TopP float32
TopK int32
}
type OllamaResponse struct {
Response string `json:"response"`
}
type OllamaRequestOptions struct {
Temperature float64 `json:"temperature"`
}
type OllamaRequest struct {
Model string `json:"model"`
System string `json:"system"`
Prompt string `json:"prompt"`
Stream bool `json:"stream"`
Format string `json:"format"`
Options OllamaRequestOptions `json:"options"`
}
func printResponse(resp *genai.GenerateContentResponse) string {
result := ""
for _, cand := range resp.Candidates {
if cand.Content != nil {
for _, part := range cand.Content.Parts {
result += fmt.Sprintln(part)
log.Println(part)
}
}
}
return result
}
func runIRC(appConfig TomlConfig, ircChan chan *girc.Client) {
irc := girc.New(girc.Config{
Server: appConfig.IrcServer,
Port: appConfig.IrcPort,
Nick: appConfig.IrcNick,
User: appConfig.IrcNick,
Name: appConfig.IrcNick,
SSL: true,
TLSConfig: &tls.Config{InsecureSkipVerify: true},
})
saslUser := appConfig.IrcSaslUser
saslPass := appConfig.IrcSaslPass
if appConfig.EnableSasl && saslUser != "" && saslPass != "" {
irc.Config.SASL = &girc.SASLPlain{
User: appConfig.IrcSaslUser,
Pass: appConfig.IrcSaslPass,
}
}
irc.Handlers.AddBg(girc.CONNECTED, func(c *girc.Client, e girc.Event) {
channels := strings.Split(appConfig.IrcChannel, " ")
for _, channel := range channels {
c.Cmd.Join(channel)
}
})
if appConfig.Provider == "ollama" {
irc.Handlers.AddBg(girc.PRIVMSG, func(client *girc.Client, event girc.Event) {
if strings.HasPrefix(event.Last(), appConfig.IrcNick+": ") {
prompt := strings.TrimPrefix(event.Last(), appConfig.IrcNick+": ")
log.Println(prompt)
ollamaRequest := OllamaRequest{
Model: appConfig.Model,
System: appConfig.OllamaSystem,
Prompt: prompt,
Stream: false,
Format: "json",
Options: OllamaRequestOptions{
Temperature: appConfig.Temp,
},
}
jsonPayload, err := json.Marshal(ollamaRequest)
if err != nil {
client.Cmd.ReplyTo(event, girc.Fmt(fmt.Sprintf("error: %s", err.Error())))
return
}
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(appConfig.RequestTimeout)*time.Second)
defer cancel()
request, err := http.NewRequest(http.MethodPost, appConfig.OllamaEndpoint, bytes.NewBuffer(jsonPayload))
request = request.WithContext(ctx)
if err != nil {
client.Cmd.ReplyTo(event, girc.Fmt(fmt.Sprintf("error: %s", err.Error())))
return
}
request.Header.Set("Content-Type", "application/json")
httpClient := http.Client{}
response, err := httpClient.Do(request)
if err != nil {
client.Cmd.ReplyTo(event, girc.Fmt(fmt.Sprintf("error: %s", err.Error())))
return
}
defer response.Body.Close()
var ollamaResponse OllamaResponse
err = json.NewDecoder(response.Body).Decode(&ollamaResponse)
if err != nil {
client.Cmd.ReplyTo(event, girc.Fmt(fmt.Sprintf("error: %s", err.Error())))
return
}
var writer bytes.Buffer
err = quick.Highlight(&writer,
ollamaResponse.Response,
"markdown",
appConfig.ChromaFormatter,
appConfig.ChromaStyle)
if err != nil {
client.Cmd.ReplyTo(event, girc.Fmt(fmt.Sprintf("error: %s", err.Error())))
return
}
client.Cmd.ReplyTo(event, girc.Fmt("\033[0m"+writer.String()))
}
})
} else if appConfig.Provider == "gemini" {
log.Println("fuck prime")
irc.Handlers.AddBg(girc.PRIVMSG, func(client *girc.Client, event girc.Event) {
if strings.HasPrefix(event.Last(), appConfig.IrcNick+": ") {
prompt := strings.TrimPrefix(event.Last(), appConfig.IrcNick+": ")
log.Println(prompt)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(appConfig.RequestTimeout)*time.Second)
defer cancel()
// dialer := proxy.FromEnvironment()
// transport := http.Transport{
// Dial: dialer.Dial,
// }
// httpClient := http.Client{
// Transport: &transport,
// Timeout: time.Duration(appConfig.RequestTimeout) * time.Second,
// }
// clientGemini, err := genai.NewClient(ctx, option.WithAPIKey(appConfig.Apikey), option.WithHTTPClient(&httpClient))
clientGemini, err := genai.NewClient(ctx, option.WithAPIKey(appConfig.Apikey))
if err != nil {
client.Cmd.ReplyTo(event, girc.Fmt(fmt.Sprintf("error: %s", err.Error())))
return
}
defer clientGemini.Close()
model := clientGemini.GenerativeModel(appConfig.Model)
model.SetTemperature(float32(appConfig.Temp))
model.SetTopK(appConfig.TopK)
model.SetTopP(appConfig.TopP)
log.Println("fuck")
resp, err := model.GenerateContent(ctx, genai.Text(prompt))
if err != nil {
client.Cmd.ReplyTo(event, girc.Fmt(fmt.Sprintf("error: %s", err.Error())))
return
}
log.Println("fuck two")
var writer bytes.Buffer
err = quick.Highlight(
&writer,
printResponse(resp),
"markdown",
appConfig.ChromaFormatter,
appConfig.ChromaStyle)
if err != nil {
client.Cmd.ReplyTo(event, girc.Fmt(fmt.Sprintf("error: %s", err.Error())))
return
}
log.Println(writer.String())
client.Cmd.ReplyTo(event, girc.Fmt("\033[0m"+writer.String()))
}
})
} else if appConfig.Provider == "chatgpt" {
irc.Handlers.AddBg(girc.PRIVMSG, func(client *girc.Client, event girc.Event) {
if strings.HasPrefix(event.Last(), appConfig.IrcNick+": ") {
prompt := strings.TrimPrefix(event.Last(), appConfig.IrcNick+": ")
log.Println(prompt)
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(appConfig.RequestTimeout)*time.Second)
defer cancel()
gptClient := openai.NewClient(appConfig.Apikey)
messages := make([]openai.ChatCompletionMessage, 0)
messages = append(messages, openai.ChatCompletionMessage{
Role: "system",
Content: prompt,
})
resp, err := gptClient.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: appConfig.Model,
Messages: messages,
})
if err != nil {
client.Cmd.ReplyTo(event, girc.Fmt(fmt.Sprintf("error: %s", err.Error())))
return
}
var writer bytes.Buffer
err = quick.Highlight(
&writer,
resp.Choices[0].Message.Content,
"markdown",
appConfig.ChromaFormatter,
appConfig.ChromaStyle)
if err != nil {
client.Cmd.ReplyTo(event, girc.Fmt(fmt.Sprintf("error: %s", err.Error())))
return
}
log.Println(writer.String())
client.Cmd.ReplyTo(event, girc.Fmt("\033[0m"+writer.String()))
}
})
}
ircChan <- irc
for {
if err := irc.Connect(); err != nil {
log.Println(err)
log.Println("reconnecting in 30 seconds")
time.Sleep(time.Duration(appConfig.MillaReconnectDelay) * time.Second)
} else {
return
}
}
}
func main() {
var appConfig TomlConfig
configPath := flag.String("config", "./config-gemini.toml", "path to the config file")
flag.Parse()
data, err := os.ReadFile(*configPath)
if err != nil {
log.Fatal(err)
}
err = toml.Unmarshal(data, &appConfig)
if err != nil {
log.Fatal(err)
}
log.Println(appConfig)
ircChan := make(chan *girc.Client, 1)
runIRC(appConfig, ircChan)
}