feat(plugin): rebase main
This commit is contained in:
@ -40,6 +40,7 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/application/search"
|
||||
"github.com/coze-dev/coze-studio/backend/application/singleagent"
|
||||
"github.com/coze-dev/coze-studio/backend/application/template"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
// PublicGetProductList .
|
||||
@ -273,3 +274,47 @@ func PublicDuplicateProduct(ctx context.Context, c *app.RequestContext) {
|
||||
|
||||
c.JSON(consts.StatusOK, resp)
|
||||
}
|
||||
|
||||
// PublicSearchProduct .
|
||||
// @router /api/marketplace/product/search [GET]
|
||||
func PublicSearchProduct(ctx context.Context, c *app.RequestContext) {
|
||||
var err error
|
||||
var req product_public_api.SearchProductRequest
|
||||
err = c.BindAndValidate(&req)
|
||||
if err != nil {
|
||||
invalidParamRequestResponse(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Call plugin application service
|
||||
resp, err := plugin.PluginApplicationSVC.PublicSearchProduct(ctx, &req)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "PublicSearchProduct failed: %v", err)
|
||||
internalServerErrorResponse(ctx, c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(consts.StatusOK, resp)
|
||||
}
|
||||
|
||||
// PublicSearchSuggest .
|
||||
// @router /api/marketplace/product/search/suggest [GET]
|
||||
func PublicSearchSuggest(ctx context.Context, c *app.RequestContext) {
|
||||
var err error
|
||||
var req product_public_api.SearchSuggestRequest
|
||||
err = c.BindAndValidate(&req)
|
||||
if err != nil {
|
||||
invalidParamRequestResponse(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Call plugin application service
|
||||
resp, err := plugin.PluginApplicationSVC.PublicSearchSuggest(ctx, &req)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "PublicSearchSuggest failed: %v", err)
|
||||
internalServerErrorResponse(ctx, c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(consts.StatusOK, resp)
|
||||
}
|
||||
|
||||
@ -1,4 +1,20 @@
|
||||
// Code generated by thriftgo (0.4.1). DO NOT EDIT.
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Code generated by thriftgo (0.4.2). DO NOT EDIT.
|
||||
|
||||
package marketplace_common
|
||||
|
||||
|
||||
@ -26,6 +26,110 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/marketplace/marketplace_common"
|
||||
)
|
||||
|
||||
type BotModType int64
|
||||
|
||||
const (
|
||||
BotModType_SingleAgent BotModType = 1
|
||||
BotModType_MultiAgent BotModType = 2
|
||||
)
|
||||
|
||||
func (p BotModType) String() string {
|
||||
switch p {
|
||||
case BotModType_SingleAgent:
|
||||
return "SingleAgent"
|
||||
case BotModType_MultiAgent:
|
||||
return "MultiAgent"
|
||||
}
|
||||
return "<UNSET>"
|
||||
}
|
||||
|
||||
func BotModTypeFromString(s string) (BotModType, error) {
|
||||
switch s {
|
||||
case "SingleAgent":
|
||||
return BotModType_SingleAgent, nil
|
||||
case "MultiAgent":
|
||||
return BotModType_MultiAgent, nil
|
||||
}
|
||||
return BotModType(0), fmt.Errorf("not a valid BotModType string")
|
||||
}
|
||||
|
||||
func BotModTypePtr(v BotModType) *BotModType { return &v }
|
||||
func (p *BotModType) Scan(value interface{}) (err error) {
|
||||
var result sql.NullInt64
|
||||
err = result.Scan(value)
|
||||
*p = BotModType(result.Int64)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *BotModType) Value() (driver.Value, error) {
|
||||
if p == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return int64(*p), nil
|
||||
}
|
||||
|
||||
type Component int64
|
||||
|
||||
const (
|
||||
Component_UsePlugin Component = 1
|
||||
Component_UseWorkFlow Component = 2
|
||||
Component_UseKnowledge Component = 3
|
||||
Component_UseVoice Component = 4
|
||||
Component_UseCard Component = 5
|
||||
Component_UseImageWorkflow Component = 6
|
||||
)
|
||||
|
||||
func (p Component) String() string {
|
||||
switch p {
|
||||
case Component_UsePlugin:
|
||||
return "UsePlugin"
|
||||
case Component_UseWorkFlow:
|
||||
return "UseWorkFlow"
|
||||
case Component_UseKnowledge:
|
||||
return "UseKnowledge"
|
||||
case Component_UseVoice:
|
||||
return "UseVoice"
|
||||
case Component_UseCard:
|
||||
return "UseCard"
|
||||
case Component_UseImageWorkflow:
|
||||
return "UseImageWorkflow"
|
||||
}
|
||||
return "<UNSET>"
|
||||
}
|
||||
|
||||
func ComponentFromString(s string) (Component, error) {
|
||||
switch s {
|
||||
case "UsePlugin":
|
||||
return Component_UsePlugin, nil
|
||||
case "UseWorkFlow":
|
||||
return Component_UseWorkFlow, nil
|
||||
case "UseKnowledge":
|
||||
return Component_UseKnowledge, nil
|
||||
case "UseVoice":
|
||||
return Component_UseVoice, nil
|
||||
case "UseCard":
|
||||
return Component_UseCard, nil
|
||||
case "UseImageWorkflow":
|
||||
return Component_UseImageWorkflow, nil
|
||||
}
|
||||
return Component(0), fmt.Errorf("not a valid Component string")
|
||||
}
|
||||
|
||||
func ComponentPtr(v Component) *Component { return &v }
|
||||
func (p *Component) Scan(value interface{}) (err error) {
|
||||
var result sql.NullInt64
|
||||
err = result.Scan(value)
|
||||
*p = Component(result.Int64)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Component) Value() (driver.Value, error) {
|
||||
if p == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return int64(*p), nil
|
||||
}
|
||||
|
||||
type ProductEntityType int64
|
||||
|
||||
const (
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -165,6 +165,9 @@ func Register(r *server.Hertz) {
|
||||
_favorite := _product.Group("/favorite", _favoriteMw()...)
|
||||
_favorite.GET("/list.v2", append(_publicgetuserfavoritelistv2Mw(), coze.PublicGetUserFavoriteListV2)...)
|
||||
_product.GET("/list", append(_publicgetproductlistMw(), coze.PublicGetProductList)...)
|
||||
_product.GET("/search", append(_publicsearchproductMw(), coze.PublicSearchProduct)...)
|
||||
_search0 := _product.Group("/search", _search0Mw()...)
|
||||
_search0.GET("/suggest", append(_publicsearchsuggestMw(), coze.PublicSearchSuggest)...)
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
@ -1570,3 +1570,18 @@ func _getonlineappdataMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _search0Mw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _publicsearchproductMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
func _publicsearchsuggestMw() []app.HandlerFunc {
|
||||
// your code...
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -381,10 +381,65 @@ func (p *PluginApplicationService) GetQueriedOAuthPluginList(ctx context.Context
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (t *PluginApplicationService) GetCozeSaasPluginList(ctx context.Context, req *productAPI.GetProductListRequest) (resp *productAPI.GetProductListResponse, err error) {
|
||||
domainReq := &service.ListPluginProductsRequest{}
|
||||
// convertPluginToProductInfo converts a plugin entity to ProductInfo
|
||||
func convertPluginToProductInfo(plugin *entity.PluginInfo) *productAPI.ProductInfo {
|
||||
return &productAPI.ProductInfo{
|
||||
MetaInfo: &productAPI.ProductMetaInfo{
|
||||
ID: plugin.ID,
|
||||
Name: plugin.GetName(),
|
||||
EntityID: plugin.ID,
|
||||
Description: plugin.GetDesc(),
|
||||
IconURL: plugin.GetIconURI(),
|
||||
ListedAt: plugin.CreatedAt,
|
||||
},
|
||||
PluginExtra: &productAPI.PluginExtraInfo{
|
||||
IsOfficial: plugin.IsOfficial(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// convertPluginsToProductInfos converts a slice of plugins to ProductInfo slice
|
||||
func convertPluginsToProductInfos(plugins []*entity.PluginInfo) []*productAPI.ProductInfo {
|
||||
products := make([]*productAPI.ProductInfo, 0, len(plugins))
|
||||
for _, plugin := range plugins {
|
||||
products = append(products, convertPluginToProductInfo(plugin))
|
||||
}
|
||||
return products
|
||||
}
|
||||
|
||||
// getSaasPluginList is a common method to get SaaS plugin list from domain service
|
||||
func (t *PluginApplicationService) getSaasPluginList(ctx context.Context) ([]*entity.PluginInfo, int64, error) {
|
||||
domainReq := &service.ListPluginProductsRequest{}
|
||||
domainResp, err := t.DomainSVC.ListSaasPluginProducts(ctx, domainReq)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return domainResp.Plugins, domainResp.Total, nil
|
||||
}
|
||||
|
||||
// convertPluginsToSuggestions converts plugins to suggestion products with deduplication and limit
|
||||
func convertPluginsToSuggestions(plugins []*entity.PluginInfo, limit int) []*productAPI.ProductInfo {
|
||||
suggestionProducts := make([]*productAPI.ProductInfo, 0, len(plugins))
|
||||
suggestionSet := make(map[string]bool) // Use map to avoid duplicates
|
||||
|
||||
for _, plugin := range plugins {
|
||||
// Add plugin as suggestion if name is unique
|
||||
if plugin.GetName() != "" && !suggestionSet[plugin.GetName()] {
|
||||
suggestionProducts = append(suggestionProducts, convertPluginToProductInfo(plugin))
|
||||
suggestionSet[plugin.GetName()] = true
|
||||
}
|
||||
|
||||
// Limit suggestions to avoid too many results
|
||||
if len(suggestionProducts) >= limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return suggestionProducts
|
||||
}
|
||||
|
||||
func (t *PluginApplicationService) GetCozeSaasPluginList(ctx context.Context, req *productAPI.GetProductListRequest) (resp *productAPI.GetProductListResponse, err error) {
|
||||
plugins, total, err := t.getSaasPluginList(ctx)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "ListSaasPluginProducts failed: %v", err)
|
||||
return &productAPI.GetProductListResponse{
|
||||
@ -393,32 +448,61 @@ func (t *PluginApplicationService) GetCozeSaasPluginList(ctx context.Context, re
|
||||
}, nil
|
||||
}
|
||||
|
||||
products := make([]*productAPI.ProductInfo, 0, len(domainResp.Plugins))
|
||||
for _, plugin := range domainResp.Plugins {
|
||||
productInfo := &productAPI.ProductInfo{
|
||||
MetaInfo: &productAPI.ProductMetaInfo{
|
||||
ID: plugin.ID,
|
||||
Name: plugin.GetName(),
|
||||
EntityID: plugin.ID,
|
||||
Description: plugin.GetDesc(),
|
||||
IconURL: plugin.GetIconURI(),
|
||||
ListedAt: plugin.CreatedAt,
|
||||
},
|
||||
PluginExtra: &productAPI.PluginExtraInfo{
|
||||
IsOfficial: plugin.IsOfficial(),
|
||||
},
|
||||
}
|
||||
products = append(products, productInfo)
|
||||
}
|
||||
products := convertPluginsToProductInfos(plugins)
|
||||
|
||||
return &productAPI.GetProductListResponse{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
Data: &productAPI.GetProductListData{
|
||||
Products: products,
|
||||
Total: int32(domainResp.Total),
|
||||
Total: int32(total),
|
||||
HasMore: false,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *PluginApplicationService) PublicSearchProduct(ctx context.Context, req *productAPI.SearchProductRequest) (resp *productAPI.SearchProductResponse, err error) {
|
||||
plugins, total, err := t.getSaasPluginList(ctx)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "ListSaasPluginProducts failed: %v", err)
|
||||
return &productAPI.SearchProductResponse{
|
||||
Code: -1,
|
||||
Message: "Failed to search SaaS plugins",
|
||||
}, nil
|
||||
}
|
||||
|
||||
products := convertPluginsToProductInfos(plugins)
|
||||
|
||||
return &productAPI.SearchProductResponse{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
Data: &productAPI.SearchProductResponseData{
|
||||
Products: products,
|
||||
Total: ptr.Of(int32(total)),
|
||||
HasMore: ptr.Of(false),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *PluginApplicationService) PublicSearchSuggest(ctx context.Context, req *productAPI.SearchSuggestRequest) (resp *productAPI.SearchSuggestResponse, err error) {
|
||||
plugins, _, err := t.getSaasPluginList(ctx)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "ListSaasPluginProducts for suggestions failed: %v", err)
|
||||
return &productAPI.SearchSuggestResponse{
|
||||
Code: -1,
|
||||
Message: "Failed to get search suggestions",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Convert plugins to suggestions with limit of 10
|
||||
suggestionProducts := convertPluginsToSuggestions(plugins, 10)
|
||||
|
||||
return &productAPI.SearchSuggestResponse{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
Data: &productAPI.SearchSuggestResponseData{
|
||||
SuggestionV2: suggestionProducts,
|
||||
HasMore: ptr.Of(false),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
89
backend/domain/plugin/entity/coze_saas.go
Normal file
89
backend/domain/plugin/entity/coze_saas.go
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 entity
|
||||
|
||||
// SearchSaasPluginRequest represents the request parameters for searching SaaS plugins
|
||||
type SearchSaasPluginRequest struct {
|
||||
Keyword *string `json:"keyword,omitempty"`
|
||||
PageNum *int `json:"page_num,omitempty"`
|
||||
PageSize *int `json:"page_size,omitempty"`
|
||||
SortType *string `json:"sort_type,omitempty"`
|
||||
CategoryID *string `json:"category_id,omitempty"`
|
||||
IsOfficial *bool `json:"is_official,omitempty"`
|
||||
}
|
||||
|
||||
// SearchSaasPluginResponse represents the response from coze.cn search API
|
||||
type SearchSaasPluginResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data *SearchSaasPluginData `json:"data"`
|
||||
}
|
||||
|
||||
// SearchSaasPluginData represents the data section of search response
|
||||
type SearchSaasPluginData struct {
|
||||
Items []*SaasPluginItem `json:"items"`
|
||||
HasMore bool `json:"has_more"`
|
||||
}
|
||||
|
||||
// SaasPluginItem represents a single plugin item in search results
|
||||
type SaasPluginItem struct {
|
||||
MetaInfo *SaasPluginMetaInfo `json:"metainfo"`
|
||||
PluginInfo *SaasPluginInfo `json:"plugin_info"`
|
||||
}
|
||||
|
||||
// SaasPluginMetaInfo represents the metadata of a SaaS plugin
|
||||
type SaasPluginMetaInfo struct {
|
||||
ProductID string `json:"product_id"`
|
||||
EntityID string `json:"entity_id"`
|
||||
EntityVersion string `json:"entity_version"`
|
||||
EntityType string `json:"entity_type"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
UserInfo *SaasPluginUserInfo `json:"user_info"`
|
||||
Category *SaasPluginCategory `json:"category"`
|
||||
IconURL string `json:"icon_url"`
|
||||
ListedAt int64 `json:"listed_at"`
|
||||
PaidType string `json:"paid_type"`
|
||||
IsOfficial bool `json:"is_official"`
|
||||
}
|
||||
|
||||
// SaasPluginUserInfo represents the user information of a SaaS plugin
|
||||
type SaasPluginUserInfo struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
UserName string `json:"user_name"`
|
||||
NickName string `json:"nick_name"`
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
}
|
||||
|
||||
// SaasPluginCategory represents the category information of a SaaS plugin
|
||||
type SaasPluginCategory struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// SaasPluginInfo represents the plugin statistics and information
|
||||
type SaasPluginInfo struct {
|
||||
Description string `json:"description"`
|
||||
TotalToolsCount int `json:"total_tools_count"`
|
||||
FavoriteCount int `json:"favorite_count"`
|
||||
Heat int `json:"heat"`
|
||||
SuccessRate float64 `json:"success_rate"`
|
||||
AvgExecDurationMs float64 `json:"avg_exec_duration_ms"`
|
||||
BotsUseCount int64 `json:"bots_use_count"`
|
||||
AssociatedBotsUseCount int64 `json:"associated_bots_use_count"`
|
||||
CallCount int64 `json:"call_count"`
|
||||
}
|
||||
@ -71,7 +71,7 @@ func (p *pluginServiceImpl) MGetAgentTools(ctx context.Context, req *model.MGetA
|
||||
for _, v := range req.VersionAgentTools {
|
||||
toolIDs = append(toolIDs, v.ToolID)
|
||||
}
|
||||
|
||||
// todo :: saas plugin or local plugin
|
||||
existTools, err := p.toolRepo.MGetOnlineTools(ctx, toolIDs, repository.WithToolID())
|
||||
if err != nil {
|
||||
return nil, errorx.Wrapf(err, "MGetOnlineTools failed, toolIDs=%v", toolIDs)
|
||||
@ -109,11 +109,16 @@ func (p *pluginServiceImpl) MGetAgentTools(ctx context.Context, req *model.MGetA
|
||||
}
|
||||
}
|
||||
|
||||
//local plugin
|
||||
tools, err = p.toolRepo.MGetVersionAgentTool(ctx, req.AgentID, vTools)
|
||||
if err != nil {
|
||||
return nil, errorx.Wrapf(err, "MGetVersionAgentTool failed, agentID=%d, vTools=%v", req.AgentID, vTools)
|
||||
}
|
||||
|
||||
// TODO::saas plugin
|
||||
|
||||
// TODO::merge saas plugin or local plugin
|
||||
|
||||
return tools, nil
|
||||
}
|
||||
|
||||
|
||||
@ -57,25 +57,15 @@ func (p *pluginServiceImpl) ListSaasPluginProducts(ctx context.Context, req *Lis
|
||||
|
||||
|
||||
func (p *pluginServiceImpl) fetchSaasPluginsFromCoze(ctx context.Context) ([]*entity.PluginInfo, error) {
|
||||
client := saasapi.NewCozeAPIClient()
|
||||
|
||||
resp, err := client.Get(ctx, "/v1/stores/plugins")
|
||||
searchReq := &entity.SearchSaasPluginRequest{}
|
||||
searchResp, err := p.searchSaasPlugin(ctx, searchReq)
|
||||
if err != nil {
|
||||
return nil, errorx.Wrapf(err, "failed to call coze.cn API")
|
||||
return nil, errorx.Wrapf(err, "failed to search SaaS plugins")
|
||||
}
|
||||
|
||||
var pluginsData struct {
|
||||
Plugins []CozePlugin `json:"plugins"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(resp.Data, &pluginsData); err != nil {
|
||||
return nil, errorx.Wrapf(err, "failed to parse coze.cn API response")
|
||||
}
|
||||
|
||||
plugins := make([]*entity.PluginInfo, 0, len(pluginsData.Plugins))
|
||||
for _, cozePlugin := range pluginsData.Plugins {
|
||||
plugin := convertCozePluginToEntity(cozePlugin)
|
||||
plugins := make([]*entity.PluginInfo, 0, len(searchResp.Data.Items))
|
||||
for _, item := range searchResp.Data.Items {
|
||||
plugin := convertSaasPluginItemToEntity(item)
|
||||
plugins = append(plugins, plugin)
|
||||
}
|
||||
|
||||
@ -84,16 +74,60 @@ func (p *pluginServiceImpl) fetchSaasPluginsFromCoze(ctx context.Context) ([]*en
|
||||
return plugins, nil
|
||||
}
|
||||
|
||||
func convertSaasPluginItemToEntity(item *entity.SaasPluginItem) *entity.PluginInfo {
|
||||
if item == nil || item.MetaInfo == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
metaInfo := item.MetaInfo
|
||||
var pluginID int64
|
||||
if id, err := strconv.ParseInt(metaInfo.ProductID, 10, 64); err == nil {
|
||||
pluginID = id
|
||||
} else {
|
||||
// 如果ID不是数字,使用简单的hash算法生成ID
|
||||
pluginID = int64(simpleHash(metaInfo.ProductID))
|
||||
}
|
||||
|
||||
// 创建插件清单
|
||||
manifest := &model.PluginManifest{
|
||||
SchemaVersion: "v1",
|
||||
NameForModel: metaInfo.Name,
|
||||
NameForHuman: metaInfo.Name,
|
||||
DescriptionForModel: metaInfo.Description,
|
||||
DescriptionForHuman: metaInfo.Description,
|
||||
LogoURL: metaInfo.IconURL,
|
||||
Auth: &model.AuthV2{
|
||||
Type: model.AuthzTypeOfNone,
|
||||
},
|
||||
API: model.APIDesc{
|
||||
Type: "openapi",
|
||||
},
|
||||
}
|
||||
|
||||
pluginInfo := &model.PluginInfo{
|
||||
ID: pluginID,
|
||||
PluginType: pluginCommon.PluginType_PLUGIN,
|
||||
SpaceID: 0,
|
||||
DeveloperID: 0,
|
||||
APPID: nil,
|
||||
IconURI: &metaInfo.IconURL,
|
||||
ServerURL: ptr.Of(""),
|
||||
CreatedAt: metaInfo.ListedAt,
|
||||
UpdatedAt: metaInfo.ListedAt,
|
||||
Manifest: manifest,
|
||||
}
|
||||
|
||||
return entity.NewPluginInfo(pluginInfo)
|
||||
}
|
||||
|
||||
func convertCozePluginToEntity(cozePlugin CozePlugin) *entity.PluginInfo {
|
||||
var pluginID int64
|
||||
if id, err := strconv.ParseInt(cozePlugin.ID, 10, 64); err == nil {
|
||||
pluginID = id
|
||||
} else {
|
||||
// 如果ID不是数字,使用简单的hash算法生成ID
|
||||
pluginID = int64(simpleHash(cozePlugin.ID))
|
||||
}
|
||||
|
||||
// 创建插件清单
|
||||
manifest := &model.PluginManifest{
|
||||
SchemaVersion: "v1",
|
||||
NameForModel: cozePlugin.Name,
|
||||
@ -142,7 +176,6 @@ func (p *pluginServiceImpl) GetSaasPluginInfo(ctx context.Context, pluginID int6
|
||||
return nil, errorx.Wrapf(err, "failed to call coze.cn API")
|
||||
}
|
||||
|
||||
// 解析响应数据
|
||||
var cozePlugin CozePlugin
|
||||
if err := json.Unmarshal(resp.Data, &cozePlugin); err != nil {
|
||||
return nil, errorx.Wrapf(err, "failed to parse coze.cn API response")
|
||||
@ -154,3 +187,45 @@ func (p *pluginServiceImpl) GetSaasPluginInfo(ctx context.Context, pluginID int6
|
||||
|
||||
return plugin, nil
|
||||
}
|
||||
|
||||
func (p *pluginServiceImpl) searchSaasPlugin(ctx context.Context, req *entity.SearchSaasPluginRequest) (resp *entity.SearchSaasPluginResponse, err error) {
|
||||
client := saasapi.NewCozeAPIClient()
|
||||
|
||||
// 构建查询参数
|
||||
queryParams := make(map[string]interface{})
|
||||
if req.Keyword != nil {
|
||||
queryParams["keyword"] = req.Keyword
|
||||
}
|
||||
if req.PageNum != nil {
|
||||
queryParams["page_num"] = req.PageNum
|
||||
}
|
||||
if req.PageSize != nil {
|
||||
queryParams["page_size"] = req.PageSize
|
||||
}
|
||||
if req.SortType != nil {
|
||||
queryParams["sort_type"] = req.SortType
|
||||
}
|
||||
if req.CategoryID != nil {
|
||||
queryParams["category_id"] = req.CategoryID
|
||||
}
|
||||
if req.IsOfficial != nil {
|
||||
queryParams["is_official"] = req.IsOfficial
|
||||
}
|
||||
|
||||
apiResp, err := client.GetWithQuery(ctx, "/v1/stores/plugins", queryParams)
|
||||
if err != nil {
|
||||
return nil, errorx.Wrapf(err, "failed to call coze.cn search API")
|
||||
}
|
||||
|
||||
var searchResp entity.SearchSaasPluginResponse
|
||||
if err := json.Unmarshal(apiResp.Data, &searchResp.Data); err != nil {
|
||||
return nil, errorx.Wrapf(err, "failed to parse coze.cn search API response")
|
||||
}
|
||||
|
||||
searchResp.Code = apiResp.Code
|
||||
searchResp.Msg = apiResp.Msg
|
||||
|
||||
logs.CtxInfof(ctx, "searched SaaS plugins from coze.cn, found %d items", len(searchResp.Data.Items))
|
||||
|
||||
return &searchResp, nil
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ type PluginService interface {
|
||||
|
||||
GetOAuthStatus(ctx context.Context, userID, pluginID int64) (resp *dto.GetOAuthStatusResponse, err error)
|
||||
GetAgentPluginsOAuthStatus(ctx context.Context, userID, agentID int64) (status []*dto.AgentPluginOAuthStatus, err error)
|
||||
//Saas Plugin
|
||||
//Saas Plugin Product
|
||||
ListSaasPluginProducts(ctx context.Context, req *ListPluginProductsRequest) (resp *ListPluginProductsResponse, err error)
|
||||
GetSaasPluginInfo(ctx context.Context, pluginID int64) (plugin *entity.PluginInfo, err error)
|
||||
|
||||
|
||||
151
backend/domain/user/entity/saas_user.go
Normal file
151
backend/domain/user/entity/saas_user.go
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* 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 entity
|
||||
|
||||
// BenefitType represents the type of benefit
|
||||
type BenefitType int32
|
||||
|
||||
const (
|
||||
BenefitTypeUnknown BenefitType = 0
|
||||
// Add specific benefit types as needed
|
||||
)
|
||||
|
||||
// UserLevel represents the user level
|
||||
type UserLevel int32
|
||||
|
||||
const (
|
||||
UserLevelUnknown UserLevel = 0
|
||||
UserLevelBasic UserLevel = 1
|
||||
UserLevelPro UserLevel = 2
|
||||
// Add more levels as needed
|
||||
)
|
||||
|
||||
// EntityBenefitStatus represents the status of a benefit entity
|
||||
type EntityBenefitStatus int32
|
||||
|
||||
const (
|
||||
EntityBenefitStatusUnknown EntityBenefitStatus = 0
|
||||
EntityBenefitStatusActive EntityBenefitStatus = 1
|
||||
EntityBenefitStatusExpired EntityBenefitStatus = 2
|
||||
// Add more statuses as needed
|
||||
)
|
||||
|
||||
// ResourceUsageStrategy represents the resource usage strategy
|
||||
type ResourceUsageStrategy int32
|
||||
|
||||
const (
|
||||
ResourceUsageStrategyUnknown ResourceUsageStrategy = 0
|
||||
ResourceUsageStrategyByQuota ResourceUsageStrategy = 1
|
||||
// Add more strategies as needed
|
||||
)
|
||||
|
||||
// GetEnterpriseBenefitRequest represents the request for getting enterprise benefit
|
||||
type GetEnterpriseBenefitRequest struct {
|
||||
BenefitType *BenefitType `json:"benefit_type,omitempty" form:"benefit_type"`
|
||||
ResourceID *string `json:"resource_id,omitempty" form:"resource_id"`
|
||||
}
|
||||
|
||||
// GetEnterpriseBenefitResponse represents the response for getting enterprise benefit
|
||||
type GetEnterpriseBenefitResponse struct {
|
||||
Code int32 `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data *BenefitData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// BenefitData represents the benefit data
|
||||
type BenefitData struct {
|
||||
BasicInfo *BasicInfo `json:"basic_info,omitempty"`
|
||||
BenefitInfo *BenefitInfo `json:"benefit_info,omitempty"`
|
||||
}
|
||||
|
||||
// BasicInfo represents the basic information
|
||||
type BasicInfo struct {
|
||||
UserLevel UserLevel `json:"user_level"`
|
||||
}
|
||||
|
||||
// BenefitInfo represents the benefit information
|
||||
type BenefitInfo struct {
|
||||
ResourceID *string `json:"resource_id,omitempty"`
|
||||
BenefitType *BenefitType `json:"benefit_type,omitempty"`
|
||||
Basic *BenefitTypeInfoItem `json:"basic,omitempty"` // Basic value
|
||||
Extra []*BenefitTypeInfoItem `json:"extra,omitempty"` // Extra values, may not exist
|
||||
}
|
||||
|
||||
// BenefitTypeInfoItem represents a benefit type info item
|
||||
type BenefitTypeInfoItem struct {
|
||||
ItemID *string `json:"item_id,omitempty"`
|
||||
ItemInfo *CommonCounter `json:"item_info,omitempty"`
|
||||
Status *EntityBenefitStatus `json:"status,omitempty"`
|
||||
BenefitID *string `json:"benefit_id,omitempty"`
|
||||
}
|
||||
|
||||
// CommonCounter represents a common counter
|
||||
type CommonCounter struct {
|
||||
Used *float64 `json:"used,omitempty"` // Used amount when Strategy == ByQuota, returns 0 if no usage data
|
||||
Total *float64 `json:"total,omitempty"` // Total limit when Strategy == ByQuota
|
||||
Strategy *ResourceUsageStrategy `json:"strategy,omitempty"` // Resource usage strategy
|
||||
StartAt *int64 `json:"start_at,omitempty"` // Start time in seconds
|
||||
EndAt *int64 `json:"end_at,omitempty"` // End time in seconds
|
||||
}
|
||||
|
||||
// String methods for enums (for better debugging and logging)
|
||||
|
||||
func (bt BenefitType) String() string {
|
||||
switch bt {
|
||||
case BenefitTypeUnknown:
|
||||
return "Unknown"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (ul UserLevel) String() string {
|
||||
switch ul {
|
||||
case UserLevelUnknown:
|
||||
return "Unknown"
|
||||
case UserLevelBasic:
|
||||
return "Basic"
|
||||
case UserLevelPro:
|
||||
return "Pro"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (ebs EntityBenefitStatus) String() string {
|
||||
switch ebs {
|
||||
case EntityBenefitStatusUnknown:
|
||||
return "Unknown"
|
||||
case EntityBenefitStatusActive:
|
||||
return "Active"
|
||||
case EntityBenefitStatusExpired:
|
||||
return "Expired"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (rus ResourceUsageStrategy) String() string {
|
||||
switch rus {
|
||||
case ResourceUsageStrategyUnknown:
|
||||
return "Unknown"
|
||||
case ResourceUsageStrategyByQuota:
|
||||
return "ByQuota"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
@ -83,28 +83,52 @@ func (s *CozeUserService) GetUserInfo(ctx context.Context, userID int64) (*entit
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetUserBenefit calls the /v1/users/benefit endpoint
|
||||
func (s *CozeUserService) GetUserBenefit(ctx context.Context, userID int64) (*entity.UserBenefit, error) {
|
||||
resp, err := s.client.Get(ctx, "/v1/users/benefit")
|
||||
// GetEnterpriseBenefit calls the /v1/commerce/benefit/benefits/get endpoint with query parameters
|
||||
func (s *CozeUserService) GetEnterpriseBenefit(ctx context.Context, req *entity.GetEnterpriseBenefitRequest) (*entity.GetEnterpriseBenefitResponse, error) {
|
||||
// Build query parameters
|
||||
queryParams := make(map[string]interface{})
|
||||
if req.BenefitType != nil {
|
||||
queryParams["benefit_type"] = int32(*req.BenefitType)
|
||||
}
|
||||
if req.ResourceID != nil {
|
||||
queryParams["resource_id"] = *req.ResourceID
|
||||
}
|
||||
|
||||
// Call API
|
||||
resp, err := s.client.GetWithQuery(ctx, "/v1/commerce/benefit/benefits/get", queryParams)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "failed to call GetUserBenefit API: %v", err)
|
||||
logs.CtxErrorf(ctx, "failed to call GetEnterpriseBenefit API: %v", err)
|
||||
return nil, errorx.New(errno.ErrUserResourceNotFound, errorx.KV("reason", "API call failed"))
|
||||
}
|
||||
|
||||
// Parse the data field
|
||||
var benefitData struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
// Add more fields here when API response format is confirmed
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(resp.Data, &benefitData); err != nil {
|
||||
// Parse response data
|
||||
var benefitResp entity.GetEnterpriseBenefitResponse
|
||||
if err := json.Unmarshal(resp.Data, &benefitResp.Data); err != nil {
|
||||
logs.CtxErrorf(ctx, "failed to parse benefit data: %v", err)
|
||||
return nil, errorx.New(errno.ErrUserResourceNotFound, errorx.KV("reason", "data parse failed"))
|
||||
}
|
||||
|
||||
// Map to entity.UserBenefit
|
||||
// Set response basic information
|
||||
benefitResp.Code = int32(resp.Code)
|
||||
benefitResp.Message = resp.Msg
|
||||
|
||||
logs.CtxInfof(ctx, "successfully retrieved enterprise benefit data")
|
||||
|
||||
return &benefitResp, nil
|
||||
}
|
||||
|
||||
// GetUserBenefit calls the /v1/users/benefit endpoint (legacy method, kept for backward compatibility)
|
||||
func (s *CozeUserService) GetUserBenefit(ctx context.Context, userID int64) (*entity.UserBenefit, error) {
|
||||
// Use new enterprise benefit interface without query parameters
|
||||
req := &entity.GetEnterpriseBenefitRequest{}
|
||||
_, err := s.GetEnterpriseBenefit(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert to old UserBenefit format for backward compatibility
|
||||
return &entity.UserBenefit{
|
||||
UserID: benefitData.UserID,
|
||||
UserID: userID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -128,3 +152,8 @@ func (u *userImpl) GetSaasUserInfo(ctx context.Context, userID int64) (*entity.U
|
||||
func (u *userImpl) GetUserBenefit(ctx context.Context, userID int64) (*entity.UserBenefit, error) {
|
||||
return getCozeUserService().GetUserBenefit(ctx, userID)
|
||||
}
|
||||
|
||||
// GetEnterpriseBenefit gets enterprise benefit with query parameters
|
||||
func (u *userImpl) GetEnterpriseBenefit(ctx context.Context, req *entity.GetEnterpriseBenefitRequest) (*entity.GetEnterpriseBenefitResponse, error) {
|
||||
return getCozeUserService().GetEnterpriseBenefit(ctx, req)
|
||||
}
|
||||
|
||||
@ -23,7 +23,9 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
@ -61,6 +63,11 @@ func (c *CozeAPIClient) Get(ctx context.Context, path string) (*CozeAPIResponse,
|
||||
return c.request(ctx, "GET", path, nil)
|
||||
}
|
||||
|
||||
// GetWithQuery performs a GET request to the coze.cn API with query parameters
|
||||
func (c *CozeAPIClient) GetWithQuery(ctx context.Context, path string, queryParams map[string]interface{}) (*CozeAPIResponse, error) {
|
||||
return c.requestWithQuery(ctx, "GET", path, nil, queryParams)
|
||||
}
|
||||
|
||||
// Post performs a POST request to the coze.cn API
|
||||
func (c *CozeAPIClient) Post(ctx context.Context, path string, body interface{}) (*CozeAPIResponse, error) {
|
||||
var bodyBytes []byte
|
||||
@ -146,6 +153,114 @@ func (c *CozeAPIClient) request(ctx context.Context, method, path string, body [
|
||||
return &apiResp, nil
|
||||
}
|
||||
|
||||
// requestWithQuery is the core method for making HTTP requests to coze.cn API with query parameters
|
||||
func (c *CozeAPIClient) requestWithQuery(ctx context.Context, method, path string, body []byte, queryParams map[string]interface{}) (*CozeAPIResponse, error) {
|
||||
baseURL := fmt.Sprintf("%s%s", c.BaseURL, path)
|
||||
|
||||
// Build query parameters
|
||||
if queryParams != nil && len(queryParams) > 0 {
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse URL: %w", err)
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
for key, value := range queryParams {
|
||||
if value != nil {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
if v != "" {
|
||||
q.Set(key, v)
|
||||
}
|
||||
case int:
|
||||
q.Set(key, strconv.Itoa(v))
|
||||
case bool:
|
||||
q.Set(key, strconv.FormatBool(v))
|
||||
case *string:
|
||||
if v != nil && *v != "" {
|
||||
q.Set(key, *v)
|
||||
}
|
||||
case *int:
|
||||
if v != nil {
|
||||
q.Set(key, strconv.Itoa(*v))
|
||||
}
|
||||
case *bool:
|
||||
if v != nil {
|
||||
q.Set(key, strconv.FormatBool(*v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
baseURL = u.String()
|
||||
}
|
||||
|
||||
var req *http.Request
|
||||
var err error
|
||||
|
||||
if body != nil {
|
||||
req, err = http.NewRequestWithContext(ctx, method, baseURL, bytes.NewReader(body))
|
||||
} else {
|
||||
req, err = http.NewRequestWithContext(ctx, method, baseURL, nil)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
// Set headers
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
// Add API key if available
|
||||
if c.APIKey != "" {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.APIKey))
|
||||
}
|
||||
|
||||
// Make request with retries
|
||||
var resp *http.Response
|
||||
for i := 0; i <= c.MaxRetries; i++ {
|
||||
resp, err = c.HTTPClient.Do(req)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if i < c.MaxRetries {
|
||||
logs.CtxWarnf(ctx, "coze API request failed, retrying (%d/%d): %v", i+1, c.MaxRetries, err)
|
||||
time.Sleep(time.Duration(i+1) * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request failed after %d retries: %w", c.MaxRetries, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read response body
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
// Check HTTP status code
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
// Parse response
|
||||
var apiResp CozeAPIResponse
|
||||
if err := json.Unmarshal(respBody, &apiResp); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse API response: %w", err)
|
||||
}
|
||||
|
||||
// Check API response code
|
||||
if apiResp.Code != 0 {
|
||||
return nil, fmt.Errorf("API returned error: code=%d, msg=%s", apiResp.Code, apiResp.Msg)
|
||||
}
|
||||
|
||||
return &apiResp, nil
|
||||
}
|
||||
|
||||
// getEnvOrDefault returns environment variable value or default if not set
|
||||
func getSaasOpenAPIUrl() string {
|
||||
if value := os.Getenv("COZE_SAAS_API_BASE_URL"); value != "" {
|
||||
|
||||
@ -2,6 +2,20 @@ include "marketplace_common.thrift"
|
||||
|
||||
namespace go marketplace.product_common
|
||||
|
||||
enum BotModType {
|
||||
SingleAgent = 1,
|
||||
MultiAgent = 2,
|
||||
}
|
||||
|
||||
enum Component {
|
||||
UsePlugin = 1,
|
||||
UseWorkFlow = 2,
|
||||
UseKnowledge = 3,
|
||||
UseVoice = 4,
|
||||
UseCard = 5,
|
||||
UseImageWorkflow = 6,
|
||||
}
|
||||
|
||||
enum ProductEntityType {
|
||||
Bot = 1 ,
|
||||
Plugin = 2 ,
|
||||
|
||||
@ -11,8 +11,73 @@ service PublicProductService {
|
||||
FavoriteProductResponse PublicFavoriteProduct(1: FavoriteProductRequest req)(api.post = "/api/marketplace/product/favorite", api.category = "PublicAPI")
|
||||
GetUserFavoriteListV2Response PublicGetUserFavoriteListV2(1: GetUserFavoriteListV2Request req)(api.get = "/api/marketplace/product/favorite/list.v2", api.category = "PublicAPI")
|
||||
DuplicateProductResponse PublicDuplicateProduct (1: DuplicateProductRequest req) (api.post = "/api/marketplace/product/duplicate", api.category = "PublicAPI")
|
||||
|
||||
SearchProductResponse PublicSearchProduct(1: SearchProductRequest req)(api.get = "/api/marketplace/product/search", api.category = "PublicAPI")
|
||||
SearchSuggestResponse PublicSearchSuggest(1: SearchSuggestRequest req)(api.get = "/api/marketplace/product/search/suggest", api.category = "PublicAPI")
|
||||
}
|
||||
|
||||
struct SearchProductRequest{
|
||||
1 : required string Keyword (api.query = "keyword") ,
|
||||
2 : required i32 PageNum (api.query = "page_num") ,
|
||||
3 : required i32 PageSize (api.query = "page_size") ,
|
||||
4 : optional product_common.ProductEntityType EntityType (agw.key = "entity_type") ,
|
||||
5 : optional product_common.SortType SortType (api.query = "sort_type") ,
|
||||
|
||||
11 : optional product_common.ProductPublishMode PublishMode (agw.key = "publish_mode") , // Open/closed source
|
||||
12 : optional list<i64> ModelIDs (agw.js_conv="str", agw.cli_conv="str", api.query = "model_ids") , // Models used
|
||||
13 : optional product_common.BotModType BotModType (agw.key = "bot_mod_type") , // Multimodal type
|
||||
14 : optional list<product_common.Component> Components (agw.key = "components") , // Sub-attributes
|
||||
15 : optional list<i64> PublishPlatformIDs (agw.js_conv="str", agw.cli_conv="str", api.query = "publish_platform_ids"), // Publish platform IDs
|
||||
16 : optional list<i64> CategoryIDs (agw.js_conv="str", agw.cli_conv="str", api.query = "category_ids"), // Product category IDs
|
||||
17 : optional bool IsOfficial (api.query = "is_official"), // Is official
|
||||
18 : optional bool IsRecommend (api.query = "is_recommend"), // Is recommended
|
||||
|
||||
19 : optional list<product_common.ProductEntityType> EntityTypes ( api.query = "entity_types"), // Product type list, use this parameter first, then EntityType
|
||||
20 : optional product_common.PluginType PluginType (agw.key = "plugin_type"), // Plugin type
|
||||
21 : optional product_common.ProductPaidType ProductPaidType (agw.key = "product_paid_type"), // Product paid type
|
||||
|
||||
255: optional base.Base Base ,
|
||||
}
|
||||
|
||||
struct SearchProductResponse{
|
||||
1 : required i32 Code (agw.key = "code") ,
|
||||
2 : required string Message (agw.key = "message"),
|
||||
3 : optional SearchProductResponseData Data (agw.key = "data") ,
|
||||
255: optional base.BaseResp BaseResp ,
|
||||
}
|
||||
|
||||
struct SearchProductResponseData{
|
||||
1: optional list<ProductInfo> Products (agw.key = "products"),
|
||||
2: optional i32 Total (agw.key = "total") ,
|
||||
3: optional bool HasMore (agw.key = "has_more"),
|
||||
4: optional map<product_common.ProductEntityType, i32> EntityTotal (agw.key = "entity_total"), // Entity count
|
||||
}
|
||||
|
||||
struct SearchSuggestResponse{
|
||||
1 : required i32 Code (agw.key = "code") ,
|
||||
2 : required string Message (agw.key = "message"),
|
||||
3 : optional SearchSuggestResponseData Data (agw.key = "data") ,
|
||||
255: optional base.BaseResp BaseResp ,
|
||||
}
|
||||
|
||||
struct SearchSuggestResponseData{
|
||||
1: optional list<ProductMetaInfo> Suggestions (agw.key = "suggestions"), // Deprecated
|
||||
2: optional bool HasMore (agw.key = "has_more"),
|
||||
3: optional list<ProductInfo> SuggestionV2(agw.key = "suggestion_v2"),
|
||||
}
|
||||
|
||||
|
||||
struct SearchSuggestRequest {
|
||||
1: optional string Keyword (api.query = "keyword"),
|
||||
2: optional product_common.ProductEntityType EntityType (api.query = "entity_type"), // Optional, defaults to bot recommendation if not provided
|
||||
3: optional i32 PageNum (api.query = "page_num"),
|
||||
4: optional i32 PageSize (api.query = "page_size"),
|
||||
5: optional list<product_common.ProductEntityType> EntityTypes (api.query = "entity_types"), // Product type list, use this parameter first, then EntityType
|
||||
|
||||
255: optional base.Base Base ,
|
||||
}
|
||||
|
||||
|
||||
struct FavoriteProductResponse {
|
||||
1 : required i32 Code (agw.key = "code", api.body = "code") ,
|
||||
2 : required string Message (agw.key = "message", api.body = "message") ,
|
||||
@ -566,4 +631,4 @@ struct DuplicateProductData {
|
||||
// New ID after copy
|
||||
1: i64 NewEntityID (agw.js_conv="str", api.js_conv="str", agw.cli_conv="str", api.body = "new_entity_id")
|
||||
2: optional i64 NewPluginID (agw.js_conv="str", api.js_conv="str", agw.cli_conv="str", api.body = "new_plugin_id") // Plugin ID for workflow
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user