x

Programs, configuration and documentation that don't fit anywhere else
Log | Files | Refs | README | LICENSE

llmweb.go (2532B)


      1 package main
      2 
      3 import (
      4 	"fmt"
      5 	"html/template"
      6 	"log"
      7 	"net"
      8 	"net/http"
      9 	"os"
     10 	"path"
     11 
     12 	"olowe.co/x/openai"
     13 )
     14 
     15 type Chat struct {
     16 	client   *openai.Client
     17 	template *template.Template
     18 }
     19 
     20 func (c *Chat) ServeHTTP(w http.ResponseWriter, req *http.Request) {
     21 	log.Println(req.RemoteAddr, req.Method, req.URL)
     22 	chat := openai.Chat{
     23 		Model: "mistral-small-latest",
     24 		Messages: []openai.Message{
     25 			{openai.RoleSystem, ""},
     26 		},
     27 	}
     28 
     29 	if req.Method == http.MethodGet {
     30 		if err := c.template.Execute(w, &chat); err != nil {
     31 			log.Println(err)
     32 		}
     33 		return
     34 	} else if req.Method != http.MethodPost {
     35 		w.WriteHeader(http.StatusMethodNotAllowed)
     36 		return
     37 	}
     38 
     39 	if err := req.ParseForm(); err != nil {
     40 		http.Error(w, err.Error(), http.StatusBadRequest)
     41 		return
     42 	}
     43 	log.Printf("%+v\n", req.PostForm)
     44 
     45 	if sys, ok := req.PostForm[openai.RoleSystem]; ok {
     46 		chat.Messages[0].Content = sys[0]
     47 	}
     48 
     49 	nuser := len(req.PostForm[openai.RoleUser])
     50 	nassistant := len(req.PostForm[openai.RoleAssistant])
     51 	if nuser != nassistant+1 {
     52 		e := fmt.Sprintf("expected %d user messages for %d assistant messages, got %d", nassistant+1, nassistant, nuser)
     53 		http.Error(w, e, http.StatusBadRequest)
     54 		return
     55 	}
     56 
     57 	for i := 0; i < nassistant; i++ {
     58 		user := openai.Message{openai.RoleUser, req.PostForm[openai.RoleUser][i]}
     59 		chat.Messages = append(chat.Messages, user)
     60 		reply := openai.Message{openai.RoleAssistant, req.PostForm[openai.RoleAssistant][i]}
     61 		chat.Messages = append(chat.Messages, reply)
     62 	}
     63 	latest := openai.Message{openai.RoleUser, req.PostForm[openai.RoleUser][nuser-1]}
     64 	chat.Messages = append(chat.Messages, latest)
     65 
     66 	reply, err := c.client.Complete(&chat)
     67 	if err != nil {
     68 		http.Error(w, err.Error(), http.StatusInternalServerError)
     69 		return
     70 	}
     71 	chat.Messages = append(chat.Messages, *reply)
     72 	c.template.Execute(w, &chat)
     73 }
     74 
     75 func servePWA(w http.ResponseWriter, req *http.Request) {
     76 	log.Println(req.RemoteAddr, req.Method, req.URL)
     77 	http.ServeFile(w, req, "manifest.json")
     78 }
     79 
     80 func main() {
     81 	confDir, err := os.UserConfigDir()
     82 	if err != nil {
     83 		log.Fatal(err)
     84 	}
     85 	config, err := readConfig(path.Join(confDir, "openai"))
     86 	if err != nil {
     87 		log.Fatalf("read configuration: %v", err)
     88 	}
     89 	client := &openai.Client{http.DefaultClient, config.Token, config.BaseURL}
     90 
     91 	tmpl, err := template.ParseGlob("*.html")
     92 	if err != nil {
     93 		log.Fatal(err)
     94 	}
     95 
     96 	ln, err := net.Listen("tcp", ":8080")
     97 	if err != nil {
     98 		log.Fatal(err)
     99 	}
    100 	http.Handle("/", &Chat{client, tmpl})
    101 	http.HandleFunc("/manifest.json", servePWA)
    102 	log.Fatal(http.Serve(ln, nil))
    103 }