Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: save app icon to oss #976

Merged
merged 1 commit into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading
Loading