Skip to content
Merged
Binary file removed .DS_Store
Binary file not shown.
200 changes: 200 additions & 0 deletions frontend/agent/agent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package agent

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
)

type FilesRequestBody struct {
StartEvent InputFileEvent `json:"start_event"`
Context map[string]any `json:"context"`
HandlerId string `json:"handler_id"`
}

type InputFileEvent struct {
FileId string `json:"file_id"`
Username string `json:"username"`
FileName string `json:"file_name"`
}

type FilesResultValue struct {
Success bool `json:"success"`
Error *string `json:"error"`
}
type FilesResponseResult struct {
Value FilesResultValue `json:"value"`
QualifiedName string `json:"qualified_name"`
Type string `json:"type"`
Types []string `json:"types"`
}

type FilesResponseBody struct {
HandlerId string `json:"handler_id"`
WorkflowName string `json:"workflow_name"`
RunId string `json:"run_id"`
Status string `json:"status"`
StartedAt *string `json:"started_at"`
UpdatedAt *string `json:"updated_at"`
CompletedAt *string `json:"completed_at"`
Error *string `json:"error"`
Result *FilesResponseResult `json:"result"`
}

func (b *FilesResponseBody) GetErrorString() *string {
return b.Result.Value.Error
}

type SearchRequestBody struct {
StartEvent SearchInputEvent `json:"start_event"`
Context map[string]any `json:"context"`
HandlerId string `json:"handler_id"`
}

type SearchInputEvent struct {
SearchType string `json:"search_type"`
SearchInput string `json:"search_input"`
Username string `json:"username"`
FileName *string `json:"file_name"`
Category *string `json:"category"`
}

type SearchResult struct {
ResultType string `json:"result_type"`
Text string `json:"text"`
Similarity float64 `json:"similarity"`
FileName string `json:"file_name"`
Category string `json:"category"`
}

type SearchResultValue struct {
Results []SearchResult `json:"results"`
}

type SearchResponseResult struct {
Value SearchResultValue `json:"value"`
QualifiedName string `json:"qualified_name"`
Type string `json:"type"`
Types []string `json:"types"`
}

type SearchResponseBody struct {
HandlerId string `json:"handler_id"`
WorkflowName string `json:"workflow_name"`
RunId string `json:"run_id"`
Status string `json:"status"`
StartedAt *string `json:"started_at"`
UpdatedAt *string `json:"updated_at"`
CompletedAt *string `json:"completed_at"`
Error *string `json:"error"`
Result *SearchResponseResult `json:"result"`
}

func (b *SearchResponseBody) GetResults() []SearchResult {
if b.Result != nil {
return b.Result.Value.Results
}
return nil
}

func ProcessFile(fileInput InputFileEvent) (*FilesResponseBody, error) {
requestBody := FilesRequestBody{StartEvent: fileInput, Context: map[string]any{}, HandlerId: ""}
apiKey := os.Getenv("LLAMA_CLOUD_API_KEY")
apiEndpoint := os.Getenv("FILES_API_ENDPOINT")
jsonData, err := json.Marshal(requestBody)
if err != nil {
return nil, err
}
// Create the HTTP request
ctx := context.Background()
req, err := http.NewRequestWithContext(ctx, "POST", apiEndpoint, bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}

// Set headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+apiKey)

// Send the request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body))
}

// Read the response
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

var response FilesResponseBody

err = json.Unmarshal(body, &response)
if err != nil {
return nil, err
}
// Check status code
if resp.StatusCode != http.StatusOK {
return nil, errors.New(*response.Error)
}
return &response, nil
}

func ProcessSearch(searchInput SearchInputEvent) (*SearchResponseBody, error) {
requestBody := SearchRequestBody{StartEvent: searchInput, Context: map[string]any{}, HandlerId: ""}
apiKey := os.Getenv("LLAMA_CLOUD_API_KEY")
apiEndpoint := os.Getenv("SEARCH_API_ENDPOINT")
jsonData, err := json.Marshal(requestBody)
if err != nil {
return nil, err
}
// Create the HTTP request
ctx := context.Background()
req, err := http.NewRequestWithContext(ctx, "POST", apiEndpoint, bytes.NewBuffer(jsonData))
if err != nil {
return nil, err
}

// Set headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+apiKey)

// Send the request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

// Read the response
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
return nil, errors.New(string(body))
}

var response SearchResponseBody

err = json.Unmarshal(body, &response)
if err != nil {
return nil, err
}
return &response, nil
}
27 changes: 14 additions & 13 deletions frontend/files/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ import (
)

