229 lines
6.2 KiB
Go
229 lines
6.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
type Communication struct {
|
|
Communication string `json:"communication"`
|
|
Hash string `json:"hash"`
|
|
}
|
|
|
|
type Response struct {
|
|
Model string `json:"model"`
|
|
CreatedAt string `json:"created_at"`
|
|
Response string `json:"response"`
|
|
Done bool `json:"done"`
|
|
DoneReason string `json:"done_reason"`
|
|
Context []int `json:"context"`
|
|
TotalDuration int `json:"total_duration"`
|
|
LoadDuration int `json:"load_duration"`
|
|
PromptEC int `json:"prompt_eval_count"`
|
|
PromptED int `json:"prompt_eval_duration"`
|
|
EvalCount int `json:"eval_count"`
|
|
EvalDuration int `json:"eval_duration"`
|
|
}
|
|
|
|
// Chats are updated once the content is decrypted. Since this is never leaving
|
|
// the server once decrypted, and possibly will not be saved (depending on user
|
|
// settings), it will remain decrypted until it's time to transform into a
|
|
// Communication JSON object, which is the HTTP response that is re-encrypted.
|
|
|
|
// TODO: If the user has enabled conversation history, then save the encrypted
|
|
// chats to the server's drive. Additionally, only accept incoming additional
|
|
// messages from the user, rather than having the client re-send Chats already
|
|
// stored on the server.
|
|
type Chat struct {
|
|
Role bool `json:"role"`
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
type PromptWHistory struct {
|
|
Model string `json:"model"`
|
|
Messages []Chat `json:"messages"`
|
|
Stream bool `json:"stream"`
|
|
}
|
|
|
|
type ResponseWHistory struct {
|
|
Model string `json:"model"`
|
|
CreatedAt string `json:"created_at"`
|
|
Message Chat `json:"message"`
|
|
Done bool `json:"done"`
|
|
DoneReason string `json:"done_reason"`
|
|
Context []int `json:"context"`
|
|
TotalDuration int `json:"total_duration"`
|
|
LoadDuration int `json:"load_duration"`
|
|
PromptEC int `json:"prompt_eval_count"`
|
|
PromptED int `json:"prompt_eval_duration"`
|
|
EvalCount int `json:"eval_count"`
|
|
EvalDuration int `json:"eval_duration"`
|
|
}
|
|
|
|
var chats []Chat
|
|
|
|
// User HTTP GET request has the prompt decrypted and verified with a SHA-1 checksum.
|
|
// If there's been no data corruption, re-construct user prompt for ollama
|
|
// TODO: Eventually, add additional logic that will allow for re-direction and contextual awareness (pre-processing)
|
|
func chat(w http.ResponseWriter, r *http.Request) {
|
|
model := "qwen:0.5b" // qwen:0.5b used for testing while hosting from my laptop. llama3 seems to be the best to use normally.
|
|
|
|
input, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
defer r.Body.Close()
|
|
prompt := Communication{}
|
|
json.Unmarshal(input, &prompt)
|
|
|
|
newChat := Chat{}
|
|
newChat.Role = true
|
|
newChat.Content = prompt.Communication
|
|
|
|
if decrypt(&prompt, &newChat) {
|
|
chats = append(chats, newChat)
|
|
|
|
apiCall := PromptWHistory{}
|
|
apiCall.Model = model
|
|
apiCall.Messages = chats
|
|
apiCall.Stream = false // TODO: Allow for this as a user setting...
|
|
log.Print("Pre-JSON ification: ")
|
|
log.Println(apiCall)
|
|
|
|
j, err := json.Marshal(apiCall)
|
|
buf := strings.NewReader(string(j))
|
|
log.Print("Post-JSON ification: ")
|
|
log.Println(string(j))
|
|
|
|
// Port 11434 corresponds to Ollama. Direct access to Ollama is restricted.
|
|
resp, err := http.Post("http://localhost:11434/api/chat", "application/json", buf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
output, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
defer r.Body.Close()
|
|
response := ResponseWHistory{}
|
|
json.Unmarshal(output, &response)
|
|
log.Println(response)
|
|
|
|
} else {
|
|
return
|
|
}
|
|
}
|
|
|
|
func request(w http.ResponseWriter, r *http.Request) {
|
|
input, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
defer r.Body.Close()
|
|
prompt := Communication{}
|
|
json.Unmarshal(input, &prompt)
|
|
|
|
// TODO: Add support for dynamically changing models.
|
|
model := "qwen:0.5b" // qwen:0.5b used for testing while hosting from my laptop. llama3 seems to be the best to use normally.
|
|
|
|
apicall := "{\"model\": \""
|
|
apicall = apicall + model
|
|
apicall = apicall + "\", \"prompt\": \""
|
|
apicall = apicall + prompt.Communication
|
|
apicall = apicall + "\", \"stream\": false }"
|
|
|
|
buf := strings.NewReader(apicall)
|
|
|
|
// Port 11434 corresponds to Ollama. Direct access to Ollama is restricted.
|
|
resp, err := http.Post("http://localhost:11434/api/generate", "application/json", buf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
output, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
defer r.Body.Close()
|
|
response := Response{}
|
|
json.Unmarshal(output, &response)
|
|
|
|
fmt.Println(response.Response) // Print the body as a string
|
|
// TODO: Encrypt response.Response and send back as a Communication JSON object.
|
|
//c.IndentedJSON(http.StatusCreated, resp)
|
|
}
|
|
|
|
func serve() {
|
|
|
|
}
|
|
|
|
func init() {
|
|
var wg sync.WaitGroup
|
|
|
|
r := mux.NewRouter()
|
|
portfolioDir := "/home/violet/documents/development/portfolio/public_html/"
|
|
servePage(r, portfolioDir, "/", 1112)
|
|
|
|
srv := &http.Server{
|
|
Addr: "0.0.0.0:1111",
|
|
// Good practice to set timeouts to avoid Slowloris attacks.
|
|
WriteTimeout: time.Second * 15,
|
|
ReadTimeout: time.Second * 15,
|
|
IdleTimeout: time.Second * 60,
|
|
Handler: r, // Pass our instance of gorilla/mux in.
|
|
}
|
|
|
|
// Run our server in a goroutine so that it doesn't block.
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Println(err)
|
|
}
|
|
}()
|
|
|
|
log.Println("Router is running on port 1111")
|
|
|
|
c := make(chan os.Signal, 1)
|
|
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+Shift+C)
|
|
signal.Notify(c, os.Interrupt)
|
|
|
|
// Block until we receive our signal.
|
|
<-c
|
|
|
|
// Create a deadline to wait for.
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(30))
|
|
defer cancel()
|
|
// Doesn't block if no connections, but will otherwise wait
|
|
// until the timeout deadline.
|
|
srv.Shutdown(ctx)
|
|
// Optionally, you could run srv.Shutdown in a goroutine and block on
|
|
// <-ctx.Done() if your application should wait for other services
|
|
// to finalize based on context cancellation.
|
|
log.Println("shutting down")
|
|
wg.Wait()
|
|
os.Exit(0)
|
|
}
|
|
|
|
func main() {
|
|
// TODO: Authenticate user credentials to verify they are allowed to even access
|
|
serve()
|
|
}
|