package main
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/alecthomas/chroma/v2/quick"
"github.com/lrstanley/girc"
"github.com/pelletier/go-toml/v2"
)
type TomlConfig struct {
IrcServer string
IrcPort int
IrcNick string
IrcSaslUser string
IrcSaslPass string
IrcChannel string
OllamaEndpoint string
OllamaTemp float64
OllamaSystem string
RequestTimeout int
MillaReconnectDelay int
EnableSasl bool
Model string
ChromaStyle string
ChromaFormatter string
}
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 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) {
c.Cmd.Join(appConfig.IrcChannel)
})
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.OllamaTemp,
},
}
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
}
fmt.Println(writer.String())
client.Cmd.ReplyTo(event, girc.Fmt(writer.String()))
// client.Cmd.ReplyTo(event, girc.Fmt(ollamaResponse.Response))
}
})
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
data, err := os.ReadFile("/opt/milla/config.toml")
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)
}