feat: Support for Chat Flow & Agent Support for binding a single chat flow (#765)
Co-authored-by: Yu Yang <72337138+tomasyu985@users.noreply.github.com> Co-authored-by: zengxiaohui <csu.zengxiaohui@gmail.com> Co-authored-by: lijunwen.gigoo <lijunwen.gigoo@bytedance.com> Co-authored-by: lvxinyu.1117 <lvxinyu.1117@bytedance.com> Co-authored-by: liuyunchao.0510 <liuyunchao.0510@bytedance.com> Co-authored-by: haozhenfei <37089575+haozhenfei@users.noreply.github.com> Co-authored-by: July <jiangxujin@bytedance.com> Co-authored-by: tecvan-fe <fanwenjie.fe@bytedance.com>
This commit is contained in:
@@ -59,6 +59,7 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/safego"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/taskgroup"
|
||||
@@ -303,6 +304,11 @@ func (a *APPApplicationService) getAPPPublishConnectorList(ctx context.Context,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case consts.WebSDKConnectorID:
|
||||
info, err = a.packChatSDKConnectorInfo(ctx, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
logs.CtxWarnf(ctx, "unsupported connector id '%v'", c.ID)
|
||||
continue
|
||||
@@ -338,6 +344,22 @@ func (a *APPApplicationService) packAPIConnectorInfo(ctx context.Context, c *con
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (a *APPApplicationService) packChatSDKConnectorInfo(ctx context.Context, c *connectorModel.Connector) (*publishAPI.PublishConnectorInfo, error) {
|
||||
|
||||
info := &publishAPI.PublishConnectorInfo{
|
||||
ID: c.ID,
|
||||
BindType: publishAPI.ConnectorBindType_WebSDKBind,
|
||||
ConnectorClassification: publishAPI.ConnectorClassification_APIOrSDK,
|
||||
BindInfo: map[string]string{},
|
||||
Name: c.Name,
|
||||
IconURL: c.URL,
|
||||
Description: c.Desc,
|
||||
AllowPublish: true,
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (a *APPApplicationService) getLatestPublishRecord(ctx context.Context, appID int64) (info *publishAPI.LastPublishInfo, err error) {
|
||||
record, exist, err := a.DomainSVC.GetAPPPublishRecord(ctx, &service.GetAPPPublishRecordRequest{
|
||||
APPID: appID,
|
||||
@@ -1089,6 +1111,91 @@ func (a *APPApplicationService) DraftProjectCopy(ctx context.Context, req *proje
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (a *APPApplicationService) GetOnlineAppData(ctx context.Context, req *projectAPI.GetOnlineAppDataRequest) (resp *projectAPI.GetOnlineAppDataResponse, err error) {
|
||||
uid := ctxutil.GetApiAuthFromCtx(ctx).UserID
|
||||
record, exist, err := a.DomainSVC.GetAPPPublishRecord(ctx, &service.GetAPPPublishRecordRequest{
|
||||
APPID: req.GetAppID(),
|
||||
Oldest: false,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !exist {
|
||||
return nil, errorx.Wrapf(err, "GetOnlineAppDataRequest failed, app id=%d, connector id=%v", req.GetAppID(), req.GetConnectorID())
|
||||
}
|
||||
|
||||
if record.APP.OwnerID != uid {
|
||||
return nil, errorx.New(errno.ErrAppPermissionCode, errorx.KV(errno.APPMsgKey, fmt.Sprintf("user %d does not have access to app %d", uid, req.GetAppID())))
|
||||
|
||||
}
|
||||
|
||||
valid := false
|
||||
for _, v := range record.ConnectorPublishRecords {
|
||||
if v.ConnectorID == req.GetConnectorID() {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !valid {
|
||||
return nil, errorx.Wrapf(err, "GetOnlineAppDataRequest failed, invalid connector id, app id=%d, connector id=%v", req.GetAppID(), req.GetConnectorID())
|
||||
}
|
||||
|
||||
app := record.APP
|
||||
|
||||
iconURL, err := a.oss.GetObjectUrl(ctx, app.GetIconURI())
|
||||
if err != nil {
|
||||
logs.CtxWarnf(ctx, "get icon url failed with '%s', err=%v", app.GetIconURI(), err)
|
||||
}
|
||||
|
||||
varMeta, err := a.variablesSVC.GetProjectVariablesMeta(ctx, strconv.FormatInt(app.ID, 10), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vars := make([]*common.Variable, 0, len(varMeta.Variables))
|
||||
for _, v := range varMeta.Variables {
|
||||
vars = append(vars, &common.Variable{
|
||||
Keyword: v.Keyword,
|
||||
DefaultValue: v.DefaultValue,
|
||||
Description: v.Description,
|
||||
Enable: v.Enable,
|
||||
VariableType: ternary.IFElse(v.VariableType == project_memory.VariableType_KVVariable, common.VariableTypeKVVariable, common.VariableTypeListVariable),
|
||||
Channel: func() common.VariableChannel {
|
||||
switch v.Channel {
|
||||
case project_memory.VariableChannel_APP:
|
||||
return common.VariableChannelAPP
|
||||
case project_memory.VariableChannel_System:
|
||||
return common.VariableChannelSystem
|
||||
case project_memory.VariableChannel_Custom:
|
||||
return common.VariableChannelCustom
|
||||
case project_memory.VariableChannel_Feishu:
|
||||
return common.VariableChannelFeishu
|
||||
case project_memory.VariableChannel_Location:
|
||||
return common.VariableChannelLocation
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
||||
}(),
|
||||
})
|
||||
}
|
||||
|
||||
response := &projectAPI.GetOnlineAppDataResponse{
|
||||
Data: &projectAPI.AppData{
|
||||
AppID: strconv.FormatInt(record.APP.ID, 10),
|
||||
Name: *app.Name,
|
||||
Description: *app.Desc,
|
||||
Version: *app.Version,
|
||||
IconURL: iconURL,
|
||||
Variables: vars,
|
||||
},
|
||||
}
|
||||
|
||||
return response, nil
|
||||
|
||||
}
|
||||
|
||||
func (a *APPApplicationService) duplicateDraftAPP(ctx context.Context, userID int64, req *projectAPI.DraftProjectCopyRequest) (newAppID int64, err error) {
|
||||
newAppID, err = a.DomainSVC.CreateDraftAPP(ctx, &service.CreateDraftAPPRequest{
|
||||
SpaceID: req.ToSpaceID,
|
||||
|
||||
@@ -49,6 +49,7 @@ import (
|
||||
crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message"
|
||||
crossmodelmgr "github.com/coze-dev/coze-studio/backend/crossdomain/contract/modelmgr"
|
||||
crossplugin "github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin"
|
||||
crossupload "github.com/coze-dev/coze-studio/backend/crossdomain/contract/upload"
|
||||
crossuser "github.com/coze-dev/coze-studio/backend/crossdomain/contract/user"
|
||||
crossvariables "github.com/coze-dev/coze-studio/backend/crossdomain/contract/variables"
|
||||
crossworkflow "github.com/coze-dev/coze-studio/backend/crossdomain/contract/workflow"
|
||||
@@ -64,6 +65,7 @@ import (
|
||||
pluginImpl "github.com/coze-dev/coze-studio/backend/crossdomain/impl/plugin"
|
||||
searchImpl "github.com/coze-dev/coze-studio/backend/crossdomain/impl/search"
|
||||
singleagentImpl "github.com/coze-dev/coze-studio/backend/crossdomain/impl/singleagent"
|
||||
uploadImpl "github.com/coze-dev/coze-studio/backend/crossdomain/impl/upload"
|
||||
variablesImpl "github.com/coze-dev/coze-studio/backend/crossdomain/impl/variables"
|
||||
workflowImpl "github.com/coze-dev/coze-studio/backend/crossdomain/impl/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/eventbus"
|
||||
@@ -86,6 +88,7 @@ type basicServices struct {
|
||||
promptSVC *prompt.PromptApplicationService
|
||||
templateSVC *template.ApplicationService
|
||||
openAuthSVC *openauth.OpenAuthApplicationService
|
||||
uploadSVC *upload.UploadService
|
||||
}
|
||||
|
||||
type primaryServices struct {
|
||||
@@ -139,11 +142,12 @@ func Init(ctx context.Context) (err error) {
|
||||
crossconversation.SetDefaultSVC(conversationImpl.InitDomainService(complexServices.conversationSVC.ConversationDomainSVC))
|
||||
crossmessage.SetDefaultSVC(messageImpl.InitDomainService(complexServices.conversationSVC.MessageDomainSVC))
|
||||
crossagentrun.SetDefaultSVC(agentrunImpl.InitDomainService(complexServices.conversationSVC.AgentRunDomainSVC))
|
||||
crossagent.SetDefaultSVC(singleagentImpl.InitDomainService(complexServices.singleAgentSVC.DomainSVC, infra.ImageXClient))
|
||||
crossagent.SetDefaultSVC(singleagentImpl.InitDomainService(complexServices.singleAgentSVC.DomainSVC))
|
||||
crossuser.SetDefaultSVC(crossuserImpl.InitDomainService(basicServices.userSVC.DomainSVC))
|
||||
crossdatacopy.SetDefaultSVC(dataCopyImpl.InitDomainService(basicServices.infra))
|
||||
crosssearch.SetDefaultSVC(searchImpl.InitDomainService(complexServices.searchSVC.DomainSVC))
|
||||
crossmodelmgr.SetDefaultSVC(modelmgrImpl.InitDomainService(infra.ModelMgr, nil))
|
||||
crossupload.SetDefaultSVC(uploadImpl.InitDomainService(basicServices.uploadSVC.UploadSVC))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -159,7 +163,7 @@ func initEventBus(infra *appinfra.AppDependencies) *eventbusImpl {
|
||||
|
||||
// initBasicServices init basic services that only depends on infra.
|
||||
func initBasicServices(ctx context.Context, infra *appinfra.AppDependencies, e *eventbusImpl) (*basicServices, error) {
|
||||
upload.InitService(infra.TOSClient, infra.CacheCli)
|
||||
uploadSVC := upload.InitService(&upload.UploadComponents{Cache: infra.CacheCli, Oss: infra.TOSClient, DB: infra.DB, Idgen: infra.IDGenSVC})
|
||||
openAuthSVC := openauth.InitService(infra.DB, infra.IDGenSVC)
|
||||
promptSVC := prompt.InitService(infra.DB, infra.IDGenSVC, e.resourceEventBus)
|
||||
modelMgrSVC := modelmgr.InitService(infra.ModelMgr, infra.TOSClient)
|
||||
@@ -180,6 +184,7 @@ func initBasicServices(ctx context.Context, infra *appinfra.AppDependencies, e *
|
||||
promptSVC: promptSVC,
|
||||
templateSVC: templateSVC,
|
||||
openAuthSVC: openAuthSVC,
|
||||
uploadSVC: uploadSVC,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
conversationService "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/service"
|
||||
message "github.com/coze-dev/coze-studio/backend/domain/conversation/message/service"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/service"
|
||||
uploadService "github.com/coze-dev/coze-studio/backend/domain/upload/service"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
|
||||
@@ -48,6 +49,7 @@ var ConversationSVC = new(ConversationApplicationService)
|
||||
|
||||
type OpenapiAgentRunApplication struct {
|
||||
ShortcutDomainSVC service.ShortcutCmd
|
||||
UploaodDomainSVC uploadService.UploadService
|
||||
}
|
||||
|
||||
var ConversationOpenAPISVC = new(OpenapiAgentRunApplication)
|
||||
@@ -177,6 +179,7 @@ func (c *ConversationApplicationService) ListConversation(ctx context.Context, r
|
||||
LastSectionID: &conv.SectionID,
|
||||
ConnectorID: &conv.ConnectorID,
|
||||
CreatedAt: conv.CreatedAt / 1000,
|
||||
Name: ptr.Of(conv.Name),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -186,3 +189,70 @@ func (c *ConversationApplicationService) ListConversation(ctx context.Context, r
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *ConversationApplicationService) DeleteConversation(ctx context.Context, req *conversation.DeleteConversationApiRequest) (*conversation.DeleteConversationApiResponse, error) {
|
||||
resp := new(conversation.DeleteConversationApiResponse)
|
||||
convID := req.GetConversationID()
|
||||
|
||||
apiKeyInfo := ctxutil.GetApiAuthFromCtx(ctx)
|
||||
userID := apiKeyInfo.UserID
|
||||
|
||||
if userID == 0 {
|
||||
return resp, errorx.New(errno.ErrConversationPermissionCode, errorx.KV("msg", "permission check failed"))
|
||||
}
|
||||
|
||||
conversationDO, err := c.ConversationDomainSVC.GetByID(ctx, convID)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if conversationDO == nil {
|
||||
return resp, errorx.New(errno.ErrConversationNotFound)
|
||||
}
|
||||
if conversationDO.CreatorID != userID {
|
||||
return resp, errorx.New(errno.ErrConversationNotFound, errorx.KV("msg", "user not match"))
|
||||
}
|
||||
err = c.ConversationDomainSVC.Delete(ctx, convID)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *ConversationApplicationService) UpdateConversation(ctx context.Context, req *conversation.UpdateConversationApiRequest) (*conversation.UpdateConversationApiResponse, error) {
|
||||
resp := new(conversation.UpdateConversationApiResponse)
|
||||
convID := req.GetConversationID()
|
||||
|
||||
apiKeyInfo := ctxutil.GetApiAuthFromCtx(ctx)
|
||||
userID := apiKeyInfo.UserID
|
||||
|
||||
if userID == 0 {
|
||||
return resp, errorx.New(errno.ErrConversationPermissionCode, errorx.KV("msg", "permission check failed"))
|
||||
}
|
||||
|
||||
conversationDO, err := c.ConversationDomainSVC.GetByID(ctx, convID)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if conversationDO == nil {
|
||||
return resp, errorx.New(errno.ErrConversationNotFound)
|
||||
}
|
||||
if conversationDO.CreatorID != userID {
|
||||
return resp, errorx.New(errno.ErrConversationPermissionCode, errorx.KV("msg", "user not match"))
|
||||
}
|
||||
|
||||
updateResult, err := c.ConversationDomainSVC.Update(ctx, &entity.UpdateMeta{
|
||||
ID: convID,
|
||||
Name: req.GetName(),
|
||||
})
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
resp.ConversationData = &conversation.ConversationData{
|
||||
Id: updateResult.ID,
|
||||
LastSectionID: &updateResult.SectionID,
|
||||
ConnectorID: &updateResult.ConnectorID,
|
||||
CreatedAt: updateResult.CreatedAt / 1000,
|
||||
Name: ptr.Of(updateResult.Name),
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
message "github.com/coze-dev/coze-studio/backend/domain/conversation/message/service"
|
||||
shortcutRepo "github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/repository"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/service"
|
||||
uploadService "github.com/coze-dev/coze-studio/backend/domain/upload/service"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/idgen"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/imagex"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/storage"
|
||||
@@ -56,6 +57,7 @@ func InitService(s *ServiceComponents) *ConversationApplicationService {
|
||||
|
||||
arDomainComponents := &agentrun.Components{
|
||||
RunRecordRepo: repository.NewRunRecordRepo(s.DB, s.IDGen),
|
||||
ImagexSVC: s.ImageX,
|
||||
}
|
||||
|
||||
agentRunDomainSVC := agentrun.NewService(arDomainComponents)
|
||||
@@ -71,6 +73,9 @@ func InitService(s *ServiceComponents) *ConversationApplicationService {
|
||||
ConversationSVC.ShortcutDomainSVC = shortcutCmdDomainSVC
|
||||
|
||||
ConversationOpenAPISVC.ShortcutDomainSVC = shortcutCmdDomainSVC
|
||||
uploadSVC := uploadService.NewUploadSVC(s.DB, s.IDGen, s.TosClient)
|
||||
ConversationOpenAPISVC.UploaodDomainSVC = uploadSVC
|
||||
OpenapiMessageSVC.UploaodDomainSVC = uploadSVC
|
||||
|
||||
return ConversationSVC
|
||||
}
|
||||
|
||||
@@ -35,7 +35,9 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity"
|
||||
convEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity"
|
||||
cmdEntity "github.com/coze-dev/coze-studio/backend/domain/shortcutcmd/entity"
|
||||
uploadService "github.com/coze-dev/coze-studio/backend/domain/upload/service"
|
||||
sseImpl "github.com/coze-dev/coze-studio/backend/infra/impl/sse"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
@@ -237,11 +239,26 @@ func (a *OpenapiAgentRunApplication) buildMultiContent(ctx context.Context, ar *
|
||||
Text: ptr.From(one.Text),
|
||||
})
|
||||
case message.InputTypeImage, message.InputTypeFile:
|
||||
|
||||
var fileUrl, fileURI string
|
||||
if one.GetFileURL() != "" {
|
||||
fileUrl = one.GetFileURL()
|
||||
} else if one.GetFileID() != 0 {
|
||||
fileInfo, err := a.UploaodDomainSVC.GetFile(ctx, &uploadService.GetFileRequest{
|
||||
ID: one.GetFileID(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, contentType, err
|
||||
}
|
||||
fileUrl = fileInfo.File.Url
|
||||
fileURI = fileInfo.File.TosURI
|
||||
}
|
||||
multiContents = append(multiContents, &message.InputMetaData{
|
||||
Type: message.InputType(one.Type),
|
||||
FileData: []*message.FileData{
|
||||
{
|
||||
Url: one.GetFileURL(),
|
||||
Url: fileUrl,
|
||||
URI: fileURI,
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -328,3 +345,62 @@ func buildARSM2ApiChatMessage(chunk *entity.AgentRunResponse) []byte {
|
||||
mCM, _ := json.Marshal(chunkMessage)
|
||||
return mCM
|
||||
}
|
||||
|
||||
func (a *OpenapiAgentRunApplication) CancelRun(ctx context.Context, req *run.CancelChatApiRequest) (*run.CancelChatApiResponse, error) {
|
||||
resp := new(run.CancelChatApiResponse)
|
||||
|
||||
apiKeyInfo := ctxutil.GetApiAuthFromCtx(ctx)
|
||||
userID := apiKeyInfo.UserID
|
||||
|
||||
runRecord, err := ConversationSVC.AgentRunDomainSVC.GetByID(ctx, req.ChatID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if runRecord == nil {
|
||||
return nil, errorx.New(errno.ErrRecordNotFound)
|
||||
}
|
||||
|
||||
conversationData, err := ConversationSVC.ConversationDomainSVC.GetByID(ctx, req.ConversationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if userID != conversationData.CreatorID {
|
||||
return nil, errorx.New(errno.ErrConversationPermissionCode, errorx.KV("msg", "user not match"))
|
||||
}
|
||||
|
||||
if runRecord.Status != entity.RunStatusInProgress && runRecord.Status != entity.RunStatusCreated {
|
||||
return nil, errorx.New(errno.ErrInProgressCanNotCancel)
|
||||
}
|
||||
|
||||
runMeta, err := ConversationSVC.AgentRunDomainSVC.Cancel(ctx, &entity.CancelRunMeta{
|
||||
RunID: req.ChatID,
|
||||
ConversationID: req.ConversationID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if runMeta == nil {
|
||||
return nil, errorx.New(errno.ErrConversationAgentRunError)
|
||||
}
|
||||
|
||||
resp.ChatV3ChatDetail = &run.ChatV3ChatDetail{
|
||||
ID: runMeta.ID,
|
||||
ConversationID: runMeta.ConversationID,
|
||||
BotID: runMeta.AgentID,
|
||||
Status: string(runMeta.Status),
|
||||
SectionID: ptr.Of(runMeta.SectionID),
|
||||
CreatedAt: ptr.Of(int32(runMeta.CreatedAt / 1000)),
|
||||
CompletedAt: ptr.Of(int32(runMeta.CompletedAt / 1000)),
|
||||
FailedAt: ptr.Of(int32(runMeta.FailedAt / 1000)),
|
||||
}
|
||||
if runMeta.Usage != nil {
|
||||
resp.ChatV3ChatDetail.Usage = &run.Usage{
|
||||
TokenCount: ptr.Of(int32(runMeta.Usage.LlmTotalTokens)),
|
||||
InputTokens: ptr.Of(int32(runMeta.Usage.LlmPromptTokens)),
|
||||
OutputTokens: ptr.Of(int32(runMeta.Usage.LlmCompletionTokens)),
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -18,23 +18,28 @@ package conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/conversation/message"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/conversation/run"
|
||||
apiMessage "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
|
||||
message3 "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message"
|
||||
"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
|
||||
convEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity"
|
||||
uploadService "github.com/coze-dev/coze-studio/backend/domain/upload/service"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
type OpenapiMessageApplication struct{}
|
||||
type OpenapiMessageApplication struct {
|
||||
UploaodDomainSVC uploadService.UploadService
|
||||
}
|
||||
|
||||
var OpenapiMessageApplicationService = new(OpenapiMessageApplication)
|
||||
var OpenapiMessageSVC = new(OpenapiMessageApplication)
|
||||
|
||||
func (m *OpenapiMessageApplication) GetApiMessageList(ctx context.Context, mr *message.ListMessageApiRequest) (*message.ListMessageApiResponse, error) {
|
||||
// Get Conversation ID by agent id & userID & scene
|
||||
@@ -61,13 +66,21 @@ func (m *OpenapiMessageApplication) GetApiMessageList(ctx context.Context, mr *m
|
||||
ConversationID: currentConversation.ID,
|
||||
AgentID: currentConversation.AgentID,
|
||||
Limit: int(ptr.From(mr.Limit)),
|
||||
|
||||
MessageType: []*message3.MessageType{
|
||||
ptr.Of(message3.MessageTypeQuestion),
|
||||
ptr.Of(message3.MessageTypeAnswer),
|
||||
},
|
||||
}
|
||||
if mr.ChatID != nil {
|
||||
msgListMeta.RunID = []*int64{mr.ChatID}
|
||||
}
|
||||
|
||||
if mr.BeforeID != nil {
|
||||
msgListMeta.Direction = entity.ScrollPageDirectionPrev
|
||||
msgListMeta.Direction = entity.ScrollPageDirectionNext
|
||||
msgListMeta.Cursor = *mr.BeforeID
|
||||
} else {
|
||||
msgListMeta.Direction = entity.ScrollPageDirectionNext
|
||||
msgListMeta.Direction = entity.ScrollPageDirectionPrev
|
||||
msgListMeta.Cursor = ptr.From(mr.AfterID)
|
||||
}
|
||||
if mr.Order == nil {
|
||||
@@ -76,20 +89,14 @@ func (m *OpenapiMessageApplication) GetApiMessageList(ctx context.Context, mr *m
|
||||
msgListMeta.OrderBy = mr.Order
|
||||
}
|
||||
|
||||
mListMessages, err := ConversationSVC.MessageDomainSVC.List(ctx, msgListMeta)
|
||||
mListMessages, err := ConversationSVC.MessageDomainSVC.ListWithoutPair(ctx, msgListMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get agent id
|
||||
var agentIDs []int64
|
||||
for _, mOne := range mListMessages.Messages {
|
||||
agentIDs = append(agentIDs, mOne.AgentID)
|
||||
}
|
||||
|
||||
resp := m.buildMessageListResponse(ctx, mListMessages, currentConversation)
|
||||
|
||||
return resp, err
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func getConversation(ctx context.Context, conversationID int64) (*convEntity.Conversation, error) {
|
||||
@@ -106,21 +113,22 @@ func (m *OpenapiMessageApplication) buildMessageListResponse(ctx context.Context
|
||||
content := dm.Content
|
||||
|
||||
msg := &message.OpenMessageApi{
|
||||
ID: dm.ID,
|
||||
ConversationID: dm.ConversationID,
|
||||
BotID: dm.AgentID,
|
||||
Role: string(dm.Role),
|
||||
Type: string(dm.MessageType),
|
||||
Content: content,
|
||||
ContentType: string(dm.ContentType),
|
||||
SectionID: strconv.FormatInt(dm.SectionID, 10),
|
||||
CreatedAt: dm.CreatedAt / 1000,
|
||||
UpdatedAt: dm.UpdatedAt / 1000,
|
||||
ChatID: dm.RunID,
|
||||
MetaData: dm.Ext,
|
||||
ID: dm.ID,
|
||||
ConversationID: dm.ConversationID,
|
||||
BotID: dm.AgentID,
|
||||
Role: string(dm.Role),
|
||||
Type: string(dm.MessageType),
|
||||
Content: content,
|
||||
ContentType: string(dm.ContentType),
|
||||
SectionID: strconv.FormatInt(dm.SectionID, 10),
|
||||
CreatedAt: dm.CreatedAt / 1000,
|
||||
UpdatedAt: dm.UpdatedAt / 1000,
|
||||
ChatID: dm.RunID,
|
||||
MetaData: dm.Ext,
|
||||
ReasoningContent: ptr.Of(dm.ReasoningContent),
|
||||
}
|
||||
if dm.ContentType == message3.ContentTypeMix && dm.DisplayContent != "" {
|
||||
msg.Content = dm.DisplayContent
|
||||
msg.Content = m.parseDisplayContent(ctx, dm)
|
||||
msg.ContentType = run.ContentTypeMixApi
|
||||
}
|
||||
return msg
|
||||
@@ -135,3 +143,38 @@ func (m *OpenapiMessageApplication) buildMessageListResponse(ctx context.Context
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func (m *OpenapiMessageApplication) parseDisplayContent(ctx context.Context, dm *entity.Message) string {
|
||||
|
||||
var inputs []*run.AdditionalContent
|
||||
err := json.Unmarshal([]byte(dm.DisplayContent), &inputs)
|
||||
|
||||
if err != nil {
|
||||
return dm.DisplayContent
|
||||
}
|
||||
for k, one := range inputs {
|
||||
if one == nil {
|
||||
continue
|
||||
}
|
||||
switch apiMessage.InputType(one.Type) {
|
||||
case apiMessage.InputTypeText:
|
||||
continue
|
||||
case apiMessage.InputTypeImage, apiMessage.InputTypeFile:
|
||||
if one.GetFileID() != 0 {
|
||||
fileInfo, err := m.UploaodDomainSVC.GetFile(ctx, &uploadService.GetFileRequest{
|
||||
ID: one.GetFileID(),
|
||||
})
|
||||
if err == nil {
|
||||
inputs[k].FileURL = ptr.Of(fileInfo.File.Url)
|
||||
}
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
content, err := json.Marshal(inputs)
|
||||
if err == nil {
|
||||
dm.DisplayContent = string(content)
|
||||
}
|
||||
return dm.DisplayContent
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/app/bot_open_api"
|
||||
openapimodel "github.com/coze-dev/coze-studio/backend/api/model/permission/openapiauth"
|
||||
"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
|
||||
openapi "github.com/coze-dev/coze-studio/backend/domain/openauth/openapiauth"
|
||||
@@ -80,6 +81,7 @@ func (s *OpenAuthApplicationService) CreatePersonalAccessToken(ctx context.Conte
|
||||
Name: req.Name,
|
||||
Expire: req.ExpireAt,
|
||||
UserID: *userID,
|
||||
AkType: entity.AkTypeCustomer,
|
||||
}
|
||||
|
||||
if req.DurationDay == "customize" {
|
||||
@@ -111,6 +113,32 @@ func (s *OpenAuthApplicationService) CreatePersonalAccessToken(ctx context.Conte
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *OpenAuthApplicationService) ImpersonateCozeUserAccessToken(ctx context.Context, req *bot_open_api.ImpersonateCozeUserRequest) (*bot_open_api.ImpersonateCozeUserResponse, error) {
|
||||
resp := new(bot_open_api.ImpersonateCozeUserResponse)
|
||||
userID := ctxutil.GetUIDFromCtx(ctx)
|
||||
|
||||
expiredSecond := time.Now().Add(time.Duration(time.Second * 60 * 15)).Unix()
|
||||
|
||||
appReq := &entity.CreateApiKey{
|
||||
UserID: *userID,
|
||||
AkType: entity.AkTypeTemporary,
|
||||
Expire: expiredSecond,
|
||||
Name: "temporary access token",
|
||||
}
|
||||
|
||||
apiKeyResp, err := openapiAuthDomainSVC.Create(ctx, appReq)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "OpenAuthApplicationService.CreatePersonalAccessToken failed, err=%v", err)
|
||||
return resp, errors.New("CreatePersonalAccessToken failed")
|
||||
}
|
||||
resp.Data = &bot_open_api.ImpersonateCozeUserResponseData{
|
||||
AccessToken: apiKeyResp.ApiKey,
|
||||
ExpiresIn: expiredSecond,
|
||||
TokenType: "Bearer",
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *OpenAuthApplicationService) ListPersonalAccessTokens(ctx context.Context, req *openapimodel.ListPersonalAccessTokensRequest) (*openapimodel.ListPersonalAccessTokensResponse, error) {
|
||||
|
||||
resp := new(openapimodel.ListPersonalAccessTokensResponse)
|
||||
|
||||
@@ -23,10 +23,12 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/crossdomain/database"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/data/database/table"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/resource/common"
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/knowledge/service"
|
||||
dbservice "github.com/coze-dev/coze-studio/backend/domain/memory/database/service"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ternary"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
@@ -157,8 +159,39 @@ func (w *workflowPacker) GetDataInfo(ctx context.Context) (*dataInfo, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *workflowPacker) GetActions(ctx context.Context) []*common.ResourceAction {
|
||||
actions := []*common.ResourceAction{
|
||||
{
|
||||
Key: common.ActionKey_Edit,
|
||||
Enable: true,
|
||||
},
|
||||
{
|
||||
Key: common.ActionKey_Delete,
|
||||
Enable: true,
|
||||
},
|
||||
{
|
||||
Key: common.ActionKey_Copy,
|
||||
Enable: true,
|
||||
},
|
||||
}
|
||||
meta, err := w.appContext.WorkflowDomainSVC.Get(ctx, &vo.GetPolicy{
|
||||
ID: w.resID,
|
||||
MetaOnly: true,
|
||||
})
|
||||
if err != nil {
|
||||
logs.CtxWarnf(ctx, "get policy failed with '%s', err=%v", w.resID, err)
|
||||
return actions
|
||||
}
|
||||
key := ternary.IFElse(meta.Mode == workflow.WorkflowMode_Workflow, common.ActionKey_SwitchToChatflow, common.ActionKey_SwitchToFuncflow)
|
||||
action := &common.ResourceAction{
|
||||
Key: key,
|
||||
Enable: true,
|
||||
}
|
||||
return append(actions, action)
|
||||
}
|
||||
|
||||
func (w *workflowPacker) GetProjectDefaultActions(ctx context.Context) []*common.ProjectResourceAction {
|
||||
return []*common.ProjectResourceAction{
|
||||
actions := []*common.ProjectResourceAction{
|
||||
{
|
||||
Key: common.ProjectResourceActionKey_Rename,
|
||||
Enable: true,
|
||||
@@ -184,6 +217,21 @@ func (w *workflowPacker) GetProjectDefaultActions(ctx context.Context) []*common
|
||||
Enable: true,
|
||||
},
|
||||
}
|
||||
meta, err := w.appContext.WorkflowDomainSVC.Get(ctx, &vo.GetPolicy{
|
||||
ID: w.resID,
|
||||
MetaOnly: true,
|
||||
})
|
||||
if err != nil {
|
||||
logs.CtxWarnf(ctx, "get policy failed with '%s', err=%v", w.resID, err)
|
||||
return actions
|
||||
}
|
||||
key := ternary.IFElse(meta.Mode == workflow.WorkflowMode_Workflow, common.ProjectResourceActionKey_SwitchToChatflow, common.ProjectResourceActionKey_SwitchToFuncflow)
|
||||
action := &common.ProjectResourceAction{
|
||||
Enable: true,
|
||||
Key: key,
|
||||
}
|
||||
|
||||
return append(actions, action)
|
||||
}
|
||||
|
||||
type knowledgePacker struct {
|
||||
|
||||
@@ -370,6 +370,12 @@ func (s *SingleAgentApplicationService) applyAgentUpdates(target *entity.SingleA
|
||||
}
|
||||
target.Database = patch.DatabaseList
|
||||
}
|
||||
if patch.BotMode != nil {
|
||||
target.BotMode = ptr.From(patch.BotMode)
|
||||
}
|
||||
if patch.LayoutInfo != nil {
|
||||
target.LayoutInfo = patch.LayoutInfo
|
||||
}
|
||||
|
||||
return target, nil
|
||||
}
|
||||
@@ -419,11 +425,12 @@ func (s *SingleAgentApplicationService) singleAgentDraftDo2Vo(ctx context.Contex
|
||||
TaskInfo: &bot_common.TaskInfo{},
|
||||
CreateTime: do.CreatedAt / 1000,
|
||||
UpdateTime: do.UpdatedAt / 1000,
|
||||
BotMode: bot_common.BotMode_SingleMode,
|
||||
BotMode: do.BotMode,
|
||||
BackgroundImageInfoList: do.BackgroundImageInfoList,
|
||||
Status: bot_common.BotStatus_Using,
|
||||
DatabaseList: do.Database,
|
||||
ShortcutSort: do.ShortcutCommand,
|
||||
LayoutInfo: do.LayoutInfo,
|
||||
}
|
||||
|
||||
if do.VariablesMetaID != nil {
|
||||
@@ -647,16 +654,24 @@ func (s *SingleAgentApplicationService) GetAgentOnlineInfo(ctx context.Context,
|
||||
if connectorID == 0 {
|
||||
connectorID = ctxutil.GetApiAuthFromCtx(ctx).ConnectorID
|
||||
}
|
||||
agentInfo, err := s.DomainSVC.ObtainAgentByIdentity(ctx, &entity.AgentIdentity{
|
||||
AgentID: req.BotID,
|
||||
|
||||
return s.getAgentInfo(ctx, req.BotID, connectorID, uid, req.Version)
|
||||
}
|
||||
|
||||
func (s *SingleAgentApplicationService) getAgentInfo(ctx context.Context, botID int64, connectorID int64, uid int64, version *string) (*bot_common.OpenAPIBotInfo, error) {
|
||||
ae := &entity.AgentIdentity{
|
||||
AgentID: botID,
|
||||
ConnectorID: connectorID,
|
||||
Version: ptr.From(req.Version),
|
||||
})
|
||||
}
|
||||
if version != nil {
|
||||
ae.Version = ptr.From(version)
|
||||
}
|
||||
agentInfo, err := s.DomainSVC.ObtainAgentByIdentity(ctx, ae)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if agentInfo == nil {
|
||||
logs.CtxErrorf(ctx, "agent(%d) is not exist", req.BotID)
|
||||
logs.CtxErrorf(ctx, "agent(%d) is not exist", botID)
|
||||
return nil, errorx.New(errno.ErrAgentPermissionCode, errorx.KV("msg", "agent not exist"))
|
||||
}
|
||||
if agentInfo.CreatorID != uid {
|
||||
@@ -731,3 +746,21 @@ func (s *SingleAgentApplicationService) GetAgentOnlineInfo(ctx context.Context,
|
||||
}
|
||||
return combineInfo, nil
|
||||
}
|
||||
|
||||
func (s *SingleAgentApplicationService) OpenGetBotInfo(ctx context.Context, req *bot_open_api.OpenGetBotInfoRequest) (*bot_open_api.OpenGetBotInfoResponse, error) {
|
||||
resp := new(bot_open_api.OpenGetBotInfoResponse)
|
||||
|
||||
uid := ctxutil.MustGetUIDFromApiAuthCtx(ctx)
|
||||
|
||||
connectorID := ptr.From(req.ConnectorID)
|
||||
|
||||
if connectorID == 0 {
|
||||
connectorID = ctxutil.GetApiAuthFromCtx(ctx).ConnectorID
|
||||
}
|
||||
agentInfo, err := s.getAgentInfo(ctx, req.BotID, connectorID, uid, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.Data = agentInfo
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
|
||||
_ "golang.org/x/image/tiff"
|
||||
_ "golang.org/x/image/webp"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
@@ -50,7 +51,9 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/api/model/playground"
|
||||
"github.com/coze-dev/coze-studio/backend/application/base/ctxutil"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/upload/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/upload/service"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/cache"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/idgen"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/storage"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/errorx"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
|
||||
@@ -61,16 +64,26 @@ import (
|
||||
"github.com/coze-dev/coze-studio/backend/types/errno"
|
||||
)
|
||||
|
||||
func InitService(oss storage.Storage, cache cache.Cmdable) {
|
||||
SVC.cache = cache
|
||||
SVC.oss = oss
|
||||
func InitService(components *UploadComponents) *UploadService {
|
||||
SVC.cache = components.Cache
|
||||
SVC.oss = components.Oss
|
||||
SVC.UploadSVC = service.NewUploadSVC(components.DB, components.Idgen, components.Oss)
|
||||
return SVC
|
||||
}
|
||||
|
||||
type UploadComponents struct {
|
||||
Oss storage.Storage
|
||||
Cache cache.Cmdable
|
||||
DB *gorm.DB
|
||||
Idgen idgen.IDGenerator
|
||||
}
|
||||
|
||||
var SVC = &UploadService{}
|
||||
|
||||
type UploadService struct {
|
||||
oss storage.Storage
|
||||
cache cache.Cmdable
|
||||
oss storage.Storage
|
||||
cache cache.Cmdable
|
||||
UploadSVC service.UploadService
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -427,6 +440,23 @@ func (u *UploadService) UploadFileOpen(ctx context.Context, req *bot_open_api.Up
|
||||
}
|
||||
resp.File.CreatedAt = time.Now().Unix()
|
||||
resp.File.URL = url
|
||||
fileEntity := entity.File{
|
||||
Name: fileHeader.Filename,
|
||||
FileSize: fileHeader.Size,
|
||||
TosURI: objName,
|
||||
Status: entity.FileStatusValid,
|
||||
CreatorID: strconv.FormatInt(uid, 10),
|
||||
Source: entity.FileSourceAPI,
|
||||
CozeAccountID: uid,
|
||||
ContentType: fileHeader.Header.Get("Content-Type"),
|
||||
CreatedAt: time.Now().UnixMilli(),
|
||||
UpdatedAt: time.Now().UnixMilli(),
|
||||
}
|
||||
domainResp, err := u.UploadSVC.UploadFile(ctx, &service.UploadFileRequest{File: &fileEntity})
|
||||
if err != nil {
|
||||
return &resp, err
|
||||
}
|
||||
resp.File.ID = strconv.FormatInt(domainResp.File.ID, 10)
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
|
||||
1630
backend/application/workflow/chatflow.go
Normal file
1630
backend/application/workflow/chatflow.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,7 @@ import (
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/crossdomain/impl/code"
|
||||
|
||||
knowledge "github.com/coze-dev/coze-studio/backend/domain/knowledge/service"
|
||||
dbservice "github.com/coze-dev/coze-studio/backend/domain/memory/database/service"
|
||||
variables "github.com/coze-dev/coze-studio/backend/domain/memory/variables/service"
|
||||
@@ -85,15 +86,20 @@ func InitService(_ context.Context, components *ServiceComponents) (*Application
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
workflowRepo := service.NewWorkflowRepository(components.IDGen, components.DB, components.Cache,
|
||||
components.Tos, components.CPStore, components.WorkflowBuildInChatModel, cfg)
|
||||
|
||||
workflowRepo, err := service.NewWorkflowRepository(components.IDGen, components.DB, components.Cache,
|
||||
components.Tos, components.CPStore, components.WorkflowBuildInChatModel, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workflow.SetRepository(workflowRepo)
|
||||
|
||||
workflowDomainSVC := service.NewWorkflowService(workflowRepo)
|
||||
|
||||
code.SetCodeRunner(components.CodeRunner)
|
||||
callbacks.AppendGlobalHandlers(workflowservice.GetTokenCallbackHandler())
|
||||
|
||||
setEventBus(components.DomainNotifier)
|
||||
|
||||
SVC.DomainSVC = workflowDomainSVC
|
||||
|
||||
@@ -49,7 +49,6 @@ import (
|
||||
crossuser "github.com/coze-dev/coze-studio/backend/crossdomain/contract/user"
|
||||
search "github.com/coze-dev/coze-studio/backend/domain/search/entity"
|
||||
domainWorkflow "github.com/coze-dev/coze-studio/backend/domain/workflow"
|
||||
workflowDomain "github.com/coze-dev/coze-studio/backend/domain/workflow"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/idgen"
|
||||
@@ -70,7 +69,7 @@ import (
|
||||
)
|
||||
|
||||
type ApplicationService struct {
|
||||
DomainSVC workflowDomain.Service
|
||||
DomainSVC domainWorkflow.Service
|
||||
ImageX imagex.ImageX // we set Imagex here, because Imagex is used as a proxy to get auth token, there is no actual correlation with the workflow domain.
|
||||
TosClient storage.Storage
|
||||
IDGenerator idgen.IDGenerator
|
||||
@@ -169,6 +168,21 @@ func (w *ApplicationService) CreateWorkflow(ctx context.Context, req *workflow.C
|
||||
if err := checkUserSpace(ctx, uID, spaceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var createConversation bool
|
||||
if req.ProjectID != nil && req.IsSetFlowMode() && req.GetFlowMode() == workflow.WorkflowMode_ChatFlow && req.IsSetCreateConversation() && req.GetCreateConversation() {
|
||||
createConversation = true
|
||||
_, err := GetWorkflowDomainSVC().CreateDraftConversationTemplate(ctx, &vo.CreateConversationTemplateMeta{
|
||||
AppID: mustParseInt64(req.GetProjectID()),
|
||||
UserID: uID,
|
||||
SpaceID: spaceID,
|
||||
Name: req.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
wf := &vo.MetaCreate{
|
||||
CreatorID: uID,
|
||||
SpaceID: spaceID,
|
||||
@@ -180,6 +194,14 @@ func (w *ApplicationService) CreateWorkflow(ctx context.Context, req *workflow.C
|
||||
Mode: ternary.IFElse(req.IsSetFlowMode(), req.GetFlowMode(), workflow.WorkflowMode_Workflow),
|
||||
InitCanvasSchema: vo.GetDefaultInitCanvasJsonSchema(i18n.GetLocale(ctx)),
|
||||
}
|
||||
if req.IsSetFlowMode() && req.GetFlowMode() == workflow.WorkflowMode_ChatFlow {
|
||||
conversationName := req.Name
|
||||
if !req.IsSetProjectID() || mustParseInt64(req.GetProjectID()) == 0 || !createConversation {
|
||||
conversationName = "Default"
|
||||
}
|
||||
|
||||
wf.InitCanvasSchema = vo.GetDefaultInitCanvasJsonSchemaChat(i18n.GetLocale(ctx), conversationName)
|
||||
}
|
||||
|
||||
id, err := GetWorkflowDomainSVC().Create(ctx, wf)
|
||||
if err != nil {
|
||||
@@ -249,10 +271,12 @@ func (w *ApplicationService) UpdateWorkflowMeta(ctx context.Context, req *workfl
|
||||
}
|
||||
|
||||
workflowID := mustParseInt64(req.GetWorkflowID())
|
||||
err = GetWorkflowDomainSVC().UpdateMeta(ctx, workflowID, &vo.MetaUpdate{
|
||||
Name: req.Name,
|
||||
Desc: req.Desc,
|
||||
IconURI: req.IconURI,
|
||||
|
||||
err = GetWorkflowDomainSVC().UpdateMeta(ctx, mustParseInt64(req.GetWorkflowID()), &vo.MetaUpdate{
|
||||
Name: req.Name,
|
||||
Desc: req.Desc,
|
||||
IconURI: req.IconURI,
|
||||
WorkflowMode: req.FlowMode,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -2106,6 +2130,10 @@ func (w *ApplicationService) ListWorkflow(ctx context.Context, req *workflow.Get
|
||||
option.IDs = ids
|
||||
}
|
||||
|
||||
if req.IsSetFlowMode() && req.GetFlowMode() != workflow.WorkflowMode_All {
|
||||
option.Mode = ptr.Of(workflowModel.WorkflowMode(req.GetFlowMode()))
|
||||
}
|
||||
|
||||
spaceID, err := strconv.ParseInt(req.GetSpaceID(), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("space id is invalid, parse to int64 failed, err: %w", err)
|
||||
@@ -2157,6 +2185,13 @@ func (w *ApplicationService) ListWorkflow(ctx context.Context, req *workflow.Get
|
||||
},
|
||||
}
|
||||
|
||||
if len(req.Checker) > 0 && status == workflow.WorkFlowListStatus_HadPublished {
|
||||
ww.CheckResult, err = GetWorkflowDomainSVC().WorkflowSchemaCheck(ctx, w, req.Checker)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if qType == workflowModel.FromDraft {
|
||||
ww.UpdateTime = w.DraftMeta.Timestamp.Unix()
|
||||
} else if qType == workflowModel.FromLatestVersion || qType == workflowModel.FromSpecificVersion {
|
||||
@@ -3736,3 +3771,419 @@ func checkUserSpace(ctx context.Context, uid int64, spaceID int64) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *ApplicationService) populateChatFlowRoleFields(role *workflow.ChatFlowRole, targetRole interface{}) error {
|
||||
var avatarUri, audioStr, bgStr, obStr, srStr, uiStr string
|
||||
var err error
|
||||
|
||||
if role.Avatar != nil {
|
||||
avatarUri = role.Avatar.ImageUri
|
||||
|
||||
}
|
||||
if role.AudioConfig != nil {
|
||||
audioStr, err = sonic.MarshalString(*role.AudioConfig)
|
||||
if err != nil {
|
||||
return vo.WrapError(errno.ErrSerializationDeserializationFail, err)
|
||||
}
|
||||
}
|
||||
if role.BackgroundImageInfo != nil {
|
||||
bgStr, err = sonic.MarshalString(*role.BackgroundImageInfo)
|
||||
if err != nil {
|
||||
return vo.WrapError(errno.ErrSerializationDeserializationFail, err)
|
||||
}
|
||||
}
|
||||
if role.OnboardingInfo != nil {
|
||||
obStr, err = sonic.MarshalString(*role.OnboardingInfo)
|
||||
if err != nil {
|
||||
return vo.WrapError(errno.ErrSerializationDeserializationFail, err)
|
||||
}
|
||||
}
|
||||
if role.SuggestReplyInfo != nil {
|
||||
srStr, err = sonic.MarshalString(*role.SuggestReplyInfo)
|
||||
if err != nil {
|
||||
return vo.WrapError(errno.ErrSerializationDeserializationFail, err)
|
||||
}
|
||||
}
|
||||
if role.UserInputConfig != nil {
|
||||
uiStr, err = sonic.MarshalString(*role.UserInputConfig)
|
||||
if err != nil {
|
||||
return vo.WrapError(errno.ErrSerializationDeserializationFail, err)
|
||||
}
|
||||
}
|
||||
|
||||
switch r := targetRole.(type) {
|
||||
case *vo.ChatFlowRoleCreate:
|
||||
if role.Name != nil {
|
||||
r.Name = *role.Name
|
||||
}
|
||||
if role.Description != nil {
|
||||
r.Description = *role.Description
|
||||
}
|
||||
if avatarUri != "" {
|
||||
r.AvatarUri = avatarUri
|
||||
}
|
||||
if audioStr != "" {
|
||||
r.AudioConfig = audioStr
|
||||
}
|
||||
if bgStr != "" {
|
||||
r.BackgroundImageInfo = bgStr
|
||||
}
|
||||
if obStr != "" {
|
||||
r.OnboardingInfo = obStr
|
||||
}
|
||||
if srStr != "" {
|
||||
r.SuggestReplyInfo = srStr
|
||||
}
|
||||
if uiStr != "" {
|
||||
r.UserInputConfig = uiStr
|
||||
}
|
||||
case *vo.ChatFlowRoleUpdate:
|
||||
r.Name = role.Name
|
||||
r.Description = role.Description
|
||||
if avatarUri != "" {
|
||||
r.AvatarUri = ptr.Of(avatarUri)
|
||||
}
|
||||
if audioStr != "" {
|
||||
r.AudioConfig = ptr.Of(audioStr)
|
||||
}
|
||||
if bgStr != "" {
|
||||
r.BackgroundImageInfo = ptr.Of(bgStr)
|
||||
}
|
||||
if obStr != "" {
|
||||
r.OnboardingInfo = ptr.Of(obStr)
|
||||
}
|
||||
if srStr != "" {
|
||||
r.SuggestReplyInfo = ptr.Of(srStr)
|
||||
}
|
||||
if uiStr != "" {
|
||||
r.UserInputConfig = ptr.Of(uiStr)
|
||||
}
|
||||
default:
|
||||
return vo.WrapError(errno.ErrInvalidParameter, fmt.Errorf("invalid type for targetRole: %T", targetRole))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsChatFlow(wf *entity.Workflow) bool {
|
||||
if wf == nil || wf.ID == 0 {
|
||||
return false
|
||||
}
|
||||
return wf.Meta.Mode == workflow.WorkflowMode_ChatFlow
|
||||
}
|
||||
|
||||
func (w *ApplicationService) CreateChatFlowRole(ctx context.Context, req *workflow.CreateChatFlowRoleRequest) (
|
||||
_ *workflow.CreateChatFlowRoleResponse, err error) {
|
||||
defer func() {
|
||||
if panicErr := recover(); panicErr != nil {
|
||||
err = safego.NewPanicErr(panicErr, debug.Stack())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = vo.WrapIfNeeded(errno.ErrChatFlowRoleOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
}()
|
||||
|
||||
uID := ctxutil.MustGetUIDFromCtx(ctx)
|
||||
wf, err := GetWorkflowDomainSVC().Get(ctx, &vo.GetPolicy{
|
||||
ID: mustParseInt64(req.GetChatFlowRole().GetWorkflowID()),
|
||||
MetaOnly: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = checkUserSpace(ctx, uID, wf.Meta.SpaceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
role := req.GetChatFlowRole()
|
||||
|
||||
if !IsChatFlow(wf) {
|
||||
logs.CtxWarnf(ctx, "CreateChatFlowRole not chat flow, workflowID: %d", wf.ID)
|
||||
return nil, vo.WrapError(errno.ErrChatFlowRoleOperationFail, fmt.Errorf("workflow %d is not a chat flow", wf.ID))
|
||||
}
|
||||
|
||||
oldRole, err := GetWorkflowDomainSVC().GetChatFlowRole(ctx, mustParseInt64(role.WorkflowID), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var roleID int64
|
||||
if oldRole != nil {
|
||||
role.ID = strconv.FormatInt(oldRole.ID, 10)
|
||||
roleID = oldRole.ID
|
||||
}
|
||||
|
||||
if role.GetID() == "" || role.GetID() == "0" {
|
||||
chatFlowRole := &vo.ChatFlowRoleCreate{
|
||||
WorkflowID: mustParseInt64(role.WorkflowID),
|
||||
CreatorID: uID,
|
||||
}
|
||||
if err = w.populateChatFlowRoleFields(role, chatFlowRole); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
roleID, err = GetWorkflowDomainSVC().CreateChatFlowRole(ctx, chatFlowRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
chatFlowRole := &vo.ChatFlowRoleUpdate{
|
||||
WorkflowID: mustParseInt64(role.WorkflowID),
|
||||
}
|
||||
|
||||
if err = w.populateChatFlowRoleFields(role, chatFlowRole); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = GetWorkflowDomainSVC().UpdateChatFlowRole(ctx, chatFlowRole.WorkflowID, chatFlowRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &workflow.CreateChatFlowRoleResponse{
|
||||
ID: strconv.FormatInt(roleID, 10),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *ApplicationService) DeleteChatFlowRole(ctx context.Context, req *workflow.DeleteChatFlowRoleRequest) (
|
||||
_ *workflow.DeleteChatFlowRoleResponse, err error) {
|
||||
defer func() {
|
||||
if panicErr := recover(); panicErr != nil {
|
||||
err = safego.NewPanicErr(panicErr, debug.Stack())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = vo.WrapIfNeeded(errno.ErrChatFlowRoleOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
}()
|
||||
|
||||
uID := ctxutil.MustGetUIDFromCtx(ctx)
|
||||
wf, err := GetWorkflowDomainSVC().Get(ctx, &vo.GetPolicy{
|
||||
ID: mustParseInt64(req.GetWorkflowID()),
|
||||
MetaOnly: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = checkUserSpace(ctx, uID, wf.Meta.SpaceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = GetWorkflowDomainSVC().DeleteChatFlowRole(ctx, mustParseInt64(req.ID), mustParseInt64(req.WorkflowID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &workflow.DeleteChatFlowRoleResponse{}, nil
|
||||
}
|
||||
|
||||
func (w *ApplicationService) GetChatFlowRole(ctx context.Context, req *workflow.GetChatFlowRoleRequest) (
|
||||
_ *workflow.GetChatFlowRoleResponse, err error) {
|
||||
defer func() {
|
||||
if panicErr := recover(); panicErr != nil {
|
||||
err = safego.NewPanicErr(panicErr, debug.Stack())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = vo.WrapIfNeeded(errno.ErrChatFlowRoleOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
}()
|
||||
|
||||
uID := ctxutil.MustGetUIDFromCtx(ctx)
|
||||
wf, err := GetWorkflowDomainSVC().Get(ctx, &vo.GetPolicy{
|
||||
ID: mustParseInt64(req.GetWorkflowID()),
|
||||
MetaOnly: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = checkUserSpace(ctx, uID, wf.Meta.SpaceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !IsChatFlow(wf) {
|
||||
logs.CtxWarnf(ctx, "GetChatFlowRole not chat flow, workflowID: %d", wf.ID)
|
||||
return nil, vo.WrapError(errno.ErrChatFlowRoleOperationFail, fmt.Errorf("workflow %d is not a chat flow", wf.ID))
|
||||
}
|
||||
|
||||
var version string
|
||||
if wf.Meta.AppID != nil {
|
||||
if vl, err := GetWorkflowDomainSVC().GetWorkflowVersionsByConnector(ctx, mustParseInt64(req.GetConnectorID()), wf.ID, 1); err != nil {
|
||||
return nil, err
|
||||
} else if len(vl) > 0 {
|
||||
version = vl[0]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
role, err := GetWorkflowDomainSVC().GetChatFlowRole(ctx, mustParseInt64(req.WorkflowID), version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if role == nil {
|
||||
logs.CtxWarnf(ctx, "GetChatFlowRole role nil, workflowID: %d", wf.ID)
|
||||
// Return nil for the error to align with the production behavior,
|
||||
// where the GET API may be called before the CREATE API during chatflow creation.
|
||||
return &workflow.GetChatFlowRoleResponse{}, nil
|
||||
}
|
||||
|
||||
wfRole, err := w.convertChatFlowRole(ctx, role)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get chat flow role config, internal data processing error: %+v", err)
|
||||
}
|
||||
|
||||
return &workflow.GetChatFlowRoleResponse{
|
||||
Role: wfRole,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *ApplicationService) convertChatFlowRole(ctx context.Context, role *entity.ChatFlowRole) (*workflow.ChatFlowRole, error) {
|
||||
var err error
|
||||
res := &workflow.ChatFlowRole{
|
||||
ID: strconv.FormatInt(role.ID, 10),
|
||||
WorkflowID: strconv.FormatInt(role.WorkflowID, 10),
|
||||
Name: ptr.Of(role.Name),
|
||||
Description: ptr.Of(role.Description),
|
||||
}
|
||||
|
||||
if role.AvatarUri != "" {
|
||||
url, err := w.ImageX.GetResourceURL(ctx, role.AvatarUri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.Avatar = &workflow.AvatarConfig{
|
||||
ImageUri: role.AvatarUri,
|
||||
ImageUrl: url.URL,
|
||||
}
|
||||
}
|
||||
|
||||
if role.AudioConfig != "" {
|
||||
err = sonic.UnmarshalString(role.AudioConfig, &res.AudioConfig)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "GetChatFlowRole AudioConfig UnmarshalString err: %+v", err)
|
||||
return nil, vo.WrapError(errno.ErrSerializationDeserializationFail, err)
|
||||
}
|
||||
}
|
||||
|
||||
if role.OnboardingInfo != "" {
|
||||
err = sonic.UnmarshalString(role.OnboardingInfo, &res.OnboardingInfo)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "GetChatFlowRole OnboardingInfo UnmarshalString err: %+v", err)
|
||||
return nil, vo.WrapError(errno.ErrSerializationDeserializationFail, err)
|
||||
}
|
||||
}
|
||||
|
||||
if role.SuggestReplyInfo != "" {
|
||||
err = sonic.UnmarshalString(role.SuggestReplyInfo, &res.SuggestReplyInfo)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "GetChatFlowRole SuggestReplyInfo UnmarshalString err: %+v", err)
|
||||
return nil, vo.WrapError(errno.ErrSerializationDeserializationFail, err)
|
||||
}
|
||||
}
|
||||
|
||||
if role.UserInputConfig != "" {
|
||||
err = sonic.UnmarshalString(role.UserInputConfig, &res.UserInputConfig)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "GetChatFlowRole UserInputConfig UnmarshalString err: %+v", err)
|
||||
return nil, vo.WrapError(errno.ErrSerializationDeserializationFail, err)
|
||||
}
|
||||
}
|
||||
|
||||
if role.BackgroundImageInfo != "" {
|
||||
res.BackgroundImageInfo = &workflow.BackgroundImageInfo{}
|
||||
err = sonic.UnmarshalString(role.BackgroundImageInfo, res.BackgroundImageInfo)
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "GetChatFlowRole BackgroundImageInfo UnmarshalString err: %+v", err)
|
||||
return nil, vo.WrapError(errno.ErrSerializationDeserializationFail, err)
|
||||
}
|
||||
if res.BackgroundImageInfo != nil {
|
||||
if res.BackgroundImageInfo.WebBackgroundImage != nil && res.BackgroundImageInfo.WebBackgroundImage.OriginImageUri != nil {
|
||||
url, err := w.ImageX.GetResourceURL(ctx, res.BackgroundImageInfo.WebBackgroundImage.GetOriginImageUri())
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "get url by uri err, err:%s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
res.BackgroundImageInfo.WebBackgroundImage.ImageUrl = &url.URL
|
||||
}
|
||||
|
||||
if res.BackgroundImageInfo.MobileBackgroundImage != nil && res.BackgroundImageInfo.MobileBackgroundImage.OriginImageUri != nil {
|
||||
url, err := w.ImageX.GetResourceURL(ctx, res.BackgroundImageInfo.MobileBackgroundImage.GetOriginImageUri())
|
||||
if err != nil {
|
||||
logs.CtxErrorf(ctx, "get url by uri err, err:%s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
res.BackgroundImageInfo.MobileBackgroundImage.ImageUrl = &url.URL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (w *ApplicationService) OpenAPIGetWorkflowInfo(ctx context.Context, req *workflow.OpenAPIGetWorkflowInfoRequest) (
|
||||
_ *workflow.OpenAPIGetWorkflowInfoResponse, err error) {
|
||||
defer func() {
|
||||
if panicErr := recover(); panicErr != nil {
|
||||
err = safego.NewPanicErr(panicErr, debug.Stack())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = vo.WrapIfNeeded(errno.ErrChatFlowRoleOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error()))
|
||||
}
|
||||
}()
|
||||
|
||||
uID := ctxutil.GetApiAuthFromCtx(ctx).UserID
|
||||
wf, err := GetWorkflowDomainSVC().Get(ctx, &vo.GetPolicy{
|
||||
ID: mustParseInt64(req.GetWorkflowID()),
|
||||
MetaOnly: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = checkUserSpace(ctx, uID, wf.Meta.SpaceID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !IsChatFlow(wf) {
|
||||
logs.CtxWarnf(ctx, "GetChatFlowRole not chat flow, workflowID: %d", wf.ID)
|
||||
return nil, vo.WrapError(errno.ErrChatFlowRoleOperationFail, fmt.Errorf("workflow %d is not a chat flow", wf.ID))
|
||||
}
|
||||
|
||||
var version string
|
||||
if wf.Meta.AppID != nil {
|
||||
if vl, err := GetWorkflowDomainSVC().GetWorkflowVersionsByConnector(ctx, mustParseInt64(req.GetConnectorID()), wf.ID, 1); err != nil {
|
||||
return nil, err
|
||||
} else if len(vl) > 0 {
|
||||
version = vl[0]
|
||||
}
|
||||
}
|
||||
|
||||
role, err := GetWorkflowDomainSVC().GetChatFlowRole(ctx, mustParseInt64(req.WorkflowID), version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if role == nil {
|
||||
logs.CtxWarnf(ctx, "GetChatFlowRole role nil, workflowID: %d", wf.ID)
|
||||
// Return nil for the error to align with the production behavior,
|
||||
// where the GET API may be called before the CREATE API during chatflow creation.
|
||||
return &workflow.OpenAPIGetWorkflowInfoResponse{}, nil
|
||||
}
|
||||
|
||||
wfRole, err := w.convertChatFlowRole(ctx, role)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get chat flow role config, internal data processing error: %+v", err)
|
||||
}
|
||||
|
||||
return &workflow.OpenAPIGetWorkflowInfoResponse{
|
||||
WorkflowInfo: &workflow.WorkflowInfo{
|
||||
Role: wfRole,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user