Skip to content

Commit

Permalink
refactor: save app icon to oss
Browse files Browse the repository at this point in the history
  • Loading branch information
0xff-dev committed Apr 3, 2024
1 parent 2ea1955 commit 8806fb7
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 30 deletions.
2 changes: 0 additions & 2 deletions api/base/v1alpha1/application_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ type NodeConfig struct {
// ApplicationSpec defines the desired state of Application
type ApplicationSpec struct {
CommonSpec `json:",inline"`
// Icon base64 image icon
Icon string `json:"icon,omitempty"`
// IsPublic Set whether the current application provides services to the public
IsPublic bool `json:"isPublic,omitempty"`
// IsRecommended Set whether the current application is recognized as recommended to users
Expand Down
81 changes: 65 additions & 16 deletions apiserver/pkg/application/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ limitations under the License.
package application

import (
"bytes"
"context"
"errors"
"fmt"
"reflect"
"strings"

"github.com/minio/minio-go/v7"
"k8s.io/apimachinery/pkg/api/equality"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -40,6 +42,9 @@ import (
"github.com/kubeagi/arcadia/apiserver/graph/generated"
"github.com/kubeagi/arcadia/apiserver/pkg/common"
"github.com/kubeagi/arcadia/apiserver/pkg/utils"
"github.com/kubeagi/arcadia/pkg/config"
"github.com/kubeagi/arcadia/pkg/datasource"
pkgutils "github.com/kubeagi/arcadia/pkg/utils"
)

func addCategory(app *v1alpha1.Application, category []*string) *v1alpha1.Application {
Expand Down Expand Up @@ -68,14 +73,15 @@ func addDefaultValue(gApp *generated.Application, app *v1alpha1.Application) {
gApp.ConversionWindowSize = pointer.Int(5)
}

func cr2app(prompt *apiprompt.Prompt, chainConfig *apichain.CommonChainConfig, retriever *apiretriever.CommonRetrieverConfig, app *v1alpha1.Application, agent *apiagent.Agent, doc *apidocumentloader.DocumentLoader, enableRerank, enableMultiQuery *bool, rerankModel *string) (*generated.Application, error) {
func cr2app(ctx context.Context, c client.Client, prompt *apiprompt.Prompt, chainConfig *apichain.CommonChainConfig, retriever *apiretriever.CommonRetrieverConfig, app *v1alpha1.Application, agent *apiagent.Agent, doc *apidocumentloader.DocumentLoader, enableRerank, enableMultiQuery *bool, rerankModel *string) (*generated.Application, error) {
if app == nil {
return nil, errors.New("no app found")
}
condition := app.Status.GetCondition(v1alpha1.TypeReady)
UpdateTimestamp := &condition.LastTransitionTime.Time
status := common.GetObjStatus(app)

icon, _ := common.AppIconLink(ctx, app, c)
gApp := &generated.Application{
Metadata: &generated.ApplicationMetadata{
Name: app.Name,
Expand All @@ -85,7 +91,7 @@ func cr2app(prompt *apiprompt.Prompt, chainConfig *apichain.CommonChainConfig, r
Annotations: utils.MapStr2Any(app.Annotations),
DisplayName: pointer.String(app.Spec.DisplayName),
Description: pointer.String(app.Spec.Description),
Icon: pointer.String(app.Spec.Icon),
Icon: &icon,
Creator: pointer.String(app.Spec.Creator),
CreationTimestamp: &app.CreationTimestamp.Time,
UpdateTimestamp: UpdateTimestamp,
Expand Down Expand Up @@ -147,19 +153,27 @@ func cr2app(prompt *apiprompt.Prompt, chainConfig *apichain.CommonChainConfig, r
return gApp, nil
}

func app2metadataConverter(objApp client.Object) (generated.PageNode, error) {
app, ok := objApp.(*v1alpha1.Application)
if !ok {
return nil, errors.New("can't convert client.Object to Application")
func appConverterHelper(ctx context.Context, c client.Client) common.ResourceConverter {
return func(objApp client.Object) (generated.PageNode, error) {
app, ok := objApp.(*v1alpha1.Application)
if !ok {
return nil, errors.New("can't convert client.Object to Application")
}
return app2metadata(ctx, c, app)
}
return app2metadata(app)
}

func app2metadata(app *v1alpha1.Application) (*generated.ApplicationMetadata, error) {
/*
func app2metadataConverter(ctx context.Context, c client.Client, objApp client.Object) (generated.PageNode, error) {
}
*/
func app2metadata(ctx context.Context, c client.Client, app *v1alpha1.Application) (*generated.ApplicationMetadata, error) {
condition := app.Status.GetCondition(v1alpha1.TypeReady)
UpdateTimestamp := &condition.LastTransitionTime.Time
status := common.GetObjStatus(app)

icon, _ := common.AppIconLink(ctx, app, c)
return &generated.ApplicationMetadata{
Name: app.Name,
Namespace: app.Namespace,
Expand All @@ -171,7 +185,7 @@ func app2metadata(app *v1alpha1.Application) (*generated.ApplicationMetadata, er
Description: pointer.String(app.Spec.Description),
CreationTimestamp: &app.CreationTimestamp.Time,
UpdateTimestamp: UpdateTimestamp,
Icon: pointer.String(app.Spec.Icon),
Icon: &icon,
IsPublic: pointer.Bool(app.Spec.IsPublic),
IsRecommended: pointer.Bool(app.Spec.IsRecommended),
Status: pointer.String(status),
Expand All @@ -192,7 +206,6 @@ func CreateApplication(ctx context.Context, c client.Client, input generated.Cre
DisplayName: input.DisplayName,
Description: pointer.StringPtrDerefOr(input.Description, ""),
},
Icon: input.Icon,
IsPublic: pointer.BoolDeref(input.IsPublic, false),
IsRecommended: pointer.BoolDeref(input.IsRecommended, false),
Prologue: "",
Expand All @@ -202,11 +215,16 @@ func CreateApplication(ctx context.Context, c client.Client, input generated.Cre
if len(input.Category) > 0 {
app = addCategory(app, input.Category)
}

_, err := UploadIcon(ctx, c, input.Icon, input.Name, input.Namespace)
if err != nil {
return nil, err
}
common.SetCreator(ctx, &app.Spec.CommonSpec)
if err := c.Create(ctx, app); err != nil {
return nil, err
}
return app2metadata(app)
return app2metadata(ctx, c, app)
}

func UpdateApplication(ctx context.Context, c client.Client, input generated.UpdateApplicationMetadataInput) (*generated.ApplicationMetadata, error) {
Expand All @@ -226,15 +244,19 @@ func UpdateApplication(ctx context.Context, c client.Client, input generated.Upd
}
app.Spec.DisplayName = input.DisplayName
app.Spec.Description = pointer.StringDeref(input.Description, app.Spec.Description)
app.Spec.Icon = input.Icon
// app.Spec.Icon = input.Icon
app.Spec.IsPublic = pointer.BoolDeref(input.IsPublic, app.Spec.IsPublic)
app.Spec.IsRecommended = pointer.BoolDeref(input.IsRecommended, app.Spec.IsRecommended)
_, err := UploadIcon(ctx, c, input.Icon, input.Name, input.Namespace)
if err != nil {
return nil, err
}
if !reflect.DeepEqual(app, oldApp) {
if err := c.Update(ctx, app); err != nil {
return nil, err
}
}
return app2metadata(app)
return app2metadata(ctx, c, app)
}

func DeleteApplication(ctx context.Context, c client.Client, input generated.DeleteCommonInput) (*string, error) {
Expand Down Expand Up @@ -386,7 +408,7 @@ func GetApplication(ctx context.Context, c client.Client, name, namespace string
return nil, err
}

return cr2app(prompt, chainConfig, retriever, app, agent, doc, pointer.Bool(enableRerankRetriever), pointer.Bool(enableMultiQueryRetriever), pointer.String(rerankModel))
return cr2app(ctx, c, prompt, chainConfig, retriever, app, agent, doc, pointer.Bool(enableRerankRetriever), pointer.Bool(enableMultiQueryRetriever), pointer.String(rerankModel))
}

func ListApplicationMeatadatas(ctx context.Context, c client.Client, input generated.ListCommonInput) (*generated.PaginatedResult, error) {
Expand All @@ -409,7 +431,7 @@ func ListApplicationMeatadatas(ctx context.Context, c client.Client, input gener
for i := range res.Items {
items[i] = &res.Items[i]
}
return common.ListReources(items, page, pageSize, app2metadataConverter, filter...)
return common.ListReources(items, page, pageSize, appConverterHelper(ctx, c), filter...)
}

func UpdateApplicationConfig(ctx context.Context, c client.Client, input generated.UpdateApplicationConfigInput) (*generated.Application, error) {
Expand Down Expand Up @@ -743,7 +765,7 @@ func UpdateApplicationConfig(ctx context.Context, c client.Client, input generat
}
}

return cr2app(prompt, chainConfig, retriever, app, agent, documentLoader, pointer.Bool(hasRerankRetriever), pointer.Bool(hasMultiQueryRetriever), pointer.String(rerankModel))
return cr2app(ctx, c, prompt, chainConfig, retriever, app, agent, documentLoader, pointer.Bool(hasRerankRetriever), pointer.Bool(hasMultiQueryRetriever), pointer.String(rerankModel))
}

func mutateApp(app *v1alpha1.Application, input generated.UpdateApplicationConfigInput, hasMultiQueryRetriever, hasRerankRetriever bool) error {
Expand Down Expand Up @@ -963,3 +985,30 @@ func redefineNodes(knowledgebase *string, namespace string, name string, llmName
})
return nodes
}

func UploadIcon(ctx context.Context, client client.Client, icon, appName, namespace string) (string, error) {
if strings.HasPrefix(icon, "data:image") {
imgBytes, err := pkgutils.ParseBase64ImageBytes(icon)
if err != nil {
return "", err
}

system, err := config.GetSystemDatasource(ctx, client)
if err != nil {
return "", err
}

endpoint := system.Spec.Endpoint.DeepCopy()
if endpoint != nil && endpoint.AuthSecret != nil {
endpoint.AuthSecret.WithNameSpace(namespace)
}
ds, err := datasource.NewOSS(ctx, client, endpoint)
if err != nil {
return "", err
}
iconName := fmt.Sprintf("application/%s/icon/%s", appName, appName)
_, err = ds.Client.PutObject(ctx, namespace, iconName, bytes.NewReader(imgBytes), int64(len(imgBytes)), minio.PutObjectOptions{})
return icon, err
}
return icon, nil
}
7 changes: 5 additions & 2 deletions apiserver/pkg/chat/chat_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"github.com/kubeagi/arcadia/apiserver/pkg/auth"
"github.com/kubeagi/arcadia/apiserver/pkg/chat/storage"
"github.com/kubeagi/arcadia/apiserver/pkg/client"
"github.com/kubeagi/arcadia/apiserver/pkg/common"
"github.com/kubeagi/arcadia/pkg/appruntime"
"github.com/kubeagi/arcadia/pkg/appruntime/base"
appruntimechain "github.com/kubeagi/arcadia/pkg/appruntime/chain"
Expand Down Expand Up @@ -430,14 +431,16 @@ func (cs *ChatServer) FillAppIconToConversations(ctx context.Context, conversati
if !ok {
return nil
}
err := cs.cli.Get(ctx, types.NamespacedName{Namespace: ns, Name: name}, app)
app.Name = name
app.Namespace = ns
link, err := common.AppIconLink(ctx, app, cs.cli)
if err != nil {
// FIXME: Currently, there is a request for an application that cannot be found in a conversation in the database,
// causing other conversations to be unable to add icons, so an error is encountered here and no error is returned.
klog.Errorf("failed to get application %s in namespace %s, error %s", name, ns, err)
return nil
}
result[index] = app.Spec.Icon
result[index] = link
return nil
})
}
Expand Down
15 changes: 15 additions & 0 deletions apiserver/pkg/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import (
"context"
"errors"
"fmt"
"net/url"
"strings"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
Expand Down Expand Up @@ -306,3 +308,16 @@ func NewListOptions(input generated.ListCommonInput) ([]client.ListOption, error
}
return opts, nil
}

func AppIconLink(ctx context.Context, app *v1alpha1.Application, client client.Client) (string, error) {
ds, err := SystemDatasourceOSS(ctx, client)
if err != nil {
return "", err
}
name := fmt.Sprintf("application/%s/icon/%s", app.Name, app.Name)
u, err := ds.Client.PresignedGetObject(ctx, app.Namespace, name, 24*time.Hour, url.Values{})
if err != nil {
return "", err
}
return u.String(), nil
}
9 changes: 5 additions & 4 deletions apiserver/pkg/gpt/gpt.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,20 @@ var (
chatStorage storage.Storage
)

func app2gpt(app *v1alpha1.Application, c client.Client) (*generated.Gpt, error) {
func app2gpt(ctx context.Context, app *v1alpha1.Application, c client.Client) (*generated.Gpt, error) {
if app == nil {
return nil, errors.New("no app found")
}

icon, _ := common.AppIconLink(ctx, app, c)
gpt := &generated.Gpt{
Name: pointer.String(strings.Join([]string{app.Namespace, app.Name}, "/")),
DisplayName: pointer.String(app.Spec.DisplayName),
Description: pointer.String(app.Spec.Description),
Hot: pointer.Int64(getHot(app, c)),
Creator: pointer.String(app.Spec.Creator),
Category: common.GetAppCategory(app),
Icon: pointer.String(app.Spec.Icon),
Icon: &icon,
Prologue: pointer.String(app.Spec.Prologue),
ShowRespInfo: pointer.Bool(app.Spec.ShowRespInfo),
ShowRetrievalInfo: pointer.Bool(app.Spec.ShowRetrievalInfo),
Expand Down Expand Up @@ -144,7 +145,7 @@ func GetGPT(ctx context.Context, c client.Client, name string) (*generated.Gpt,
return nil, fmt.Errorf("not a valid app or the app is not public")
}

return app2gpt(app, c)
return app2gpt(ctx, app, c)
}

// ListGPT list all gpt
Expand Down Expand Up @@ -179,7 +180,7 @@ func ListGPT(ctx context.Context, c client.Client, input generated.ListGPTInput)
if !ok {
return nil, errors.New("can't convert obj to Application")
}
return app2gpt(app, c)
return app2gpt(ctx, app, c)
}, filter...)
}

Expand Down
3 changes: 0 additions & 3 deletions config/crd/bases/arcadia.kubeagi.k8s.com.cn_applications.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,6 @@ spec:
enableUploadFile:
default: true
type: boolean
icon:
description: Icon base64 image icon
type: string
isPublic:
description: IsPublic Set whether the current application provides
services to the public
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,6 @@ spec:
enableUploadFile:
default: true
type: boolean
icon:
description: Icon base64 image icon
type: string
isPublic:
description: IsPublic Set whether the current application provides
services to the public
Expand Down
13 changes: 13 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"

arcadiav1alpha1 "github.com/kubeagi/arcadia/api/base/v1alpha1"
"github.com/kubeagi/arcadia/pkg/datasource"
"github.com/kubeagi/arcadia/pkg/utils"
)

Expand Down Expand Up @@ -162,3 +163,15 @@ func GetDefaultRerankModel(ctx context.Context, c client.Client) (*arcadiav1alph
}
return config.Rerank, nil
}

func GetSystemDatasourceOSS(ctx context.Context, mgrClient client.Client) (*datasource.OSS, error) {
systemDatasource, err := GetSystemDatasource(ctx, mgrClient)
if err != nil {
return nil, err
}
endpoint := systemDatasource.Spec.Endpoint.DeepCopy()
if endpoint.AuthSecret != nil && endpoint.AuthSecret.Namespace == nil {
endpoint.AuthSecret.WithNameSpace(systemDatasource.Namespace)
}
return datasource.NewOSS(ctx, mgrClient, endpoint)
}
13 changes: 13 additions & 0 deletions pkg/utils/image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package utils

import (
"encoding/base64"
)

func ParseBase64ImageBytes(img string) ([]byte, error) {
i := 0
for ; i < len(img) && img[i] != ','; i++ {
}
cut := img[i+1:]
return base64.StdEncoding.DecodeString(cut)
}

0 comments on commit 8806fb7

Please sign in to comment.