190 lines
5.9 KiB
Go
190 lines
5.9 KiB
Go
// Copyright 2023 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package genai
|
|
|
|
//go:generate ../devtools/generate_discovery_client.sh
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
gl "cloud.google.com/go/ai/generativelanguage/apiv1beta"
|
|
pb "cloud.google.com/go/ai/generativelanguage/apiv1beta/generativelanguagepb"
|
|
gld "github.com/google/generative-ai-go/genai/internal/generativelanguage/v1beta" // discovery client
|
|
"google.golang.org/api/googleapi"
|
|
"google.golang.org/api/iterator"
|
|
)
|
|
|
|
// UploadFileOptions are options for [Client.UploadFile].
|
|
type UploadFileOptions struct {
|
|
// A more readable name for the file.
|
|
DisplayName string
|
|
|
|
// The IANA standard MIME type of the file. It will be stored with the file as metadata.
|
|
// If omitted, the service will try to infer it. You may instead wish to use
|
|
// [http.DetectContentType].
|
|
// The supported MIME types are documented on [this page].
|
|
//
|
|
// [this page]: https://ai.google.dev/gemini-api/docs/document-processing?lang=go#technical-details
|
|
MIMEType string
|
|
}
|
|
|
|
// UploadFile copies the contents of the given io.Reader to file storage associated
|
|
// with the service, and returns information about the resulting file.
|
|
//
|
|
// The name is a relatively short, unique identifier for the file (rather than a typical
|
|
// filename).
|
|
// Typically it should be left empty, in which case a unique name will be generated.
|
|
// Otherwise, it can contain up to 40 characters that are lowercase
|
|
// alphanumeric or dashes (-), not starting or ending with a dash.
|
|
// To generate your own unique names, consider a cryptographic hash algorithm like SHA-1.
|
|
// The string "files/" is prepended to the name if it does not contain a '/'.
|
|
//
|
|
// Use the returned file's URI field with a [FileData] Part to use it for generation.
|
|
//
|
|
// It is an error to upload a file that already exists.
|
|
func (c *Client) UploadFile(ctx context.Context, name string, r io.Reader, opts *UploadFileOptions) (*File, error) {
|
|
if name != "" {
|
|
name = userNameToServiceName(name)
|
|
}
|
|
req := &gld.CreateFileRequest{
|
|
File: &gld.File{Name: name},
|
|
}
|
|
if opts != nil && opts.DisplayName != "" {
|
|
req.File.DisplayName = opts.DisplayName
|
|
}
|
|
call := c.ds.Media.Upload(req)
|
|
var mopts []googleapi.MediaOption
|
|
if opts != nil && opts.MIMEType != "" {
|
|
mopts = append(mopts, googleapi.ContentType(opts.MIMEType))
|
|
}
|
|
call.Media(r, mopts...)
|
|
res, err := call.Do()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Don't return the result, because it contains a file as represented by the
|
|
// discovery client and we'd have to write code to convert it to this package's
|
|
// File type.
|
|
// Instead, make a GetFile call to get the proto file, which our generated code can convert.
|
|
return c.GetFile(ctx, res.File.Name)
|
|
}
|
|
|
|
// UploadFileFromPath is a convenience method wrapping [UploadFile]. It takes
|
|
// a path to read the file from, and uses a default auto-generated ID for the
|
|
// uploaded file.
|
|
func (c *Client) UploadFileFromPath(ctx context.Context, path string, opts *UploadFileOptions) (*File, error) {
|
|
osf, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer osf.Close()
|
|
|
|
return c.UploadFile(ctx, "", osf, opts)
|
|
}
|
|
|
|
// GetFile returns the named file.
|
|
func (c *Client) GetFile(ctx context.Context, name string) (*File, error) {
|
|
req := &pb.GetFileRequest{Name: userNameToServiceName(name)}
|
|
debugPrint(req)
|
|
pf, err := c.fc.GetFile(ctx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return (File{}).fromProto(pf), nil
|
|
}
|
|
|
|
// DeleteFile deletes the file with the given name.
|
|
// It is an error to delete a file that does not exist.
|
|
func (c *Client) DeleteFile(ctx context.Context, name string) error {
|
|
req := &pb.DeleteFileRequest{Name: userNameToServiceName(name)}
|
|
debugPrint(req)
|
|
return c.fc.DeleteFile(ctx, req)
|
|
}
|
|
|
|
// userNameToServiceName converts a name supplied by the user to a name required by the service.
|
|
func userNameToServiceName(name string) string {
|
|
if strings.ContainsRune(name, '/') {
|
|
return name
|
|
}
|
|
return "files/" + name
|
|
}
|
|
|
|
// ListFiles returns an iterator over the uploaded files.
|
|
func (c *Client) ListFiles(ctx context.Context) *FileIterator {
|
|
return &FileIterator{
|
|
it: c.fc.ListFiles(ctx, &pb.ListFilesRequest{}),
|
|
}
|
|
}
|
|
|
|
// A FileIterator iterates over Files.
|
|
type FileIterator struct {
|
|
it *gl.FileIterator
|
|
}
|
|
|
|
// Next returns the next result. Its second return value is iterator.Done if there are no more
|
|
// results. Once Next returns Done, all subsequent calls will return Done.
|
|
func (it *FileIterator) Next() (*File, error) {
|
|
m, err := it.it.Next()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return (File{}).fromProto(m), nil
|
|
}
|
|
|
|
// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
|
|
func (it *FileIterator) PageInfo() *iterator.PageInfo {
|
|
return it.it.PageInfo()
|
|
}
|
|
|
|
// FileMetadata holds metadata about a file.
|
|
type FileMetadata struct {
|
|
// Set if the file contains video.
|
|
Video *VideoMetadata
|
|
}
|
|
|
|
func populateFileTo(p *pb.File, f *File) {
|
|
p.Metadata = nil
|
|
if f == nil || f.Metadata == nil {
|
|
return
|
|
}
|
|
if f.Metadata.Video != nil {
|
|
p.Metadata = &pb.File_VideoMetadata{
|
|
VideoMetadata: f.Metadata.Video.toProto(),
|
|
}
|
|
}
|
|
}
|
|
|
|
func populateFileFrom(f *File, p *pb.File) {
|
|
f.Metadata = nil
|
|
if p == nil || p.Metadata == nil {
|
|
return
|
|
}
|
|
|
|
if p.Metadata != nil {
|
|
switch m := p.Metadata.(type) {
|
|
case *pb.File_VideoMetadata:
|
|
f.Metadata = &FileMetadata{
|
|
Video: (VideoMetadata{}).fromProto(m.VideoMetadata),
|
|
}
|
|
default:
|
|
// ignore other types
|
|
// TODO: signal a problem
|
|
}
|
|
}
|
|
}
|