type UploadedFile struct {
CreatedAt *string `json:"created_at"`
DataSourceID *string `json:"data_source_id"`
ExternalFileID *string `json:"external_file_id"`
FileSize *int64 `json:"file_size"`
FileType *string `json:"file_type"`
ID string `json:"id"`
LastModifiedAt *string `json:"last_modified_at"`
Name string `json:"name"`
PermissionInfo map[string]interface{} `json:"permission_info"`
ProjectID string `json:"project_id"`
ResourceInfo map[string]interface{} `json:"resource_info"`
UpdatedAt *string `json:"updated_at"`
CreatedAt *string `json:"created_at"`
DataSourceID *string `json:"data_source_id"`
ExternalFileID *string `json:"external_file_id"`
FileSize *int64 `json:"file_size"`
FileType *string `json:"file_type"`
ID string `json:"id"`
LastModifiedAt *string `json:"last_modified_at"`
Name string `json:"name"`
PermissionInfo map[string]any `json:"permission_info"`
ProjectID string `json:"project_id"`
ResourceInfo map[string]any `json:"resource_info"`
UpdatedAt *string `json:"updated_at"`
}

func UploadFile(file io.Reader, fileName string) (string, error) {
Expand All @@ -33,6 +33,7 @@ func UploadFile(file io.Reader, fileName string) (string, error) {

io.Copy(fileWriter, file)

contentType := writer.FormDataContentType()
writer.Close()
url := "https://api.cloud.llamaindex.ai/api/v1/files"
method := "POST"
Expand All @@ -43,7 +44,7 @@ func UploadFile(file io.Reader, fileName string) (string, error) {
if err != nil {
return "", err
}
req.Header.Add("Content-Type", "multipart/form-data")
req.Header.Add("Content-Type", contentType)
req.Header.Add("Accept", "application/json")
req.Header.Add("Authorization", "Bearer "+apiKey)

Expand Down
99 changes: 98 additions & 1 deletion frontend/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/run-llama/study-llama/frontend/agent"
"github.com/run-llama/study-llama/frontend/auth"
db "github.com/run-llama/study-llama/frontend/authdb"
"github.com/run-llama/study-llama/frontend/files"
Expand Down Expand Up @@ -219,10 +220,17 @@ func HandleUploadFile(c *fiber.Ctx) error {
return templates.StatusBanner(err).Render(c.Context(), c.Response().BodyWriter())
}
defer src.Close()
_, err = files.UploadFile(src, file.Filename)
fileId, err := files.UploadFile(src, file.Filename)
if err != nil {
return templates.StatusBanner(err).Render(c.Context(), c.Response().BodyWriter())
}
response, err := agent.ProcessFile(agent.InputFileEvent{FileId: fileId, FileName: file.Filename, Username: user.Username})
if err != nil {
return templates.StatusBanner(err).Render(c.Context(), c.Response().BodyWriter())
}
if response.GetErrorString() != nil {
return templates.StatusBanner(errors.New(*response.GetErrorString())).Render(c.Context(), c.Response().BodyWriter())
}
db, err := files.CreateNewDb()
if err != nil {
return templates.StatusBanner(err).Render(c.Context(), c.Response().BodyWriter())
Expand Down Expand Up @@ -262,6 +270,35 @@ func HandleDeleteFile(c *fiber.Ctx) error {
return templates.FilesList(files).Render(c.Context(), c.Response().BodyWriter())
}

func HandleSearch(c *fiber.Ctx) error {
user, err := auth.AuthorizePost(c)
c.Set("Content-Type", "text/html")
if err != nil {
return templates.StatusBanner(err).Render(c.Context(), c.Response().BodyWriter())
}
searchType := c.FormValue("search_type")
searchInput := c.FormValue("search_input")
fileName := c.FormValue("file_name") // only one file name is allowed (can be empty)
category := c.FormValue("category") // select among available categories (can be empty)
var categoryFilter *string
var fileNameFilter *string
if category == "" {
categoryFilter = nil
} else {
categoryFilter = &category
}
if fileName == "" {
fileNameFilter = nil
} else {
fileNameFilter = &fileName
}
searchResult, err := agent.ProcessSearch(agent.SearchInputEvent{SearchType: searchType, SearchInput: searchInput, Category: categoryFilter, FileName: fileNameFilter, Username: user.Username})
if err != nil {
return templates.StatusBanner(err).Render(c.Context(), c.Response().BodyWriter())
}
return templates.SearchResultsList(searchResult.GetResults()).Render(c.Context(), c.Response().BodyWriter())
}

func LoginRoute(c *fiber.Ctx) error {
if c.Method() != fiber.MethodGet {
return c.SendStatus(fiber.StatusMethodNotAllowed)
Expand Down Expand Up @@ -319,3 +356,63 @@ func CategoriesRoute(c *fiber.Ctx) error {

return templates.RulesPage(user.Username, rules).Render(c.Context(), c.Response().BodyWriter())
}

func FilesRoute(c *fiber.Ctx) error {
if c.Method() != fiber.MethodGet {
return c.SendStatus(fiber.StatusMethodNotAllowed)
}
user, err := auth.AuthorizeGet(c)
c.Set("Content-Type", "text/html")
if err != nil {
return templates.AuthFailedPage().Render(c.Context(), c.Response().BodyWriter())
}
db, err := files.CreateNewDb()
if err != nil {
return templates.Page500(err).Render(c.Context(), c.Response().BodyWriter())
}
queries := filesdb.New(db)
files, err := queries.GetFiles(context.Background(), user.Username)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return templates.FilesPage([]filesdb.File{}).Render(c.Context(), c.Response().BodyWriter())
}
return templates.Page500(err).Render(c.Context(), c.Response().BodyWriter())
}
return templates.FilesPage(files).Render(c.Context(), c.Response().BodyWriter())
}

func SearchRoute(c *fiber.Ctx) error {
if c.Method() != fiber.MethodGet {
return c.SendStatus(fiber.StatusMethodNotAllowed)
}
user, err := auth.AuthorizeGet(c)
c.Set("Content-Type", "text/html")
if err != nil {
return templates.AuthFailedPage().Render(c.Context(), c.Response().BodyWriter())
}
db, err := rules.CreateNewDb()
if err != nil {
return templates.Page500(err).Render(c.Context(), c.Response().BodyWriter())
}
queries := rulesdb.New(db)
rules, err := queries.GetRules(context.Background(), user.Username)
if err != nil {
if !errors.Is(err, pgx.ErrNoRows) {
return templates.Page500(err).Render(c.Context(), c.Response().BodyWriter())
}
rules = []rulesdb.Rule{}
}
dbFiles, err := files.CreateNewDb()
if err != nil {
return templates.Page500(err).Render(c.Context(), c.Response().BodyWriter())
}
queriesFiles := filesdb.New(dbFiles)
files, err := queriesFiles.GetFiles(context.Background(), user.Username)
if err != nil {
if !errors.Is(err, pgx.ErrNoRows) {
return templates.Page500(err).Render(c.Context(), c.Response().BodyWriter())
}
files = []filesdb.File{}
}
return templates.SearchPage(rules, files).Render(c.Context(), c.Response().BodyWriter())
}
9 changes: 7 additions & 2 deletions frontend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func cacheSetupGet(keyGen func(*fiber.Ctx) string) fiber.Handler {
}

func corsSetup(methods string) fiber.Handler {
allowedOrigins := []string{"https://gityear.re"}
allowedOrigins := []string{"https://studyllama.my.id"}
corsHandler := cors.New(
cors.Config{
AllowOriginsFunc: func(origin string) bool {
Expand Down Expand Up @@ -122,7 +122,12 @@ func Setup() *fiber.App {
app.Get("/categories", corsSetup("GET"), handlers.CategoriesRoute)
app.Post("/rules", limiterSetup(10), corsSetup("POST"), handlers.HandleCreateRule)
app.Patch("/rules", limiterSetup(10), corsSetup("POST"), handlers.HandleUpdateRule)
app.Delete("/rules/:id", limiterSetup(10), corsSetup("POST"), handlers.HandleDeleteRule)
app.Delete("/rules/:id", limiterSetup(10), corsSetup("DELETE"), handlers.HandleDeleteRule)
app.Get("/notes", corsSetup("GET"), handlers.FilesRoute)
app.Post("/notes", limiterSetup(10), corsSetup("POST"), handlers.HandleUploadFile)
app.Delete("/notes/:id", limiterSetup(10), corsSetup("DELETE"), handlers.HandleDeleteFile)
app.Get("/review", corsSetup("GET"), handlers.SearchRoute)
app.Post("/review", limiterSetup(10), corsSetup("POST"), handlers.HandleSearch)
app.Get("/", handlers.HomeRoute)
app.Static("/static", "./static/")
app.Use(handlers.PageDoesNotExistRoute)
Expand Down
Loading