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 3234d74
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 31 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
75 changes: 59 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,22 @@ 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 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 +180,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 +201,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 +210,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 +239,18 @@ 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.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 +402,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 +425,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 +759,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 +979,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
2 changes: 1 addition & 1 deletion config/samples/arcadia_v1alpha1_embedders_zhipu.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ metadata:
namespace: arcadia
type: Opaque
data:
apiKey: "MTZlZDcxYzcwMDE0NGFiMjIyMmI5YmEwZDFhMTBhZTUuUTljWVZtWWxmdjlnZGtDeQ==" # replace this with your API key
apiKey: "YTgyNTlhNjFmN2EwZGYzNmQ5N2Q3ZDIwOGVlMTQ0NTUuODc5OGJyeldwaGUzWUlCOA==" # replace this with your API key
---
apiVersion: arcadia.kubeagi.k8s.com.cn/v1alpha1
kind: Embedder
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)
}
28 changes: 28 additions & 0 deletions pkg/utils/image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
Copyright 2023 KubeAGI.
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 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 3234d74

Please sign in to comment.