paperless-gpt/app_http_handlers.go

353 lines
9.8 KiB
Go
Raw Normal View History

2024-10-28 11:34:41 -05:00
package main
import (
"encoding/json"
2024-10-28 11:34:41 -05:00
"fmt"
"net/http"
"os"
"strconv"
"text/template"
"time"
"github.com/gin-gonic/gin"
)
// getPromptsHandler handles the GET /api/prompts endpoint
func getPromptsHandler(c *gin.Context) {
templateMutex.RLock()
defer templateMutex.RUnlock()
// Read the templates from files or use default content
titleTemplateContent, err := os.ReadFile("prompts/title_prompt.tmpl")
if err != nil {
titleTemplateContent = []byte(defaultTitleTemplate)
}
tagTemplateContent, err := os.ReadFile("prompts/tag_prompt.tmpl")
if err != nil {
tagTemplateContent = []byte(defaultTagTemplate)
}
c.JSON(http.StatusOK, gin.H{
"title_template": string(titleTemplateContent),
"tag_template": string(tagTemplateContent),
})
}
// updatePromptsHandler handles the POST /api/prompts endpoint
func updatePromptsHandler(c *gin.Context) {
var req struct {
TitleTemplate string `json:"title_template"`
TagTemplate string `json:"tag_template"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request payload"})
return
}
templateMutex.Lock()
defer templateMutex.Unlock()
// Update title template
if req.TitleTemplate != "" {
t, err := template.New("title").Parse(req.TitleTemplate)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid title template: %v", err)})
return
}
titleTemplate = t
err = os.WriteFile("prompts/title_prompt.tmpl", []byte(req.TitleTemplate), 0644)
if err != nil {
log.Errorf("Failed to write title_prompt.tmpl: %v", err)
2024-10-28 11:34:41 -05:00
}
}
// Update tag template
if req.TagTemplate != "" {
t, err := template.New("tag").Parse(req.TagTemplate)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid tag template: %v", err)})
return
}
tagTemplate = t
err = os.WriteFile("prompts/tag_prompt.tmpl", []byte(req.TagTemplate), 0644)
if err != nil {
log.Errorf("Failed to write tag_prompt.tmpl: %v", err)
2024-10-28 11:34:41 -05:00
}
}
c.Status(http.StatusOK)
}
// getAllTagsHandler handles the GET /api/tags endpoint
func (app *App) getAllTagsHandler(c *gin.Context) {
ctx := c.Request.Context()
tags, err := app.Client.GetAllTags(ctx)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Error fetching tags: %v", err)})
log.Errorf("Error fetching tags: %v", err)
2024-10-28 11:34:41 -05:00
return
}
c.JSON(http.StatusOK, tags)
}
// documentsHandler handles the GET /api/documents endpoint
func (app *App) documentsHandler(c *gin.Context) {
ctx := c.Request.Context()
documents, err := app.Client.GetDocumentsByTags(ctx, []string{manualTag}, 25)
2024-10-28 11:34:41 -05:00
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Error fetching documents: %v", err)})
log.Errorf("Error fetching documents: %v", err)
2024-10-28 11:34:41 -05:00
return
}
c.JSON(http.StatusOK, documents)
}
// generateSuggestionsHandler handles the POST /api/generate-suggestions endpoint
func (app *App) generateSuggestionsHandler(c *gin.Context) {
ctx := c.Request.Context()
var suggestionRequest GenerateSuggestionsRequest
if err := c.ShouldBindJSON(&suggestionRequest); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid request payload: %v", err)})
log.Errorf("Invalid request payload: %v", err)
2024-10-28 11:34:41 -05:00
return
}
results, err := app.generateDocumentSuggestions(ctx, suggestionRequest, log.WithContext(ctx))
2024-10-28 11:34:41 -05:00
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Error processing documents: %v", err)})
log.Errorf("Error processing documents: %v", err)
2024-10-28 11:34:41 -05:00
return
}
c.JSON(http.StatusOK, results)
}
// updateDocumentsHandler handles the PATCH /api/update-documents endpoint
func (app *App) updateDocumentsHandler(c *gin.Context) {
ctx := c.Request.Context()
var documents []DocumentSuggestion
if err := c.ShouldBindJSON(&documents); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid request payload: %v", err)})
log.Errorf("Invalid request payload: %v", err)
2024-10-28 11:34:41 -05:00
return
}
err := app.Client.UpdateDocuments(ctx, documents, app.Database, false)
2024-10-28 11:34:41 -05:00
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Error updating documents: %v", err)})
log.Errorf("Error updating documents: %v", err)
2024-10-28 11:34:41 -05:00
return
}
c.Status(http.StatusOK)
}
func (app *App) submitOCRJobHandler(c *gin.Context) {
documentIDStr := c.Param("id")
documentID, err := strconv.Atoi(documentIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid document ID"})
return
}
// Create a new job
jobID := generateJobID() // Implement a function to generate unique job IDs
job := &Job{
ID: jobID,
DocumentID: documentID,
Status: "pending",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// Add job to store and queue
jobStore.addJob(job)
jobQueue <- job
// Return the job ID to the client
c.JSON(http.StatusAccepted, gin.H{"job_id": jobID})
}
func (app *App) getJobStatusHandler(c *gin.Context) {
jobID := c.Param("job_id")
job, exists := jobStore.getJob(jobID)
if !exists {
c.JSON(http.StatusNotFound, gin.H{"error": "Job not found"})
return
}
response := gin.H{
"job_id": job.ID,
"status": job.Status,
"created_at": job.CreatedAt,
"updated_at": job.UpdatedAt,
"pages_done": job.PagesDone,
}
if job.Status == "completed" {
response["result"] = job.Result
} else if job.Status == "failed" {
response["error"] = job.Result
}
c.JSON(http.StatusOK, response)
}
func (app *App) getAllJobsHandler(c *gin.Context) {
jobs := jobStore.GetAllJobs()
jobList := make([]gin.H, 0, len(jobs))
for _, job := range jobs {
response := gin.H{
"job_id": job.ID,
"status": job.Status,
"created_at": job.CreatedAt,
"updated_at": job.UpdatedAt,
"pages_done": job.PagesDone,
}
if job.Status == "completed" {
response["result"] = job.Result
} else if job.Status == "failed" {
response["error"] = job.Result
}
jobList = append(jobList, response)
}
c.JSON(http.StatusOK, jobList)
}
// getDocumentHandler handles the retrieval of a document by its ID
func (app *App) getDocumentHandler() gin.HandlerFunc {
return func(c *gin.Context) {
id := c.Param("id")
parsedID, err := strconv.Atoi(id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid document ID"})
return
}
document, err := app.Client.GetDocument(c, parsedID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
log.Errorf("Error fetching document: %v", err)
2024-10-28 11:34:41 -05:00
return
}
c.JSON(http.StatusOK, document)
}
}
// Section for local-db actions
func (app *App) getModificationHistoryHandler(c *gin.Context) {
// Parse pagination parameters
page := 1
pageSize := 20
if p, err := strconv.Atoi(c.DefaultQuery("page", "1")); err == nil && p > 0 {
page = p
}
if ps, err := strconv.Atoi(c.DefaultQuery("pageSize", "20")); err == nil && ps > 0 && ps <= 100 {
pageSize = ps
}
// Get paginated modifications and total count
modifications, total, err := GetPaginatedModifications(app.Database, page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve modification history"})
log.Errorf("Failed to retrieve modification history: %v", err)
return
}
totalPages := (int(total) + pageSize - 1) / pageSize
c.JSON(http.StatusOK, gin.H{
"items": modifications,
"totalItems": total,
"totalPages": totalPages,
"currentPage": page,
"pageSize": pageSize,
})
}
func (app *App) undoModificationHandler(c *gin.Context) {
id := c.Param("id")
modID, err := strconv.Atoi(id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid modification ID"})
log.Errorf("Invalid modification ID: %v", err)
return
}
modification, err := GetModification(app.Database, uint(modID))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve modification"})
log.Errorf("Failed to retrieve modification: %v", err)
return
}
if modification.Undone {
c.JSON(http.StatusBadRequest, gin.H{"error": "Modification has already been undone"})
log.Errorf("Modification has already been undone: %v", id)
return
}
// Ok, we're actually doing the update:
ctx := c.Request.Context()
// Make the document suggestions for UpdateDocuments
var suggestion DocumentSuggestion
suggestion.ID = int(modification.DocumentID)
suggestion.OriginalDocument, err = app.Client.GetDocument(ctx, int(modification.DocumentID))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve original document"})
log.Errorf("Failed to retrieve original document: %v", err)
return
}
switch modification.ModField {
case "title":
suggestion.SuggestedTitle = modification.PreviousValue
case "tags":
var tags []string
err := json.Unmarshal([]byte(modification.PreviousValue), &tags)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to unmarshal previous tags"})
log.Errorf("Failed to unmarshal previous tags: %v", err)
return
}
suggestion.SuggestedTags = tags
case "content":
suggestion.SuggestedContent = modification.PreviousValue
default:
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid modification field"})
log.Errorf("Invalid modification field: %v", modification.ModField)
return
}
// Update the document
err = app.Client.UpdateDocuments(ctx, []DocumentSuggestion{suggestion}, app.Database, true)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update document"})
log.Errorf("Failed to update document: %v", err)
return
}
// Successful, so set modification as undone
err = SetModificationUndone(app.Database, modification)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to mark modification as undone"})
return
}
// Else all was ok
c.Status(http.StatusOK)
}