Skip to content

Commit

Permalink
Feature/UI upgrade (#6)
Browse files Browse the repository at this point in the history
* feat!: UI update

* refactor: Update copyright symbol
  • Loading branch information
GNITOAHC authored Oct 23, 2024
1 parent faa83b2 commit f65855b
Show file tree
Hide file tree
Showing 26 changed files with 3,483 additions and 479 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.env
.DS_Store
node_modules
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": false
}
7 changes: 5 additions & 2 deletions internal/app/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package app
import (
"encoding/json"
"net/http"
"past-papers-web/templates"
"strings"
)

Expand Down Expand Up @@ -39,10 +40,12 @@ func (a *App) RegisterAdminRoutes(prefix string, mux *http.ServeMux) {
}

func (a *App) Admin(w http.ResponseWriter, r *http.Request) {
a.tmplExecute(w, []string{"templates/admin.html"}, map[string]interface{}{
templates.Render(w, "admin.html", map[string]interface{}{
"WaitingList": a.helper.GetWaitingList(),
})
return
// a.tmplExecute(w, []string{"templates/admin.html"}, map[string]interface{}{
// "WaitingList": a.helper.GetWaitingList(),
// })
}

// ApproveRegistration approves the registration of the user.
Expand Down
6 changes: 6 additions & 0 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"past-papers-web/internal/config"
"past-papers-web/internal/helper"
"past-papers-web/mailer"
"past-papers-web/templates"
)

var (
Expand All @@ -36,6 +37,8 @@ func StartServer() {

app := NewApp()

templates.NewTemplates()

err = http.Serve(lis, app.Routes())
if err != nil {
return
Expand All @@ -61,6 +64,9 @@ func NewApp() *App {
func (a *App) Routes() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/", a.Login)
mux.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, r.URL.Path[1:])
})
a.RegisterAdminRoutes("/admin", mux)
mux.HandleFunc("/refresh-tree", a.RefreshTree)
mux.HandleFunc("/register", a.Register)
Expand Down
12 changes: 3 additions & 9 deletions internal/app/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"html/template"
"net/http"
"past-papers-web/templates"
"strings"
"time"
)
Expand Down Expand Up @@ -60,13 +61,6 @@ func (a *App) Register(w http.ResponseWriter, r *http.Request) {
}

func (a *App) Login(w http.ResponseWriter, r *http.Request) {
renderTmpl := func() {
tmpl := template.Must(template.ParseFiles("templates/base.html", "templates/entry.html"))
if err := tmpl.Execute(w, nil); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
if r.Method == "POST" {
email := r.FormValue("email")
http.SetCookie(w, &http.Cookie{ // Set a cookie
Expand All @@ -87,9 +81,9 @@ func (a *App) Login(w http.ResponseWriter, r *http.Request) {
return
}

renderTmpl()
templates.Render(w, "entry.html", nil)
return
}
renderTmpl()
templates.Render(w, "entry.html", nil)
return
}
157 changes: 150 additions & 7 deletions internal/app/chat.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,82 @@
package app

import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"past-papers-web/templates"
"strings"
"time"

"github.com/google/generative-ai-go/genai"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
)

type ChatHis struct {
Role string `json:"role"`
Text string `json:"text"`
}

func (a *App) Chat(w http.ResponseWriter, r *http.Request) {
urlpath := r.URL.Path[len("/chat/"):]
a.tmplExecute(w, []string{"templates/chat.html"}, map[string]interface{}{
"Src": "/file/" + urlpath,
})
switch r.Method {
case http.MethodGet:
// case http.MethodPut:
templates.Render(w, "chat.html", map[string]interface{}{
"Src": "/file/" + urlpath,
})
case http.MethodPost:
log.Print("Post request to chat")

var chathis []ChatHis
if err := json.NewDecoder(r.Body).Decode(&chathis); err != nil {
http.Error(w, "Error decoding request: "+err.Error(), http.StatusBadRequest)
return
}
filepath := urlpath
filename := urlpath
strings.ReplaceAll(filename, "/", "_")

responseChan, err := a.chatComplete(chathis, filepath, filename, r.Context())
if err != nil {
http.Error(w, "Error completing chat: "+err.Error(), http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Transfer-Encoding", "chunked")
w.Header().Set("Cache-Control", "no-cache")
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming not supported", http.StatusInternalServerError)
return
}

for resp := range responseChan {
fmt.Fprintf(w, "%s", resp)
fmt.Print(resp)
flusher.Flush()
}

// for i := 0; i < 5; i++ {
// // Send a chunk of data
// fmt.Fprintf(w, "Message %d\n", i+1)
// log.Print("data: Message ", i+1)
//
// // Flush the buffer so the client receives the chunk immediately
// flusher.Flush()
//
// // Simulate some processing delay
// time.Sleep(1 * time.Second)
// }
flusher.Flush()
// w.Write([]byte("Hello"))
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}

type ChatRequest struct {
Expand All @@ -25,17 +86,99 @@ type ChatRequest struct {
FileName string `json:"fileName"`
}

// chatComplete will complete the chat given the history
func (a *App) chatComplete(his []ChatHis, filepath, filename string, ctx context.Context) (chan string, error) {
client, err := genai.NewClient(ctx, option.WithAPIKey(a.config.GEMINI_API_KEY))
if err != nil {
return nil, err
}
defer client.Close()

uri := ""
if u, has := a.chatcache.Get(filepath); has {
uri = u
log.Print("Cache hit")
} else {
opts := genai.UploadFileOptions{DisplayName: filename}
fileReader, err := a.helper.FileReader(filepath)
if err != nil {
return nil, err
}
doc, err := client.UploadFile(ctx, "", fileReader, &opts)
if err != nil {
return nil, err
}
uri = doc.URI
a.chatcache.Set(filepath, uri, time.Hour*48) // Cache for 2 days
}

model := client.GenerativeModel("gemini-1.5-flash")

cs := model.StartChat()
cs.History = []*genai.Content{
{
Parts: []genai.Part{
genai.FileData{URI: uri},
genai.Text("Please answer the following questions according to the content of the file."),
},
Role: "user",
},
}

for _, c := range his[:len(his)-1] {
cs.History = append(cs.History, &genai.Content{
Parts: []genai.Part{genai.Text(c.Text)},
Role: c.Role,
})
}

responseChan := make(chan string)
iter := cs.SendMessageStream(ctx, genai.Text(his[len(his)-1].Text))
go func() {
defer close(responseChan)
for {
res, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
log.Fatal(err)
}
for _, part := range res.Candidates[0].Content.Parts {
responseChan <- fmt.Sprint(part)
}
}
}()
return responseChan, nil

go func() {
defer close(responseChan)

resp, err := cs.SendMessage(ctx, genai.Text(his[len(his)-1].Text))
if err != nil {
log.Print("Error sending message:", err)
return
}

// Process streaming response
if resp.Candidates[0].Content != nil {
for _, part := range resp.Candidates[0].Content.Parts {
// Send each part to the channel
responseChan <- fmt.Sprint(part)
}
}
}()

return responseChan, nil
}

func (a *App) ChatEndpoint(w http.ResponseWriter, r *http.Request) {
var req ChatRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Error decoding request: "+err.Error(), http.StatusBadRequest)
return
}

type ChatHis struct {
Role string `json:"role"`
Text string `json:"text"`
}
var chatHis []ChatHis
if err := json.Unmarshal(req.Content, &chatHis); err != nil {
http.Error(w, "Error decoding request: "+err.Error(), http.StatusBadRequest)
Expand Down
43 changes: 39 additions & 4 deletions internal/app/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"sort"

"past-papers-web/internal/helper"
"past-papers-web/templates"
)

type contentItem struct {
Expand Down Expand Up @@ -87,6 +88,9 @@ func (a *App) uploadFile(w http.ResponseWriter, r *http.Request) {
}

func (a *App) GetContent(w http.ResponseWriter, r *http.Request, urlpath string) {
// Define files to be ignored
ignores := []string{"README.md", ".gitignore"}

items := make([]contentItem, 0)

treeNode, err := a.helper.TreeNode.GetChildren(urlpath)
Expand All @@ -100,6 +104,9 @@ func (a *App) GetContent(w http.ResponseWriter, r *http.Request, urlpath string)
}
sort.Strings(keys)

hasreadme := false

outer:
for _, k := range keys {
v := treeNode.Children[k]
link := ""
Expand All @@ -108,6 +115,16 @@ func (a *App) GetContent(w http.ResponseWriter, r *http.Request, urlpath string)
} else {
link = "/chat" + v.Path
}
if v.Name == "README.md" {
hasreadme = true
}

// Ignore files
for _, ignore := range ignores {
if v.Name == ignore {
continue outer
}
}
items = append(items, contentItem{
Link: link, // Absolute path (starts with /content)
Name: v.Name,
Expand All @@ -116,13 +133,31 @@ func (a *App) GetContent(w http.ResponseWriter, r *http.Request, urlpath string)
})
}

var readme string
if hasreadme {
readmeb, err := a.helper.GetFile(urlpath + "/README.md")
if err != nil {
fmt.Println("Error getting readme", err)
}
readme = string(readmeb)
}

// Prepend slash to url if not empty
if urlpath != "" {
urlpath = "/" + urlpath
}
a.tmplExecute(w, []string{"templates/content.html"}, map[string]interface{}{
"Path": "content" + urlpath,
"Items": items,
// a.tmplExecute(w, []string{"templates/content.html"}, map[string]interface{}{
// "Path": "content" + urlpath,
// "Items": items,
// "HasReadme": hasreadme,
// "Readme": readme,
// })
// log.Println(items)
templates.Render(w, "content.html", map[string]interface{}{
"Path": "content" + urlpath,
"Items": items,
"HasReadme": hasreadme,
"Readme": readme,
})
return
// return
}
15 changes: 0 additions & 15 deletions internal/app/tmpl_exec.go

This file was deleted.

Loading

0 comments on commit f65855b

Please sign in to comment.