Skip to content

Commit

Permalink
Add an endpoint to fill the description of a file with AI
Browse files Browse the repository at this point in the history
  • Loading branch information
nono committed Oct 3, 2024
1 parent 902130a commit 1be1469
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 2 deletions.
77 changes: 75 additions & 2 deletions docs/files.md
Original file line number Diff line number Diff line change
Expand Up @@ -958,8 +958,8 @@ Get an image that shows the first page of a PDF (at most 1080x1920).
Get a thumbnail of a file (for an image & pdf only). `:format` can be `tiny` (96x96)
`small` (640x480), `medium` (1280x720), or `large` (1920x1080).

This API does not require authentication because the secret acts as a token.
This secret is valid for 10 minutes, after which the link will return an error.
This API does not require authentication because the secret acts as a token.
This secret is valid for 10 minutes, after which the link will return an error.
To retrieve a new functional link, you must query the files API again to obtain
a new secret.

Expand Down Expand Up @@ -1400,6 +1400,79 @@ Content-Type: application/vnd.api+json

The same status codes can be encountered as the `PATCH /files/:file-id` route.

### POST /files/:id/description

This endpoint fills the `metadata.description` field of a file with a
description generated by the IA from the content of this file.

#### Request

```http
POST /files/9152d568-7e7c-11e6-a377-37cbfb190b4b/description HTTP/1.1
```

#### Response

```http
HTTP/1.1 200 OK
Content-Type: application/json.vnd+api
```

```json
{
"data": {
"type": "io.cozy.files",
"id": "9152d568-7e7c-11e6-a377-37cbfb190b4b",
"meta": {
"rev": "2-20900ae0"
},
"attributes": {
"type": "file",
"name": "hi.txt",
"trashed": false,
"md5sum": "ODZmYjI2OWQxOTBkMmM4NQo=",
"created_at": "2016-09-19T12:38:04Z",
"updated_at": "2016-09-19T12:38:04Z",
"tags": ["poem"],
"size": 12,
"executable": false,
"class": "document",
"mime": "text/plain",
"metadata": {
"description": "Explores love's complexities through vivid imagery and heartfelt emotions"
},
"cozyMetadata": {
"doctypeVersion": "1",
"metadataVersion": 1,
"createdAt": "2016-09-20T18:32:49Z",
"createdByApp": "drive",
"createdOn": "https://cozy.example.com/",
"updatedAt": "2016-09-22T13:32:51Z",
"uploadedAt": "2016-09-21T04:27:50Z",
"uploadedOn": "https://cozy.example.com/",
"uploadedBy": {
"slug": "drive"
}
}
},
"relationships": {
"parent": {
"links": {
"related": "/files/f2f36fec-8018-11e6-abd8-8b3814d9a465"
},
"data": {
"type": "io.cozy.files",
"id": "f2f36fec-8018-11e6-abd8-8b3814d9a465"
}
}
},
"links": {
"self": "/files/9152d568-7e7c-11e6-a377-37cbfb190b4b"
}
}
}
```

### POST /files/archive

Create an archive. The body of the request lists the files and directories that
Expand Down
72 changes: 72 additions & 0 deletions model/rag/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package rag

import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"

"github.com/cozy/cozy-stack/model/instance"
"github.com/cozy/cozy-stack/model/vfs"
"github.com/cozy/cozy-stack/pkg/couchdb"
"github.com/labstack/echo/v4"
)

func AddDescriptionToFile(inst *instance.Instance, file *vfs.FileDoc) (*vfs.FileDoc, error) {
content, err := inst.VFS().OpenFile(file)
if err != nil {
return nil, err
}
defer content.Close()
description, err := callRAGDescription(inst, content, file.Mime)
if err != nil {
return nil, err
}
newfile := file.Clone().(*vfs.FileDoc)
if newfile.Metadata == nil {
newfile.Metadata = make(map[string]interface{})
}
newfile.Metadata["description"] = description
if err := couchdb.UpdateDocWithOld(inst, newfile, file); err != nil {
return nil, err
}
return newfile, nil
}

func callRAGDescription(inst *instance.Instance, content io.Reader, mime string) (string, error) {
ragServer := inst.RAGServer()
if ragServer.URL == "" {
return "", errors.New("no RAG server configured")
}
u, err := url.Parse(ragServer.URL)
if err != nil {
return "", err
}
u.Path = "/description"
req, err := http.NewRequest(http.MethodPost, u.String(), content)
if err != nil {
return "", err
}
req.Header.Add(echo.HeaderAuthorization, "Bearer "+ragServer.APIKey)
req.Header.Add("Content-Type", mime)
res, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer res.Body.Close()
if res.StatusCode >= 300 {
return "", fmt.Errorf("POST status code: %d", res.StatusCode)
}
var data struct {
Description string `json:"description"`
}
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
return "", err
}
if data.Description == "" {
return "", errors.New("no description")
}
return data.Description, nil
}
20 changes: 20 additions & 0 deletions web/files/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/cozy/cozy-stack/model/note"
"github.com/cozy/cozy-stack/model/oauth"
"github.com/cozy/cozy-stack/model/permission"
"github.com/cozy/cozy-stack/model/rag"
"github.com/cozy/cozy-stack/model/sharing"
"github.com/cozy/cozy-stack/model/vfs"
"github.com/cozy/cozy-stack/pkg/assets/statik"
Expand Down Expand Up @@ -385,6 +386,24 @@ func FileCopyHandler(c echo.Context) error {
return FileData(c, http.StatusCreated, newdoc, false, nil)
}

func AddDescription(c echo.Context) error {
inst := middlewares.GetInstance(c)
fileID := c.Param("file-id")
doc, err := inst.VFS().FileByID(fileID)
if err != nil {
return WrapVfsError(err)
}
err = checkPerm(c, permission.POST, nil, doc)
if err != nil {
return err
}
newdoc, err := rag.AddDescriptionToFile(inst, doc)
if err != nil {
return WrapVfsError(err)
}
return FileData(c, http.StatusOK, newdoc, false, nil)
}

// ModifyMetadataByIDHandler handles PATCH requests on /files/:file-id
//
// It can be used to modify the file or directory metadata, as well as
Expand Down Expand Up @@ -1932,6 +1951,7 @@ func Routes(router *echo.Group) {
router.PATCH("/metadata", ModifyMetadataByPathHandler)
router.PATCH("/:file-id", ModifyMetadataByIDHandler)
router.PATCH("/", ModifyMetadataByIDInBatchHandler)
router.POST("/:file-id/description", AddDescription)

router.POST("/shared-drives", SharedDrivesCreationHandler)
router.POST("/", CreationHandler)
Expand Down

0 comments on commit 1be1469

Please sign in to comment.