From d70101c97984f304d3928cf85532f06c60f0483a Mon Sep 17 00:00:00 2001 From: Zhj Date: Thu, 28 Aug 2025 21:53:32 +0800 Subject: [PATCH] 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 Co-authored-by: lijunwen.gigoo Co-authored-by: lvxinyu.1117 Co-authored-by: liuyunchao.0510 Co-authored-by: haozhenfei <37089575+haozhenfei@users.noreply.github.com> Co-authored-by: July Co-authored-by: tecvan-fe --- backend/Dockerfile | 2 + backend/api/handler/coze/agent_run_service.go | 21 + .../api/handler/coze/bot_open_api_service.go | 46 +- .../api/handler/coze/conversation_service.go | 39 + .../api/handler/coze/intelligence_service.go | 20 + backend/api/handler/coze/message_service.go | 2 +- backend/api/handler/coze/upload_service.go | 20 +- backend/api/handler/coze/workflow_service.go | 156 +- .../api/handler/coze/workflow_service_test.go | 13 +- backend/api/middleware/openapi_auth.go | 28 +- .../model/app/bot_open_api/bot_open_api.go | 2620 ++++++++++++++++- .../app/intelligence/common/common_struct.go | 446 +++ .../model/app/intelligence/intelligence.go | 353 +++ .../model/app/intelligence/project/project.go | 968 ++++++ .../conversation/agentrun/agentrun_service.go | 369 +++ .../api/model/conversation/common/common.go | 22 + .../conversation/conversation/conversation.go | 1062 +++++++ .../conversation/conversation_service.go | 721 +++++ .../api/model/conversation/message/message.go | 115 +- backend/api/model/conversation/run/run.go | 520 ++++ backend/api/model/coze/api.go | 16 - .../crossdomain/conversation/conversation.go | 1 + .../model/crossdomain/modelmgr/modelmgr.go | 1 + .../crossdomain/singleagent/single_agent.go | 12 +- .../model/crossdomain/workflow/workflow.go | 48 +- backend/api/model/playground/playground.go | 340 +-- backend/api/model/workflow/workflow.go | 1687 ++++++++++- backend/api/model/workflow/workflow_svc.go | 369 +++ backend/api/router/coze/api.go | 41 +- backend/api/router/coze/middleware.go | 65 + backend/application/app/app.go | 107 + backend/application/application.go | 9 +- .../application/conversation/conversation.go | 70 + backend/application/conversation/init.go | 5 + .../conversation/openapi_agent_run.go | 78 +- .../conversation/openapi_message.go | 93 +- backend/application/openauth/openapiauth.go | 28 + backend/application/search/resource_pack.go | 50 +- .../application/singleagent/single_agent.go | 45 +- backend/application/upload/icon.go | 40 +- backend/application/workflow/chatflow.go | 1630 ++++++++++ backend/application/workflow/init.go | 10 +- backend/application/workflow/workflow.go | 463 ++- .../contract/agent/single_agent.go | 20 +- .../contract/agentrun/agent_run.go | 4 + .../contract/conversation/conversation.go | 9 + .../conversationmock/conversation_mock.go | 120 + .../crossdomain/contract/message/message.go | 62 + .../message/messagemock/message_mock.go | 224 ++ backend/crossdomain/contract/upload/upload.go | 37 + .../crossdomain/contract/workflow/workflow.go | 48 +- .../crossdomain/impl/agentrun/agent_run.go | 9 + .../impl/conversation/conversation.go | 15 + backend/crossdomain/impl/message/message.go | 212 ++ .../crossdomain/impl/message/message_test.go | 362 +++ .../impl/singleagent/single_agent.go | 163 +- backend/crossdomain/impl/upload/upload.go | 42 + backend/crossdomain/impl/workflow/workflow.go | 12 +- .../dal/model/single_agent_draft.gen.go | 2 + .../dal/model/single_agent_version.gen.go | 2 + .../dal/query/single_agent_draft.gen.go | 26 +- .../dal/query/single_agent_version.gen.go | 26 +- .../internal/dal/single_agent_draft.go | 8 +- .../singleagent/repository/repository.go | 1 - backend/domain/app/entity/connector.go | 1 + backend/domain/app/service/publish_app.go | 7 +- backend/domain/app/service/service_impl.go | 8 + .../connector/service/connector_impl.go | 4 +- .../agentrun/entity/run_record.go | 16 + .../agentrun/internal/agent_info.go | 45 + .../agentrun/internal/chatflow_run.go | 215 ++ .../conversation/agentrun/internal/dal/dao.go | 71 +- .../conversation/agentrun/internal/event.go | 78 - .../agentrun/internal/message_builder.go | 512 ++++ .../agentrun/internal/message_event.go | 428 +++ .../conversation/agentrun/internal/run.go | 214 ++ .../{run_process.go => run_process_event.go} | 5 +- .../agentrun/internal/singleagent_run.go | 450 +++ .../agentrun/repository/repository.go | 6 +- .../agentrun/service/agent_run.go | 5 +- .../agentrun/service/agent_run_impl.go | 1155 +------- .../agentrun/service/agent_run_test.go | 166 ++ .../conversation/entity/conversation.go | 6 + .../conversation/internal/dal/dao.go | 21 +- .../internal/dal/model/conversation.gen.go | 17 + .../internal/dal/query/conversation.gen.go | 22 +- .../conversation/repository/repository.go | 1 + .../conversation/service/conversation.go | 1 + .../conversation/service/conversation_impl.go | 5 + .../conversation/message/entity/message.go | 26 +- .../message/internal/dal/message.go | 91 +- .../message/repository/repository.go | 5 +- .../conversation/message/service/message.go | 1 + .../message/service/message_impl.go | 31 +- .../message/service/message_test.go | 326 +- .../openauth/openapiauth/entity/api_auth.go | 1 + .../openauth/openapiauth/entity/consts.go | 24 + .../openapiauth/internal/dal/api_key.go | 3 +- .../internal/dal/model/api_key.gen.go | 17 + .../internal/dal/query/api_key.gen.go | 22 +- backend/domain/upload/entity/file.go | 46 + .../domain/upload/internal/dal/dao/files.go | 113 + .../upload/internal/dal/model/files.gen.go | 49 + .../upload/internal/dal/query/files.gen.go | 445 +++ .../domain/upload/internal/dal/query/gen.go | 119 + .../domain/upload/repository/repository.go | 39 + backend/domain/upload/service/interface.go | 60 + backend/domain/upload/service/service.go | 97 + .../domain/workflow/component_interface.go | 57 + .../domain/workflow/config/workflow_config.go | 2 +- .../domain/workflow/entity/chatflow_role.go | 37 + .../domain/workflow/entity/conversation.go | 39 + .../domain/workflow/entity/interrupt_event.go | 6 + backend/domain/workflow/entity/node_meta.go | 137 +- backend/domain/workflow/entity/vo/canvas.go | 137 +- .../workflow/entity/vo/chat_flow_role.go | 48 + backend/domain/workflow/entity/vo/chatflow.go | 84 + .../domain/workflow/entity/vo/conversation.go | 74 + backend/domain/workflow/entity/vo/meta.go | 2 + .../workflow/entity/vo/workflow_publish.go | 1 + backend/domain/workflow/interface.go | 23 +- .../internal/canvas/adaptor/to_schema.go | 31 + .../examples/chatflow/new_chatflow.json | 93 + .../canvas/examples/chatflow/start_exit.json | 89 + .../conversation_list.json | 193 ++ .../create_conversation.json | 137 + .../delete_conversation.json | 129 + .../update_conversation.json | 191 ++ .../update_dynamic_conversation.json | 262 ++ .../examples/message/create_message.json | 234 ++ .../canvas/examples/message/message_list.json | 310 ++ .../canvas/validate/canvas_validate.go | 7 +- .../domain/workflow/internal/compose/state.go | 6 + .../internal/compose/test/llm_test.go | 10 + .../compose/test/question_answer_test.go | 17 +- .../workflow/internal/execute/event_handle.go | 2 +- .../conversation/clearconversationhistory.go | 141 + .../nodes/conversation/conversationhistory.go | 190 ++ .../nodes/conversation/conversationlist.go | 153 + .../nodes/conversation/createconversation.go | 142 + .../nodes/conversation/createmessage.go | 299 ++ .../nodes/conversation/deleteconversation.go | 122 + .../nodes/conversation/deletemessage.go | 162 + .../nodes/conversation/editmessage.go | 181 ++ .../nodes/conversation/messagelist.go | 207 ++ .../nodes/conversation/updateconversation.go | 149 + .../nodes/intentdetector/intent_detector.go | 102 +- .../internal/nodes/intentdetector/prompt.go | 85 + .../nodes/knowledge/knowledge_retrieve.go | 118 +- .../domain/workflow/internal/nodes/llm/llm.go | 139 +- .../workflow/internal/nodes/llm/prompt.go | 256 +- .../domain/workflow/internal/nodes/utils.go | 32 +- .../internal/repo/conversation_repository.go | 940 ++++++ .../app_conversation_template_draft.gen.go | 29 + .../app_conversation_template_online.gen.go | 24 + .../app_dynamic_conversation_draft.gen.go | 28 + .../app_dynamic_conversation_online.gen.go | 28 + .../app_static_conversation_draft.gen.go | 27 + .../app_static_conversation_online.gen.go | 22 + .../dal/model/chat_flow_role_config.gen.go | 36 + .../app_conversation_template_draft.gen.go | 412 +++ .../app_conversation_template_online.gen.go | 408 +++ .../app_dynamic_conversation_draft.gen.go | 408 +++ .../app_dynamic_conversation_online.gen.go | 408 +++ .../app_static_conversation_draft.gen.go | 404 +++ .../app_static_conversation_online.gen.go | 400 +++ .../dal/query/chat_flow_role_config.gen.go | 440 +++ .../workflow/internal/repo/dal/query/gen.go | 176 +- .../internal/repo/interrupt_event_store.go | 33 + .../workflow/internal/repo/repository.go | 195 +- .../domain/workflow/internal/repo/suggest.go | 104 + .../workflow/service/conversation_impl.go | 490 +++ .../workflow/service/executable_impl.go | 129 + .../domain/workflow/service/service_impl.go | 455 ++- backend/domain/workflow/service/utils.go | 233 +- .../mock/domain/workflow/interface.go | 810 ++++- backend/pkg/lang/maps/maps.go | 12 + backend/types/ddl/gen_orm_query.go | 16 + backend/types/errno/conversation.go | 22 + backend/types/errno/workflow.go | 103 +- .../subspaces/default/common-versions.json | 4 +- .../config/subspaces/default/pnpm-config.json | 2 +- .../config/subspaces/default/pnpm-lock.yaml | 1544 ++++++---- .../config/subspaces/default/repo-state.json | 2 +- .../migrations/20250718104121_update.sql | 100 + .../migrations/20250812093734_update.sql | 2 + .../migrations/20250813081543_update.sql | 7 + .../migrations/20250822060516_update.sql | 2 + docker/atlas/migrations/atlas.sum | 10 +- docker/atlas/opencoze_latest_schema.hcl | 553 ++++ docker/docker-compose-debug.yml | 49 +- docker/docker-compose.yml | 47 +- docker/volumes/mysql/schema.sql | 962 +++++- frontend/apps/coze-studio/rsbuild.config.ts | 6 + frontend/config/rsbuild-config/src/index.ts | 2 +- frontend/config/tailwind-config/package.json | 1 + frontend/config/tailwind-config/src/util.js | 282 ++ .../layout/src/components/header/index.tsx | 6 +- .../hook/use-create-bot/form-switch/index.tsx | 4 +- .../arch/bot-env-adapter/package.json | 4 + .../studio-i18n-resource/src/locales/en.json | 26 +- .../src/locales/zh-CN.json | 26 +- .../action-bar-hover-container/index.tsx | 10 +- .../components/copy-text-message/index.tsx | 20 +- .../chat-answer-action/tsconfig.dev.json | 13 + .../chat-answer-action/tsconfig.json | 3 + .../chat-area/src/chat-area-main/index.tsx | 4 + .../src/components/chat-input/index.tsx | 16 +- .../message-group/wrapper/index.tsx | 14 + .../chat-area-context/chat-area-callback.ts | 6 +- .../chat-area-context/default-props.ts | 1 + .../src/context/chat-area-context/type.ts | 1 + .../src/context/chat-input-props/context.tsx | 3 +- .../copywriting/copywriting-context.tsx | 1 + .../src/context/copywriting/types.ts | 1 + .../context/preference/preference-context.tsx | 4 +- .../chat-area/src/context/preference/types.ts | 9 + .../upload-controller-context/provider.tsx | 7 +- .../common/chat-area/chat-area/src/index.tsx | 2 + .../plugin/types/plugin-component/index.ts | 3 + .../src/service/upload-controller.ts | 4 +- .../chat-area/chat-core/src/chat-sdk/index.ts | 8 +- .../chat-core/src/request-manager/index.ts | 8 +- .../chat-core/src/request-manager/types.ts | 2 +- .../src/types/chat-input/index.ts | 7 +- .../src/components/chat/chat-upload/index.tsx | 11 +- .../components/common/message-box/index.tsx | 6 +- .../common/message-box/message-box-wrap.tsx | 26 +- .../src/components/common/message-box/type.ts | 7 +- .../contents/text-content/index.tsx | 2 - .../chat-flow-render/components/index.tsx | 60 + .../components/input-node-render.tsx | 102 + .../components/node-wrapper-ui.tsx | 23 + .../components/question-node-render.tsx | 67 + .../chat-flow-render/components/type.ts | 54 + .../chat-flow-render/components/utils.ts | 40 + .../src/components/chat-flow-render/index.tsx | 87 + .../chat-workflow-render/src/index.ts | 1 + .../plugin-chat-background/src/index.ts | 1 + .../src/hooks/shortcut.ts | 5 +- .../src/shortcut-bar/index.tsx | 6 +- .../src/utils/get-ui-mode-by-biz-scene.ts | 4 +- .../common/md-editor-adapter/src/editor.tsx | 9 + .../src/conversation/chat-history/index.tsx | 8 +- .../src/conversation/constants/index.ts | 3 - .../hooks/use-delete-chat/index.tsx | 6 +- .../conversation/static-chat-list/index.tsx | 6 +- .../src/hooks/use-workflow-resource.tsx | 40 +- .../packages/project-ide/main/package.json | 2 +- .../src/components/configuration/index.tsx | 29 +- .../packages/project-ide/main/src/index.tsx | 3 +- .../open-platform/chat-app-sdk/.env.default | 6 + .../open-platform/chat-app-sdk/.env.local | 6 + .../open-platform/chat-app-sdk/.gitignore | 4 + .../chat-app-sdk/.stylelintrc.js | 24 + .../studio/open-platform/chat-app-sdk/OWNERS | 9 + .../open-platform/chat-app-sdk/README.md | 3 + .../chat-app-sdk/config/rush-project.json | 16 + .../chat-app-sdk/config/rushx-config.json | 6 + .../chat-app-sdk/eslint.config.js | 32 + .../open-platform/chat-app-sdk/package.json | 126 + .../chat-app-sdk/rspack-config/app.ts | 36 + .../chat-app-sdk/rspack-config/base.ts | 67 + .../rspack-config/build.config.ts | 166 ++ .../chat-app-sdk/rspack-config/dev.config.ts | 199 ++ .../chat-app-sdk/rspack-config/dev.ts | 21 + .../chat-app-sdk/rspack-config/env.ts | 90 + .../chat-app-sdk/rspack-config/export.ts | 27 + .../chat-app-sdk/rspack-config/rules.ts | 129 + .../semi-css-var-postcss-plugin.ts | 90 + .../chat-app-sdk/src/assets/widget.png | Bin 0 -> 25830 bytes .../src/client/__tests__/index.test.tsx | 148 + .../chat-app-sdk/src/client/auth.ts | 74 + .../chat-app-sdk/src/client/index.tsx | 142 + .../chat-app-sdk/src/client/main.less | 29 + .../src/components/icons/close.tsx | 44 + .../src/components/icons/index.module.less | 38 + .../src/components/icons/spin.tsx | 72 + .../src/components/widget/ast-btn.tsx | 58 + .../src/components/widget/chat-content.tsx | 113 + .../src/components/widget/chat-non-iframe.tsx | 77 + .../src/components/widget/image-preview.tsx | 85 + .../src/components/widget/index.module.less | 111 + .../src/components/widget/index.tsx | 56 + .../widget/non-iframe-app/index.module.less | 4 + .../widget/non-iframe-app/index.tsx | 93 + .../widget/non-iframe-bot/index.module.less | 8 + .../widget/non-iframe-bot/index.tsx | 68 + .../chat-app-sdk/src/dev-app/App.tsx | 43 + .../chat-app-sdk/src/dev-app/index.tsx | 28 + .../chat-app-sdk/src/dev-app/main.less | 11 + .../src/dev-app/page/AppWidget.tsx | 49 + .../chat-app-sdk/src/dev-app/page/Chat.tsx | 48 + .../chat-app-sdk/src/dev-app/page/Client.tsx | 119 + .../chat-app-sdk/src/dev-app/routes/index.tsx | 45 + .../chat-app-sdk/src/export-ui/index.ts | 17 + .../src/hooks/use-image-preview.ts | 64 + .../src/hooks/use-message-interact.ts | 120 + .../open-platform/chat-app-sdk/src/index.ts | 20 + .../src/store/__tests__/setter.test.ts | 107 + .../chat-app-sdk/src/store/context.tsx | 47 + .../chat-app-sdk/src/store/global.ts | 125 + .../chat-app-sdk/src/store/index.ts | 17 + .../chat-app-sdk/src/test/setup.ts | 32 + .../chat-app-sdk/src/types/chat.ts | 29 + .../chat-app-sdk/src/types/client.ts | 52 + .../chat-app-sdk/src/types/post.ts | 34 + .../chat-app-sdk/src/typings.d.ts | 25 + .../chat-app-sdk/src/util/get-chat-config.ts | 60 + .../chat-app-sdk/src/util/style.ts | 28 + .../chat-app-sdk/tailwind.config.js | 36 + .../chat-app-sdk/tsconfig.build.json | 62 + .../open-platform/chat-app-sdk/tsconfig.json | 21 + .../chat-app-sdk/vitest.config.ts | 25 + .../open-platform/open-chat/package.json | 59 +- .../open-chat/src/assets/add-new-chat.svg | 3 + .../open-chat/src/assets/chatflow-logo.png | Bin 0 -> 3215 bytes .../open-chat/src/assets/coze-logo.png | Bin 0 -> 4645 bytes .../open-chat/src/assets/error-default.png | Bin 0 -> 4296 bytes .../open-chat/src/assets/error-unbind.png | Bin 0 -> 4690 bytes .../open-chat/src/assets/widget.png | Bin 0 -> 25830 bytes .../components/audit-panel/index.module.less | 23 + .../components/audit-panel/index.tsx | 57 + .../components/background/index.module.less | 53 + .../components/background/index.tsx | 49 + .../context/builder-chat-context.tsx | 74 + .../src/chat/builder-chat/coze-chat.tsx | 341 +++ .../src/chat/builder-chat/data-type.ts | 116 + .../builder-chat/helper/get-connector-id.ts | 32 + .../builder-chat/hooks/use-bot-user-update.ts | 46 + .../builder-chat/hooks/use-core-manager.ts | 131 + .../chat/builder-chat/hooks/use-init-chat.ts | 159 + .../hooks/use-on-boarding-update.ts | 33 + .../builder-chat/hooks/use-request-init.ts | 56 + .../src/chat/builder-chat/index.module.less | 97 + .../open-chat/src/chat/builder-chat/index.tsx | 26 + .../plugins/event-callback/index.ts | 40 + .../plugins/event-callback/plugin.ts | 47 + .../event-callback/services/life-cycle/app.ts | 23 + .../services/life-cycle/command.ts | 23 + .../services/life-cycle/index.ts | 32 + .../services/life-cycle/message.ts | 67 + .../services/life-cycle/render.ts | 23 + .../event-callback/types/biz-context.ts | 20 + .../plugins/event-callback/typings.d.ts | 17 + .../services/create-conversation.ts | 99 + .../builder-chat/services/get-bot-info.ts | 188 ++ .../open-chat/src/chat/builder-chat/type.ts | 138 + .../src/chat/web-sdk/index.module.less | 19 + .../open-chat/src/chat/web-sdk/index.tsx | 152 + .../src/components/builder-chat/index.tsx | 170 -- .../conversation-item/index.ts | 18 + .../mobile/index.module.less | 66 + .../conversation-item/mobile/index.tsx | 165 ++ .../mobile/operate/index.tsx | 67 + .../conversation-item/pc/index.module.less | 39 + .../conversation-item/pc/index.tsx | 113 + .../conversation-item/pc/operate/index.tsx | 68 + .../conversation-list/index.module.less | 40 + .../conversation-list/index.tsx | 296 ++ .../conversation-list-sider/index.module.less | 17 + .../conversation-list-sider/index.tsx | 256 ++ .../src/components/error-boundary/index.tsx | 42 + .../error-fallback/index.module.less | 32 + .../src/components/error-fallback/index.tsx | 99 + .../src/components/footer/index.module.less | 34 + .../open-chat/src/components/footer/index.tsx | 106 + .../src/components/header/index.module.less | 5 + .../open-chat/src/components/header/index.tsx | 77 + .../header/mobile/index.module.less | 43 + .../src/components/header/mobile/index.tsx | 101 + .../components/header/pc/index.module.less | 44 + .../src/components/header/pc/index.tsx | 131 + .../base.ts => components/header/type.ts} | 24 +- .../components/icon/add-new-conversation.tsx | 46 + .../src/components/loading/index.module.less | 8 + .../src/components/loading/index.tsx | 25 + .../studio-open-chat/area/index.module.less | 27 + .../studio-open-chat/area/index.tsx | 130 + .../audio-unfocus-text/index.module.less | 3 + .../components/audio-unfocus-text/index.tsx | 24 + .../chat-input-let-slot/index.module.less | 37 + .../components/chat-input-let-slot/index.tsx | 125 + .../components/shortcut-bar/index.tsx | 108 + .../hooks/__test__/user-info.test.tsx | 61 + .../studio-open-chat/hooks/index.ts | 18 + .../hooks/use-chat-op-info.tsx | 124 + .../hooks/use-conversation-list.ts | 196 ++ .../studio-open-chat/hooks/use-error.ts | 54 + .../studio-open-chat/hooks/use-get-theme.ts | 22 + .../hooks/use-is-show-background.ts | 29 + .../hooks/use-pagination-request.ts | 148 + ...use-update-conversation-name-by-message.ts | 52 + .../studio-open-chat/hooks/use-user-info.ts | 46 + .../src/components/studio-open-chat/index.ts | 18 + .../studio-open-chat/plugin/index.ts | 38 + .../studio-open-chat/plugin/plugin.ts | 47 + .../plugin/services/life-cycle/app.ts | 27 + .../plugin/services/life-cycle/command.ts | 30 + .../plugin/services/life-cycle/index.ts | 32 + .../plugin/services/life-cycle/message.ts | 52 + .../plugin/services/life-cycle/render.ts | 23 + .../plugin/types/biz-context.ts | 22 + .../studio-open-chat/plugin/typings.d.ts | 17 + .../provider/coz-sdk/api-adapter/index.ts | 32 + .../coz-sdk/api-adapter/message/index.ts | 19 + .../message/message-convert-to-coze.ts | 197 ++ .../message/message-convert-to-sdk.ts | 269 ++ .../api-adapter/message/message-parser.ts | 251 ++ .../coz-sdk/api-adapter/use-break-message.ts | 40 + .../coz-sdk/api-adapter/use-clear-history.ts | 73 + .../api-adapter/use-clear-message-context.ts | 54 + .../coz-sdk/api-adapter/use-common-hooks.ts | 93 + .../coz-sdk/api-adapter/use-message-list.ts | 128 + .../coz-sdk/api-adapter/use-send-message.ts | 87 + .../provider/coz-sdk/chat-provider.tsx | 287 ++ .../provider/coz-sdk/context.tsx | 67 + .../coz-sdk/helper/convert-shortcut-data.ts | 150 + .../coz-sdk/helper/coze-api-custom.ts | 102 + .../coz-sdk/hooks/use-bg-background-plugin.ts | 51 + .../components/delete-message/index.tsx | 85 + .../message-box-footer/index.tsx | 96 + .../use-message-footer-info.ts | 53 + .../message-box-hover-slot/index.tsx | 129 + .../use-message-hover-info.ts | 56 + .../message-box/index.module.less | 5 + .../custom-components/message-box/index.tsx | 37 + .../message-group-footer/index.tsx | 30 + .../use-message-group-footer-info.ts | 49 + .../plugin/hooks/use-cur-message-info.ts | 36 + .../plugin/hooks/use-delete-message.ts | 77 + .../plugin/hooks/use-submit-feedback-api.ts | 60 + .../provider/coz-sdk/plugin/index.ts | 37 + .../provider/coz-sdk/plugin/plugin.ts | 57 + .../coz-sdk/plugin/services/life-cycle/app.ts | 23 + .../plugin/services/life-cycle/command.ts | 23 + .../plugin/services/life-cycle/index.ts | 32 + .../plugin/services/life-cycle/message.ts | 42 + .../plugin/services/life-cycle/render.ts | 23 + .../coz-sdk/plugin/types/biz-context.ts | 20 + .../provider/coz-sdk/plugin/typings.d.ts | 17 + .../studio-open-chat/provider/coz-sdk/type.ts | 22 + .../provider/coz-sdk/use-api-client.ts | 98 + .../provider/coz-sdk/use-core-manager.ts | 95 + .../coz-sdk/use-core-override-config.ts | 45 + .../provider/coz-sdk/use-request-init.ts | 380 +++ .../provider/coz-sdk/use-upload-file-api.ts | 37 + .../studio-open-chat/provider/index.tsx | 68 + .../studio-open-chat/store/context.tsx | 157 + .../studio-open-chat/store/index.tsx | 17 + .../studio-open-chat/store/store.ts | 354 +++ .../open-chat/src/exports/types.ts | 38 + .../src/helper/clear-local-message-history.ts | 23 + .../open-chat/src/helper/coze-api-upload.ts | 124 + .../helper/get-local-message-history-key.ts | 20 + .../src/helper/get-local-message-history.ts | 28 + .../open-chat/src/helper/index.ts | 26 + .../open-chat/src/helper/is-show-feedback.ts | 22 + .../src/helper/studio-open-client-reporter.ts | 78 + .../open-platform/open-chat/src/index.ts | 30 +- .../open-chat/src/types/builder-chat.ts | 103 - .../open-chat/src/types/client.ts | 207 ++ .../open-chat/src/types/conversations.ts | 45 + .../open-platform/open-chat/src/types/core.ts | 25 + .../open-platform/open-chat/src/types/i18n.ts | 20 + .../open-chat/src/types/index.ts | 29 +- .../open-platform/open-chat/src/types/open.ts | 22 + .../open-platform/open-chat/src/types/post.ts | 31 + .../open-chat/src/types/props.ts | 142 + .../open-platform/open-chat/src/types/user.ts | 20 + .../open-chat/src/util/connector.ts | 18 + .../open-platform/open-chat/src/util/env.ts | 24 + .../open-platform/open-chat/src/util/error.ts | 121 + .../open-platform/open-chat/src/util/index.ts | 29 + .../open-chat/src/util/is-promise-like.ts | 18 + .../open-chat/src/util/json-handle.ts | 36 + .../open-chat/src/util/storage.ts | 57 + .../open-chat/tsconfig.build.json | 51 + .../src/pages/library/hooks/use-columns.tsx | 13 +- .../use-workflow-config.tsx | 21 +- .../base/src/utils/get-enabled-node-types.ts | 20 +- .../src/workflow-modal/filter/index.tsx | 9 +- .../sider/create-workflow-btn.tsx | 3 +- .../conversation-select/conversations.tsx | 5 +- .../test-form-float-button.module.less | 6 + .../test-form-float-button.tsx | 4 +- .../test-run/chat-history/index.tsx | 8 +- .../form-materials/full-input/full-input.tsx | 44 +- helm/charts/opencoze/files/mysql/schema.sql | 962 +++++- idl/app/bot_open_api.thrift | 60 +- idl/app/common_struct/common_struct.thrift | 25 +- idl/app/intelligence.thrift | 5 + idl/app/project.thrift | 27 +- idl/conversation/agentrun_service.thrift | 1 + idl/conversation/common.thrift | 1 + idl/conversation/conversation.thrift | 26 + idl/conversation/conversation_service.thrift | 3 +- idl/conversation/message.thrift | 2 + idl/conversation/run.thrift | 12 + idl/playground/playground.thrift | 7 - idl/workflow/workflow.thrift | 66 +- idl/workflow/workflow_svc.thrift | 2 + rush.json | 5 + 503 files changed, 48036 insertions(+), 3427 deletions(-) create mode 100644 backend/application/workflow/chatflow.go create mode 100644 backend/crossdomain/contract/conversation/conversationmock/conversation_mock.go create mode 100644 backend/crossdomain/contract/message/messagemock/message_mock.go create mode 100644 backend/crossdomain/contract/upload/upload.go create mode 100644 backend/crossdomain/impl/message/message_test.go create mode 100644 backend/crossdomain/impl/upload/upload.go create mode 100644 backend/domain/conversation/agentrun/internal/agent_info.go create mode 100644 backend/domain/conversation/agentrun/internal/chatflow_run.go delete mode 100644 backend/domain/conversation/agentrun/internal/event.go create mode 100644 backend/domain/conversation/agentrun/internal/message_builder.go create mode 100644 backend/domain/conversation/agentrun/internal/message_event.go create mode 100644 backend/domain/conversation/agentrun/internal/run.go rename backend/domain/conversation/agentrun/internal/{run_process.go => run_process_event.go} (97%) create mode 100644 backend/domain/conversation/agentrun/internal/singleagent_run.go create mode 100644 backend/domain/openauth/openapiauth/entity/consts.go create mode 100644 backend/domain/upload/entity/file.go create mode 100644 backend/domain/upload/internal/dal/dao/files.go create mode 100644 backend/domain/upload/internal/dal/model/files.gen.go create mode 100644 backend/domain/upload/internal/dal/query/files.gen.go create mode 100644 backend/domain/upload/internal/dal/query/gen.go create mode 100644 backend/domain/upload/repository/repository.go create mode 100644 backend/domain/upload/service/interface.go create mode 100644 backend/domain/upload/service/service.go create mode 100644 backend/domain/workflow/entity/chatflow_role.go create mode 100644 backend/domain/workflow/entity/conversation.go create mode 100644 backend/domain/workflow/entity/vo/chat_flow_role.go create mode 100644 backend/domain/workflow/entity/vo/chatflow.go create mode 100644 backend/domain/workflow/entity/vo/conversation.go create mode 100644 backend/domain/workflow/internal/canvas/examples/chatflow/new_chatflow.json create mode 100644 backend/domain/workflow/internal/canvas/examples/chatflow/start_exit.json create mode 100644 backend/domain/workflow/internal/canvas/examples/conversation_manager/conversation_list.json create mode 100644 backend/domain/workflow/internal/canvas/examples/conversation_manager/create_conversation.json create mode 100644 backend/domain/workflow/internal/canvas/examples/conversation_manager/delete_conversation.json create mode 100644 backend/domain/workflow/internal/canvas/examples/conversation_manager/update_conversation.json create mode 100644 backend/domain/workflow/internal/canvas/examples/conversation_manager/update_dynamic_conversation.json create mode 100644 backend/domain/workflow/internal/canvas/examples/message/create_message.json create mode 100644 backend/domain/workflow/internal/canvas/examples/message/message_list.json create mode 100644 backend/domain/workflow/internal/nodes/conversation/clearconversationhistory.go create mode 100644 backend/domain/workflow/internal/nodes/conversation/conversationhistory.go create mode 100644 backend/domain/workflow/internal/nodes/conversation/conversationlist.go create mode 100644 backend/domain/workflow/internal/nodes/conversation/createconversation.go create mode 100644 backend/domain/workflow/internal/nodes/conversation/createmessage.go create mode 100644 backend/domain/workflow/internal/nodes/conversation/deleteconversation.go create mode 100644 backend/domain/workflow/internal/nodes/conversation/deletemessage.go create mode 100644 backend/domain/workflow/internal/nodes/conversation/editmessage.go create mode 100644 backend/domain/workflow/internal/nodes/conversation/messagelist.go create mode 100644 backend/domain/workflow/internal/nodes/conversation/updateconversation.go create mode 100644 backend/domain/workflow/internal/nodes/intentdetector/prompt.go create mode 100644 backend/domain/workflow/internal/repo/conversation_repository.go create mode 100644 backend/domain/workflow/internal/repo/dal/model/app_conversation_template_draft.gen.go create mode 100644 backend/domain/workflow/internal/repo/dal/model/app_conversation_template_online.gen.go create mode 100644 backend/domain/workflow/internal/repo/dal/model/app_dynamic_conversation_draft.gen.go create mode 100644 backend/domain/workflow/internal/repo/dal/model/app_dynamic_conversation_online.gen.go create mode 100644 backend/domain/workflow/internal/repo/dal/model/app_static_conversation_draft.gen.go create mode 100644 backend/domain/workflow/internal/repo/dal/model/app_static_conversation_online.gen.go create mode 100644 backend/domain/workflow/internal/repo/dal/model/chat_flow_role_config.gen.go create mode 100644 backend/domain/workflow/internal/repo/dal/query/app_conversation_template_draft.gen.go create mode 100644 backend/domain/workflow/internal/repo/dal/query/app_conversation_template_online.gen.go create mode 100644 backend/domain/workflow/internal/repo/dal/query/app_dynamic_conversation_draft.gen.go create mode 100644 backend/domain/workflow/internal/repo/dal/query/app_dynamic_conversation_online.gen.go create mode 100644 backend/domain/workflow/internal/repo/dal/query/app_static_conversation_draft.gen.go create mode 100644 backend/domain/workflow/internal/repo/dal/query/app_static_conversation_online.gen.go create mode 100644 backend/domain/workflow/internal/repo/dal/query/chat_flow_role_config.gen.go create mode 100644 backend/domain/workflow/internal/repo/suggest.go create mode 100644 backend/domain/workflow/service/conversation_impl.go create mode 100644 docker/atlas/migrations/20250718104121_update.sql create mode 100644 docker/atlas/migrations/20250812093734_update.sql create mode 100644 docker/atlas/migrations/20250813081543_update.sql create mode 100644 docker/atlas/migrations/20250822060516_update.sql create mode 100644 frontend/config/tailwind-config/src/util.js create mode 100644 frontend/packages/common/chat-area/chat-answer-action/tsconfig.dev.json create mode 100644 frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/index.tsx create mode 100644 frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/input-node-render.tsx create mode 100644 frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/node-wrapper-ui.tsx create mode 100644 frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/question-node-render.tsx create mode 100644 frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/type.ts create mode 100644 frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/utils.ts create mode 100644 frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/index.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/.env.default create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/.env.local create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/.gitignore create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/.stylelintrc.js create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/OWNERS create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/README.md create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/config/rush-project.json create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/config/rushx-config.json create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/eslint.config.js create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/package.json create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/app.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/base.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/build.config.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/dev.config.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/dev.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/env.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/export.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/rules.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/semi-css-var-postcss-plugin.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/assets/widget.png create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/client/__tests__/index.test.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/client/auth.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/client/index.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/client/main.less create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/components/icons/close.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/components/icons/index.module.less create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/components/icons/spin.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/ast-btn.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/chat-content.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/chat-non-iframe.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/image-preview.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/index.module.less create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/index.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-app/index.module.less create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-app/index.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-bot/index.module.less create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-bot/index.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/App.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/index.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/main.less create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/page/AppWidget.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/page/Chat.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/page/Client.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/routes/index.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/export-ui/index.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/hooks/use-image-preview.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/hooks/use-message-interact.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/index.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/store/__tests__/setter.test.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/store/context.tsx create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/store/global.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/store/index.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/test/setup.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/types/chat.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/types/client.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/types/post.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/typings.d.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/util/get-chat-config.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/src/util/style.ts create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/tailwind.config.js create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/tsconfig.build.json create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/tsconfig.json create mode 100644 frontend/packages/studio/open-platform/chat-app-sdk/vitest.config.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/assets/add-new-chat.svg create mode 100644 frontend/packages/studio/open-platform/open-chat/src/assets/chatflow-logo.png create mode 100644 frontend/packages/studio/open-platform/open-chat/src/assets/coze-logo.png create mode 100644 frontend/packages/studio/open-platform/open-chat/src/assets/error-default.png create mode 100644 frontend/packages/studio/open-platform/open-chat/src/assets/error-unbind.png create mode 100644 frontend/packages/studio/open-platform/open-chat/src/assets/widget.png create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/audit-panel/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/audit-panel/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/background/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/background/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/context/builder-chat-context.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/coze-chat.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/data-type.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/helper/get-connector-id.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-bot-user-update.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-core-manager.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-init-chat.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-on-boarding-update.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-request-init.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/index.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/plugin.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/app.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/command.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/index.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/message.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/render.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/types/biz-context.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/typings.d.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/services/create-conversation.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/services/get-bot-info.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/type.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/web-sdk/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/chat/web-sdk/index.tsx delete mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/builder-chat/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/index.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/mobile/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/mobile/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/mobile/operate/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/pc/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/pc/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/pc/operate/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-list/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-list/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/error-boundary/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/error-fallback/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/error-fallback/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/footer/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/footer/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/header/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/header/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/header/mobile/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/header/mobile/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/header/pc/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/header/pc/index.tsx rename frontend/packages/studio/open-platform/open-chat/src/{types/base.ts => components/header/type.ts} (62%) create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/icon/add-new-conversation.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/loading/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/loading/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/area/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/area/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/audio-unfocus-text/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/audio-unfocus-text/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/chat-input-let-slot/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/chat-input-let-slot/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/shortcut-bar/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/__test__/user-info.test.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/index.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-chat-op-info.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-conversation-list.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-error.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-get-theme.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-is-show-background.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-pagination-request.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-update-conversation-name-by-message.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-user-info.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/index.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/index.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/plugin.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/app.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/command.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/index.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/message.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/render.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/types/biz-context.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/typings.d.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/index.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/index.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/message-convert-to-coze.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/message-convert-to-sdk.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/message-parser.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-break-message.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-clear-history.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-clear-message-context.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-common-hooks.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-message-list.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-send-message.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/chat-provider.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/context.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/helper/convert-shortcut-data.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/helper/coze-api-custom.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/hooks/use-bg-background-plugin.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/components/delete-message/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-footer/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-footer/use-message-footer-info.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-hover-slot/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-hover-slot/use-message-hover-info.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box/index.module.less create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-group-footer/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-group-footer/use-message-group-footer-info.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/hooks/use-cur-message-info.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/hooks/use-delete-message.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/hooks/use-submit-feedback-api.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/index.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/plugin.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/app.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/command.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/index.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/message.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/render.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/types/biz-context.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/typings.d.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/type.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-api-client.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-core-manager.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-core-override-config.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-request-init.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-upload-file-api.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/store/context.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/store/index.tsx create mode 100644 frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/store/store.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/exports/types.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/helper/clear-local-message-history.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/helper/coze-api-upload.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/helper/get-local-message-history-key.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/helper/get-local-message-history.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/helper/index.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/helper/is-show-feedback.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/helper/studio-open-client-reporter.ts delete mode 100644 frontend/packages/studio/open-platform/open-chat/src/types/builder-chat.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/types/client.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/types/conversations.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/types/core.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/types/i18n.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/types/open.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/types/post.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/types/props.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/types/user.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/util/connector.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/util/env.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/util/error.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/util/index.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/util/is-promise-like.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/util/json-handle.ts create mode 100644 frontend/packages/studio/open-platform/open-chat/src/util/storage.ts diff --git a/backend/Dockerfile b/backend/Dockerfile index 38ba5cc2..1b978b7a 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -27,6 +27,8 @@ WORKDIR /app # bind-tools for nslookup etc., file for debugging file types RUN apk add --no-cache pax-utils python3 python3-dev bind-tools file deno curl +RUN deno run -A jsr:@eyurtsev/pyodide-sandbox -c "print('Hello, World')" + # Install Python build dependencies, create venv, install packages, then remove build deps RUN apk add --no-cache --virtual .python-build-deps build-base py3-pip git && \ python3 -m venv --copies --upgrade-deps /app/.venv && \ diff --git a/backend/api/handler/coze/agent_run_service.go b/backend/api/handler/coze/agent_run_service.go index 0347f267..dac4a8e7 100644 --- a/backend/api/handler/coze/agent_run_service.go +++ b/backend/api/handler/coze/agent_run_service.go @@ -24,6 +24,7 @@ import ( "github.com/hertz-contrib/sse" "github.com/cloudwego/hertz/pkg/app" + "github.com/cloudwego/hertz/pkg/protocol/consts" "github.com/coze-dev/coze-studio/backend/api/model/conversation/run" @@ -123,3 +124,23 @@ func checkParamsV3(_ context.Context, ar *run.ChatV3Request) error { } return nil } + +// CancelChatApi . +// @router /v3/chat/cancel [POST] +func CancelChatApi(ctx context.Context, c *app.RequestContext) { + var err error + var req run.CancelChatApiRequest + err = c.BindAndValidate(&req) + if err != nil { + invalidParamRequestResponse(c, err.Error()) + return + } + + resp, err := conversation.ConversationOpenAPISVC.CancelRun(ctx, &req) + if err != nil { + invalidParamRequestResponse(c, err.Error()) + return + } + + c.JSON(consts.StatusOK, resp) +} diff --git a/backend/api/handler/coze/bot_open_api_service.go b/backend/api/handler/coze/bot_open_api_service.go index 9faba088..4294729a 100644 --- a/backend/api/handler/coze/bot_open_api_service.go +++ b/backend/api/handler/coze/bot_open_api_service.go @@ -22,6 +22,7 @@ import ( "context" "fmt" + openapiauthApp "github.com/coze-dev/coze-studio/backend/application/openauth" "github.com/coze-dev/coze-studio/backend/application/plugin" "github.com/coze-dev/coze-studio/backend/application/singleagent" "github.com/coze-dev/coze-studio/backend/application/upload" @@ -73,7 +74,7 @@ func UploadFileOpen(ctx context.Context, c *app.RequestContext) { var req bot_open_api.UploadFileOpenRequest err = c.BindAndValidate(&req) if err != nil { - c.String(consts.StatusBadRequest, err.Error()) + invalidParamRequestResponse(c, err.Error()) return } @@ -83,6 +84,7 @@ func UploadFileOpen(ctx context.Context, c *app.RequestContext) { internalServerErrorResponse(ctx, c, err) return } + c.JSON(consts.StatusOK, resp) } @@ -93,7 +95,7 @@ func GetBotOnlineInfo(ctx context.Context, c *app.RequestContext) { var req bot_open_api.GetBotOnlineInfoReq err = c.BindAndValidate(&req) if err != nil { - c.String(consts.StatusBadRequest, err.Error()) + invalidParamRequestResponse(c, err.Error()) return } @@ -105,3 +107,43 @@ func GetBotOnlineInfo(ctx context.Context, c *app.RequestContext) { } c.JSON(consts.StatusOK, resp) } + +// ImpersonateCozeUser . +// @router /api/permission_api/coze_web_app/impersonate_coze_user [POST] +func ImpersonateCozeUser(ctx context.Context, c *app.RequestContext) { + var err error + var req bot_open_api.ImpersonateCozeUserRequest + err = c.BindAndValidate(&req) + if err != nil { + invalidParamRequestResponse(c, err.Error()) + return + } + + resp, err := openapiauthApp.OpenAuthApplication.ImpersonateCozeUserAccessToken(ctx, &req) + + if err != nil { + internalServerErrorResponse(ctx, c, err) + return + } + + c.JSON(consts.StatusOK, resp) +} + +// OpenGetBotInfo . +// @router /v1/bots/:bot_id [GET] +func OpenGetBotInfo(ctx context.Context, c *app.RequestContext) { + var err error + var req bot_open_api.OpenGetBotInfoRequest + err = c.BindAndValidate(&req) + if err != nil { + invalidParamRequestResponse(c, err.Error()) + return + } + + resp, err := singleagent.SingleAgentSVC.OpenGetBotInfo(ctx, &req) + if err != nil { + internalServerErrorResponse(ctx, c, err) + return + } + c.JSON(consts.StatusOK, resp) +} diff --git a/backend/api/handler/coze/conversation_service.go b/backend/api/handler/coze/conversation_service.go index 09a4672d..7d1709eb 100644 --- a/backend/api/handler/coze/conversation_service.go +++ b/backend/api/handler/coze/conversation_service.go @@ -170,3 +170,42 @@ func ListConversationsApi(ctx context.Context, c *app.RequestContext) { c.JSON(consts.StatusOK, resp) } + +// UpdateConversationApi . +// @router /v1/conversations/:conversation_id [PUT] +func UpdateConversationApi(ctx context.Context, c *app.RequestContext) { + var err error + var req conversation.UpdateConversationApiRequest + err = c.BindAndValidate(&req) + if err != nil { + invalidParamRequestResponse(c, err.Error()) + return + } + + resp, err := application.ConversationSVC.UpdateConversation(ctx, &req) + if err != nil { + internalServerErrorResponse(ctx, c, err) + return + } + + c.JSON(consts.StatusOK, resp) +} + +// DeleteConversationApi . +// @router /v1/conversations/:conversation_id [DELETE] +func DeleteConversationApi(ctx context.Context, c *app.RequestContext) { + var err error + var req conversation.DeleteConversationApiRequest + err = c.BindAndValidate(&req) + if err != nil { + invalidParamRequestResponse(c, err.Error()) + return + } + resp, err := application.ConversationSVC.DeleteConversation(ctx, &req) + if err != nil { + internalServerErrorResponse(ctx, c, err) + return + } + + c.JSON(consts.StatusOK, resp) +} diff --git a/backend/api/handler/coze/intelligence_service.go b/backend/api/handler/coze/intelligence_service.go index 76938d70..95c08f32 100644 --- a/backend/api/handler/coze/intelligence_service.go +++ b/backend/api/handler/coze/intelligence_service.go @@ -404,3 +404,23 @@ func DraftProjectCopy(ctx context.Context, c *app.RequestContext) { c.JSON(consts.StatusOK, resp) } + +// GetOnlineAppData . +// @router /v1/apps/:app_id [GET] +func GetOnlineAppData(ctx context.Context, c *app.RequestContext) { + var err error + var req project.GetOnlineAppDataRequest + err = c.BindAndValidate(&req) + if err != nil { + c.String(consts.StatusBadRequest, err.Error()) + return + } + + resp, err := appApplication.APPApplicationSVC.GetOnlineAppData(ctx, &req) + if err != nil { + internalServerErrorResponse(ctx, c, err) + return + } + + c.JSON(consts.StatusOK, resp) +} diff --git a/backend/api/handler/coze/message_service.go b/backend/api/handler/coze/message_service.go index be45c1b1..911bfba7 100644 --- a/backend/api/handler/coze/message_service.go +++ b/backend/api/handler/coze/message_service.go @@ -140,7 +140,7 @@ func GetApiMessageList(ctx context.Context, c *app.RequestContext) { return } - resp, err := application.OpenapiMessageApplicationService.GetApiMessageList(ctx, &req) + resp, err := application.OpenapiMessageSVC.GetApiMessageList(ctx, &req) if err != nil { internalServerErrorResponse(ctx, c, err) return diff --git a/backend/api/handler/coze/upload_service.go b/backend/api/handler/coze/upload_service.go index f6f135e6..126823c9 100644 --- a/backend/api/handler/coze/upload_service.go +++ b/backend/api/handler/coze/upload_service.go @@ -1,3 +1,19 @@ +/* + * 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 hertz generator. package coze @@ -21,7 +37,7 @@ func CommonUpload(ctx context.Context, c *app.RequestContext) { var req upload.CommonUploadRequest err = c.BindAndValidate(&req) if err != nil { - c.String(consts.StatusBadRequest, err.Error()) + invalidParamRequestResponse(c, err.Error()) return } fullUrl := string(c.Request.URI().FullURI()) @@ -41,7 +57,7 @@ func ApplyUploadAction(ctx context.Context, c *app.RequestContext) { var req upload.ApplyUploadActionRequest err = c.BindAndValidate(&req) if err != nil { - c.String(consts.StatusBadRequest, err.Error()) + invalidParamRequestResponse(c, err.Error()) return } resp := new(upload.ApplyUploadActionResponse) diff --git a/backend/api/handler/coze/workflow_service.go b/backend/api/handler/coze/workflow_service.go index 4512576c..2640ef5e 100644 --- a/backend/api/handler/coze/workflow_service.go +++ b/backend/api/handler/coze/workflow_service.go @@ -23,6 +23,8 @@ import ( "errors" "fmt" "io" + "net/url" + "strconv" "github.com/cloudwego/eino/schema" "github.com/cloudwego/hertz/pkg/app" @@ -555,7 +557,11 @@ func CreateProjectConversationDef(ctx context.Context, c *app.RequestContext) { return } - resp := new(workflow.CreateProjectConversationDefResponse) + resp, err := appworkflow.SVC.CreateApplicationConversationDef(ctx, &req) + if err != nil { + internalServerErrorResponse(ctx, c, err) + return + } c.JSON(consts.StatusOK, resp) } @@ -570,8 +576,11 @@ func UpdateProjectConversationDef(ctx context.Context, c *app.RequestContext) { invalidParamRequestResponse(c, err.Error()) return } - - resp := new(workflow.UpdateProjectConversationDefResponse) + resp, err := appworkflow.SVC.UpdateApplicationConversationDef(ctx, &req) + if err != nil { + internalServerErrorResponse(ctx, c, err) + return + } c.JSON(consts.StatusOK, resp) } @@ -587,7 +596,11 @@ func DeleteProjectConversationDef(ctx context.Context, c *app.RequestContext) { return } - resp := new(workflow.DeleteProjectConversationDefResponse) + resp, err := appworkflow.SVC.DeleteApplicationConversationDef(ctx, &req) + if err != nil { + internalServerErrorResponse(ctx, c, err) + return + } c.JSON(consts.StatusOK, resp) } @@ -603,7 +616,11 @@ func ListProjectConversationDef(ctx context.Context, c *app.RequestContext) { return } - resp := new(workflow.ListProjectConversationResponse) + resp, err := appworkflow.SVC.ListApplicationConversationDef(ctx, &req) + if err != nil { + internalServerErrorResponse(ctx, c, err) + return + } c.JSON(consts.StatusOK, resp) } @@ -723,7 +740,11 @@ func GetChatFlowRole(ctx context.Context, c *app.RequestContext) { return } - resp := new(workflow.GetChatFlowRoleResponse) + resp, err := appworkflow.SVC.GetChatFlowRole(ctx, &req) + if err != nil { + internalServerErrorResponse(ctx, c, err) + return + } c.JSON(consts.StatusOK, resp) } @@ -738,8 +759,11 @@ func CreateChatFlowRole(ctx context.Context, c *app.RequestContext) { invalidParamRequestResponse(c, err.Error()) return } - - resp := new(workflow.CreateChatFlowRoleResponse) + resp, err := appworkflow.SVC.CreateChatFlowRole(ctx, &req) + if err != nil { + internalServerErrorResponse(ctx, c, err) + return + } c.JSON(consts.StatusOK, resp) } @@ -755,7 +779,11 @@ func DeleteChatFlowRole(ctx context.Context, c *app.RequestContext) { return } - resp := new(workflow.DeleteChatFlowRoleResponse) + resp, err := appworkflow.SVC.DeleteChatFlowRole(ctx, &req) + if err != nil { + internalServerErrorResponse(ctx, c, err) + return + } c.JSON(consts.StatusOK, resp) } @@ -1061,6 +1089,11 @@ func OpenAPIGetWorkflowRunHistory(ctx context.Context, c *app.RequestContext) { // @router /v1/workflows/chat [POST] func OpenAPIChatFlowRun(ctx context.Context, c *app.RequestContext) { var err error + if err = preprocessWorkflowRequestBody(ctx, c); err != nil { + invalidParamRequestResponse(c, err.Error()) + return + } + var req workflow.ChatFlowRunRequest err = c.BindAndValidate(&req) if err != nil { @@ -1068,27 +1101,107 @@ func OpenAPIChatFlowRun(ctx context.Context, c *app.RequestContext) { return } - resp := new(workflow.ChatFlowRunResponse) + w := sse.NewWriter(c) + c.SetContentType("text/event-stream; charset=utf-8") + c.Response.Header.Set("Cache-Control", "no-cache") + c.Response.Header.Set("Connection", "keep-alive") + c.Response.Header.Set("Access-Control-Allow-Origin", "*") - c.JSON(consts.StatusOK, resp) + sr, err := appworkflow.SVC.OpenAPIChatFlowRun(ctx, &req) + if err != nil { + internalServerErrorResponse(ctx, c, err) + return + } + sendChatFlowStreamRunSSE(ctx, w, sr) + +} + +func sendChatFlowStreamRunSSE(ctx context.Context, w *sse.Writer, sr *schema.StreamReader[[]*workflow.ChatFlowRunResponse]) { + defer func() { + _ = w.Close() + sr.Close() + }() + seq := int64(1) + for { + respList, err := sr.Recv() + + if err != nil { + if errors.Is(err, io.EOF) { + // finish + break + } + + event := &sse.Event{ + Type: "error", + Data: []byte(err.Error()), + } + + if err = w.Write(event); err != nil { + logs.CtxErrorf(ctx, "publish stream event failed, err:%v", err) + } + return + } + + for _, resp := range respList { + event := &sse.Event{ + ID: strconv.FormatInt(seq, 10), + Type: resp.Event, + Data: []byte(resp.Data), + } + + if err = w.Write(event); err != nil { + logs.CtxErrorf(ctx, "publish stream event failed, err:%v", err) + return + } + seq++ + } + + } } // OpenAPIGetWorkflowInfo . // @router /v1/workflows/:workflow_id [GET] func OpenAPIGetWorkflowInfo(ctx context.Context, c *app.RequestContext) { var err error + + if err = processOpenAPIGetWorkflowInfoRequest(ctx, c); err != nil { + invalidParamRequestResponse(c, err.Error()) + return + } + var req workflow.OpenAPIGetWorkflowInfoRequest + err = c.BindAndValidate(&req) if err != nil { invalidParamRequestResponse(c, err.Error()) return } - resp := new(workflow.OpenAPIGetWorkflowInfoResponse) + resp, err := appworkflow.SVC.OpenAPIGetWorkflowInfo(ctx, &req) + if err != nil { + internalServerErrorResponse(ctx, c, err) + return + } c.JSON(consts.StatusOK, resp) } +func processOpenAPIGetWorkflowInfoRequest(_ context.Context, c *app.RequestContext) error { + queryString := c.Request.QueryString() + + values, err := url.ParseQuery(string(queryString)) + if err != nil { + return fmt.Errorf("parse query parameter failed, err:%v", err) + } + isDebug := values.Get("is_debug") + if len(isDebug) == 0 { + values.Set("is_debug", "false") + } + c.Request.SetQueryString(values.Encode()) + + return nil +} + // GetHistorySchema . // @router /api/workflow_api/history_schema [POST] func GetHistorySchema(ctx context.Context, c *app.RequestContext) { @@ -1128,3 +1241,22 @@ func GetExampleWorkFlowList(ctx context.Context, c *app.RequestContext) { c.JSON(consts.StatusOK, resp) } + +// OpenAPICreateConversation . +// @router /v1/workflow/conversation/create [POST] +func OpenAPICreateConversation(ctx context.Context, c *app.RequestContext) { + var err error + var req workflow.CreateConversationRequest + err = c.BindAndValidate(&req) + if err != nil { + c.String(consts.StatusBadRequest, err.Error()) + return + } + resp, err := appworkflow.SVC.OpenAPICreateConversation(ctx, &req) + if err != nil { + internalServerErrorResponse(ctx, c, err) + return + } + + c.JSON(consts.StatusOK, resp) +} diff --git a/backend/api/handler/coze/workflow_service_test.go b/backend/api/handler/coze/workflow_service_test.go index dd1fe940..c22b7530 100644 --- a/backend/api/handler/coze/workflow_service_test.go +++ b/backend/api/handler/coze/workflow_service_test.go @@ -21,6 +21,7 @@ import ( "context" "errors" "fmt" + "net/http" "os" "reflect" @@ -42,6 +43,7 @@ import ( "github.com/cloudwego/hertz/pkg/common/ut" "github.com/cloudwego/hertz/pkg/protocol" "github.com/cloudwego/hertz/pkg/protocol/sse" + "github.com/coze-dev/coze-studio/backend/domain/workflow/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -250,7 +252,10 @@ func newWfTestRunner(t *testing.T) *wfTestRunner { mockTos := storageMock.NewMockStorage(ctrl) mockTos.EXPECT().GetObjectUrl(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil).AnyTimes() - workflowRepo := service.NewWorkflowRepository(mockIDGen, db, redisClient, mockTos, cpStore, utChatModel, nil) + + workflowRepo, _ := service.NewWorkflowRepository(mockIDGen, db, redisClient, mockTos, cpStore, utChatModel, &config.WorkflowConfig{ + NodeOfCodeConfig: &config.NodeOfCodeConfig{}, + }) mockey.Mock(appworkflow.GetWorkflowDomainSVC).Return(service.NewWorkflowService(workflowRepo)).Build() mockey.Mock(workflow2.GetRepository).Return(workflowRepo).Build() publishPatcher := mockey.Mock(appworkflow.PublishWorkflowResource).Return(nil).Build() @@ -4100,13 +4105,13 @@ func TestCopyWorkflowAppToLibrary(t *testing.T) { assert.NoError(t, err) validateSubWorkflowIDs(subworkflowCanvas.Nodes) case entity.NodeTypeLLM: - if node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.WorkflowFCParam != nil { + if node.Data.Inputs.LLM != nil && node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.WorkflowFCParam != nil { for _, w := range node.Data.Inputs.FCParam.WorkflowFCParam.WorkflowList { assert.True(t, copiedIDMap[w.WorkflowID]) } } - if node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.PluginFCParam != nil { + if node.Data.Inputs.LLM != nil && node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.PluginFCParam != nil { for _, p := range node.Data.Inputs.FCParam.PluginFCParam.PluginList { if p.PluginVersion == "0" { assert.Equal(t, "100100", p.PluginID) @@ -4114,7 +4119,7 @@ func TestCopyWorkflowAppToLibrary(t *testing.T) { } } - if node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.KnowledgeFCParam != nil { + if node.Data.Inputs.LLM != nil && node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.KnowledgeFCParam != nil { for _, k := range node.Data.Inputs.FCParam.KnowledgeFCParam.KnowledgeList { assert.Equal(t, "100100", k.ID) } diff --git a/backend/api/middleware/openapi_auth.go b/backend/api/middleware/openapi_auth.go index 3209f160..321a21f0 100644 --- a/backend/api/middleware/openapi_auth.go +++ b/backend/api/middleware/openapi_auth.go @@ -38,20 +38,28 @@ import ( const HeaderAuthorizationKey = "Authorization" var needAuthPath = map[string]bool{ - "/v3/chat": true, - "/v1/conversations": true, - "/v1/conversation/create": true, - "/v1/conversation/message/list": true, - "/v1/files/upload": true, - "/v1/workflow/run": true, - "/v1/workflow/stream_run": true, - "/v1/workflow/stream_resume": true, - "/v1/workflow/get_run_history": true, - "/v1/bot/get_online_info": true, + "/v3/chat": true, + "/v1/conversations": true, + "/v1/conversation/create": true, + "/v1/conversation/message/list": true, + "/v1/files/upload": true, + "/v1/workflow/run": true, + "/v1/workflow/stream_run": true, + "/v1/workflow/stream_resume": true, + "/v1/workflow/get_run_history": true, + "/v1/bot/get_online_info": true, + "/v1/workflows/chat": true, + "/v1/workflow/conversation/create": true, + "/v3/chat/cancel": true, } var needAuthFunc = map[string]bool{ "^/v1/conversations/[0-9]+/clear$": true, // v1/conversations/:conversation_id/clear + "^/v1/bots/[0-9]+$": true, + "^/v1/conversations/[0-9]+$": true, + + "^/v1/workflows/[0-9]+$": true, + "^/v1/apps/[0-9]+$": true, } func parseBearerAuthToken(authHeader string) string { diff --git a/backend/api/model/app/bot_open_api/bot_open_api.go b/backend/api/model/app/bot_open_api/bot_open_api.go index ac3fb5da..c2d2faf4 100644 --- a/backend/api/model/app/bot_open_api/bot_open_api.go +++ b/backend/api/model/app/bot_open_api/bot_open_api.go @@ -21,6 +21,7 @@ package bot_open_api import ( "context" "fmt" + "github.com/apache/thrift/lib/go/thrift" "github.com/coze-dev/coze-studio/backend/api/model/app/bot_common" "github.com/coze-dev/coze-studio/backend/api/model/base" @@ -1181,15 +1182,16 @@ func (p *UploadFileOpenResponse) String() string { } type File struct { - // 文件URI + // URI URI string `thrift:"URI,1" form:"uri" json:"uri"` - // 文件字节数 + // bytes Bytes int64 `thrift:"Bytes,2" form:"bytes" json:"bytes"` - // 上传时间戳,单位s + // create at CreatedAt int64 `thrift:"CreatedAt,3" form:"CreatedAt" json:"CreatedAt" query:"CreatedAt"` - // 文件名 + // file name FileName string `thrift:"FileName,4" form:"file_name" json:"file_name"` URL string `thrift:"URL,5" form:"url" json:"url"` + ID string `thrift:"ID,6" form:"id" json:"id"` } func NewFile() *File { @@ -1219,12 +1221,17 @@ func (p *File) GetURL() (v string) { return p.URL } +func (p *File) GetID() (v string) { + return p.ID +} + var fieldIDToName_File = map[int16]string{ 1: "URI", 2: "Bytes", 3: "CreatedAt", 4: "FileName", 5: "URL", + 6: "ID", } func (p *File) Read(iprot thrift.TProtocol) (err error) { @@ -1285,6 +1292,14 @@ func (p *File) Read(iprot thrift.TProtocol) (err error) { } else if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError } + case 6: + if fieldTypeId == thrift.STRING { + if err = p.ReadField6(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } default: if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError @@ -1369,6 +1384,17 @@ func (p *File) ReadField5(iprot thrift.TProtocol) error { p.URL = _field return nil } +func (p *File) ReadField6(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.ID = _field + return nil +} func (p *File) Write(oprot thrift.TProtocol) (err error) { var fieldId int16 @@ -1396,6 +1422,10 @@ func (p *File) Write(oprot thrift.TProtocol) (err error) { fieldId = 5 goto WriteFieldError } + if err = p.writeField6(oprot); err != nil { + fieldId = 6 + goto WriteFieldError + } } if err = oprot.WriteFieldStop(); err != nil { goto WriteFieldStopError @@ -1494,6 +1524,22 @@ WriteFieldBeginError: WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 5 end error: ", p), err) } +func (p *File) writeField6(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("ID", thrift.STRING, 6); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.ID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 6 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 6 end error: ", p), err) +} func (p *File) String() string { if p == nil { @@ -1761,10 +1807,1876 @@ func (p *GetBotOnlineInfoResp) String() string { } +type WorkspacePermission struct { + WorkspaceIDList []string `thrift:"workspace_id_list,1" form:"workspace_id_list" json:"workspace_id_list" query:"workspace_id_list"` + PermissionList []string `thrift:"permission_list,2" form:"permission_list" json:"permission_list" query:"permission_list"` +} + +func NewWorkspacePermission() *WorkspacePermission { + return &WorkspacePermission{} +} + +func (p *WorkspacePermission) InitDefault() { +} + +func (p *WorkspacePermission) GetWorkspaceIDList() (v []string) { + return p.WorkspaceIDList +} + +func (p *WorkspacePermission) GetPermissionList() (v []string) { + return p.PermissionList +} + +var fieldIDToName_WorkspacePermission = map[int16]string{ + 1: "workspace_id_list", + 2: "permission_list", +} + +func (p *WorkspacePermission) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.LIST { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.LIST { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_WorkspacePermission[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *WorkspacePermission) ReadField1(iprot thrift.TProtocol) error { + _, size, err := iprot.ReadListBegin() + if err != nil { + return err + } + _field := make([]string, 0, size) + for i := 0; i < size; i++ { + + var _elem string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _elem = v + } + + _field = append(_field, _elem) + } + if err := iprot.ReadListEnd(); err != nil { + return err + } + p.WorkspaceIDList = _field + return nil +} +func (p *WorkspacePermission) ReadField2(iprot thrift.TProtocol) error { + _, size, err := iprot.ReadListBegin() + if err != nil { + return err + } + _field := make([]string, 0, size) + for i := 0; i < size; i++ { + + var _elem string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _elem = v + } + + _field = append(_field, _elem) + } + if err := iprot.ReadListEnd(); err != nil { + return err + } + p.PermissionList = _field + return nil +} + +func (p *WorkspacePermission) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("WorkspacePermission"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *WorkspacePermission) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("workspace_id_list", thrift.LIST, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteListBegin(thrift.STRING, len(p.WorkspaceIDList)); err != nil { + return err + } + for _, v := range p.WorkspaceIDList { + if err := oprot.WriteString(v); err != nil { + return err + } + } + if err := oprot.WriteListEnd(); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *WorkspacePermission) writeField2(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("permission_list", thrift.LIST, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteListBegin(thrift.STRING, len(p.PermissionList)); err != nil { + return err + } + for _, v := range p.PermissionList { + if err := oprot.WriteString(v); err != nil { + return err + } + } + if err := oprot.WriteListEnd(); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} + +func (p *WorkspacePermission) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("WorkspacePermission(%+v)", *p) + +} + +type AccountPermission struct { + PermissionList []string `thrift:"permission_list,1" form:"permission_list" json:"permission_list" query:"permission_list"` +} + +func NewAccountPermission() *AccountPermission { + return &AccountPermission{} +} + +func (p *AccountPermission) InitDefault() { +} + +func (p *AccountPermission) GetPermissionList() (v []string) { + return p.PermissionList +} + +var fieldIDToName_AccountPermission = map[int16]string{ + 1: "permission_list", +} + +func (p *AccountPermission) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.LIST { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_AccountPermission[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *AccountPermission) ReadField1(iprot thrift.TProtocol) error { + _, size, err := iprot.ReadListBegin() + if err != nil { + return err + } + _field := make([]string, 0, size) + for i := 0; i < size; i++ { + + var _elem string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _elem = v + } + + _field = append(_field, _elem) + } + if err := iprot.ReadListEnd(); err != nil { + return err + } + p.PermissionList = _field + return nil +} + +func (p *AccountPermission) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("AccountPermission"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *AccountPermission) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("permission_list", thrift.LIST, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteListBegin(thrift.STRING, len(p.PermissionList)); err != nil { + return err + } + for _, v := range p.PermissionList { + if err := oprot.WriteString(v); err != nil { + return err + } + } + if err := oprot.WriteListEnd(); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} + +func (p *AccountPermission) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("AccountPermission(%+v)", *p) + +} + +type Scope struct { + WorkspacePermission *WorkspacePermission `thrift:"workspace_permission,1" form:"workspace_permission" json:"workspace_permission" query:"workspace_permission"` + AccountPermission *AccountPermission `thrift:"account_permission,2" form:"account_permission" json:"account_permission" query:"account_permission"` +} + +func NewScope() *Scope { + return &Scope{} +} + +func (p *Scope) InitDefault() { +} + +var Scope_WorkspacePermission_DEFAULT *WorkspacePermission + +func (p *Scope) GetWorkspacePermission() (v *WorkspacePermission) { + if !p.IsSetWorkspacePermission() { + return Scope_WorkspacePermission_DEFAULT + } + return p.WorkspacePermission +} + +var Scope_AccountPermission_DEFAULT *AccountPermission + +func (p *Scope) GetAccountPermission() (v *AccountPermission) { + if !p.IsSetAccountPermission() { + return Scope_AccountPermission_DEFAULT + } + return p.AccountPermission +} + +var fieldIDToName_Scope = map[int16]string{ + 1: "workspace_permission", + 2: "account_permission", +} + +func (p *Scope) IsSetWorkspacePermission() bool { + return p.WorkspacePermission != nil +} + +func (p *Scope) IsSetAccountPermission() bool { + return p.AccountPermission != nil +} + +func (p *Scope) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_Scope[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *Scope) ReadField1(iprot thrift.TProtocol) error { + _field := NewWorkspacePermission() + if err := _field.Read(iprot); err != nil { + return err + } + p.WorkspacePermission = _field + return nil +} +func (p *Scope) ReadField2(iprot thrift.TProtocol) error { + _field := NewAccountPermission() + if err := _field.Read(iprot); err != nil { + return err + } + p.AccountPermission = _field + return nil +} + +func (p *Scope) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("Scope"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *Scope) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("workspace_permission", thrift.STRUCT, 1); err != nil { + goto WriteFieldBeginError + } + if err := p.WorkspacePermission.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *Scope) writeField2(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("account_permission", thrift.STRUCT, 2); err != nil { + goto WriteFieldBeginError + } + if err := p.AccountPermission.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} + +func (p *Scope) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("Scope(%+v)", *p) + +} + +type ImpersonateCozeUserRequest struct { + DurationSeconds int64 `thrift:"duration_seconds,1" form:"duration_seconds" json:"duration_seconds" query:"duration_seconds"` + Scope *Scope `thrift:"scope,2" form:"scope" json:"scope" query:"scope"` +} + +func NewImpersonateCozeUserRequest() *ImpersonateCozeUserRequest { + return &ImpersonateCozeUserRequest{} +} + +func (p *ImpersonateCozeUserRequest) InitDefault() { +} + +func (p *ImpersonateCozeUserRequest) GetDurationSeconds() (v int64) { + return p.DurationSeconds +} + +var ImpersonateCozeUserRequest_Scope_DEFAULT *Scope + +func (p *ImpersonateCozeUserRequest) GetScope() (v *Scope) { + if !p.IsSetScope() { + return ImpersonateCozeUserRequest_Scope_DEFAULT + } + return p.Scope +} + +var fieldIDToName_ImpersonateCozeUserRequest = map[int16]string{ + 1: "duration_seconds", + 2: "scope", +} + +func (p *ImpersonateCozeUserRequest) IsSetScope() bool { + return p.Scope != nil +} + +func (p *ImpersonateCozeUserRequest) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.I64 { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_ImpersonateCozeUserRequest[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *ImpersonateCozeUserRequest) ReadField1(iprot thrift.TProtocol) error { + + var _field int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = v + } + p.DurationSeconds = _field + return nil +} +func (p *ImpersonateCozeUserRequest) ReadField2(iprot thrift.TProtocol) error { + _field := NewScope() + if err := _field.Read(iprot); err != nil { + return err + } + p.Scope = _field + return nil +} + +func (p *ImpersonateCozeUserRequest) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("ImpersonateCozeUserRequest"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *ImpersonateCozeUserRequest) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("duration_seconds", thrift.I64, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(p.DurationSeconds); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *ImpersonateCozeUserRequest) writeField2(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("scope", thrift.STRUCT, 2); err != nil { + goto WriteFieldBeginError + } + if err := p.Scope.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} + +func (p *ImpersonateCozeUserRequest) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("ImpersonateCozeUserRequest(%+v)", *p) + +} + +type ImpersonateCozeUserResponse struct { + Code int32 `thrift:"code,1,required" form:"code,required" json:"code,required" query:"code,required"` + Msg string `thrift:"msg,2,required" form:"msg,required" json:"msg,required" query:"msg,required"` + Data *ImpersonateCozeUserResponseData `thrift:"data,3" form:"data" json:"data" query:"data"` +} + +func NewImpersonateCozeUserResponse() *ImpersonateCozeUserResponse { + return &ImpersonateCozeUserResponse{} +} + +func (p *ImpersonateCozeUserResponse) InitDefault() { +} + +func (p *ImpersonateCozeUserResponse) GetCode() (v int32) { + return p.Code +} + +func (p *ImpersonateCozeUserResponse) GetMsg() (v string) { + return p.Msg +} + +var ImpersonateCozeUserResponse_Data_DEFAULT *ImpersonateCozeUserResponseData + +func (p *ImpersonateCozeUserResponse) GetData() (v *ImpersonateCozeUserResponseData) { + if !p.IsSetData() { + return ImpersonateCozeUserResponse_Data_DEFAULT + } + return p.Data +} + +var fieldIDToName_ImpersonateCozeUserResponse = map[int16]string{ + 1: "code", + 2: "msg", + 3: "data", +} + +func (p *ImpersonateCozeUserResponse) IsSetData() bool { + return p.Data != nil +} + +func (p *ImpersonateCozeUserResponse) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + var issetCode bool = false + var issetMsg bool = false + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.I32 { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + issetCode = true + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.STRING { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + issetMsg = true + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 3: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField3(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + if !issetCode { + fieldId = 1 + goto RequiredFieldNotSetError + } + + if !issetMsg { + fieldId = 2 + goto RequiredFieldNotSetError + } + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_ImpersonateCozeUserResponse[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +RequiredFieldNotSetError: + return thrift.NewTProtocolExceptionWithType(thrift.INVALID_DATA, fmt.Errorf("required field %s is not set", fieldIDToName_ImpersonateCozeUserResponse[fieldId])) +} + +func (p *ImpersonateCozeUserResponse) ReadField1(iprot thrift.TProtocol) error { + + var _field int32 + if v, err := iprot.ReadI32(); err != nil { + return err + } else { + _field = v + } + p.Code = _field + return nil +} +func (p *ImpersonateCozeUserResponse) ReadField2(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Msg = _field + return nil +} +func (p *ImpersonateCozeUserResponse) ReadField3(iprot thrift.TProtocol) error { + _field := NewImpersonateCozeUserResponseData() + if err := _field.Read(iprot); err != nil { + return err + } + p.Data = _field + return nil +} + +func (p *ImpersonateCozeUserResponse) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("ImpersonateCozeUserResponse"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField3(oprot); err != nil { + fieldId = 3 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *ImpersonateCozeUserResponse) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("code", thrift.I32, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI32(p.Code); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *ImpersonateCozeUserResponse) writeField2(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("msg", thrift.STRING, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Msg); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} +func (p *ImpersonateCozeUserResponse) writeField3(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("data", thrift.STRUCT, 3); err != nil { + goto WriteFieldBeginError + } + if err := p.Data.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err) +} + +func (p *ImpersonateCozeUserResponse) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("ImpersonateCozeUserResponse(%+v)", *p) + +} + +type ImpersonateCozeUserResponseData struct { + AccessToken string `thrift:"access_token,1,required" form:"access_token,required" json:"access_token,required" query:"access_token,required"` + ExpiresIn int64 `thrift:"expires_in,2,required" form:"expires_in,required" json:"expires_in,required" query:"expires_in,required"` + TokenType string `thrift:"token_type,3,required" form:"token_type,required" json:"token_type,required" query:"token_type,required"` +} + +func NewImpersonateCozeUserResponseData() *ImpersonateCozeUserResponseData { + return &ImpersonateCozeUserResponseData{} +} + +func (p *ImpersonateCozeUserResponseData) InitDefault() { +} + +func (p *ImpersonateCozeUserResponseData) GetAccessToken() (v string) { + return p.AccessToken +} + +func (p *ImpersonateCozeUserResponseData) GetExpiresIn() (v int64) { + return p.ExpiresIn +} + +func (p *ImpersonateCozeUserResponseData) GetTokenType() (v string) { + return p.TokenType +} + +var fieldIDToName_ImpersonateCozeUserResponseData = map[int16]string{ + 1: "access_token", + 2: "expires_in", + 3: "token_type", +} + +func (p *ImpersonateCozeUserResponseData) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + var issetAccessToken bool = false + var issetExpiresIn bool = false + var issetTokenType bool = false + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRING { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + issetAccessToken = true + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.I64 { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + issetExpiresIn = true + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 3: + if fieldTypeId == thrift.STRING { + if err = p.ReadField3(iprot); err != nil { + goto ReadFieldError + } + issetTokenType = true + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + if !issetAccessToken { + fieldId = 1 + goto RequiredFieldNotSetError + } + + if !issetExpiresIn { + fieldId = 2 + goto RequiredFieldNotSetError + } + + if !issetTokenType { + fieldId = 3 + goto RequiredFieldNotSetError + } + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_ImpersonateCozeUserResponseData[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +RequiredFieldNotSetError: + return thrift.NewTProtocolExceptionWithType(thrift.INVALID_DATA, fmt.Errorf("required field %s is not set", fieldIDToName_ImpersonateCozeUserResponseData[fieldId])) +} + +func (p *ImpersonateCozeUserResponseData) ReadField1(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.AccessToken = _field + return nil +} +func (p *ImpersonateCozeUserResponseData) ReadField2(iprot thrift.TProtocol) error { + + var _field int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = v + } + p.ExpiresIn = _field + return nil +} +func (p *ImpersonateCozeUserResponseData) ReadField3(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.TokenType = _field + return nil +} + +func (p *ImpersonateCozeUserResponseData) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("ImpersonateCozeUserResponseData"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField3(oprot); err != nil { + fieldId = 3 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *ImpersonateCozeUserResponseData) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("access_token", thrift.STRING, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.AccessToken); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *ImpersonateCozeUserResponseData) writeField2(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("expires_in", thrift.I64, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(p.ExpiresIn); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} +func (p *ImpersonateCozeUserResponseData) writeField3(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("token_type", thrift.STRING, 3); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.TokenType); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err) +} + +func (p *ImpersonateCozeUserResponseData) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("ImpersonateCozeUserResponseData(%+v)", *p) + +} + +type OpenGetBotInfoRequest struct { + BotID int64 `thrift:"bot_id,1" json:"bot_id,string" path:"bot_id"` + IsPublished *bool `thrift:"is_published,2,optional" json:"is_published,omitempty" query:"is_published"` + ConnectorID *int64 `thrift:"connector_id,3,optional" json:"connector_id,string,omitempty" query:"connector_id"` + Base *base.Base `thrift:"Base,255,optional" form:"-" json:"-" query:"-"` +} + +func NewOpenGetBotInfoRequest() *OpenGetBotInfoRequest { + return &OpenGetBotInfoRequest{} +} + +func (p *OpenGetBotInfoRequest) InitDefault() { +} + +func (p *OpenGetBotInfoRequest) GetBotID() (v int64) { + return p.BotID +} + +var OpenGetBotInfoRequest_IsPublished_DEFAULT bool + +func (p *OpenGetBotInfoRequest) GetIsPublished() (v bool) { + if !p.IsSetIsPublished() { + return OpenGetBotInfoRequest_IsPublished_DEFAULT + } + return *p.IsPublished +} + +var OpenGetBotInfoRequest_ConnectorID_DEFAULT int64 + +func (p *OpenGetBotInfoRequest) GetConnectorID() (v int64) { + if !p.IsSetConnectorID() { + return OpenGetBotInfoRequest_ConnectorID_DEFAULT + } + return *p.ConnectorID +} + +var OpenGetBotInfoRequest_Base_DEFAULT *base.Base + +func (p *OpenGetBotInfoRequest) GetBase() (v *base.Base) { + if !p.IsSetBase() { + return OpenGetBotInfoRequest_Base_DEFAULT + } + return p.Base +} + +var fieldIDToName_OpenGetBotInfoRequest = map[int16]string{ + 1: "bot_id", + 2: "is_published", + 3: "connector_id", + 255: "Base", +} + +func (p *OpenGetBotInfoRequest) IsSetIsPublished() bool { + return p.IsPublished != nil +} + +func (p *OpenGetBotInfoRequest) IsSetConnectorID() bool { + return p.ConnectorID != nil +} + +func (p *OpenGetBotInfoRequest) IsSetBase() bool { + return p.Base != nil +} + +func (p *OpenGetBotInfoRequest) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.I64 { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.BOOL { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 3: + if fieldTypeId == thrift.I64 { + if err = p.ReadField3(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 255: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField255(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_OpenGetBotInfoRequest[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *OpenGetBotInfoRequest) ReadField1(iprot thrift.TProtocol) error { + + var _field int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = v + } + p.BotID = _field + return nil +} +func (p *OpenGetBotInfoRequest) ReadField2(iprot thrift.TProtocol) error { + + var _field *bool + if v, err := iprot.ReadBool(); err != nil { + return err + } else { + _field = &v + } + p.IsPublished = _field + return nil +} +func (p *OpenGetBotInfoRequest) ReadField3(iprot thrift.TProtocol) error { + + var _field *int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = &v + } + p.ConnectorID = _field + return nil +} +func (p *OpenGetBotInfoRequest) ReadField255(iprot thrift.TProtocol) error { + _field := base.NewBase() + if err := _field.Read(iprot); err != nil { + return err + } + p.Base = _field + return nil +} + +func (p *OpenGetBotInfoRequest) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("OpenGetBotInfoRequest"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField3(oprot); err != nil { + fieldId = 3 + goto WriteFieldError + } + if err = p.writeField255(oprot); err != nil { + fieldId = 255 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *OpenGetBotInfoRequest) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("bot_id", thrift.I64, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(p.BotID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *OpenGetBotInfoRequest) writeField2(oprot thrift.TProtocol) (err error) { + if p.IsSetIsPublished() { + if err = oprot.WriteFieldBegin("is_published", thrift.BOOL, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteBool(*p.IsPublished); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} +func (p *OpenGetBotInfoRequest) writeField3(oprot thrift.TProtocol) (err error) { + if p.IsSetConnectorID() { + if err = oprot.WriteFieldBegin("connector_id", thrift.I64, 3); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(*p.ConnectorID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err) +} +func (p *OpenGetBotInfoRequest) writeField255(oprot thrift.TProtocol) (err error) { + if p.IsSetBase() { + if err = oprot.WriteFieldBegin("Base", thrift.STRUCT, 255); err != nil { + goto WriteFieldBeginError + } + if err := p.Base.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 end error: ", p), err) +} + +func (p *OpenGetBotInfoRequest) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("OpenGetBotInfoRequest(%+v)", *p) + +} + +type OpenGetBotInfoResponse struct { + Data *bot_common.OpenAPIBotInfo `thrift:"data,1" form:"data" json:"data" query:"data"` + Code int64 `thrift:"code,253" form:"code" json:"code" query:"code"` + Msg string `thrift:"msg,254" form:"msg" json:"msg" query:"msg"` + BaseResp *base.BaseResp `thrift:"BaseResp,255,optional" form:"BaseResp" json:"BaseResp,omitempty" query:"BaseResp"` +} + +func NewOpenGetBotInfoResponse() *OpenGetBotInfoResponse { + return &OpenGetBotInfoResponse{} +} + +func (p *OpenGetBotInfoResponse) InitDefault() { +} + +var OpenGetBotInfoResponse_Data_DEFAULT *bot_common.OpenAPIBotInfo + +func (p *OpenGetBotInfoResponse) GetData() (v *bot_common.OpenAPIBotInfo) { + if !p.IsSetData() { + return OpenGetBotInfoResponse_Data_DEFAULT + } + return p.Data +} + +func (p *OpenGetBotInfoResponse) GetCode() (v int64) { + return p.Code +} + +func (p *OpenGetBotInfoResponse) GetMsg() (v string) { + return p.Msg +} + +var OpenGetBotInfoResponse_BaseResp_DEFAULT *base.BaseResp + +func (p *OpenGetBotInfoResponse) GetBaseResp() (v *base.BaseResp) { + if !p.IsSetBaseResp() { + return OpenGetBotInfoResponse_BaseResp_DEFAULT + } + return p.BaseResp +} + +var fieldIDToName_OpenGetBotInfoResponse = map[int16]string{ + 1: "data", + 253: "code", + 254: "msg", + 255: "BaseResp", +} + +func (p *OpenGetBotInfoResponse) IsSetData() bool { + return p.Data != nil +} + +func (p *OpenGetBotInfoResponse) IsSetBaseResp() bool { + return p.BaseResp != nil +} + +func (p *OpenGetBotInfoResponse) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 253: + if fieldTypeId == thrift.I64 { + if err = p.ReadField253(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 254: + if fieldTypeId == thrift.STRING { + if err = p.ReadField254(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 255: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField255(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_OpenGetBotInfoResponse[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *OpenGetBotInfoResponse) ReadField1(iprot thrift.TProtocol) error { + _field := bot_common.NewOpenAPIBotInfo() + if err := _field.Read(iprot); err != nil { + return err + } + p.Data = _field + return nil +} +func (p *OpenGetBotInfoResponse) ReadField253(iprot thrift.TProtocol) error { + + var _field int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = v + } + p.Code = _field + return nil +} +func (p *OpenGetBotInfoResponse) ReadField254(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Msg = _field + return nil +} +func (p *OpenGetBotInfoResponse) ReadField255(iprot thrift.TProtocol) error { + _field := base.NewBaseResp() + if err := _field.Read(iprot); err != nil { + return err + } + p.BaseResp = _field + return nil +} + +func (p *OpenGetBotInfoResponse) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("OpenGetBotInfoResponse"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField253(oprot); err != nil { + fieldId = 253 + goto WriteFieldError + } + if err = p.writeField254(oprot); err != nil { + fieldId = 254 + goto WriteFieldError + } + if err = p.writeField255(oprot); err != nil { + fieldId = 255 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *OpenGetBotInfoResponse) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("data", thrift.STRUCT, 1); err != nil { + goto WriteFieldBeginError + } + if err := p.Data.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *OpenGetBotInfoResponse) writeField253(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("code", thrift.I64, 253); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(p.Code); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 253 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 253 end error: ", p), err) +} +func (p *OpenGetBotInfoResponse) writeField254(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("msg", thrift.STRING, 254); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Msg); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 254 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 254 end error: ", p), err) +} +func (p *OpenGetBotInfoResponse) writeField255(oprot thrift.TProtocol) (err error) { + if p.IsSetBaseResp() { + if err = oprot.WriteFieldBegin("BaseResp", thrift.STRUCT, 255); err != nil { + goto WriteFieldBeginError + } + if err := p.BaseResp.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 end error: ", p), err) +} + +func (p *OpenGetBotInfoResponse) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("OpenGetBotInfoResponse(%+v)", *p) + +} + type BotOpenApiService interface { OauthAuthorizationCode(ctx context.Context, request *OauthAuthorizationCodeReq) (r *OauthAuthorizationCodeResp, err error) //openapi GetBotOnlineInfo(ctx context.Context, request *GetBotOnlineInfoReq) (r *GetBotOnlineInfoResp, err error) + + OpenGetBotInfo(ctx context.Context, request *OpenGetBotInfoRequest) (r *OpenGetBotInfoResponse, err error) + + ImpersonateCozeUser(ctx context.Context, request *ImpersonateCozeUserRequest) (r *ImpersonateCozeUserResponse, err error) // File related OpenAPI UploadFileOpen(ctx context.Context, request *UploadFileOpenRequest) (r *UploadFileOpenResponse, err error) } @@ -1813,6 +3725,24 @@ func (p *BotOpenApiServiceClient) GetBotOnlineInfo(ctx context.Context, request } return _result.GetSuccess(), nil } +func (p *BotOpenApiServiceClient) OpenGetBotInfo(ctx context.Context, request *OpenGetBotInfoRequest) (r *OpenGetBotInfoResponse, err error) { + var _args BotOpenApiServiceOpenGetBotInfoArgs + _args.Request = request + var _result BotOpenApiServiceOpenGetBotInfoResult + if err = p.Client_().Call(ctx, "OpenGetBotInfo", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} +func (p *BotOpenApiServiceClient) ImpersonateCozeUser(ctx context.Context, request *ImpersonateCozeUserRequest) (r *ImpersonateCozeUserResponse, err error) { + var _args BotOpenApiServiceImpersonateCozeUserArgs + _args.Request = request + var _result BotOpenApiServiceImpersonateCozeUserResult + if err = p.Client_().Call(ctx, "ImpersonateCozeUser", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} func (p *BotOpenApiServiceClient) UploadFileOpen(ctx context.Context, request *UploadFileOpenRequest) (r *UploadFileOpenResponse, err error) { var _args BotOpenApiServiceUploadFileOpenArgs _args.Request = request @@ -1845,6 +3775,8 @@ func NewBotOpenApiServiceProcessor(handler BotOpenApiService) *BotOpenApiService self := &BotOpenApiServiceProcessor{handler: handler, processorMap: make(map[string]thrift.TProcessorFunction)} self.AddToProcessorMap("OauthAuthorizationCode", &botOpenApiServiceProcessorOauthAuthorizationCode{handler: handler}) self.AddToProcessorMap("GetBotOnlineInfo", &botOpenApiServiceProcessorGetBotOnlineInfo{handler: handler}) + self.AddToProcessorMap("OpenGetBotInfo", &botOpenApiServiceProcessorOpenGetBotInfo{handler: handler}) + self.AddToProcessorMap("ImpersonateCozeUser", &botOpenApiServiceProcessorImpersonateCozeUser{handler: handler}) self.AddToProcessorMap("UploadFileOpen", &botOpenApiServiceProcessorUploadFileOpen{handler: handler}) return self } @@ -1962,6 +3894,102 @@ func (p *botOpenApiServiceProcessorGetBotOnlineInfo) Process(ctx context.Context return true, err } +type botOpenApiServiceProcessorOpenGetBotInfo struct { + handler BotOpenApiService +} + +func (p *botOpenApiServiceProcessorOpenGetBotInfo) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := BotOpenApiServiceOpenGetBotInfoArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("OpenGetBotInfo", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := BotOpenApiServiceOpenGetBotInfoResult{} + var retval *OpenGetBotInfoResponse + if retval, err2 = p.handler.OpenGetBotInfo(ctx, args.Request); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing OpenGetBotInfo: "+err2.Error()) + oprot.WriteMessageBegin("OpenGetBotInfo", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("OpenGetBotInfo", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + +type botOpenApiServiceProcessorImpersonateCozeUser struct { + handler BotOpenApiService +} + +func (p *botOpenApiServiceProcessorImpersonateCozeUser) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := BotOpenApiServiceImpersonateCozeUserArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("ImpersonateCozeUser", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := BotOpenApiServiceImpersonateCozeUserResult{} + var retval *ImpersonateCozeUserResponse + if retval, err2 = p.handler.ImpersonateCozeUser(ctx, args.Request); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing ImpersonateCozeUser: "+err2.Error()) + oprot.WriteMessageBegin("ImpersonateCozeUser", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("ImpersonateCozeUser", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + type botOpenApiServiceProcessorUploadFileOpen struct { handler BotOpenApiService } @@ -2594,6 +4622,590 @@ func (p *BotOpenApiServiceGetBotOnlineInfoResult) String() string { } +type BotOpenApiServiceOpenGetBotInfoArgs struct { + Request *OpenGetBotInfoRequest `thrift:"request,1"` +} + +func NewBotOpenApiServiceOpenGetBotInfoArgs() *BotOpenApiServiceOpenGetBotInfoArgs { + return &BotOpenApiServiceOpenGetBotInfoArgs{} +} + +func (p *BotOpenApiServiceOpenGetBotInfoArgs) InitDefault() { +} + +var BotOpenApiServiceOpenGetBotInfoArgs_Request_DEFAULT *OpenGetBotInfoRequest + +func (p *BotOpenApiServiceOpenGetBotInfoArgs) GetRequest() (v *OpenGetBotInfoRequest) { + if !p.IsSetRequest() { + return BotOpenApiServiceOpenGetBotInfoArgs_Request_DEFAULT + } + return p.Request +} + +var fieldIDToName_BotOpenApiServiceOpenGetBotInfoArgs = map[int16]string{ + 1: "request", +} + +func (p *BotOpenApiServiceOpenGetBotInfoArgs) IsSetRequest() bool { + return p.Request != nil +} + +func (p *BotOpenApiServiceOpenGetBotInfoArgs) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_BotOpenApiServiceOpenGetBotInfoArgs[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *BotOpenApiServiceOpenGetBotInfoArgs) ReadField1(iprot thrift.TProtocol) error { + _field := NewOpenGetBotInfoRequest() + if err := _field.Read(iprot); err != nil { + return err + } + p.Request = _field + return nil +} + +func (p *BotOpenApiServiceOpenGetBotInfoArgs) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("OpenGetBotInfo_args"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *BotOpenApiServiceOpenGetBotInfoArgs) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("request", thrift.STRUCT, 1); err != nil { + goto WriteFieldBeginError + } + if err := p.Request.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} + +func (p *BotOpenApiServiceOpenGetBotInfoArgs) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("BotOpenApiServiceOpenGetBotInfoArgs(%+v)", *p) + +} + +type BotOpenApiServiceOpenGetBotInfoResult struct { + Success *OpenGetBotInfoResponse `thrift:"success,0,optional"` +} + +func NewBotOpenApiServiceOpenGetBotInfoResult() *BotOpenApiServiceOpenGetBotInfoResult { + return &BotOpenApiServiceOpenGetBotInfoResult{} +} + +func (p *BotOpenApiServiceOpenGetBotInfoResult) InitDefault() { +} + +var BotOpenApiServiceOpenGetBotInfoResult_Success_DEFAULT *OpenGetBotInfoResponse + +func (p *BotOpenApiServiceOpenGetBotInfoResult) GetSuccess() (v *OpenGetBotInfoResponse) { + if !p.IsSetSuccess() { + return BotOpenApiServiceOpenGetBotInfoResult_Success_DEFAULT + } + return p.Success +} + +var fieldIDToName_BotOpenApiServiceOpenGetBotInfoResult = map[int16]string{ + 0: "success", +} + +func (p *BotOpenApiServiceOpenGetBotInfoResult) IsSetSuccess() bool { + return p.Success != nil +} + +func (p *BotOpenApiServiceOpenGetBotInfoResult) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField0(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_BotOpenApiServiceOpenGetBotInfoResult[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *BotOpenApiServiceOpenGetBotInfoResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewOpenGetBotInfoResponse() + if err := _field.Read(iprot); err != nil { + return err + } + p.Success = _field + return nil +} + +func (p *BotOpenApiServiceOpenGetBotInfoResult) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("OpenGetBotInfo_result"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField0(oprot); err != nil { + fieldId = 0 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *BotOpenApiServiceOpenGetBotInfoResult) writeField0(oprot thrift.TProtocol) (err error) { + if p.IsSetSuccess() { + if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { + goto WriteFieldBeginError + } + if err := p.Success.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) +} + +func (p *BotOpenApiServiceOpenGetBotInfoResult) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("BotOpenApiServiceOpenGetBotInfoResult(%+v)", *p) + +} + +type BotOpenApiServiceImpersonateCozeUserArgs struct { + Request *ImpersonateCozeUserRequest `thrift:"request,1"` +} + +func NewBotOpenApiServiceImpersonateCozeUserArgs() *BotOpenApiServiceImpersonateCozeUserArgs { + return &BotOpenApiServiceImpersonateCozeUserArgs{} +} + +func (p *BotOpenApiServiceImpersonateCozeUserArgs) InitDefault() { +} + +var BotOpenApiServiceImpersonateCozeUserArgs_Request_DEFAULT *ImpersonateCozeUserRequest + +func (p *BotOpenApiServiceImpersonateCozeUserArgs) GetRequest() (v *ImpersonateCozeUserRequest) { + if !p.IsSetRequest() { + return BotOpenApiServiceImpersonateCozeUserArgs_Request_DEFAULT + } + return p.Request +} + +var fieldIDToName_BotOpenApiServiceImpersonateCozeUserArgs = map[int16]string{ + 1: "request", +} + +func (p *BotOpenApiServiceImpersonateCozeUserArgs) IsSetRequest() bool { + return p.Request != nil +} + +func (p *BotOpenApiServiceImpersonateCozeUserArgs) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_BotOpenApiServiceImpersonateCozeUserArgs[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *BotOpenApiServiceImpersonateCozeUserArgs) ReadField1(iprot thrift.TProtocol) error { + _field := NewImpersonateCozeUserRequest() + if err := _field.Read(iprot); err != nil { + return err + } + p.Request = _field + return nil +} + +func (p *BotOpenApiServiceImpersonateCozeUserArgs) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("ImpersonateCozeUser_args"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *BotOpenApiServiceImpersonateCozeUserArgs) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("request", thrift.STRUCT, 1); err != nil { + goto WriteFieldBeginError + } + if err := p.Request.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} + +func (p *BotOpenApiServiceImpersonateCozeUserArgs) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("BotOpenApiServiceImpersonateCozeUserArgs(%+v)", *p) + +} + +type BotOpenApiServiceImpersonateCozeUserResult struct { + Success *ImpersonateCozeUserResponse `thrift:"success,0,optional"` +} + +func NewBotOpenApiServiceImpersonateCozeUserResult() *BotOpenApiServiceImpersonateCozeUserResult { + return &BotOpenApiServiceImpersonateCozeUserResult{} +} + +func (p *BotOpenApiServiceImpersonateCozeUserResult) InitDefault() { +} + +var BotOpenApiServiceImpersonateCozeUserResult_Success_DEFAULT *ImpersonateCozeUserResponse + +func (p *BotOpenApiServiceImpersonateCozeUserResult) GetSuccess() (v *ImpersonateCozeUserResponse) { + if !p.IsSetSuccess() { + return BotOpenApiServiceImpersonateCozeUserResult_Success_DEFAULT + } + return p.Success +} + +var fieldIDToName_BotOpenApiServiceImpersonateCozeUserResult = map[int16]string{ + 0: "success", +} + +func (p *BotOpenApiServiceImpersonateCozeUserResult) IsSetSuccess() bool { + return p.Success != nil +} + +func (p *BotOpenApiServiceImpersonateCozeUserResult) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField0(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_BotOpenApiServiceImpersonateCozeUserResult[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *BotOpenApiServiceImpersonateCozeUserResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewImpersonateCozeUserResponse() + if err := _field.Read(iprot); err != nil { + return err + } + p.Success = _field + return nil +} + +func (p *BotOpenApiServiceImpersonateCozeUserResult) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("ImpersonateCozeUser_result"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField0(oprot); err != nil { + fieldId = 0 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *BotOpenApiServiceImpersonateCozeUserResult) writeField0(oprot thrift.TProtocol) (err error) { + if p.IsSetSuccess() { + if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { + goto WriteFieldBeginError + } + if err := p.Success.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) +} + +func (p *BotOpenApiServiceImpersonateCozeUserResult) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("BotOpenApiServiceImpersonateCozeUserResult(%+v)", *p) + +} + type BotOpenApiServiceUploadFileOpenArgs struct { Request *UploadFileOpenRequest `thrift:"request,1"` } diff --git a/backend/api/model/app/intelligence/common/common_struct.go b/backend/api/model/app/intelligence/common/common_struct.go index ac216c27..e2641d47 100644 --- a/backend/api/model/app/intelligence/common/common_struct.go +++ b/backend/api/model/app/intelligence/common/common_struct.go @@ -1,3 +1,19 @@ +/* + * 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.1). DO NOT EDIT. package common @@ -6,9 +22,26 @@ import ( "database/sql" "database/sql/driver" "fmt" + "github.com/apache/thrift/lib/go/thrift" ) +const ( + VariableTypeKVVariable = "KVVariable" + + VariableTypeListVariable = "ListVariable" + + VariableChannelCustom = "custom" + + VariableChannelSystem = "system" + + VariableChannelLocation = "location" + + VariableChannelFeishu = "feishu" + + VariableChannelAPP = "app" +) + /****************************** audit **********************************/ type AuditStatus int64 @@ -314,6 +347,10 @@ func (p *SpaceStatus) Value() (driver.Value, error) { return int64(*p), nil } +type VariableType = string + +type VariableChannel = string + type UserLabel struct { LabelID string `thrift:"label_id,1" form:"label_id" json:"label_id" query:"label_id"` LabelName string `thrift:"label_name,2" form:"label_name" json:"label_name" query:"label_name"` @@ -2275,3 +2312,412 @@ func (p *Space) String() string { return fmt.Sprintf("Space(%+v)", *p) } + +type Variable struct { + Keyword string `thrift:"keyword,1" form:"keyword" json:"keyword" query:"keyword"` + DefaultValue string `thrift:"default_value,2" form:"default_value" json:"default_value" query:"default_value"` + VariableType VariableType `thrift:"variable_type,3" form:"variable_type" json:"variable_type" query:"variable_type"` + Channel VariableChannel `thrift:"channel,4" form:"channel" json:"channel" query:"channel"` + Description string `thrift:"description,5" form:"description" json:"description" query:"description"` + Enable bool `thrift:"enable,6" form:"enable" json:"enable" query:"enable"` + PromptEnable bool `thrift:"prompt_enable,7" form:"prompt_enable" json:"prompt_enable" query:"prompt_enable"` +} + +func NewVariable() *Variable { + return &Variable{} +} + +func (p *Variable) InitDefault() { +} + +func (p *Variable) GetKeyword() (v string) { + return p.Keyword +} + +func (p *Variable) GetDefaultValue() (v string) { + return p.DefaultValue +} + +func (p *Variable) GetVariableType() (v VariableType) { + return p.VariableType +} + +func (p *Variable) GetChannel() (v VariableChannel) { + return p.Channel +} + +func (p *Variable) GetDescription() (v string) { + return p.Description +} + +func (p *Variable) GetEnable() (v bool) { + return p.Enable +} + +func (p *Variable) GetPromptEnable() (v bool) { + return p.PromptEnable +} + +var fieldIDToName_Variable = map[int16]string{ + 1: "keyword", + 2: "default_value", + 3: "variable_type", + 4: "channel", + 5: "description", + 6: "enable", + 7: "prompt_enable", +} + +func (p *Variable) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRING { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.STRING { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 3: + if fieldTypeId == thrift.STRING { + if err = p.ReadField3(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 4: + if fieldTypeId == thrift.STRING { + if err = p.ReadField4(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 5: + if fieldTypeId == thrift.STRING { + if err = p.ReadField5(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 6: + if fieldTypeId == thrift.BOOL { + if err = p.ReadField6(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 7: + if fieldTypeId == thrift.BOOL { + if err = p.ReadField7(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_Variable[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *Variable) ReadField1(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Keyword = _field + return nil +} +func (p *Variable) ReadField2(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.DefaultValue = _field + return nil +} +func (p *Variable) ReadField3(iprot thrift.TProtocol) error { + + var _field VariableType + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.VariableType = _field + return nil +} +func (p *Variable) ReadField4(iprot thrift.TProtocol) error { + + var _field VariableChannel + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Channel = _field + return nil +} +func (p *Variable) ReadField5(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Description = _field + return nil +} +func (p *Variable) ReadField6(iprot thrift.TProtocol) error { + + var _field bool + if v, err := iprot.ReadBool(); err != nil { + return err + } else { + _field = v + } + p.Enable = _field + return nil +} +func (p *Variable) ReadField7(iprot thrift.TProtocol) error { + + var _field bool + if v, err := iprot.ReadBool(); err != nil { + return err + } else { + _field = v + } + p.PromptEnable = _field + return nil +} + +func (p *Variable) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("Variable"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField3(oprot); err != nil { + fieldId = 3 + goto WriteFieldError + } + if err = p.writeField4(oprot); err != nil { + fieldId = 4 + goto WriteFieldError + } + if err = p.writeField5(oprot); err != nil { + fieldId = 5 + goto WriteFieldError + } + if err = p.writeField6(oprot); err != nil { + fieldId = 6 + goto WriteFieldError + } + if err = p.writeField7(oprot); err != nil { + fieldId = 7 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *Variable) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("keyword", thrift.STRING, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Keyword); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *Variable) writeField2(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("default_value", thrift.STRING, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.DefaultValue); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} +func (p *Variable) writeField3(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("variable_type", thrift.STRING, 3); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.VariableType); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err) +} +func (p *Variable) writeField4(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("channel", thrift.STRING, 4); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Channel); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 4 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 4 end error: ", p), err) +} +func (p *Variable) writeField5(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("description", thrift.STRING, 5); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Description); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 5 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 5 end error: ", p), err) +} +func (p *Variable) writeField6(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("enable", thrift.BOOL, 6); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteBool(p.Enable); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 6 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 6 end error: ", p), err) +} +func (p *Variable) writeField7(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("prompt_enable", thrift.BOOL, 7); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteBool(p.PromptEnable); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 7 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 7 end error: ", p), err) +} + +func (p *Variable) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("Variable(%+v)", *p) + +} diff --git a/backend/api/model/app/intelligence/intelligence.go b/backend/api/model/app/intelligence/intelligence.go index 2901bb33..0dc7167a 100644 --- a/backend/api/model/app/intelligence/intelligence.go +++ b/backend/api/model/app/intelligence/intelligence.go @@ -21,6 +21,7 @@ package intelligence import ( "context" "fmt" + "github.com/apache/thrift/lib/go/thrift" "github.com/coze-dev/coze-studio/backend/api/model/app/intelligence/project" "github.com/coze-dev/coze-studio/backend/api/model/app/intelligence/publish" @@ -55,6 +56,8 @@ type IntelligenceService interface { GetPublishRecordList(ctx context.Context, request *publish.GetPublishRecordListRequest) (r *publish.GetPublishRecordListResponse, err error) GetPublishRecordDetail(ctx context.Context, request *publish.GetPublishRecordDetailRequest) (r *publish.GetPublishRecordDetailResponse, err error) + // OpenAPI + GetOnlineAppData(ctx context.Context, request *project.GetOnlineAppDataRequest) (r *project.GetOnlineAppDataResponse, err error) } type IntelligenceServiceClient struct { @@ -209,6 +212,15 @@ func (p *IntelligenceServiceClient) GetPublishRecordDetail(ctx context.Context, } return _result.GetSuccess(), nil } +func (p *IntelligenceServiceClient) GetOnlineAppData(ctx context.Context, request *project.GetOnlineAppDataRequest) (r *project.GetOnlineAppDataResponse, err error) { + var _args IntelligenceServiceGetOnlineAppDataArgs + _args.Request = request + var _result IntelligenceServiceGetOnlineAppDataResult + if err = p.Client_().Call(ctx, "GetOnlineAppData", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} type IntelligenceServiceProcessor struct { processorMap map[string]thrift.TProcessorFunction @@ -244,6 +256,7 @@ func NewIntelligenceServiceProcessor(handler IntelligenceService) *IntelligenceS self.AddToProcessorMap("PublishProject", &intelligenceServiceProcessorPublishProject{handler: handler}) self.AddToProcessorMap("GetPublishRecordList", &intelligenceServiceProcessorGetPublishRecordList{handler: handler}) self.AddToProcessorMap("GetPublishRecordDetail", &intelligenceServiceProcessorGetPublishRecordDetail{handler: handler}) + self.AddToProcessorMap("GetOnlineAppData", &intelligenceServiceProcessorGetOnlineAppData{handler: handler}) return self } func (p *IntelligenceServiceProcessor) Process(ctx context.Context, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { @@ -936,6 +949,54 @@ func (p *intelligenceServiceProcessorGetPublishRecordDetail) Process(ctx context return true, err } +type intelligenceServiceProcessorGetOnlineAppData struct { + handler IntelligenceService +} + +func (p *intelligenceServiceProcessorGetOnlineAppData) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := IntelligenceServiceGetOnlineAppDataArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("GetOnlineAppData", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := IntelligenceServiceGetOnlineAppDataResult{} + var retval *project.GetOnlineAppDataResponse + if retval, err2 = p.handler.GetOnlineAppData(ctx, args.Request); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing GetOnlineAppData: "+err2.Error()) + oprot.WriteMessageBegin("GetOnlineAppData", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("GetOnlineAppData", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + type IntelligenceServiceDraftProjectCreateArgs struct { Request *project.DraftProjectCreateRequest `thrift:"request,1"` } @@ -5023,3 +5084,295 @@ func (p *IntelligenceServiceGetPublishRecordDetailResult) String() string { return fmt.Sprintf("IntelligenceServiceGetPublishRecordDetailResult(%+v)", *p) } + +type IntelligenceServiceGetOnlineAppDataArgs struct { + Request *project.GetOnlineAppDataRequest `thrift:"request,1"` +} + +func NewIntelligenceServiceGetOnlineAppDataArgs() *IntelligenceServiceGetOnlineAppDataArgs { + return &IntelligenceServiceGetOnlineAppDataArgs{} +} + +func (p *IntelligenceServiceGetOnlineAppDataArgs) InitDefault() { +} + +var IntelligenceServiceGetOnlineAppDataArgs_Request_DEFAULT *project.GetOnlineAppDataRequest + +func (p *IntelligenceServiceGetOnlineAppDataArgs) GetRequest() (v *project.GetOnlineAppDataRequest) { + if !p.IsSetRequest() { + return IntelligenceServiceGetOnlineAppDataArgs_Request_DEFAULT + } + return p.Request +} + +var fieldIDToName_IntelligenceServiceGetOnlineAppDataArgs = map[int16]string{ + 1: "request", +} + +func (p *IntelligenceServiceGetOnlineAppDataArgs) IsSetRequest() bool { + return p.Request != nil +} + +func (p *IntelligenceServiceGetOnlineAppDataArgs) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_IntelligenceServiceGetOnlineAppDataArgs[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *IntelligenceServiceGetOnlineAppDataArgs) ReadField1(iprot thrift.TProtocol) error { + _field := project.NewGetOnlineAppDataRequest() + if err := _field.Read(iprot); err != nil { + return err + } + p.Request = _field + return nil +} + +func (p *IntelligenceServiceGetOnlineAppDataArgs) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("GetOnlineAppData_args"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *IntelligenceServiceGetOnlineAppDataArgs) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("request", thrift.STRUCT, 1); err != nil { + goto WriteFieldBeginError + } + if err := p.Request.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} + +func (p *IntelligenceServiceGetOnlineAppDataArgs) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("IntelligenceServiceGetOnlineAppDataArgs(%+v)", *p) + +} + +type IntelligenceServiceGetOnlineAppDataResult struct { + Success *project.GetOnlineAppDataResponse `thrift:"success,0,optional"` +} + +func NewIntelligenceServiceGetOnlineAppDataResult() *IntelligenceServiceGetOnlineAppDataResult { + return &IntelligenceServiceGetOnlineAppDataResult{} +} + +func (p *IntelligenceServiceGetOnlineAppDataResult) InitDefault() { +} + +var IntelligenceServiceGetOnlineAppDataResult_Success_DEFAULT *project.GetOnlineAppDataResponse + +func (p *IntelligenceServiceGetOnlineAppDataResult) GetSuccess() (v *project.GetOnlineAppDataResponse) { + if !p.IsSetSuccess() { + return IntelligenceServiceGetOnlineAppDataResult_Success_DEFAULT + } + return p.Success +} + +var fieldIDToName_IntelligenceServiceGetOnlineAppDataResult = map[int16]string{ + 0: "success", +} + +func (p *IntelligenceServiceGetOnlineAppDataResult) IsSetSuccess() bool { + return p.Success != nil +} + +func (p *IntelligenceServiceGetOnlineAppDataResult) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField0(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_IntelligenceServiceGetOnlineAppDataResult[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *IntelligenceServiceGetOnlineAppDataResult) ReadField0(iprot thrift.TProtocol) error { + _field := project.NewGetOnlineAppDataResponse() + if err := _field.Read(iprot); err != nil { + return err + } + p.Success = _field + return nil +} + +func (p *IntelligenceServiceGetOnlineAppDataResult) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("GetOnlineAppData_result"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField0(oprot); err != nil { + fieldId = 0 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *IntelligenceServiceGetOnlineAppDataResult) writeField0(oprot thrift.TProtocol) (err error) { + if p.IsSetSuccess() { + if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { + goto WriteFieldBeginError + } + if err := p.Success.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) +} + +func (p *IntelligenceServiceGetOnlineAppDataResult) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("IntelligenceServiceGetOnlineAppDataResult(%+v)", *p) + +} diff --git a/backend/api/model/app/intelligence/project/project.go b/backend/api/model/app/intelligence/project/project.go index 13cd47c2..ac95a361 100644 --- a/backend/api/model/app/intelligence/project/project.go +++ b/backend/api/model/app/intelligence/project/project.go @@ -1,9 +1,26 @@ +/* + * 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.1). DO NOT EDIT. package project import ( "fmt" + "github.com/apache/thrift/lib/go/thrift" "github.com/coze-dev/coze-studio/backend/api/model/app/intelligence/common" "github.com/coze-dev/coze-studio/backend/api/model/base" @@ -3278,3 +3295,954 @@ func (p *DraftProjectCopyResponseData) String() string { return fmt.Sprintf("DraftProjectCopyResponseData(%+v)", *p) } + +type GetOnlineAppDataRequest struct { + AppID *int64 `thrift:"app_id,1,optional" json:"app_id,string,omitempty" path:"app_id"` + ConnectorID *int64 `thrift:"connector_id,2,optional" form:"connector_id" json:"connector_id,string,omitempty" query:"connector_id"` + Base *base.Base `thrift:"Base,255,optional" form:"Base" json:"Base,omitempty" query:"Base"` +} + +func NewGetOnlineAppDataRequest() *GetOnlineAppDataRequest { + return &GetOnlineAppDataRequest{} +} + +func (p *GetOnlineAppDataRequest) InitDefault() { +} + +var GetOnlineAppDataRequest_AppID_DEFAULT int64 + +func (p *GetOnlineAppDataRequest) GetAppID() (v int64) { + if !p.IsSetAppID() { + return GetOnlineAppDataRequest_AppID_DEFAULT + } + return *p.AppID +} + +var GetOnlineAppDataRequest_ConnectorID_DEFAULT int64 + +func (p *GetOnlineAppDataRequest) GetConnectorID() (v int64) { + if !p.IsSetConnectorID() { + return GetOnlineAppDataRequest_ConnectorID_DEFAULT + } + return *p.ConnectorID +} + +var GetOnlineAppDataRequest_Base_DEFAULT *base.Base + +func (p *GetOnlineAppDataRequest) GetBase() (v *base.Base) { + if !p.IsSetBase() { + return GetOnlineAppDataRequest_Base_DEFAULT + } + return p.Base +} + +var fieldIDToName_GetOnlineAppDataRequest = map[int16]string{ + 1: "app_id", + 2: "connector_id", + 255: "Base", +} + +func (p *GetOnlineAppDataRequest) IsSetAppID() bool { + return p.AppID != nil +} + +func (p *GetOnlineAppDataRequest) IsSetConnectorID() bool { + return p.ConnectorID != nil +} + +func (p *GetOnlineAppDataRequest) IsSetBase() bool { + return p.Base != nil +} + +func (p *GetOnlineAppDataRequest) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.I64 { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.I64 { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 255: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField255(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_GetOnlineAppDataRequest[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *GetOnlineAppDataRequest) ReadField1(iprot thrift.TProtocol) error { + + var _field *int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = &v + } + p.AppID = _field + return nil +} +func (p *GetOnlineAppDataRequest) ReadField2(iprot thrift.TProtocol) error { + + var _field *int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = &v + } + p.ConnectorID = _field + return nil +} +func (p *GetOnlineAppDataRequest) ReadField255(iprot thrift.TProtocol) error { + _field := base.NewBase() + if err := _field.Read(iprot); err != nil { + return err + } + p.Base = _field + return nil +} + +func (p *GetOnlineAppDataRequest) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("GetOnlineAppDataRequest"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField255(oprot); err != nil { + fieldId = 255 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *GetOnlineAppDataRequest) writeField1(oprot thrift.TProtocol) (err error) { + if p.IsSetAppID() { + if err = oprot.WriteFieldBegin("app_id", thrift.I64, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(*p.AppID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *GetOnlineAppDataRequest) writeField2(oprot thrift.TProtocol) (err error) { + if p.IsSetConnectorID() { + if err = oprot.WriteFieldBegin("connector_id", thrift.I64, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(*p.ConnectorID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} +func (p *GetOnlineAppDataRequest) writeField255(oprot thrift.TProtocol) (err error) { + if p.IsSetBase() { + if err = oprot.WriteFieldBegin("Base", thrift.STRUCT, 255); err != nil { + goto WriteFieldBeginError + } + if err := p.Base.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 end error: ", p), err) +} + +func (p *GetOnlineAppDataRequest) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("GetOnlineAppDataRequest(%+v)", *p) + +} + +type AppData struct { + AppID string `thrift:"app_id,1" form:"app_id" json:"app_id" query:"app_id"` + Version string `thrift:"version,2" form:"version" json:"version" query:"version"` + Name string `thrift:"name,3" form:"name" json:"name" query:"name"` + Description string `thrift:"description,4" form:"description" json:"description" query:"description"` + IconURL string `thrift:"icon_url,5" form:"icon_url" json:"icon_url" query:"icon_url"` + Variables []*common.Variable `thrift:"variables,6" form:"variables" json:"variables" query:"variables"` +} + +func NewAppData() *AppData { + return &AppData{} +} + +func (p *AppData) InitDefault() { +} + +func (p *AppData) GetAppID() (v string) { + return p.AppID +} + +func (p *AppData) GetVersion() (v string) { + return p.Version +} + +func (p *AppData) GetName() (v string) { + return p.Name +} + +func (p *AppData) GetDescription() (v string) { + return p.Description +} + +func (p *AppData) GetIconURL() (v string) { + return p.IconURL +} + +func (p *AppData) GetVariables() (v []*common.Variable) { + return p.Variables +} + +var fieldIDToName_AppData = map[int16]string{ + 1: "app_id", + 2: "version", + 3: "name", + 4: "description", + 5: "icon_url", + 6: "variables", +} + +func (p *AppData) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRING { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.STRING { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 3: + if fieldTypeId == thrift.STRING { + if err = p.ReadField3(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 4: + if fieldTypeId == thrift.STRING { + if err = p.ReadField4(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 5: + if fieldTypeId == thrift.STRING { + if err = p.ReadField5(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 6: + if fieldTypeId == thrift.LIST { + if err = p.ReadField6(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_AppData[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *AppData) ReadField1(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.AppID = _field + return nil +} +func (p *AppData) ReadField2(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Version = _field + return nil +} +func (p *AppData) ReadField3(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Name = _field + return nil +} +func (p *AppData) ReadField4(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Description = _field + return nil +} +func (p *AppData) ReadField5(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.IconURL = _field + return nil +} +func (p *AppData) ReadField6(iprot thrift.TProtocol) error { + _, size, err := iprot.ReadListBegin() + if err != nil { + return err + } + _field := make([]*common.Variable, 0, size) + values := make([]common.Variable, size) + for i := 0; i < size; i++ { + _elem := &values[i] + _elem.InitDefault() + + if err := _elem.Read(iprot); err != nil { + return err + } + + _field = append(_field, _elem) + } + if err := iprot.ReadListEnd(); err != nil { + return err + } + p.Variables = _field + return nil +} + +func (p *AppData) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("AppData"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField3(oprot); err != nil { + fieldId = 3 + goto WriteFieldError + } + if err = p.writeField4(oprot); err != nil { + fieldId = 4 + goto WriteFieldError + } + if err = p.writeField5(oprot); err != nil { + fieldId = 5 + goto WriteFieldError + } + if err = p.writeField6(oprot); err != nil { + fieldId = 6 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *AppData) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("app_id", thrift.STRING, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.AppID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *AppData) writeField2(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("version", thrift.STRING, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Version); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} +func (p *AppData) writeField3(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("name", thrift.STRING, 3); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Name); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err) +} +func (p *AppData) writeField4(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("description", thrift.STRING, 4); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Description); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 4 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 4 end error: ", p), err) +} +func (p *AppData) writeField5(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("icon_url", thrift.STRING, 5); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.IconURL); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 5 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 5 end error: ", p), err) +} +func (p *AppData) writeField6(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("variables", thrift.LIST, 6); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteListBegin(thrift.STRUCT, len(p.Variables)); err != nil { + return err + } + for _, v := range p.Variables { + if err := v.Write(oprot); err != nil { + return err + } + } + if err := oprot.WriteListEnd(); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 6 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 6 end error: ", p), err) +} + +func (p *AppData) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("AppData(%+v)", *p) + +} + +type GetOnlineAppDataResponse struct { + Code *int32 `thrift:"Code,1,optional" form:"code" json:"code,omitempty"` + Message *string `thrift:"Message,2,optional" form:"message" json:"message,omitempty"` + Data *AppData `thrift:"Data,3" form:"data" json:"data"` + BaseResp *base.BaseResp `thrift:"BaseResp,255" form:"BaseResp" json:"BaseResp" query:"BaseResp"` +} + +func NewGetOnlineAppDataResponse() *GetOnlineAppDataResponse { + return &GetOnlineAppDataResponse{} +} + +func (p *GetOnlineAppDataResponse) InitDefault() { +} + +var GetOnlineAppDataResponse_Code_DEFAULT int32 + +func (p *GetOnlineAppDataResponse) GetCode() (v int32) { + if !p.IsSetCode() { + return GetOnlineAppDataResponse_Code_DEFAULT + } + return *p.Code +} + +var GetOnlineAppDataResponse_Message_DEFAULT string + +func (p *GetOnlineAppDataResponse) GetMessage() (v string) { + if !p.IsSetMessage() { + return GetOnlineAppDataResponse_Message_DEFAULT + } + return *p.Message +} + +var GetOnlineAppDataResponse_Data_DEFAULT *AppData + +func (p *GetOnlineAppDataResponse) GetData() (v *AppData) { + if !p.IsSetData() { + return GetOnlineAppDataResponse_Data_DEFAULT + } + return p.Data +} + +var GetOnlineAppDataResponse_BaseResp_DEFAULT *base.BaseResp + +func (p *GetOnlineAppDataResponse) GetBaseResp() (v *base.BaseResp) { + if !p.IsSetBaseResp() { + return GetOnlineAppDataResponse_BaseResp_DEFAULT + } + return p.BaseResp +} + +var fieldIDToName_GetOnlineAppDataResponse = map[int16]string{ + 1: "Code", + 2: "Message", + 3: "Data", + 255: "BaseResp", +} + +func (p *GetOnlineAppDataResponse) IsSetCode() bool { + return p.Code != nil +} + +func (p *GetOnlineAppDataResponse) IsSetMessage() bool { + return p.Message != nil +} + +func (p *GetOnlineAppDataResponse) IsSetData() bool { + return p.Data != nil +} + +func (p *GetOnlineAppDataResponse) IsSetBaseResp() bool { + return p.BaseResp != nil +} + +func (p *GetOnlineAppDataResponse) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.I32 { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.STRING { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 3: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField3(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 255: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField255(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_GetOnlineAppDataResponse[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *GetOnlineAppDataResponse) ReadField1(iprot thrift.TProtocol) error { + + var _field *int32 + if v, err := iprot.ReadI32(); err != nil { + return err + } else { + _field = &v + } + p.Code = _field + return nil +} +func (p *GetOnlineAppDataResponse) ReadField2(iprot thrift.TProtocol) error { + + var _field *string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = &v + } + p.Message = _field + return nil +} +func (p *GetOnlineAppDataResponse) ReadField3(iprot thrift.TProtocol) error { + _field := NewAppData() + if err := _field.Read(iprot); err != nil { + return err + } + p.Data = _field + return nil +} +func (p *GetOnlineAppDataResponse) ReadField255(iprot thrift.TProtocol) error { + _field := base.NewBaseResp() + if err := _field.Read(iprot); err != nil { + return err + } + p.BaseResp = _field + return nil +} + +func (p *GetOnlineAppDataResponse) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("GetOnlineAppDataResponse"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField3(oprot); err != nil { + fieldId = 3 + goto WriteFieldError + } + if err = p.writeField255(oprot); err != nil { + fieldId = 255 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *GetOnlineAppDataResponse) writeField1(oprot thrift.TProtocol) (err error) { + if p.IsSetCode() { + if err = oprot.WriteFieldBegin("Code", thrift.I32, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI32(*p.Code); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *GetOnlineAppDataResponse) writeField2(oprot thrift.TProtocol) (err error) { + if p.IsSetMessage() { + if err = oprot.WriteFieldBegin("Message", thrift.STRING, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(*p.Message); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} +func (p *GetOnlineAppDataResponse) writeField3(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("Data", thrift.STRUCT, 3); err != nil { + goto WriteFieldBeginError + } + if err := p.Data.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err) +} +func (p *GetOnlineAppDataResponse) writeField255(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("BaseResp", thrift.STRUCT, 255); err != nil { + goto WriteFieldBeginError + } + if err := p.BaseResp.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 end error: ", p), err) +} + +func (p *GetOnlineAppDataResponse) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("GetOnlineAppDataResponse(%+v)", *p) + +} diff --git a/backend/api/model/conversation/agentrun/agentrun_service.go b/backend/api/model/conversation/agentrun/agentrun_service.go index 1355f406..90fc978f 100644 --- a/backend/api/model/conversation/agentrun/agentrun_service.go +++ b/backend/api/model/conversation/agentrun/agentrun_service.go @@ -1,3 +1,19 @@ +/* + * 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.1). DO NOT EDIT. package agentrun @@ -5,6 +21,7 @@ package agentrun import ( "context" "fmt" + "github.com/apache/thrift/lib/go/thrift" "github.com/coze-dev/coze-studio/backend/api/model/conversation/run" ) @@ -13,6 +30,8 @@ type AgentRunService interface { AgentRun(ctx context.Context, request *run.AgentRunRequest) (r *run.AgentRunResponse, err error) ChatV3(ctx context.Context, request *run.ChatV3Request) (r *run.ChatV3Response, err error) + + CancelChatApi(ctx context.Context, request *run.CancelChatApiRequest) (r *run.CancelChatApiResponse, err error) } type AgentRunServiceClient struct { @@ -59,6 +78,15 @@ func (p *AgentRunServiceClient) ChatV3(ctx context.Context, request *run.ChatV3R } return _result.GetSuccess(), nil } +func (p *AgentRunServiceClient) CancelChatApi(ctx context.Context, request *run.CancelChatApiRequest) (r *run.CancelChatApiResponse, err error) { + var _args AgentRunServiceCancelChatApiArgs + _args.Request = request + var _result AgentRunServiceCancelChatApiResult + if err = p.Client_().Call(ctx, "CancelChatApi", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} type AgentRunServiceProcessor struct { processorMap map[string]thrift.TProcessorFunction @@ -82,6 +110,7 @@ func NewAgentRunServiceProcessor(handler AgentRunService) *AgentRunServiceProces self := &AgentRunServiceProcessor{handler: handler, processorMap: make(map[string]thrift.TProcessorFunction)} self.AddToProcessorMap("AgentRun", &agentRunServiceProcessorAgentRun{handler: handler}) self.AddToProcessorMap("ChatV3", &agentRunServiceProcessorChatV3{handler: handler}) + self.AddToProcessorMap("CancelChatApi", &agentRunServiceProcessorCancelChatApi{handler: handler}) return self } func (p *AgentRunServiceProcessor) Process(ctx context.Context, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { @@ -198,6 +227,54 @@ func (p *agentRunServiceProcessorChatV3) Process(ctx context.Context, seqId int3 return true, err } +type agentRunServiceProcessorCancelChatApi struct { + handler AgentRunService +} + +func (p *agentRunServiceProcessorCancelChatApi) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := AgentRunServiceCancelChatApiArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("CancelChatApi", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := AgentRunServiceCancelChatApiResult{} + var retval *run.CancelChatApiResponse + if retval, err2 = p.handler.CancelChatApi(ctx, args.Request); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing CancelChatApi: "+err2.Error()) + oprot.WriteMessageBegin("CancelChatApi", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("CancelChatApi", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + type AgentRunServiceAgentRunArgs struct { Request *run.AgentRunRequest `thrift:"request,1"` } @@ -781,3 +858,295 @@ func (p *AgentRunServiceChatV3Result) String() string { return fmt.Sprintf("AgentRunServiceChatV3Result(%+v)", *p) } + +type AgentRunServiceCancelChatApiArgs struct { + Request *run.CancelChatApiRequest `thrift:"request,1"` +} + +func NewAgentRunServiceCancelChatApiArgs() *AgentRunServiceCancelChatApiArgs { + return &AgentRunServiceCancelChatApiArgs{} +} + +func (p *AgentRunServiceCancelChatApiArgs) InitDefault() { +} + +var AgentRunServiceCancelChatApiArgs_Request_DEFAULT *run.CancelChatApiRequest + +func (p *AgentRunServiceCancelChatApiArgs) GetRequest() (v *run.CancelChatApiRequest) { + if !p.IsSetRequest() { + return AgentRunServiceCancelChatApiArgs_Request_DEFAULT + } + return p.Request +} + +var fieldIDToName_AgentRunServiceCancelChatApiArgs = map[int16]string{ + 1: "request", +} + +func (p *AgentRunServiceCancelChatApiArgs) IsSetRequest() bool { + return p.Request != nil +} + +func (p *AgentRunServiceCancelChatApiArgs) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_AgentRunServiceCancelChatApiArgs[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *AgentRunServiceCancelChatApiArgs) ReadField1(iprot thrift.TProtocol) error { + _field := run.NewCancelChatApiRequest() + if err := _field.Read(iprot); err != nil { + return err + } + p.Request = _field + return nil +} + +func (p *AgentRunServiceCancelChatApiArgs) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("CancelChatApi_args"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *AgentRunServiceCancelChatApiArgs) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("request", thrift.STRUCT, 1); err != nil { + goto WriteFieldBeginError + } + if err := p.Request.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} + +func (p *AgentRunServiceCancelChatApiArgs) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("AgentRunServiceCancelChatApiArgs(%+v)", *p) + +} + +type AgentRunServiceCancelChatApiResult struct { + Success *run.CancelChatApiResponse `thrift:"success,0,optional"` +} + +func NewAgentRunServiceCancelChatApiResult() *AgentRunServiceCancelChatApiResult { + return &AgentRunServiceCancelChatApiResult{} +} + +func (p *AgentRunServiceCancelChatApiResult) InitDefault() { +} + +var AgentRunServiceCancelChatApiResult_Success_DEFAULT *run.CancelChatApiResponse + +func (p *AgentRunServiceCancelChatApiResult) GetSuccess() (v *run.CancelChatApiResponse) { + if !p.IsSetSuccess() { + return AgentRunServiceCancelChatApiResult_Success_DEFAULT + } + return p.Success +} + +var fieldIDToName_AgentRunServiceCancelChatApiResult = map[int16]string{ + 0: "success", +} + +func (p *AgentRunServiceCancelChatApiResult) IsSetSuccess() bool { + return p.Success != nil +} + +func (p *AgentRunServiceCancelChatApiResult) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField0(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_AgentRunServiceCancelChatApiResult[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *AgentRunServiceCancelChatApiResult) ReadField0(iprot thrift.TProtocol) error { + _field := run.NewCancelChatApiResponse() + if err := _field.Read(iprot); err != nil { + return err + } + p.Success = _field + return nil +} + +func (p *AgentRunServiceCancelChatApiResult) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("CancelChatApi_result"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField0(oprot); err != nil { + fieldId = 0 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *AgentRunServiceCancelChatApiResult) writeField0(oprot thrift.TProtocol) (err error) { + if p.IsSetSuccess() { + if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { + goto WriteFieldBeginError + } + if err := p.Success.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) +} + +func (p *AgentRunServiceCancelChatApiResult) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("AgentRunServiceCancelChatApiResult(%+v)", *p) + +} diff --git a/backend/api/model/conversation/common/common.go b/backend/api/model/conversation/common/common.go index 4e4e362b..d06ddf81 100644 --- a/backend/api/model/conversation/common/common.go +++ b/backend/api/model/conversation/common/common.go @@ -1,3 +1,19 @@ +/* + * 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.1). DO NOT EDIT. package common @@ -26,6 +42,8 @@ const ( Scene_GenerateAgentInfo Scene = 8 //openapi Scene_SceneOpenApi Scene = 9 + // workflow + Scene_SceneWorkflow Scene = 50 ) func (p Scene) String() string { @@ -50,6 +68,8 @@ func (p Scene) String() string { return "GenerateAgentInfo" case Scene_SceneOpenApi: return "SceneOpenApi" + case Scene_SceneWorkflow: + return "SceneWorkflow" } return "" } @@ -76,6 +96,8 @@ func SceneFromString(s string) (Scene, error) { return Scene_GenerateAgentInfo, nil case "SceneOpenApi": return Scene_SceneOpenApi, nil + case "SceneWorkflow": + return Scene_SceneWorkflow, nil } return Scene(0), fmt.Errorf("not a valid Scene string") } diff --git a/backend/api/model/conversation/conversation/conversation.go b/backend/api/model/conversation/conversation/conversation.go index e2aae59a..eecb2fc2 100644 --- a/backend/api/model/conversation/conversation/conversation.go +++ b/backend/api/model/conversation/conversation/conversation.go @@ -1,9 +1,26 @@ +/* + * 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.1). DO NOT EDIT. package conversation import ( "fmt" + "github.com/apache/thrift/lib/go/thrift" "github.com/coze-dev/coze-studio/backend/api/model/base" "github.com/coze-dev/coze-studio/backend/api/model/conversation/common" @@ -1032,6 +1049,7 @@ type ConversationData struct { ConnectorID *int64 `thrift:"ConnectorID,5,optional" form:"connector_id" json:"connector_id,string,omitempty"` LastSectionID *int64 `thrift:"LastSectionID,6,optional" form:"last_section_id" json:"last_section_id,string,omitempty"` AccountID *int64 `thrift:"AccountID,7,optional" form:"account_id" json:"account_id,omitempty"` + Name *string `thrift:"Name,8,optional" form:"name" json:"name,omitempty"` } func NewConversationData() *ConversationData { @@ -1089,6 +1107,15 @@ func (p *ConversationData) GetAccountID() (v int64) { return *p.AccountID } +var ConversationData_Name_DEFAULT string + +func (p *ConversationData) GetName() (v string) { + if !p.IsSetName() { + return ConversationData_Name_DEFAULT + } + return *p.Name +} + var fieldIDToName_ConversationData = map[int16]string{ 1: "Id", 2: "CreatedAt", @@ -1097,6 +1124,7 @@ var fieldIDToName_ConversationData = map[int16]string{ 5: "ConnectorID", 6: "LastSectionID", 7: "AccountID", + 8: "Name", } func (p *ConversationData) IsSetCreatorID() bool { @@ -1115,6 +1143,10 @@ func (p *ConversationData) IsSetAccountID() bool { return p.AccountID != nil } +func (p *ConversationData) IsSetName() bool { + return p.Name != nil +} + func (p *ConversationData) Read(iprot thrift.TProtocol) (err error) { var fieldTypeId thrift.TType var fieldId int16 @@ -1189,6 +1221,14 @@ func (p *ConversationData) Read(iprot thrift.TProtocol) (err error) { } else if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError } + case 8: + if fieldTypeId == thrift.STRING { + if err = p.ReadField8(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } default: if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError @@ -1313,6 +1353,17 @@ func (p *ConversationData) ReadField7(iprot thrift.TProtocol) error { p.AccountID = _field return nil } +func (p *ConversationData) ReadField8(iprot thrift.TProtocol) error { + + var _field *string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = &v + } + p.Name = _field + return nil +} func (p *ConversationData) Write(oprot thrift.TProtocol) (err error) { var fieldId int16 @@ -1348,6 +1399,10 @@ func (p *ConversationData) Write(oprot thrift.TProtocol) (err error) { fieldId = 7 goto WriteFieldError } + if err = p.writeField8(oprot); err != nil { + fieldId = 8 + goto WriteFieldError + } } if err = oprot.WriteFieldStop(); err != nil { goto WriteFieldStopError @@ -1497,6 +1552,24 @@ WriteFieldBeginError: WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 7 end error: ", p), err) } +func (p *ConversationData) writeField8(oprot thrift.TProtocol) (err error) { + if p.IsSetName() { + if err = oprot.WriteFieldBegin("Name", thrift.STRING, 8); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(*p.Name); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 8 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 8 end error: ", p), err) +} func (p *ConversationData) String() string { if p == nil { @@ -3633,3 +3706,992 @@ func (p *ListConversationData) String() string { return fmt.Sprintf("ListConversationData(%+v)", *p) } + +type UpdateConversationApiRequest struct { + ConversationID *int64 `thrift:"ConversationID,1,optional" json:"ConversationID,string,omitempty" path:"conversation_id"` + Name *string `thrift:"Name,2,optional" form:"name" json:"name,omitempty"` + Base *base.Base `thrift:"Base,255" form:"Base" json:"Base" query:"Base"` +} + +func NewUpdateConversationApiRequest() *UpdateConversationApiRequest { + return &UpdateConversationApiRequest{} +} + +func (p *UpdateConversationApiRequest) InitDefault() { +} + +var UpdateConversationApiRequest_ConversationID_DEFAULT int64 + +func (p *UpdateConversationApiRequest) GetConversationID() (v int64) { + if !p.IsSetConversationID() { + return UpdateConversationApiRequest_ConversationID_DEFAULT + } + return *p.ConversationID +} + +var UpdateConversationApiRequest_Name_DEFAULT string + +func (p *UpdateConversationApiRequest) GetName() (v string) { + if !p.IsSetName() { + return UpdateConversationApiRequest_Name_DEFAULT + } + return *p.Name +} + +var UpdateConversationApiRequest_Base_DEFAULT *base.Base + +func (p *UpdateConversationApiRequest) GetBase() (v *base.Base) { + if !p.IsSetBase() { + return UpdateConversationApiRequest_Base_DEFAULT + } + return p.Base +} + +var fieldIDToName_UpdateConversationApiRequest = map[int16]string{ + 1: "ConversationID", + 2: "Name", + 255: "Base", +} + +func (p *UpdateConversationApiRequest) IsSetConversationID() bool { + return p.ConversationID != nil +} + +func (p *UpdateConversationApiRequest) IsSetName() bool { + return p.Name != nil +} + +func (p *UpdateConversationApiRequest) IsSetBase() bool { + return p.Base != nil +} + +func (p *UpdateConversationApiRequest) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.I64 { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.STRING { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 255: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField255(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UpdateConversationApiRequest[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *UpdateConversationApiRequest) ReadField1(iprot thrift.TProtocol) error { + + var _field *int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = &v + } + p.ConversationID = _field + return nil +} +func (p *UpdateConversationApiRequest) ReadField2(iprot thrift.TProtocol) error { + + var _field *string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = &v + } + p.Name = _field + return nil +} +func (p *UpdateConversationApiRequest) ReadField255(iprot thrift.TProtocol) error { + _field := base.NewBase() + if err := _field.Read(iprot); err != nil { + return err + } + p.Base = _field + return nil +} + +func (p *UpdateConversationApiRequest) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("UpdateConversationApiRequest"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField255(oprot); err != nil { + fieldId = 255 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UpdateConversationApiRequest) writeField1(oprot thrift.TProtocol) (err error) { + if p.IsSetConversationID() { + if err = oprot.WriteFieldBegin("ConversationID", thrift.I64, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(*p.ConversationID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *UpdateConversationApiRequest) writeField2(oprot thrift.TProtocol) (err error) { + if p.IsSetName() { + if err = oprot.WriteFieldBegin("Name", thrift.STRING, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(*p.Name); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} +func (p *UpdateConversationApiRequest) writeField255(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("Base", thrift.STRUCT, 255); err != nil { + goto WriteFieldBeginError + } + if err := p.Base.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 end error: ", p), err) +} + +func (p *UpdateConversationApiRequest) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UpdateConversationApiRequest(%+v)", *p) + +} + +type UpdateConversationApiResponse struct { + ConversationData *ConversationData `thrift:"ConversationData,1,optional" form:"data" json:"data,omitempty"` + Code int32 `thrift:"Code,2" form:"code" json:"code"` + Msg string `thrift:"Msg,3" form:"msg" json:"msg"` + BaseResp *base.BaseResp `thrift:"BaseResp,255,required" form:"BaseResp,required" json:"BaseResp,required" query:"BaseResp,required"` +} + +func NewUpdateConversationApiResponse() *UpdateConversationApiResponse { + return &UpdateConversationApiResponse{} +} + +func (p *UpdateConversationApiResponse) InitDefault() { +} + +var UpdateConversationApiResponse_ConversationData_DEFAULT *ConversationData + +func (p *UpdateConversationApiResponse) GetConversationData() (v *ConversationData) { + if !p.IsSetConversationData() { + return UpdateConversationApiResponse_ConversationData_DEFAULT + } + return p.ConversationData +} + +func (p *UpdateConversationApiResponse) GetCode() (v int32) { + return p.Code +} + +func (p *UpdateConversationApiResponse) GetMsg() (v string) { + return p.Msg +} + +var UpdateConversationApiResponse_BaseResp_DEFAULT *base.BaseResp + +func (p *UpdateConversationApiResponse) GetBaseResp() (v *base.BaseResp) { + if !p.IsSetBaseResp() { + return UpdateConversationApiResponse_BaseResp_DEFAULT + } + return p.BaseResp +} + +var fieldIDToName_UpdateConversationApiResponse = map[int16]string{ + 1: "ConversationData", + 2: "Code", + 3: "Msg", + 255: "BaseResp", +} + +func (p *UpdateConversationApiResponse) IsSetConversationData() bool { + return p.ConversationData != nil +} + +func (p *UpdateConversationApiResponse) IsSetBaseResp() bool { + return p.BaseResp != nil +} + +func (p *UpdateConversationApiResponse) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + var issetBaseResp bool = false + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.I32 { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 3: + if fieldTypeId == thrift.STRING { + if err = p.ReadField3(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 255: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField255(iprot); err != nil { + goto ReadFieldError + } + issetBaseResp = true + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + if !issetBaseResp { + fieldId = 255 + goto RequiredFieldNotSetError + } + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_UpdateConversationApiResponse[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +RequiredFieldNotSetError: + return thrift.NewTProtocolExceptionWithType(thrift.INVALID_DATA, fmt.Errorf("required field %s is not set", fieldIDToName_UpdateConversationApiResponse[fieldId])) +} + +func (p *UpdateConversationApiResponse) ReadField1(iprot thrift.TProtocol) error { + _field := NewConversationData() + if err := _field.Read(iprot); err != nil { + return err + } + p.ConversationData = _field + return nil +} +func (p *UpdateConversationApiResponse) ReadField2(iprot thrift.TProtocol) error { + + var _field int32 + if v, err := iprot.ReadI32(); err != nil { + return err + } else { + _field = v + } + p.Code = _field + return nil +} +func (p *UpdateConversationApiResponse) ReadField3(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Msg = _field + return nil +} +func (p *UpdateConversationApiResponse) ReadField255(iprot thrift.TProtocol) error { + _field := base.NewBaseResp() + if err := _field.Read(iprot); err != nil { + return err + } + p.BaseResp = _field + return nil +} + +func (p *UpdateConversationApiResponse) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("UpdateConversationApiResponse"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField3(oprot); err != nil { + fieldId = 3 + goto WriteFieldError + } + if err = p.writeField255(oprot); err != nil { + fieldId = 255 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *UpdateConversationApiResponse) writeField1(oprot thrift.TProtocol) (err error) { + if p.IsSetConversationData() { + if err = oprot.WriteFieldBegin("ConversationData", thrift.STRUCT, 1); err != nil { + goto WriteFieldBeginError + } + if err := p.ConversationData.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *UpdateConversationApiResponse) writeField2(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("Code", thrift.I32, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI32(p.Code); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} +func (p *UpdateConversationApiResponse) writeField3(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("Msg", thrift.STRING, 3); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Msg); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err) +} +func (p *UpdateConversationApiResponse) writeField255(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("BaseResp", thrift.STRUCT, 255); err != nil { + goto WriteFieldBeginError + } + if err := p.BaseResp.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 end error: ", p), err) +} + +func (p *UpdateConversationApiResponse) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("UpdateConversationApiResponse(%+v)", *p) + +} + +type DeleteConversationApiRequest struct { + ConversationID *int64 `thrift:"ConversationID,1,optional" json:"ConversationID,omitempty" path:"conversation_id"` + Base *base.Base `thrift:"Base,255" form:"Base" json:"Base" query:"Base"` +} + +func NewDeleteConversationApiRequest() *DeleteConversationApiRequest { + return &DeleteConversationApiRequest{} +} + +func (p *DeleteConversationApiRequest) InitDefault() { +} + +var DeleteConversationApiRequest_ConversationID_DEFAULT int64 + +func (p *DeleteConversationApiRequest) GetConversationID() (v int64) { + if !p.IsSetConversationID() { + return DeleteConversationApiRequest_ConversationID_DEFAULT + } + return *p.ConversationID +} + +var DeleteConversationApiRequest_Base_DEFAULT *base.Base + +func (p *DeleteConversationApiRequest) GetBase() (v *base.Base) { + if !p.IsSetBase() { + return DeleteConversationApiRequest_Base_DEFAULT + } + return p.Base +} + +var fieldIDToName_DeleteConversationApiRequest = map[int16]string{ + 1: "ConversationID", + 255: "Base", +} + +func (p *DeleteConversationApiRequest) IsSetConversationID() bool { + return p.ConversationID != nil +} + +func (p *DeleteConversationApiRequest) IsSetBase() bool { + return p.Base != nil +} + +func (p *DeleteConversationApiRequest) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.I64 { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 255: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField255(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_DeleteConversationApiRequest[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *DeleteConversationApiRequest) ReadField1(iprot thrift.TProtocol) error { + + var _field *int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = &v + } + p.ConversationID = _field + return nil +} +func (p *DeleteConversationApiRequest) ReadField255(iprot thrift.TProtocol) error { + _field := base.NewBase() + if err := _field.Read(iprot); err != nil { + return err + } + p.Base = _field + return nil +} + +func (p *DeleteConversationApiRequest) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("DeleteConversationApiRequest"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField255(oprot); err != nil { + fieldId = 255 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *DeleteConversationApiRequest) writeField1(oprot thrift.TProtocol) (err error) { + if p.IsSetConversationID() { + if err = oprot.WriteFieldBegin("ConversationID", thrift.I64, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(*p.ConversationID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *DeleteConversationApiRequest) writeField255(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("Base", thrift.STRUCT, 255); err != nil { + goto WriteFieldBeginError + } + if err := p.Base.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 end error: ", p), err) +} + +func (p *DeleteConversationApiRequest) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("DeleteConversationApiRequest(%+v)", *p) + +} + +type DeleteConversationApiResponse struct { + Code int32 `thrift:"Code,1" form:"code" json:"code"` + Msg string `thrift:"Msg,2" form:"msg" json:"msg"` + BaseResp *base.BaseResp `thrift:"BaseResp,255" form:"BaseResp" json:"BaseResp" query:"BaseResp"` +} + +func NewDeleteConversationApiResponse() *DeleteConversationApiResponse { + return &DeleteConversationApiResponse{} +} + +func (p *DeleteConversationApiResponse) InitDefault() { +} + +func (p *DeleteConversationApiResponse) GetCode() (v int32) { + return p.Code +} + +func (p *DeleteConversationApiResponse) GetMsg() (v string) { + return p.Msg +} + +var DeleteConversationApiResponse_BaseResp_DEFAULT *base.BaseResp + +func (p *DeleteConversationApiResponse) GetBaseResp() (v *base.BaseResp) { + if !p.IsSetBaseResp() { + return DeleteConversationApiResponse_BaseResp_DEFAULT + } + return p.BaseResp +} + +var fieldIDToName_DeleteConversationApiResponse = map[int16]string{ + 1: "Code", + 2: "Msg", + 255: "BaseResp", +} + +func (p *DeleteConversationApiResponse) IsSetBaseResp() bool { + return p.BaseResp != nil +} + +func (p *DeleteConversationApiResponse) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.I32 { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.STRING { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 255: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField255(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_DeleteConversationApiResponse[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *DeleteConversationApiResponse) ReadField1(iprot thrift.TProtocol) error { + + var _field int32 + if v, err := iprot.ReadI32(); err != nil { + return err + } else { + _field = v + } + p.Code = _field + return nil +} +func (p *DeleteConversationApiResponse) ReadField2(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Msg = _field + return nil +} +func (p *DeleteConversationApiResponse) ReadField255(iprot thrift.TProtocol) error { + _field := base.NewBaseResp() + if err := _field.Read(iprot); err != nil { + return err + } + p.BaseResp = _field + return nil +} + +func (p *DeleteConversationApiResponse) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("DeleteConversationApiResponse"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField255(oprot); err != nil { + fieldId = 255 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *DeleteConversationApiResponse) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("Code", thrift.I32, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI32(p.Code); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *DeleteConversationApiResponse) writeField2(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("Msg", thrift.STRING, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Msg); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} +func (p *DeleteConversationApiResponse) writeField255(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("BaseResp", thrift.STRUCT, 255); err != nil { + goto WriteFieldBeginError + } + if err := p.BaseResp.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 end error: ", p), err) +} + +func (p *DeleteConversationApiResponse) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("DeleteConversationApiResponse(%+v)", *p) + +} diff --git a/backend/api/model/conversation/conversation/conversation_service.go b/backend/api/model/conversation/conversation/conversation_service.go index 9c9ed8cc..303743fd 100644 --- a/backend/api/model/conversation/conversation/conversation_service.go +++ b/backend/api/model/conversation/conversation/conversation_service.go @@ -1,3 +1,19 @@ +/* + * 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.1). DO NOT EDIT. package conversation @@ -5,6 +21,7 @@ package conversation import ( "context" "fmt" + "github.com/apache/thrift/lib/go/thrift" ) @@ -18,6 +35,10 @@ type ConversationService interface { ClearConversationApi(ctx context.Context, req *ClearConversationApiRequest) (r *ClearConversationApiResponse, err error) ListConversationsApi(ctx context.Context, request *ListConversationsApiRequest) (r *ListConversationsApiResponse, err error) + + UpdateConversationApi(ctx context.Context, request *UpdateConversationApiRequest) (r *UpdateConversationApiResponse, err error) + + DeleteConversationApi(ctx context.Context, req *DeleteConversationApiRequest) (r *DeleteConversationApiResponse, err error) } type ConversationServiceClient struct { @@ -91,6 +112,24 @@ func (p *ConversationServiceClient) ListConversationsApi(ctx context.Context, re } return _result.GetSuccess(), nil } +func (p *ConversationServiceClient) UpdateConversationApi(ctx context.Context, request *UpdateConversationApiRequest) (r *UpdateConversationApiResponse, err error) { + var _args ConversationServiceUpdateConversationApiArgs + _args.Request = request + var _result ConversationServiceUpdateConversationApiResult + if err = p.Client_().Call(ctx, "UpdateConversationApi", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} +func (p *ConversationServiceClient) DeleteConversationApi(ctx context.Context, req *DeleteConversationApiRequest) (r *DeleteConversationApiResponse, err error) { + var _args ConversationServiceDeleteConversationApiArgs + _args.Req = req + var _result ConversationServiceDeleteConversationApiResult + if err = p.Client_().Call(ctx, "DeleteConversationApi", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} type ConversationServiceProcessor struct { processorMap map[string]thrift.TProcessorFunction @@ -117,6 +156,8 @@ func NewConversationServiceProcessor(handler ConversationService) *ConversationS self.AddToProcessorMap("CreateConversation", &conversationServiceProcessorCreateConversation{handler: handler}) self.AddToProcessorMap("ClearConversationApi", &conversationServiceProcessorClearConversationApi{handler: handler}) self.AddToProcessorMap("ListConversationsApi", &conversationServiceProcessorListConversationsApi{handler: handler}) + self.AddToProcessorMap("UpdateConversationApi", &conversationServiceProcessorUpdateConversationApi{handler: handler}) + self.AddToProcessorMap("DeleteConversationApi", &conversationServiceProcessorDeleteConversationApi{handler: handler}) return self } func (p *ConversationServiceProcessor) Process(ctx context.Context, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { @@ -377,6 +418,102 @@ func (p *conversationServiceProcessorListConversationsApi) Process(ctx context.C return true, err } +type conversationServiceProcessorUpdateConversationApi struct { + handler ConversationService +} + +func (p *conversationServiceProcessorUpdateConversationApi) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := ConversationServiceUpdateConversationApiArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("UpdateConversationApi", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := ConversationServiceUpdateConversationApiResult{} + var retval *UpdateConversationApiResponse + if retval, err2 = p.handler.UpdateConversationApi(ctx, args.Request); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing UpdateConversationApi: "+err2.Error()) + oprot.WriteMessageBegin("UpdateConversationApi", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("UpdateConversationApi", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + +type conversationServiceProcessorDeleteConversationApi struct { + handler ConversationService +} + +func (p *conversationServiceProcessorDeleteConversationApi) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := ConversationServiceDeleteConversationApiArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("DeleteConversationApi", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := ConversationServiceDeleteConversationApiResult{} + var retval *DeleteConversationApiResponse + if retval, err2 = p.handler.DeleteConversationApi(ctx, args.Req); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing DeleteConversationApi: "+err2.Error()) + oprot.WriteMessageBegin("DeleteConversationApi", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("DeleteConversationApi", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + type ConversationServiceClearConversationCtxArgs struct { Request *ClearConversationCtxRequest `thrift:"request,1"` } @@ -1836,3 +1973,587 @@ func (p *ConversationServiceListConversationsApiResult) String() string { return fmt.Sprintf("ConversationServiceListConversationsApiResult(%+v)", *p) } + +type ConversationServiceUpdateConversationApiArgs struct { + Request *UpdateConversationApiRequest `thrift:"request,1"` +} + +func NewConversationServiceUpdateConversationApiArgs() *ConversationServiceUpdateConversationApiArgs { + return &ConversationServiceUpdateConversationApiArgs{} +} + +func (p *ConversationServiceUpdateConversationApiArgs) InitDefault() { +} + +var ConversationServiceUpdateConversationApiArgs_Request_DEFAULT *UpdateConversationApiRequest + +func (p *ConversationServiceUpdateConversationApiArgs) GetRequest() (v *UpdateConversationApiRequest) { + if !p.IsSetRequest() { + return ConversationServiceUpdateConversationApiArgs_Request_DEFAULT + } + return p.Request +} + +var fieldIDToName_ConversationServiceUpdateConversationApiArgs = map[int16]string{ + 1: "request", +} + +func (p *ConversationServiceUpdateConversationApiArgs) IsSetRequest() bool { + return p.Request != nil +} + +func (p *ConversationServiceUpdateConversationApiArgs) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_ConversationServiceUpdateConversationApiArgs[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *ConversationServiceUpdateConversationApiArgs) ReadField1(iprot thrift.TProtocol) error { + _field := NewUpdateConversationApiRequest() + if err := _field.Read(iprot); err != nil { + return err + } + p.Request = _field + return nil +} + +func (p *ConversationServiceUpdateConversationApiArgs) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("UpdateConversationApi_args"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *ConversationServiceUpdateConversationApiArgs) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("request", thrift.STRUCT, 1); err != nil { + goto WriteFieldBeginError + } + if err := p.Request.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} + +func (p *ConversationServiceUpdateConversationApiArgs) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("ConversationServiceUpdateConversationApiArgs(%+v)", *p) + +} + +type ConversationServiceUpdateConversationApiResult struct { + Success *UpdateConversationApiResponse `thrift:"success,0,optional"` +} + +func NewConversationServiceUpdateConversationApiResult() *ConversationServiceUpdateConversationApiResult { + return &ConversationServiceUpdateConversationApiResult{} +} + +func (p *ConversationServiceUpdateConversationApiResult) InitDefault() { +} + +var ConversationServiceUpdateConversationApiResult_Success_DEFAULT *UpdateConversationApiResponse + +func (p *ConversationServiceUpdateConversationApiResult) GetSuccess() (v *UpdateConversationApiResponse) { + if !p.IsSetSuccess() { + return ConversationServiceUpdateConversationApiResult_Success_DEFAULT + } + return p.Success +} + +var fieldIDToName_ConversationServiceUpdateConversationApiResult = map[int16]string{ + 0: "success", +} + +func (p *ConversationServiceUpdateConversationApiResult) IsSetSuccess() bool { + return p.Success != nil +} + +func (p *ConversationServiceUpdateConversationApiResult) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField0(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_ConversationServiceUpdateConversationApiResult[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *ConversationServiceUpdateConversationApiResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewUpdateConversationApiResponse() + if err := _field.Read(iprot); err != nil { + return err + } + p.Success = _field + return nil +} + +func (p *ConversationServiceUpdateConversationApiResult) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("UpdateConversationApi_result"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField0(oprot); err != nil { + fieldId = 0 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *ConversationServiceUpdateConversationApiResult) writeField0(oprot thrift.TProtocol) (err error) { + if p.IsSetSuccess() { + if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { + goto WriteFieldBeginError + } + if err := p.Success.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) +} + +func (p *ConversationServiceUpdateConversationApiResult) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("ConversationServiceUpdateConversationApiResult(%+v)", *p) + +} + +type ConversationServiceDeleteConversationApiArgs struct { + Req *DeleteConversationApiRequest `thrift:"req,1"` +} + +func NewConversationServiceDeleteConversationApiArgs() *ConversationServiceDeleteConversationApiArgs { + return &ConversationServiceDeleteConversationApiArgs{} +} + +func (p *ConversationServiceDeleteConversationApiArgs) InitDefault() { +} + +var ConversationServiceDeleteConversationApiArgs_Req_DEFAULT *DeleteConversationApiRequest + +func (p *ConversationServiceDeleteConversationApiArgs) GetReq() (v *DeleteConversationApiRequest) { + if !p.IsSetReq() { + return ConversationServiceDeleteConversationApiArgs_Req_DEFAULT + } + return p.Req +} + +var fieldIDToName_ConversationServiceDeleteConversationApiArgs = map[int16]string{ + 1: "req", +} + +func (p *ConversationServiceDeleteConversationApiArgs) IsSetReq() bool { + return p.Req != nil +} + +func (p *ConversationServiceDeleteConversationApiArgs) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_ConversationServiceDeleteConversationApiArgs[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *ConversationServiceDeleteConversationApiArgs) ReadField1(iprot thrift.TProtocol) error { + _field := NewDeleteConversationApiRequest() + if err := _field.Read(iprot); err != nil { + return err + } + p.Req = _field + return nil +} + +func (p *ConversationServiceDeleteConversationApiArgs) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("DeleteConversationApi_args"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *ConversationServiceDeleteConversationApiArgs) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("req", thrift.STRUCT, 1); err != nil { + goto WriteFieldBeginError + } + if err := p.Req.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} + +func (p *ConversationServiceDeleteConversationApiArgs) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("ConversationServiceDeleteConversationApiArgs(%+v)", *p) + +} + +type ConversationServiceDeleteConversationApiResult struct { + Success *DeleteConversationApiResponse `thrift:"success,0,optional"` +} + +func NewConversationServiceDeleteConversationApiResult() *ConversationServiceDeleteConversationApiResult { + return &ConversationServiceDeleteConversationApiResult{} +} + +func (p *ConversationServiceDeleteConversationApiResult) InitDefault() { +} + +var ConversationServiceDeleteConversationApiResult_Success_DEFAULT *DeleteConversationApiResponse + +func (p *ConversationServiceDeleteConversationApiResult) GetSuccess() (v *DeleteConversationApiResponse) { + if !p.IsSetSuccess() { + return ConversationServiceDeleteConversationApiResult_Success_DEFAULT + } + return p.Success +} + +var fieldIDToName_ConversationServiceDeleteConversationApiResult = map[int16]string{ + 0: "success", +} + +func (p *ConversationServiceDeleteConversationApiResult) IsSetSuccess() bool { + return p.Success != nil +} + +func (p *ConversationServiceDeleteConversationApiResult) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField0(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_ConversationServiceDeleteConversationApiResult[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *ConversationServiceDeleteConversationApiResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewDeleteConversationApiResponse() + if err := _field.Read(iprot); err != nil { + return err + } + p.Success = _field + return nil +} + +func (p *ConversationServiceDeleteConversationApiResult) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("DeleteConversationApi_result"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField0(oprot); err != nil { + fieldId = 0 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *ConversationServiceDeleteConversationApiResult) writeField0(oprot thrift.TProtocol) (err error) { + if p.IsSetSuccess() { + if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { + goto WriteFieldBeginError + } + if err := p.Success.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) +} + +func (p *ConversationServiceDeleteConversationApiResult) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("ConversationServiceDeleteConversationApiResult(%+v)", *p) + +} diff --git a/backend/api/model/conversation/message/message.go b/backend/api/model/conversation/message/message.go index 04759f1b..29d9ff3c 100644 --- a/backend/api/model/conversation/message/message.go +++ b/backend/api/model/conversation/message/message.go @@ -1,3 +1,19 @@ +/* + * 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.1). DO NOT EDIT. package message @@ -6,6 +22,7 @@ import ( "database/sql" "database/sql/driver" "fmt" + "github.com/apache/thrift/lib/go/thrift" "github.com/coze-dev/coze-studio/backend/api/model/base" "github.com/coze-dev/coze-studio/backend/api/model/conversation/common" @@ -7567,6 +7584,8 @@ type ListMessageApiResponse struct { FirstID *int64 `thrift:"first_id,3,optional" form:"first_id" json:"first_id,string,omitempty"` // The id of the last piece of data. LastID *int64 `thrift:"last_id,4,optional" form:"last_id" json:"last_id,string,omitempty"` + Code int64 `thrift:"code,253" form:"code" json:"code" query:"code"` + Msg string `thrift:"msg,254" form:"msg" json:"msg" query:"msg"` } func NewListMessageApiResponse() *ListMessageApiResponse { @@ -7612,11 +7631,21 @@ func (p *ListMessageApiResponse) GetLastID() (v int64) { return *p.LastID } +func (p *ListMessageApiResponse) GetCode() (v int64) { + return p.Code +} + +func (p *ListMessageApiResponse) GetMsg() (v string) { + return p.Msg +} + var fieldIDToName_ListMessageApiResponse = map[int16]string{ - 1: "messages", - 2: "has_more", - 3: "first_id", - 4: "last_id", + 1: "messages", + 2: "has_more", + 3: "first_id", + 4: "last_id", + 253: "code", + 254: "msg", } func (p *ListMessageApiResponse) IsSetMessages() bool { @@ -7685,6 +7714,22 @@ func (p *ListMessageApiResponse) Read(iprot thrift.TProtocol) (err error) { } else if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError } + case 253: + if fieldTypeId == thrift.I64 { + if err = p.ReadField253(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 254: + if fieldTypeId == thrift.STRING { + if err = p.ReadField254(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } default: if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError @@ -7770,6 +7815,28 @@ func (p *ListMessageApiResponse) ReadField4(iprot thrift.TProtocol) error { p.LastID = _field return nil } +func (p *ListMessageApiResponse) ReadField253(iprot thrift.TProtocol) error { + + var _field int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = v + } + p.Code = _field + return nil +} +func (p *ListMessageApiResponse) ReadField254(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Msg = _field + return nil +} func (p *ListMessageApiResponse) Write(oprot thrift.TProtocol) (err error) { var fieldId int16 @@ -7793,6 +7860,14 @@ func (p *ListMessageApiResponse) Write(oprot thrift.TProtocol) (err error) { fieldId = 4 goto WriteFieldError } + if err = p.writeField253(oprot); err != nil { + fieldId = 253 + goto WriteFieldError + } + if err = p.writeField254(oprot); err != nil { + fieldId = 254 + goto WriteFieldError + } } if err = oprot.WriteFieldStop(); err != nil { goto WriteFieldStopError @@ -7891,6 +7966,38 @@ WriteFieldBeginError: WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 4 end error: ", p), err) } +func (p *ListMessageApiResponse) writeField253(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("code", thrift.I64, 253); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(p.Code); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 253 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 253 end error: ", p), err) +} +func (p *ListMessageApiResponse) writeField254(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("msg", thrift.STRING, 254); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Msg); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 254 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 254 end error: ", p), err) +} func (p *ListMessageApiResponse) String() string { if p == nil { diff --git a/backend/api/model/conversation/run/run.go b/backend/api/model/conversation/run/run.go index 3c540136..26adccf1 100644 --- a/backend/api/model/conversation/run/run.go +++ b/backend/api/model/conversation/run/run.go @@ -1,3 +1,19 @@ +/* + * 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.1). DO NOT EDIT. package run @@ -6,7 +22,9 @@ import ( "database/sql" "database/sql/driver" "fmt" + "github.com/apache/thrift/lib/go/thrift" + "github.com/coze-dev/coze-studio/backend/api/model/base" "github.com/coze-dev/coze-studio/backend/api/model/conversation/common" "github.com/coze-dev/coze-studio/backend/api/model/conversation/message" ) @@ -1875,6 +1893,7 @@ type AdditionalContent struct { Type string `thrift:"type,1,required" form:"type,required" json:"type,required" query:"type,required"` Text *string `thrift:"text,2,optional" form:"text" json:"text,omitempty" query:"text"` FileURL *string `thrift:"file_url,3,optional" form:"file_url" json:"file_url,omitempty" query:"file_url"` + FileID *int64 `thrift:"file_id,4,optional" form:"file_id" json:"file_id,string,omitempty" query:"file_id"` } func NewAdditionalContent() *AdditionalContent { @@ -1906,10 +1925,20 @@ func (p *AdditionalContent) GetFileURL() (v string) { return *p.FileURL } +var AdditionalContent_FileID_DEFAULT int64 + +func (p *AdditionalContent) GetFileID() (v int64) { + if !p.IsSetFileID() { + return AdditionalContent_FileID_DEFAULT + } + return *p.FileID +} + var fieldIDToName_AdditionalContent = map[int16]string{ 1: "type", 2: "text", 3: "file_url", + 4: "file_id", } func (p *AdditionalContent) IsSetText() bool { @@ -1920,6 +1949,10 @@ func (p *AdditionalContent) IsSetFileURL() bool { return p.FileURL != nil } +func (p *AdditionalContent) IsSetFileID() bool { + return p.FileID != nil +} + func (p *AdditionalContent) Read(iprot thrift.TProtocol) (err error) { var fieldTypeId thrift.TType var fieldId int16 @@ -1964,6 +1997,14 @@ func (p *AdditionalContent) Read(iprot thrift.TProtocol) (err error) { } else if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError } + case 4: + if fieldTypeId == thrift.I64 { + if err = p.ReadField4(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } default: if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError @@ -2032,6 +2073,17 @@ func (p *AdditionalContent) ReadField3(iprot thrift.TProtocol) error { p.FileURL = _field return nil } +func (p *AdditionalContent) ReadField4(iprot thrift.TProtocol) error { + + var _field *int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = &v + } + p.FileID = _field + return nil +} func (p *AdditionalContent) Write(oprot thrift.TProtocol) (err error) { var fieldId int16 @@ -2051,6 +2103,10 @@ func (p *AdditionalContent) Write(oprot thrift.TProtocol) (err error) { fieldId = 3 goto WriteFieldError } + if err = p.writeField4(oprot); err != nil { + fieldId = 4 + goto WriteFieldError + } } if err = oprot.WriteFieldStop(); err != nil { goto WriteFieldStopError @@ -2121,6 +2177,24 @@ WriteFieldBeginError: WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err) } +func (p *AdditionalContent) writeField4(oprot thrift.TProtocol) (err error) { + if p.IsSetFileID() { + if err = oprot.WriteFieldBegin("file_id", thrift.I64, 4); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(*p.FileID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 4 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 4 end error: ", p), err) +} func (p *AdditionalContent) String() string { if p == nil { @@ -9459,3 +9533,449 @@ func (p *ChatV3Response) String() string { return fmt.Sprintf("ChatV3Response(%+v)", *p) } + +type CancelChatApiRequest struct { + ChatID int64 `thrift:"ChatID,1,required" form:"chat_id,required" json:"chat_id,string,required"` + ConversationID int64 `thrift:"ConversationID,2,required" form:"conversation_id,required" json:"conversation_id,string,required"` + Base *base.Base `thrift:"Base,255" form:"Base" json:"Base" query:"Base"` +} + +func NewCancelChatApiRequest() *CancelChatApiRequest { + return &CancelChatApiRequest{} +} + +func (p *CancelChatApiRequest) InitDefault() { +} + +func (p *CancelChatApiRequest) GetChatID() (v int64) { + return p.ChatID +} + +func (p *CancelChatApiRequest) GetConversationID() (v int64) { + return p.ConversationID +} + +var CancelChatApiRequest_Base_DEFAULT *base.Base + +func (p *CancelChatApiRequest) GetBase() (v *base.Base) { + if !p.IsSetBase() { + return CancelChatApiRequest_Base_DEFAULT + } + return p.Base +} + +var fieldIDToName_CancelChatApiRequest = map[int16]string{ + 1: "ChatID", + 2: "ConversationID", + 255: "Base", +} + +func (p *CancelChatApiRequest) IsSetBase() bool { + return p.Base != nil +} + +func (p *CancelChatApiRequest) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + var issetChatID bool = false + var issetConversationID bool = false + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.I64 { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + issetChatID = true + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.I64 { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + issetConversationID = true + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 255: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField255(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + if !issetChatID { + fieldId = 1 + goto RequiredFieldNotSetError + } + + if !issetConversationID { + fieldId = 2 + goto RequiredFieldNotSetError + } + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_CancelChatApiRequest[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +RequiredFieldNotSetError: + return thrift.NewTProtocolExceptionWithType(thrift.INVALID_DATA, fmt.Errorf("required field %s is not set", fieldIDToName_CancelChatApiRequest[fieldId])) +} + +func (p *CancelChatApiRequest) ReadField1(iprot thrift.TProtocol) error { + + var _field int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = v + } + p.ChatID = _field + return nil +} +func (p *CancelChatApiRequest) ReadField2(iprot thrift.TProtocol) error { + + var _field int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = v + } + p.ConversationID = _field + return nil +} +func (p *CancelChatApiRequest) ReadField255(iprot thrift.TProtocol) error { + _field := base.NewBase() + if err := _field.Read(iprot); err != nil { + return err + } + p.Base = _field + return nil +} + +func (p *CancelChatApiRequest) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("CancelChatApiRequest"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField255(oprot); err != nil { + fieldId = 255 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *CancelChatApiRequest) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("ChatID", thrift.I64, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(p.ChatID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *CancelChatApiRequest) writeField2(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("ConversationID", thrift.I64, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(p.ConversationID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} +func (p *CancelChatApiRequest) writeField255(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("Base", thrift.STRUCT, 255); err != nil { + goto WriteFieldBeginError + } + if err := p.Base.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 end error: ", p), err) +} + +func (p *CancelChatApiRequest) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("CancelChatApiRequest(%+v)", *p) + +} + +type CancelChatApiResponse struct { + ChatV3ChatDetail *ChatV3ChatDetail `thrift:"ChatV3ChatDetail,1" form:"ChatV3ChatDetail" json:"ChatV3ChatDetail" query:"ChatV3ChatDetail"` + BaseResp *base.BaseResp `thrift:"BaseResp,255" form:"BaseResp" json:"BaseResp" query:"BaseResp"` +} + +func NewCancelChatApiResponse() *CancelChatApiResponse { + return &CancelChatApiResponse{} +} + +func (p *CancelChatApiResponse) InitDefault() { +} + +var CancelChatApiResponse_ChatV3ChatDetail_DEFAULT *ChatV3ChatDetail + +func (p *CancelChatApiResponse) GetChatV3ChatDetail() (v *ChatV3ChatDetail) { + if !p.IsSetChatV3ChatDetail() { + return CancelChatApiResponse_ChatV3ChatDetail_DEFAULT + } + return p.ChatV3ChatDetail +} + +var CancelChatApiResponse_BaseResp_DEFAULT *base.BaseResp + +func (p *CancelChatApiResponse) GetBaseResp() (v *base.BaseResp) { + if !p.IsSetBaseResp() { + return CancelChatApiResponse_BaseResp_DEFAULT + } + return p.BaseResp +} + +var fieldIDToName_CancelChatApiResponse = map[int16]string{ + 1: "ChatV3ChatDetail", + 255: "BaseResp", +} + +func (p *CancelChatApiResponse) IsSetChatV3ChatDetail() bool { + return p.ChatV3ChatDetail != nil +} + +func (p *CancelChatApiResponse) IsSetBaseResp() bool { + return p.BaseResp != nil +} + +func (p *CancelChatApiResponse) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 255: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField255(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_CancelChatApiResponse[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *CancelChatApiResponse) ReadField1(iprot thrift.TProtocol) error { + _field := NewChatV3ChatDetail() + if err := _field.Read(iprot); err != nil { + return err + } + p.ChatV3ChatDetail = _field + return nil +} +func (p *CancelChatApiResponse) ReadField255(iprot thrift.TProtocol) error { + _field := base.NewBaseResp() + if err := _field.Read(iprot); err != nil { + return err + } + p.BaseResp = _field + return nil +} + +func (p *CancelChatApiResponse) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("CancelChatApiResponse"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField255(oprot); err != nil { + fieldId = 255 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *CancelChatApiResponse) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("ChatV3ChatDetail", thrift.STRUCT, 1); err != nil { + goto WriteFieldBeginError + } + if err := p.ChatV3ChatDetail.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *CancelChatApiResponse) writeField255(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("BaseResp", thrift.STRUCT, 255); err != nil { + goto WriteFieldBeginError + } + if err := p.BaseResp.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 end error: ", p), err) +} + +func (p *CancelChatApiResponse) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("CancelChatApiResponse(%+v)", *p) + +} diff --git a/backend/api/model/coze/api.go b/backend/api/model/coze/api.go index a0e9afdd..1072569f 100644 --- a/backend/api/model/coze/api.go +++ b/backend/api/model/coze/api.go @@ -1,19 +1,3 @@ -/* - * 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.1). DO NOT EDIT. package coze diff --git a/backend/api/model/crossdomain/conversation/conversation.go b/backend/api/model/crossdomain/conversation/conversation.go index e0d995fb..6f6af5ef 100644 --- a/backend/api/model/crossdomain/conversation/conversation.go +++ b/backend/api/model/crossdomain/conversation/conversation.go @@ -42,6 +42,7 @@ const ( type Conversation struct { ID int64 `json:"id"` + Name string `json:"name"` SectionID int64 `json:"section_id"` AgentID int64 `json:"agent_id"` ConnectorID int64 `json:"connector_id"` diff --git a/backend/api/model/crossdomain/modelmgr/modelmgr.go b/backend/api/model/crossdomain/modelmgr/modelmgr.go index 1aad1d01..f49e5645 100644 --- a/backend/api/model/crossdomain/modelmgr/modelmgr.go +++ b/backend/api/model/crossdomain/modelmgr/modelmgr.go @@ -13,6 +13,7 @@ type LLMParams struct { EnableChatHistory bool `json:"enableChatHistory"` SystemPrompt string `json:"systemPrompt"` ResponseFormat ResponseFormat `json:"responseFormat"` + ChatHistoryRound int64 `json:"chatHistoryRound"` } type ResponseFormat int64 diff --git a/backend/api/model/crossdomain/singleagent/single_agent.go b/backend/api/model/crossdomain/singleagent/single_agent.go index b237455d..a20c32d5 100644 --- a/backend/api/model/crossdomain/singleagent/single_agent.go +++ b/backend/api/model/crossdomain/singleagent/single_agent.go @@ -26,14 +26,6 @@ import ( crossworkflow "github.com/coze-dev/coze-studio/backend/crossdomain/contract/workflow" ) -type AgentRuntime struct { - AgentVersion string - IsDraft bool - SpaceID int64 - ConnectorID int64 - PreRetrieveTools []*agentrun.Tool -} - type EventType string const ( @@ -84,6 +76,8 @@ type SingleAgent struct { JumpConfig *bot_common.JumpConfig BackgroundImageInfoList []*bot_common.BackgroundImageInfo Database []*bot_common.Database + BotMode bot_common.BotMode + LayoutInfo *bot_common.LayoutInfo ShortcutCommand []string } @@ -106,6 +100,8 @@ type InterruptInfo struct { ToolCallID string InterruptType InterruptEventType InterruptID string + + ChatflowInterrupt *crossworkflow.StateMessage } type ExecuteRequest struct { diff --git a/backend/api/model/crossdomain/workflow/workflow.go b/backend/api/model/crossdomain/workflow/workflow.go index 2f3ffa97..c8487759 100644 --- a/backend/api/model/crossdomain/workflow/workflow.go +++ b/backend/api/model/crossdomain/workflow/workflow.go @@ -16,6 +16,13 @@ package workflow +import ( + "github.com/cloudwego/eino/schema" + + "github.com/coze-dev/coze-studio/backend/api/model/workflow" + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" +) + type Locator uint8 const ( @@ -25,21 +32,30 @@ const ( ) type ExecuteConfig struct { - ID int64 - From Locator - Version string - CommitID string - Operator int64 - Mode ExecuteMode - AppID *int64 - AgentID *int64 - ConnectorID int64 - ConnectorUID string - TaskType TaskType - SyncPattern SyncPattern - InputFailFast bool // whether to fail fast if input conversion has warnings - BizType BizType - Cancellable bool + ID int64 + From Locator + Version string + CommitID string + Operator int64 + Mode ExecuteMode + AppID *int64 + AgentID *int64 + ConnectorID int64 + ConnectorUID string + TaskType TaskType + SyncPattern SyncPattern + InputFailFast bool // whether to fail fast if input conversion has warnings + BizType BizType + Cancellable bool + WorkflowMode WorkflowMode + RoundID *int64 // if workflow is chat flow, conversation round id is required + InitRoundID *int64 // if workflow is chat flow, init conversation round id is required + ConversationID *int64 // if workflow is chat flow, conversation id is required + UserMessage *schema.Message + ConversationHistory []*crossmessage.WfMessage + ConversationHistorySchemaMessages []*schema.Message + SectionID *int64 + MaxHistoryRounds *int32 } type ExecuteMode string @@ -50,6 +66,8 @@ const ( ExecuteModeNodeDebug ExecuteMode = "node_debug" ) +type WorkflowMode = workflow.WorkflowMode + type TaskType string const ( diff --git a/backend/api/model/playground/playground.go b/backend/api/model/playground/playground.go index b28e04c0..421341f4 100644 --- a/backend/api/model/playground/playground.go +++ b/backend/api/model/playground/playground.go @@ -1,3 +1,19 @@ +/* + * 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.1). DO NOT EDIT. package playground @@ -7,6 +23,7 @@ import ( "database/sql" "database/sql/driver" "fmt" + "github.com/apache/thrift/lib/go/thrift" "github.com/coze-dev/coze-studio/backend/api/model/app/bot_common" "github.com/coze-dev/coze-studio/backend/api/model/base" @@ -15881,329 +15898,6 @@ func (p *GetFileUrlsResponse) String() string { } -type File struct { - // File URI - URI string `thrift:"URI,1" form:"uri" json:"uri"` - // file bytes - Bytes int64 `thrift:"Bytes,2" form:"bytes" json:"bytes"` - // Upload timestamp in s - CreatedAt int64 `thrift:"CreatedAt,3" form:"CreatedAt" json:"CreatedAt" query:"CreatedAt"` - // file name - FileName string `thrift:"FileName,4" form:"file_name" json:"file_name"` - URL string `thrift:"URL,5" form:"url" json:"url"` -} - -func NewFile() *File { - return &File{} -} - -func (p *File) InitDefault() { -} - -func (p *File) GetURI() (v string) { - return p.URI -} - -func (p *File) GetBytes() (v int64) { - return p.Bytes -} - -func (p *File) GetCreatedAt() (v int64) { - return p.CreatedAt -} - -func (p *File) GetFileName() (v string) { - return p.FileName -} - -func (p *File) GetURL() (v string) { - return p.URL -} - -var fieldIDToName_File = map[int16]string{ - 1: "URI", - 2: "Bytes", - 3: "CreatedAt", - 4: "FileName", - 5: "URL", -} - -func (p *File) Read(iprot thrift.TProtocol) (err error) { - var fieldTypeId thrift.TType - var fieldId int16 - - if _, err = iprot.ReadStructBegin(); err != nil { - goto ReadStructBeginError - } - - for { - _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() - if err != nil { - goto ReadFieldBeginError - } - if fieldTypeId == thrift.STOP { - break - } - - switch fieldId { - case 1: - if fieldTypeId == thrift.STRING { - if err = p.ReadField1(iprot); err != nil { - goto ReadFieldError - } - } else if err = iprot.Skip(fieldTypeId); err != nil { - goto SkipFieldError - } - case 2: - if fieldTypeId == thrift.I64 { - if err = p.ReadField2(iprot); err != nil { - goto ReadFieldError - } - } else if err = iprot.Skip(fieldTypeId); err != nil { - goto SkipFieldError - } - case 3: - if fieldTypeId == thrift.I64 { - if err = p.ReadField3(iprot); err != nil { - goto ReadFieldError - } - } else if err = iprot.Skip(fieldTypeId); err != nil { - goto SkipFieldError - } - case 4: - if fieldTypeId == thrift.STRING { - if err = p.ReadField4(iprot); err != nil { - goto ReadFieldError - } - } else if err = iprot.Skip(fieldTypeId); err != nil { - goto SkipFieldError - } - case 5: - if fieldTypeId == thrift.STRING { - if err = p.ReadField5(iprot); err != nil { - goto ReadFieldError - } - } else if err = iprot.Skip(fieldTypeId); err != nil { - goto SkipFieldError - } - default: - if err = iprot.Skip(fieldTypeId); err != nil { - goto SkipFieldError - } - } - if err = iprot.ReadFieldEnd(); err != nil { - goto ReadFieldEndError - } - } - if err = iprot.ReadStructEnd(); err != nil { - goto ReadStructEndError - } - - return nil -ReadStructBeginError: - return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) -ReadFieldBeginError: - return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) -ReadFieldError: - return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_File[fieldId]), err) -SkipFieldError: - return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) - -ReadFieldEndError: - return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) -ReadStructEndError: - return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) -} - -func (p *File) ReadField1(iprot thrift.TProtocol) error { - - var _field string - if v, err := iprot.ReadString(); err != nil { - return err - } else { - _field = v - } - p.URI = _field - return nil -} -func (p *File) ReadField2(iprot thrift.TProtocol) error { - - var _field int64 - if v, err := iprot.ReadI64(); err != nil { - return err - } else { - _field = v - } - p.Bytes = _field - return nil -} -func (p *File) ReadField3(iprot thrift.TProtocol) error { - - var _field int64 - if v, err := iprot.ReadI64(); err != nil { - return err - } else { - _field = v - } - p.CreatedAt = _field - return nil -} -func (p *File) ReadField4(iprot thrift.TProtocol) error { - - var _field string - if v, err := iprot.ReadString(); err != nil { - return err - } else { - _field = v - } - p.FileName = _field - return nil -} -func (p *File) ReadField5(iprot thrift.TProtocol) error { - - var _field string - if v, err := iprot.ReadString(); err != nil { - return err - } else { - _field = v - } - p.URL = _field - return nil -} - -func (p *File) Write(oprot thrift.TProtocol) (err error) { - var fieldId int16 - if err = oprot.WriteStructBegin("File"); err != nil { - goto WriteStructBeginError - } - if p != nil { - if err = p.writeField1(oprot); err != nil { - fieldId = 1 - goto WriteFieldError - } - if err = p.writeField2(oprot); err != nil { - fieldId = 2 - goto WriteFieldError - } - if err = p.writeField3(oprot); err != nil { - fieldId = 3 - goto WriteFieldError - } - if err = p.writeField4(oprot); err != nil { - fieldId = 4 - goto WriteFieldError - } - if err = p.writeField5(oprot); err != nil { - fieldId = 5 - goto WriteFieldError - } - } - if err = oprot.WriteFieldStop(); err != nil { - goto WriteFieldStopError - } - if err = oprot.WriteStructEnd(); err != nil { - goto WriteStructEndError - } - return nil -WriteStructBeginError: - return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) -WriteFieldError: - return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) -WriteFieldStopError: - return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) -WriteStructEndError: - return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) -} - -func (p *File) writeField1(oprot thrift.TProtocol) (err error) { - if err = oprot.WriteFieldBegin("URI", thrift.STRING, 1); err != nil { - goto WriteFieldBeginError - } - if err := oprot.WriteString(p.URI); err != nil { - return err - } - if err = oprot.WriteFieldEnd(); err != nil { - goto WriteFieldEndError - } - return nil -WriteFieldBeginError: - return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) -WriteFieldEndError: - return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) -} -func (p *File) writeField2(oprot thrift.TProtocol) (err error) { - if err = oprot.WriteFieldBegin("Bytes", thrift.I64, 2); err != nil { - goto WriteFieldBeginError - } - if err := oprot.WriteI64(p.Bytes); err != nil { - return err - } - if err = oprot.WriteFieldEnd(); err != nil { - goto WriteFieldEndError - } - return nil -WriteFieldBeginError: - return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) -WriteFieldEndError: - return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) -} -func (p *File) writeField3(oprot thrift.TProtocol) (err error) { - if err = oprot.WriteFieldBegin("CreatedAt", thrift.I64, 3); err != nil { - goto WriteFieldBeginError - } - if err := oprot.WriteI64(p.CreatedAt); err != nil { - return err - } - if err = oprot.WriteFieldEnd(); err != nil { - goto WriteFieldEndError - } - return nil -WriteFieldBeginError: - return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err) -WriteFieldEndError: - return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err) -} -func (p *File) writeField4(oprot thrift.TProtocol) (err error) { - if err = oprot.WriteFieldBegin("FileName", thrift.STRING, 4); err != nil { - goto WriteFieldBeginError - } - if err := oprot.WriteString(p.FileName); err != nil { - return err - } - if err = oprot.WriteFieldEnd(); err != nil { - goto WriteFieldEndError - } - return nil -WriteFieldBeginError: - return thrift.PrependError(fmt.Sprintf("%T write field 4 begin error: ", p), err) -WriteFieldEndError: - return thrift.PrependError(fmt.Sprintf("%T write field 4 end error: ", p), err) -} -func (p *File) writeField5(oprot thrift.TProtocol) (err error) { - if err = oprot.WriteFieldBegin("URL", thrift.STRING, 5); err != nil { - goto WriteFieldBeginError - } - if err := oprot.WriteString(p.URL); err != nil { - return err - } - if err = oprot.WriteFieldEnd(); err != nil { - goto WriteFieldEndError - } - return nil -WriteFieldBeginError: - return thrift.PrependError(fmt.Sprintf("%T write field 5 begin error: ", p), err) -WriteFieldEndError: - return thrift.PrependError(fmt.Sprintf("%T write field 5 end error: ", p), err) -} - -func (p *File) String() string { - if p == nil { - return "" - } - return fmt.Sprintf("File(%+v)", *p) - -} - type PlaygroundService interface { UpdateDraftBotInfoAgw(ctx context.Context, request *UpdateDraftBotInfoAgwRequest) (r *UpdateDraftBotInfoAgwResponse, err error) diff --git a/backend/api/model/workflow/workflow.go b/backend/api/model/workflow/workflow.go index 80eb5f76..d69d84d8 100644 --- a/backend/api/model/workflow/workflow.go +++ b/backend/api/model/workflow/workflow.go @@ -1,3 +1,19 @@ +/* + * 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.1). DO NOT EDIT. package workflow @@ -6,6 +22,7 @@ import ( "database/sql" "database/sql/driver" "fmt" + "github.com/apache/thrift/lib/go/thrift" "github.com/coze-dev/coze-studio/backend/api/model/base" ) @@ -473,7 +490,10 @@ const ( NodeType_Input NodeType = 30 NodeType_Batch NodeType = 28 NodeType_Continue NodeType = 29 + NodeType_MessageList NodeType = 37 NodeType_AssignVariable NodeType = 40 + NodeType_ConversationList NodeType = 53 + NodeType_CreateMessage NodeType = 55 NodeType_JsonSerialization NodeType = 58 NodeType_JsonDeserialization NodeType = 59 NodeType_DatasetDelete NodeType = 60 @@ -533,8 +553,14 @@ func (p NodeType) String() string { return "Batch" case NodeType_Continue: return "Continue" + case NodeType_MessageList: + return "MessageList" case NodeType_AssignVariable: return "AssignVariable" + case NodeType_ConversationList: + return "ConversationList" + case NodeType_CreateMessage: + return "CreateMessage" case NodeType_JsonSerialization: return "JsonSerialization" case NodeType_JsonDeserialization: @@ -599,8 +625,14 @@ func NodeTypeFromString(s string) (NodeType, error) { return NodeType_Batch, nil case "Continue": return NodeType_Continue, nil + case "MessageList": + return NodeType_MessageList, nil case "AssignVariable": return NodeType_AssignVariable, nil + case "ConversationList": + return NodeType_ConversationList, nil + case "CreateMessage": + return NodeType_CreateMessage, nil case "JsonSerialization": return NodeType_JsonSerialization, nil case "JsonDeserialization": @@ -657,11 +689,14 @@ const ( NodeTemplateType_Input NodeTemplateType = 30 NodeTemplateType_Batch NodeTemplateType = 28 NodeTemplateType_Continue NodeTemplateType = 29 + NodeTemplateType_MessageList NodeTemplateType = 37 NodeTemplateType_AssignVariable NodeTemplateType = 40 NodeTemplateType_DatabaseInsert NodeTemplateType = 41 NodeTemplateType_DatabaseUpdate NodeTemplateType = 42 NodeTemplateType_DatabasesELECT NodeTemplateType = 43 NodeTemplateType_DatabaseDelete NodeTemplateType = 44 + NodeTemplateType_ConversationList NodeTemplateType = 53 + NodeTemplateType_CreateMessage NodeTemplateType = 55 NodeTemplateType_JsonSerialization NodeTemplateType = 58 NodeTemplateType_JsonDeserialization NodeTemplateType = 59 NodeTemplateType_DatasetDelete NodeTemplateType = 60 @@ -723,6 +758,8 @@ func (p NodeTemplateType) String() string { return "Batch" case NodeTemplateType_Continue: return "Continue" + case NodeTemplateType_MessageList: + return "MessageList" case NodeTemplateType_AssignVariable: return "AssignVariable" case NodeTemplateType_DatabaseInsert: @@ -733,6 +770,10 @@ func (p NodeTemplateType) String() string { return "DatabasesELECT" case NodeTemplateType_DatabaseDelete: return "DatabaseDelete" + case NodeTemplateType_ConversationList: + return "ConversationList" + case NodeTemplateType_CreateMessage: + return "CreateMessage" case NodeTemplateType_JsonSerialization: return "JsonSerialization" case NodeTemplateType_JsonDeserialization: @@ -799,6 +840,8 @@ func NodeTemplateTypeFromString(s string) (NodeTemplateType, error) { return NodeTemplateType_Batch, nil case "Continue": return NodeTemplateType_Continue, nil + case "MessageList": + return NodeTemplateType_MessageList, nil case "AssignVariable": return NodeTemplateType_AssignVariable, nil case "DatabaseInsert": @@ -809,6 +852,10 @@ func NodeTemplateTypeFromString(s string) (NodeTemplateType, error) { return NodeTemplateType_DatabasesELECT, nil case "DatabaseDelete": return NodeTemplateType_DatabaseDelete, nil + case "ConversationList": + return NodeTemplateType_ConversationList, nil + case "CreateMessage": + return NodeTemplateType_CreateMessage, nil case "JsonSerialization": return NodeTemplateType_JsonSerialization, nil case "JsonDeserialization": @@ -64103,7 +64150,7 @@ func (p *ChatFlowRole) String() string { } type CreateChatFlowRoleRequest struct { - ChatFlowRole *ChatFlowRole `thrift:"ChatFlowRole,1" json:"chat_flow_role" form:"ChatFlowRole" query:"ChatFlowRole"` + ChatFlowRole *ChatFlowRole `thrift:"ChatFlowRole,1" json:"chat_flow_role" query:"chat_flow_role" ` Base *base.Base `thrift:"Base,255,optional" form:"Base" json:"Base,omitempty" query:"Base"` } @@ -64302,7 +64349,9 @@ func (p *CreateChatFlowRoleRequest) String() string { type CreateChatFlowRoleResponse struct { // ID in the database - ID string `thrift:"ID,1" form:"ID" json:"ID" query:"ID"` + ID string `thrift:"ID,1" json:"id" query:"id" ` + Code int64 `thrift:"code,253,required" form:"code,required" json:"code,required" query:"code,required"` + Msg string `thrift:"msg,254,required" form:"msg,required" json:"msg,required" query:"msg,required"` BaseResp *base.BaseResp `thrift:"BaseResp,255,required" form:"BaseResp,required" json:"BaseResp,required" query:"BaseResp,required"` } @@ -64317,6 +64366,14 @@ func (p *CreateChatFlowRoleResponse) GetID() (v string) { return p.ID } +func (p *CreateChatFlowRoleResponse) GetCode() (v int64) { + return p.Code +} + +func (p *CreateChatFlowRoleResponse) GetMsg() (v string) { + return p.Msg +} + var CreateChatFlowRoleResponse_BaseResp_DEFAULT *base.BaseResp func (p *CreateChatFlowRoleResponse) GetBaseResp() (v *base.BaseResp) { @@ -64328,6 +64385,8 @@ func (p *CreateChatFlowRoleResponse) GetBaseResp() (v *base.BaseResp) { var fieldIDToName_CreateChatFlowRoleResponse = map[int16]string{ 1: "ID", + 253: "code", + 254: "msg", 255: "BaseResp", } @@ -64338,6 +64397,8 @@ func (p *CreateChatFlowRoleResponse) IsSetBaseResp() bool { func (p *CreateChatFlowRoleResponse) Read(iprot thrift.TProtocol) (err error) { var fieldTypeId thrift.TType var fieldId int16 + var issetCode bool = false + var issetMsg bool = false var issetBaseResp bool = false if _, err = iprot.ReadStructBegin(); err != nil { @@ -64362,6 +64423,24 @@ func (p *CreateChatFlowRoleResponse) Read(iprot thrift.TProtocol) (err error) { } else if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError } + case 253: + if fieldTypeId == thrift.I64 { + if err = p.ReadField253(iprot); err != nil { + goto ReadFieldError + } + issetCode = true + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 254: + if fieldTypeId == thrift.STRING { + if err = p.ReadField254(iprot); err != nil { + goto ReadFieldError + } + issetMsg = true + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } case 255: if fieldTypeId == thrift.STRUCT { if err = p.ReadField255(iprot); err != nil { @@ -64384,6 +64463,16 @@ func (p *CreateChatFlowRoleResponse) Read(iprot thrift.TProtocol) (err error) { goto ReadStructEndError } + if !issetCode { + fieldId = 253 + goto RequiredFieldNotSetError + } + + if !issetMsg { + fieldId = 254 + goto RequiredFieldNotSetError + } + if !issetBaseResp { fieldId = 255 goto RequiredFieldNotSetError @@ -64417,6 +64506,28 @@ func (p *CreateChatFlowRoleResponse) ReadField1(iprot thrift.TProtocol) error { p.ID = _field return nil } +func (p *CreateChatFlowRoleResponse) ReadField253(iprot thrift.TProtocol) error { + + var _field int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = v + } + p.Code = _field + return nil +} +func (p *CreateChatFlowRoleResponse) ReadField254(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Msg = _field + return nil +} func (p *CreateChatFlowRoleResponse) ReadField255(iprot thrift.TProtocol) error { _field := base.NewBaseResp() if err := _field.Read(iprot); err != nil { @@ -64436,6 +64547,14 @@ func (p *CreateChatFlowRoleResponse) Write(oprot thrift.TProtocol) (err error) { fieldId = 1 goto WriteFieldError } + if err = p.writeField253(oprot); err != nil { + fieldId = 253 + goto WriteFieldError + } + if err = p.writeField254(oprot); err != nil { + fieldId = 254 + goto WriteFieldError + } if err = p.writeField255(oprot); err != nil { fieldId = 255 goto WriteFieldError @@ -64474,6 +64593,38 @@ WriteFieldBeginError: WriteFieldEndError: return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) } +func (p *CreateChatFlowRoleResponse) writeField253(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("code", thrift.I64, 253); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(p.Code); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 253 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 253 end error: ", p), err) +} +func (p *CreateChatFlowRoleResponse) writeField254(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("msg", thrift.STRING, 254); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Msg); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 254 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 254 end error: ", p), err) +} func (p *CreateChatFlowRoleResponse) writeField255(oprot thrift.TProtocol) (err error) { if err = oprot.WriteFieldBegin("BaseResp", thrift.STRUCT, 255); err != nil { goto WriteFieldBeginError @@ -64500,10 +64651,10 @@ func (p *CreateChatFlowRoleResponse) String() string { } type DeleteChatFlowRoleRequest struct { - WorkflowID string `thrift:"WorkflowID,1" form:"WorkflowID" json:"WorkflowID" query:"WorkflowID"` - ConnectorID string `thrift:"ConnectorID,2" form:"ConnectorID" json:"ConnectorID" query:"ConnectorID"` + WorkflowID string `thrift:"WorkflowID,1" json:"workflow_id" query:"workflow_id" ` + ConnectorID string `thrift:"ConnectorID,2" json:"connector_id" query:"connector_id" ` // ID in the database - ID string `thrift:"ID,4" form:"ID" json:"ID" query:"ID"` + ID string `thrift:"ID,4" json:"id" query:"id" ` Base *base.Base `thrift:"Base,255,optional" form:"Base" json:"Base,omitempty" query:"Base"` } @@ -64936,12 +65087,12 @@ func (p *DeleteChatFlowRoleResponse) String() string { } type GetChatFlowRoleRequest struct { - WorkflowID string `thrift:"WorkflowID,1" form:"WorkflowID" json:"WorkflowID" query:"WorkflowID"` - ConnectorID string `thrift:"ConnectorID,2" form:"ConnectorID" json:"ConnectorID" query:"ConnectorID"` - IsDebug bool `thrift:"IsDebug,3" form:"IsDebug" json:"IsDebug" query:"IsDebug"` + WorkflowID string `thrift:"WorkflowID,1" json:"workflow_id" query:"workflow_id" ` + ConnectorID string `thrift:"ConnectorID,2" json:"connector_id" query:"connector_id" ` + IsDebug bool `thrift:"IsDebug,3" json:"is_debug" query:"is_debug" ` // 4: optional string AppID (api.query = "app_id") Ext map[string]string `thrift:"Ext,5,optional" json:"Ext,omitempty" query:"ext"` - Base *base.Base `thrift:"Base,255,optional" form:"Base" json:"Base,omitempty" query:"Base"` + Base *base.Base `thrift:"Base,255,optional" json:"base" query:"base" ` } func NewGetChatFlowRoleRequest() *GetChatFlowRoleRequest { @@ -65304,8 +65455,10 @@ func (p *GetChatFlowRoleRequest) String() string { } type GetChatFlowRoleResponse struct { - Role *ChatFlowRole `thrift:"Role,1,optional" form:"Role" json:"Role,omitempty" query:"Role"` - BaseResp *base.BaseResp `thrift:"BaseResp,255,required" form:"BaseResp,required" json:"BaseResp,required" query:"BaseResp,required"` + Code int64 `thrift:"code,1,required" form:"code,required" json:"code,required" query:"code,required"` + Msg string `thrift:"msg,2,required" form:"msg,required" json:"msg,required" query:"msg,required"` + Role *ChatFlowRole `thrift:"Role,3,optional" json:"role" query:"role" ` + BaseResp *base.BaseResp `thrift:"BaseResp,255,required" json:"base_resp" query:"base_resp,required" ` } func NewGetChatFlowRoleResponse() *GetChatFlowRoleResponse { @@ -65315,6 +65468,14 @@ func NewGetChatFlowRoleResponse() *GetChatFlowRoleResponse { func (p *GetChatFlowRoleResponse) InitDefault() { } +func (p *GetChatFlowRoleResponse) GetCode() (v int64) { + return p.Code +} + +func (p *GetChatFlowRoleResponse) GetMsg() (v string) { + return p.Msg +} + var GetChatFlowRoleResponse_Role_DEFAULT *ChatFlowRole func (p *GetChatFlowRoleResponse) GetRole() (v *ChatFlowRole) { @@ -65334,7 +65495,9 @@ func (p *GetChatFlowRoleResponse) GetBaseResp() (v *base.BaseResp) { } var fieldIDToName_GetChatFlowRoleResponse = map[int16]string{ - 1: "Role", + 1: "code", + 2: "msg", + 3: "Role", 255: "BaseResp", } @@ -65349,6 +65512,8 @@ func (p *GetChatFlowRoleResponse) IsSetBaseResp() bool { func (p *GetChatFlowRoleResponse) Read(iprot thrift.TProtocol) (err error) { var fieldTypeId thrift.TType var fieldId int16 + var issetCode bool = false + var issetMsg bool = false var issetBaseResp bool = false if _, err = iprot.ReadStructBegin(); err != nil { @@ -65366,10 +65531,28 @@ func (p *GetChatFlowRoleResponse) Read(iprot thrift.TProtocol) (err error) { switch fieldId { case 1: - if fieldTypeId == thrift.STRUCT { + if fieldTypeId == thrift.I64 { if err = p.ReadField1(iprot); err != nil { goto ReadFieldError } + issetCode = true + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.STRING { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + issetMsg = true + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 3: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField3(iprot); err != nil { + goto ReadFieldError + } } else if err = iprot.Skip(fieldTypeId); err != nil { goto SkipFieldError } @@ -65395,6 +65578,16 @@ func (p *GetChatFlowRoleResponse) Read(iprot thrift.TProtocol) (err error) { goto ReadStructEndError } + if !issetCode { + fieldId = 1 + goto RequiredFieldNotSetError + } + + if !issetMsg { + fieldId = 2 + goto RequiredFieldNotSetError + } + if !issetBaseResp { fieldId = 255 goto RequiredFieldNotSetError @@ -65418,6 +65611,28 @@ RequiredFieldNotSetError: } func (p *GetChatFlowRoleResponse) ReadField1(iprot thrift.TProtocol) error { + + var _field int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = v + } + p.Code = _field + return nil +} +func (p *GetChatFlowRoleResponse) ReadField2(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Msg = _field + return nil +} +func (p *GetChatFlowRoleResponse) ReadField3(iprot thrift.TProtocol) error { _field := NewChatFlowRole() if err := _field.Read(iprot); err != nil { return err @@ -65444,6 +65659,14 @@ func (p *GetChatFlowRoleResponse) Write(oprot thrift.TProtocol) (err error) { fieldId = 1 goto WriteFieldError } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField3(oprot); err != nil { + fieldId = 3 + goto WriteFieldError + } if err = p.writeField255(oprot); err != nil { fieldId = 255 goto WriteFieldError @@ -65467,8 +65690,40 @@ WriteStructEndError: } func (p *GetChatFlowRoleResponse) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("code", thrift.I64, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(p.Code); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *GetChatFlowRoleResponse) writeField2(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("msg", thrift.STRING, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Msg); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} +func (p *GetChatFlowRoleResponse) writeField3(oprot thrift.TProtocol) (err error) { if p.IsSetRole() { - if err = oprot.WriteFieldBegin("Role", thrift.STRUCT, 1); err != nil { + if err = oprot.WriteFieldBegin("Role", thrift.STRUCT, 3); err != nil { goto WriteFieldBeginError } if err := p.Role.Write(oprot); err != nil { @@ -65480,9 +65735,9 @@ func (p *GetChatFlowRoleResponse) writeField1(oprot thrift.TProtocol) (err error } return nil WriteFieldBeginError: - return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) + return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err) WriteFieldEndError: - return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) + return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err) } func (p *GetChatFlowRoleResponse) writeField255(oprot thrift.TProtocol) (err error) { if err = oprot.WriteFieldBegin("BaseResp", thrift.STRUCT, 255); err != nil { @@ -78119,3 +78374,1403 @@ func (p *OpenAPIGetWorkflowInfoResponse) String() string { return fmt.Sprintf("OpenAPIGetWorkflowInfoResponse(%+v)", *p) } + +type CreateConversationRequest struct { + //自定义透传字段 + MetaData map[string]string `thrift:"MetaData,1,optional" form:"meta_data" json:"meta_data,omitempty"` + BotId *int64 `thrift:"BotId,3,optional" form:"bot_id" json:"bot_id,string,omitempty"` + ConnectorId *int64 `thrift:"ConnectorId,4,optional" form:"connector_id" json:"connector_id,string,omitempty"` + SpaceID *string `thrift:"SpaceID,5,optional" form:"space_id" json:"space_id,string,omitempty"` + AppID *string `thrift:"AppID,9,optional" json:"app_id" form:"AppID" query:"AppID"` + WorkflowID *string `thrift:"WorkflowID,10,optional" json:"workflow_id" form:"WorkflowID" query:"WorkflowID"` + ConversationMame *string `thrift:"ConversationMame,11,optional" json:"conversation_name" form:"ConversationMame" query:"ConversationMame"` + GetOrCreate *bool `thrift:"GetOrCreate,12,optional" json:"get_or_create" form:"GetOrCreate" query:"GetOrCreate"` + DraftMode *bool `thrift:"DraftMode,13,optional" json:"draft_mode" form:"DraftMode" query:"DraftMode"` + Base *base.Base `thrift:"Base,255,optional" form:"Base" json:"Base,omitempty" query:"Base"` +} + +func NewCreateConversationRequest() *CreateConversationRequest { + return &CreateConversationRequest{} +} + +func (p *CreateConversationRequest) InitDefault() { +} + +var CreateConversationRequest_MetaData_DEFAULT map[string]string + +func (p *CreateConversationRequest) GetMetaData() (v map[string]string) { + if !p.IsSetMetaData() { + return CreateConversationRequest_MetaData_DEFAULT + } + return p.MetaData +} + +var CreateConversationRequest_BotId_DEFAULT int64 + +func (p *CreateConversationRequest) GetBotId() (v int64) { + if !p.IsSetBotId() { + return CreateConversationRequest_BotId_DEFAULT + } + return *p.BotId +} + +var CreateConversationRequest_ConnectorId_DEFAULT int64 + +func (p *CreateConversationRequest) GetConnectorId() (v int64) { + if !p.IsSetConnectorId() { + return CreateConversationRequest_ConnectorId_DEFAULT + } + return *p.ConnectorId +} + +var CreateConversationRequest_SpaceID_DEFAULT string + +func (p *CreateConversationRequest) GetSpaceID() (v string) { + if !p.IsSetSpaceID() { + return CreateConversationRequest_SpaceID_DEFAULT + } + return *p.SpaceID +} + +var CreateConversationRequest_AppID_DEFAULT string + +func (p *CreateConversationRequest) GetAppID() (v string) { + if !p.IsSetAppID() { + return CreateConversationRequest_AppID_DEFAULT + } + return *p.AppID +} + +var CreateConversationRequest_WorkflowID_DEFAULT string + +func (p *CreateConversationRequest) GetWorkflowID() (v string) { + if !p.IsSetWorkflowID() { + return CreateConversationRequest_WorkflowID_DEFAULT + } + return *p.WorkflowID +} + +var CreateConversationRequest_ConversationMame_DEFAULT string + +func (p *CreateConversationRequest) GetConversationMame() (v string) { + if !p.IsSetConversationMame() { + return CreateConversationRequest_ConversationMame_DEFAULT + } + return *p.ConversationMame +} + +var CreateConversationRequest_GetOrCreate_DEFAULT bool + +func (p *CreateConversationRequest) GetGetOrCreate() (v bool) { + if !p.IsSetGetOrCreate() { + return CreateConversationRequest_GetOrCreate_DEFAULT + } + return *p.GetOrCreate +} + +var CreateConversationRequest_DraftMode_DEFAULT bool + +func (p *CreateConversationRequest) GetDraftMode() (v bool) { + if !p.IsSetDraftMode() { + return CreateConversationRequest_DraftMode_DEFAULT + } + return *p.DraftMode +} + +var CreateConversationRequest_Base_DEFAULT *base.Base + +func (p *CreateConversationRequest) GetBase() (v *base.Base) { + if !p.IsSetBase() { + return CreateConversationRequest_Base_DEFAULT + } + return p.Base +} + +var fieldIDToName_CreateConversationRequest = map[int16]string{ + 1: "MetaData", + 3: "BotId", + 4: "ConnectorId", + 5: "SpaceID", + 9: "AppID", + 10: "WorkflowID", + 11: "ConversationMame", + 12: "GetOrCreate", + 13: "DraftMode", + 255: "Base", +} + +func (p *CreateConversationRequest) IsSetMetaData() bool { + return p.MetaData != nil +} + +func (p *CreateConversationRequest) IsSetBotId() bool { + return p.BotId != nil +} + +func (p *CreateConversationRequest) IsSetConnectorId() bool { + return p.ConnectorId != nil +} + +func (p *CreateConversationRequest) IsSetSpaceID() bool { + return p.SpaceID != nil +} + +func (p *CreateConversationRequest) IsSetAppID() bool { + return p.AppID != nil +} + +func (p *CreateConversationRequest) IsSetWorkflowID() bool { + return p.WorkflowID != nil +} + +func (p *CreateConversationRequest) IsSetConversationMame() bool { + return p.ConversationMame != nil +} + +func (p *CreateConversationRequest) IsSetGetOrCreate() bool { + return p.GetOrCreate != nil +} + +func (p *CreateConversationRequest) IsSetDraftMode() bool { + return p.DraftMode != nil +} + +func (p *CreateConversationRequest) IsSetBase() bool { + return p.Base != nil +} + +func (p *CreateConversationRequest) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.MAP { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 3: + if fieldTypeId == thrift.I64 { + if err = p.ReadField3(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 4: + if fieldTypeId == thrift.I64 { + if err = p.ReadField4(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 5: + if fieldTypeId == thrift.STRING { + if err = p.ReadField5(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 9: + if fieldTypeId == thrift.STRING { + if err = p.ReadField9(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 10: + if fieldTypeId == thrift.STRING { + if err = p.ReadField10(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 11: + if fieldTypeId == thrift.STRING { + if err = p.ReadField11(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 12: + if fieldTypeId == thrift.BOOL { + if err = p.ReadField12(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 13: + if fieldTypeId == thrift.BOOL { + if err = p.ReadField13(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 255: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField255(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_CreateConversationRequest[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *CreateConversationRequest) ReadField1(iprot thrift.TProtocol) error { + _, _, size, err := iprot.ReadMapBegin() + if err != nil { + return err + } + _field := make(map[string]string, size) + for i := 0; i < size; i++ { + var _key string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _key = v + } + + var _val string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _val = v + } + + _field[_key] = _val + } + if err := iprot.ReadMapEnd(); err != nil { + return err + } + p.MetaData = _field + return nil +} +func (p *CreateConversationRequest) ReadField3(iprot thrift.TProtocol) error { + + var _field *int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = &v + } + p.BotId = _field + return nil +} +func (p *CreateConversationRequest) ReadField4(iprot thrift.TProtocol) error { + + var _field *int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = &v + } + p.ConnectorId = _field + return nil +} +func (p *CreateConversationRequest) ReadField5(iprot thrift.TProtocol) error { + + var _field *string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = &v + } + p.SpaceID = _field + return nil +} +func (p *CreateConversationRequest) ReadField9(iprot thrift.TProtocol) error { + + var _field *string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = &v + } + p.AppID = _field + return nil +} +func (p *CreateConversationRequest) ReadField10(iprot thrift.TProtocol) error { + + var _field *string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = &v + } + p.WorkflowID = _field + return nil +} +func (p *CreateConversationRequest) ReadField11(iprot thrift.TProtocol) error { + + var _field *string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = &v + } + p.ConversationMame = _field + return nil +} +func (p *CreateConversationRequest) ReadField12(iprot thrift.TProtocol) error { + + var _field *bool + if v, err := iprot.ReadBool(); err != nil { + return err + } else { + _field = &v + } + p.GetOrCreate = _field + return nil +} +func (p *CreateConversationRequest) ReadField13(iprot thrift.TProtocol) error { + + var _field *bool + if v, err := iprot.ReadBool(); err != nil { + return err + } else { + _field = &v + } + p.DraftMode = _field + return nil +} +func (p *CreateConversationRequest) ReadField255(iprot thrift.TProtocol) error { + _field := base.NewBase() + if err := _field.Read(iprot); err != nil { + return err + } + p.Base = _field + return nil +} + +func (p *CreateConversationRequest) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("CreateConversationRequest"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField3(oprot); err != nil { + fieldId = 3 + goto WriteFieldError + } + if err = p.writeField4(oprot); err != nil { + fieldId = 4 + goto WriteFieldError + } + if err = p.writeField5(oprot); err != nil { + fieldId = 5 + goto WriteFieldError + } + if err = p.writeField9(oprot); err != nil { + fieldId = 9 + goto WriteFieldError + } + if err = p.writeField10(oprot); err != nil { + fieldId = 10 + goto WriteFieldError + } + if err = p.writeField11(oprot); err != nil { + fieldId = 11 + goto WriteFieldError + } + if err = p.writeField12(oprot); err != nil { + fieldId = 12 + goto WriteFieldError + } + if err = p.writeField13(oprot); err != nil { + fieldId = 13 + goto WriteFieldError + } + if err = p.writeField255(oprot); err != nil { + fieldId = 255 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *CreateConversationRequest) writeField1(oprot thrift.TProtocol) (err error) { + if p.IsSetMetaData() { + if err = oprot.WriteFieldBegin("MetaData", thrift.MAP, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteMapBegin(thrift.STRING, thrift.STRING, len(p.MetaData)); err != nil { + return err + } + for k, v := range p.MetaData { + if err := oprot.WriteString(k); err != nil { + return err + } + if err := oprot.WriteString(v); err != nil { + return err + } + } + if err := oprot.WriteMapEnd(); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *CreateConversationRequest) writeField3(oprot thrift.TProtocol) (err error) { + if p.IsSetBotId() { + if err = oprot.WriteFieldBegin("BotId", thrift.I64, 3); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(*p.BotId); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err) +} +func (p *CreateConversationRequest) writeField4(oprot thrift.TProtocol) (err error) { + if p.IsSetConnectorId() { + if err = oprot.WriteFieldBegin("ConnectorId", thrift.I64, 4); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(*p.ConnectorId); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 4 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 4 end error: ", p), err) +} +func (p *CreateConversationRequest) writeField5(oprot thrift.TProtocol) (err error) { + if p.IsSetSpaceID() { + if err = oprot.WriteFieldBegin("SpaceID", thrift.STRING, 5); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(*p.SpaceID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 5 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 5 end error: ", p), err) +} +func (p *CreateConversationRequest) writeField9(oprot thrift.TProtocol) (err error) { + if p.IsSetAppID() { + if err = oprot.WriteFieldBegin("AppID", thrift.STRING, 9); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(*p.AppID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 9 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 9 end error: ", p), err) +} +func (p *CreateConversationRequest) writeField10(oprot thrift.TProtocol) (err error) { + if p.IsSetWorkflowID() { + if err = oprot.WriteFieldBegin("WorkflowID", thrift.STRING, 10); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(*p.WorkflowID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 10 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 10 end error: ", p), err) +} +func (p *CreateConversationRequest) writeField11(oprot thrift.TProtocol) (err error) { + if p.IsSetConversationMame() { + if err = oprot.WriteFieldBegin("ConversationMame", thrift.STRING, 11); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(*p.ConversationMame); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 11 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 11 end error: ", p), err) +} +func (p *CreateConversationRequest) writeField12(oprot thrift.TProtocol) (err error) { + if p.IsSetGetOrCreate() { + if err = oprot.WriteFieldBegin("GetOrCreate", thrift.BOOL, 12); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteBool(*p.GetOrCreate); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 12 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 12 end error: ", p), err) +} +func (p *CreateConversationRequest) writeField13(oprot thrift.TProtocol) (err error) { + if p.IsSetDraftMode() { + if err = oprot.WriteFieldBegin("DraftMode", thrift.BOOL, 13); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteBool(*p.DraftMode); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 13 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 13 end error: ", p), err) +} +func (p *CreateConversationRequest) writeField255(oprot thrift.TProtocol) (err error) { + if p.IsSetBase() { + if err = oprot.WriteFieldBegin("Base", thrift.STRUCT, 255); err != nil { + goto WriteFieldBeginError + } + if err := p.Base.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 255 end error: ", p), err) +} + +func (p *CreateConversationRequest) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("CreateConversationRequest(%+v)", *p) + +} + +type CreateConversationResponse struct { + Code int64 `thrift:"code,1" form:"code" json:"code" query:"code"` + Msg string `thrift:"msg,2" form:"msg" json:"msg" query:"msg"` + ConversationData *ConversationData `thrift:"ConversationData,3,optional" form:"data" json:"data,omitempty"` +} + +func NewCreateConversationResponse() *CreateConversationResponse { + return &CreateConversationResponse{} +} + +func (p *CreateConversationResponse) InitDefault() { +} + +func (p *CreateConversationResponse) GetCode() (v int64) { + return p.Code +} + +func (p *CreateConversationResponse) GetMsg() (v string) { + return p.Msg +} + +var CreateConversationResponse_ConversationData_DEFAULT *ConversationData + +func (p *CreateConversationResponse) GetConversationData() (v *ConversationData) { + if !p.IsSetConversationData() { + return CreateConversationResponse_ConversationData_DEFAULT + } + return p.ConversationData +} + +var fieldIDToName_CreateConversationResponse = map[int16]string{ + 1: "code", + 2: "msg", + 3: "ConversationData", +} + +func (p *CreateConversationResponse) IsSetConversationData() bool { + return p.ConversationData != nil +} + +func (p *CreateConversationResponse) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.I64 { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.STRING { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 3: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField3(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_CreateConversationResponse[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *CreateConversationResponse) ReadField1(iprot thrift.TProtocol) error { + + var _field int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = v + } + p.Code = _field + return nil +} +func (p *CreateConversationResponse) ReadField2(iprot thrift.TProtocol) error { + + var _field string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _field = v + } + p.Msg = _field + return nil +} +func (p *CreateConversationResponse) ReadField3(iprot thrift.TProtocol) error { + _field := NewConversationData() + if err := _field.Read(iprot); err != nil { + return err + } + p.ConversationData = _field + return nil +} + +func (p *CreateConversationResponse) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("CreateConversationResponse"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField3(oprot); err != nil { + fieldId = 3 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *CreateConversationResponse) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("code", thrift.I64, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(p.Code); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *CreateConversationResponse) writeField2(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("msg", thrift.STRING, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteString(p.Msg); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} +func (p *CreateConversationResponse) writeField3(oprot thrift.TProtocol) (err error) { + if p.IsSetConversationData() { + if err = oprot.WriteFieldBegin("ConversationData", thrift.STRUCT, 3); err != nil { + goto WriteFieldBeginError + } + if err := p.ConversationData.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err) +} + +func (p *CreateConversationResponse) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("CreateConversationResponse(%+v)", *p) + +} + +type ConversationData struct { + Id int64 `thrift:"Id,1" form:"id" json:"id,string"` + CreatedAt int64 `thrift:"CreatedAt,2" form:"created_at" json:"created_at"` + MetaData map[string]string `thrift:"MetaData,3" form:"meta_data" json:"meta_data"` + CreatorID *int64 `thrift:"CreatorID,4,optional" form:"creator_d" json:"creator_d,string,omitempty"` + ConnectorID *int64 `thrift:"ConnectorID,5,optional" form:"connector_id" json:"connector_id,string,omitempty"` + LastSectionID *int64 `thrift:"LastSectionID,6,optional" form:"last_section_id" json:"last_section_id,string,omitempty"` + AccountID *int64 `thrift:"AccountID,7,optional" form:"account_id" json:"account_id,omitempty"` +} + +func NewConversationData() *ConversationData { + return &ConversationData{} +} + +func (p *ConversationData) InitDefault() { +} + +func (p *ConversationData) GetId() (v int64) { + return p.Id +} + +func (p *ConversationData) GetCreatedAt() (v int64) { + return p.CreatedAt +} + +func (p *ConversationData) GetMetaData() (v map[string]string) { + return p.MetaData +} + +var ConversationData_CreatorID_DEFAULT int64 + +func (p *ConversationData) GetCreatorID() (v int64) { + if !p.IsSetCreatorID() { + return ConversationData_CreatorID_DEFAULT + } + return *p.CreatorID +} + +var ConversationData_ConnectorID_DEFAULT int64 + +func (p *ConversationData) GetConnectorID() (v int64) { + if !p.IsSetConnectorID() { + return ConversationData_ConnectorID_DEFAULT + } + return *p.ConnectorID +} + +var ConversationData_LastSectionID_DEFAULT int64 + +func (p *ConversationData) GetLastSectionID() (v int64) { + if !p.IsSetLastSectionID() { + return ConversationData_LastSectionID_DEFAULT + } + return *p.LastSectionID +} + +var ConversationData_AccountID_DEFAULT int64 + +func (p *ConversationData) GetAccountID() (v int64) { + if !p.IsSetAccountID() { + return ConversationData_AccountID_DEFAULT + } + return *p.AccountID +} + +var fieldIDToName_ConversationData = map[int16]string{ + 1: "Id", + 2: "CreatedAt", + 3: "MetaData", + 4: "CreatorID", + 5: "ConnectorID", + 6: "LastSectionID", + 7: "AccountID", +} + +func (p *ConversationData) IsSetCreatorID() bool { + return p.CreatorID != nil +} + +func (p *ConversationData) IsSetConnectorID() bool { + return p.ConnectorID != nil +} + +func (p *ConversationData) IsSetLastSectionID() bool { + return p.LastSectionID != nil +} + +func (p *ConversationData) IsSetAccountID() bool { + return p.AccountID != nil +} + +func (p *ConversationData) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.I64 { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 2: + if fieldTypeId == thrift.I64 { + if err = p.ReadField2(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 3: + if fieldTypeId == thrift.MAP { + if err = p.ReadField3(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 4: + if fieldTypeId == thrift.I64 { + if err = p.ReadField4(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 5: + if fieldTypeId == thrift.I64 { + if err = p.ReadField5(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 6: + if fieldTypeId == thrift.I64 { + if err = p.ReadField6(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + case 7: + if fieldTypeId == thrift.I64 { + if err = p.ReadField7(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_ConversationData[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *ConversationData) ReadField1(iprot thrift.TProtocol) error { + + var _field int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = v + } + p.Id = _field + return nil +} +func (p *ConversationData) ReadField2(iprot thrift.TProtocol) error { + + var _field int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = v + } + p.CreatedAt = _field + return nil +} +func (p *ConversationData) ReadField3(iprot thrift.TProtocol) error { + _, _, size, err := iprot.ReadMapBegin() + if err != nil { + return err + } + _field := make(map[string]string, size) + for i := 0; i < size; i++ { + var _key string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _key = v + } + + var _val string + if v, err := iprot.ReadString(); err != nil { + return err + } else { + _val = v + } + + _field[_key] = _val + } + if err := iprot.ReadMapEnd(); err != nil { + return err + } + p.MetaData = _field + return nil +} +func (p *ConversationData) ReadField4(iprot thrift.TProtocol) error { + + var _field *int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = &v + } + p.CreatorID = _field + return nil +} +func (p *ConversationData) ReadField5(iprot thrift.TProtocol) error { + + var _field *int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = &v + } + p.ConnectorID = _field + return nil +} +func (p *ConversationData) ReadField6(iprot thrift.TProtocol) error { + + var _field *int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = &v + } + p.LastSectionID = _field + return nil +} +func (p *ConversationData) ReadField7(iprot thrift.TProtocol) error { + + var _field *int64 + if v, err := iprot.ReadI64(); err != nil { + return err + } else { + _field = &v + } + p.AccountID = _field + return nil +} + +func (p *ConversationData) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("ConversationData"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + if err = p.writeField2(oprot); err != nil { + fieldId = 2 + goto WriteFieldError + } + if err = p.writeField3(oprot); err != nil { + fieldId = 3 + goto WriteFieldError + } + if err = p.writeField4(oprot); err != nil { + fieldId = 4 + goto WriteFieldError + } + if err = p.writeField5(oprot); err != nil { + fieldId = 5 + goto WriteFieldError + } + if err = p.writeField6(oprot); err != nil { + fieldId = 6 + goto WriteFieldError + } + if err = p.writeField7(oprot); err != nil { + fieldId = 7 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *ConversationData) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("Id", thrift.I64, 1); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(p.Id); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} +func (p *ConversationData) writeField2(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("CreatedAt", thrift.I64, 2); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(p.CreatedAt); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 2 end error: ", p), err) +} +func (p *ConversationData) writeField3(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("MetaData", thrift.MAP, 3); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteMapBegin(thrift.STRING, thrift.STRING, len(p.MetaData)); err != nil { + return err + } + for k, v := range p.MetaData { + if err := oprot.WriteString(k); err != nil { + return err + } + if err := oprot.WriteString(v); err != nil { + return err + } + } + if err := oprot.WriteMapEnd(); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 3 end error: ", p), err) +} +func (p *ConversationData) writeField4(oprot thrift.TProtocol) (err error) { + if p.IsSetCreatorID() { + if err = oprot.WriteFieldBegin("CreatorID", thrift.I64, 4); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(*p.CreatorID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 4 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 4 end error: ", p), err) +} +func (p *ConversationData) writeField5(oprot thrift.TProtocol) (err error) { + if p.IsSetConnectorID() { + if err = oprot.WriteFieldBegin("ConnectorID", thrift.I64, 5); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(*p.ConnectorID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 5 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 5 end error: ", p), err) +} +func (p *ConversationData) writeField6(oprot thrift.TProtocol) (err error) { + if p.IsSetLastSectionID() { + if err = oprot.WriteFieldBegin("LastSectionID", thrift.I64, 6); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(*p.LastSectionID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 6 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 6 end error: ", p), err) +} +func (p *ConversationData) writeField7(oprot thrift.TProtocol) (err error) { + if p.IsSetAccountID() { + if err = oprot.WriteFieldBegin("AccountID", thrift.I64, 7); err != nil { + goto WriteFieldBeginError + } + if err := oprot.WriteI64(*p.AccountID); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 7 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 7 end error: ", p), err) +} + +func (p *ConversationData) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("ConversationData(%+v)", *p) + +} diff --git a/backend/api/model/workflow/workflow_svc.go b/backend/api/model/workflow/workflow_svc.go index 6035fd9d..6a3d96a8 100644 --- a/backend/api/model/workflow/workflow_svc.go +++ b/backend/api/model/workflow/workflow_svc.go @@ -1,3 +1,19 @@ +/* + * 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.1). DO NOT EDIT. package workflow @@ -5,6 +21,7 @@ package workflow import ( "context" "fmt" + "github.com/apache/thrift/lib/go/thrift" ) @@ -106,6 +123,8 @@ type WorkflowService interface { OpenAPIChatFlowRun(ctx context.Context, request *ChatFlowRunRequest) (r *ChatFlowRunResponse, err error) OpenAPIGetWorkflowInfo(ctx context.Context, request *OpenAPIGetWorkflowInfoRequest) (r *OpenAPIGetWorkflowInfoResponse, err error) + + OpenAPICreateConversation(ctx context.Context, request *CreateConversationRequest) (r *CreateConversationResponse, err error) } type WorkflowServiceClient struct { @@ -566,6 +585,15 @@ func (p *WorkflowServiceClient) OpenAPIGetWorkflowInfo(ctx context.Context, requ } return _result.GetSuccess(), nil } +func (p *WorkflowServiceClient) OpenAPICreateConversation(ctx context.Context, request *CreateConversationRequest) (r *CreateConversationResponse, err error) { + var _args WorkflowServiceOpenAPICreateConversationArgs + _args.Request = request + var _result WorkflowServiceOpenAPICreateConversationResult + if err = p.Client_().Call(ctx, "OpenAPICreateConversation", &_args, &_result); err != nil { + return + } + return _result.GetSuccess(), nil +} type WorkflowServiceProcessor struct { processorMap map[string]thrift.TProcessorFunction @@ -635,6 +663,7 @@ func NewWorkflowServiceProcessor(handler WorkflowService) *WorkflowServiceProces self.AddToProcessorMap("OpenAPIGetWorkflowRunHistory", &workflowServiceProcessorOpenAPIGetWorkflowRunHistory{handler: handler}) self.AddToProcessorMap("OpenAPIChatFlowRun", &workflowServiceProcessorOpenAPIChatFlowRun{handler: handler}) self.AddToProcessorMap("OpenAPIGetWorkflowInfo", &workflowServiceProcessorOpenAPIGetWorkflowInfo{handler: handler}) + self.AddToProcessorMap("OpenAPICreateConversation", &workflowServiceProcessorOpenAPICreateConversation{handler: handler}) return self } func (p *WorkflowServiceProcessor) Process(ctx context.Context, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { @@ -2959,6 +2988,54 @@ func (p *workflowServiceProcessorOpenAPIGetWorkflowInfo) Process(ctx context.Con return true, err } +type workflowServiceProcessorOpenAPICreateConversation struct { + handler WorkflowService +} + +func (p *workflowServiceProcessorOpenAPICreateConversation) Process(ctx context.Context, seqId int32, iprot, oprot thrift.TProtocol) (success bool, err thrift.TException) { + args := WorkflowServiceOpenAPICreateConversationArgs{} + if err = args.Read(iprot); err != nil { + iprot.ReadMessageEnd() + x := thrift.NewTApplicationException(thrift.PROTOCOL_ERROR, err.Error()) + oprot.WriteMessageBegin("OpenAPICreateConversation", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return false, err + } + + iprot.ReadMessageEnd() + var err2 error + result := WorkflowServiceOpenAPICreateConversationResult{} + var retval *CreateConversationResponse + if retval, err2 = p.handler.OpenAPICreateConversation(ctx, args.Request); err2 != nil { + x := thrift.NewTApplicationException(thrift.INTERNAL_ERROR, "Internal error processing OpenAPICreateConversation: "+err2.Error()) + oprot.WriteMessageBegin("OpenAPICreateConversation", thrift.EXCEPTION, seqId) + x.Write(oprot) + oprot.WriteMessageEnd() + oprot.Flush(ctx) + return true, err2 + } else { + result.Success = retval + } + if err2 = oprot.WriteMessageBegin("OpenAPICreateConversation", thrift.REPLY, seqId); err2 != nil { + err = err2 + } + if err2 = result.Write(oprot); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.WriteMessageEnd(); err == nil && err2 != nil { + err = err2 + } + if err2 = oprot.Flush(ctx); err == nil && err2 != nil { + err = err2 + } + if err != nil { + return + } + return true, err +} + type WorkflowServiceCreateWorkflowArgs struct { Request *CreateWorkflowRequest `thrift:"request,1"` } @@ -16974,3 +17051,295 @@ func (p *WorkflowServiceOpenAPIGetWorkflowInfoResult) String() string { return fmt.Sprintf("WorkflowServiceOpenAPIGetWorkflowInfoResult(%+v)", *p) } + +type WorkflowServiceOpenAPICreateConversationArgs struct { + Request *CreateConversationRequest `thrift:"request,1"` +} + +func NewWorkflowServiceOpenAPICreateConversationArgs() *WorkflowServiceOpenAPICreateConversationArgs { + return &WorkflowServiceOpenAPICreateConversationArgs{} +} + +func (p *WorkflowServiceOpenAPICreateConversationArgs) InitDefault() { +} + +var WorkflowServiceOpenAPICreateConversationArgs_Request_DEFAULT *CreateConversationRequest + +func (p *WorkflowServiceOpenAPICreateConversationArgs) GetRequest() (v *CreateConversationRequest) { + if !p.IsSetRequest() { + return WorkflowServiceOpenAPICreateConversationArgs_Request_DEFAULT + } + return p.Request +} + +var fieldIDToName_WorkflowServiceOpenAPICreateConversationArgs = map[int16]string{ + 1: "request", +} + +func (p *WorkflowServiceOpenAPICreateConversationArgs) IsSetRequest() bool { + return p.Request != nil +} + +func (p *WorkflowServiceOpenAPICreateConversationArgs) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 1: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField1(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_WorkflowServiceOpenAPICreateConversationArgs[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *WorkflowServiceOpenAPICreateConversationArgs) ReadField1(iprot thrift.TProtocol) error { + _field := NewCreateConversationRequest() + if err := _field.Read(iprot); err != nil { + return err + } + p.Request = _field + return nil +} + +func (p *WorkflowServiceOpenAPICreateConversationArgs) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("OpenAPICreateConversation_args"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField1(oprot); err != nil { + fieldId = 1 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *WorkflowServiceOpenAPICreateConversationArgs) writeField1(oprot thrift.TProtocol) (err error) { + if err = oprot.WriteFieldBegin("request", thrift.STRUCT, 1); err != nil { + goto WriteFieldBeginError + } + if err := p.Request.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err) +} + +func (p *WorkflowServiceOpenAPICreateConversationArgs) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("WorkflowServiceOpenAPICreateConversationArgs(%+v)", *p) + +} + +type WorkflowServiceOpenAPICreateConversationResult struct { + Success *CreateConversationResponse `thrift:"success,0,optional"` +} + +func NewWorkflowServiceOpenAPICreateConversationResult() *WorkflowServiceOpenAPICreateConversationResult { + return &WorkflowServiceOpenAPICreateConversationResult{} +} + +func (p *WorkflowServiceOpenAPICreateConversationResult) InitDefault() { +} + +var WorkflowServiceOpenAPICreateConversationResult_Success_DEFAULT *CreateConversationResponse + +func (p *WorkflowServiceOpenAPICreateConversationResult) GetSuccess() (v *CreateConversationResponse) { + if !p.IsSetSuccess() { + return WorkflowServiceOpenAPICreateConversationResult_Success_DEFAULT + } + return p.Success +} + +var fieldIDToName_WorkflowServiceOpenAPICreateConversationResult = map[int16]string{ + 0: "success", +} + +func (p *WorkflowServiceOpenAPICreateConversationResult) IsSetSuccess() bool { + return p.Success != nil +} + +func (p *WorkflowServiceOpenAPICreateConversationResult) Read(iprot thrift.TProtocol) (err error) { + var fieldTypeId thrift.TType + var fieldId int16 + + if _, err = iprot.ReadStructBegin(); err != nil { + goto ReadStructBeginError + } + + for { + _, fieldTypeId, fieldId, err = iprot.ReadFieldBegin() + if err != nil { + goto ReadFieldBeginError + } + if fieldTypeId == thrift.STOP { + break + } + + switch fieldId { + case 0: + if fieldTypeId == thrift.STRUCT { + if err = p.ReadField0(iprot); err != nil { + goto ReadFieldError + } + } else if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + default: + if err = iprot.Skip(fieldTypeId); err != nil { + goto SkipFieldError + } + } + if err = iprot.ReadFieldEnd(); err != nil { + goto ReadFieldEndError + } + } + if err = iprot.ReadStructEnd(); err != nil { + goto ReadStructEndError + } + + return nil +ReadStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err) +ReadFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err) +ReadFieldError: + return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_WorkflowServiceOpenAPICreateConversationResult[fieldId]), err) +SkipFieldError: + return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err) + +ReadFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err) +ReadStructEndError: + return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err) +} + +func (p *WorkflowServiceOpenAPICreateConversationResult) ReadField0(iprot thrift.TProtocol) error { + _field := NewCreateConversationResponse() + if err := _field.Read(iprot); err != nil { + return err + } + p.Success = _field + return nil +} + +func (p *WorkflowServiceOpenAPICreateConversationResult) Write(oprot thrift.TProtocol) (err error) { + var fieldId int16 + if err = oprot.WriteStructBegin("OpenAPICreateConversation_result"); err != nil { + goto WriteStructBeginError + } + if p != nil { + if err = p.writeField0(oprot); err != nil { + fieldId = 0 + goto WriteFieldError + } + } + if err = oprot.WriteFieldStop(); err != nil { + goto WriteFieldStopError + } + if err = oprot.WriteStructEnd(); err != nil { + goto WriteStructEndError + } + return nil +WriteStructBeginError: + return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err) +WriteFieldError: + return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err) +WriteFieldStopError: + return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err) +WriteStructEndError: + return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err) +} + +func (p *WorkflowServiceOpenAPICreateConversationResult) writeField0(oprot thrift.TProtocol) (err error) { + if p.IsSetSuccess() { + if err = oprot.WriteFieldBegin("success", thrift.STRUCT, 0); err != nil { + goto WriteFieldBeginError + } + if err := p.Success.Write(oprot); err != nil { + return err + } + if err = oprot.WriteFieldEnd(); err != nil { + goto WriteFieldEndError + } + } + return nil +WriteFieldBeginError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 begin error: ", p), err) +WriteFieldEndError: + return thrift.PrependError(fmt.Sprintf("%T write field 0 end error: ", p), err) +} + +func (p *WorkflowServiceOpenAPICreateConversationResult) String() string { + if p == nil { + return "" + } + return fmt.Sprintf("WorkflowServiceOpenAPICreateConversationResult(%+v)", *p) + +} diff --git a/backend/api/router/coze/api.go b/backend/api/router/coze/api.go index d3ee72cb..b095c2b8 100644 --- a/backend/api/router/coze/api.go +++ b/backend/api/router/coze/api.go @@ -1,3 +1,19 @@ +/* + * 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 hertz generator. DO NOT EDIT. package coze @@ -250,6 +266,10 @@ func Register(r *server.Hertz) { } { _permission_api := _api.Group("/permission_api", _permission_apiMw()...) + { + _coze_web_app := _permission_api.Group("/coze_web_app", _coze_web_appMw()...) + _coze_web_app.POST("/impersonate_coze_user", append(_impersonatecozeuserMw(), coze.ImpersonateCozeUser)...) + } { _pat := _permission_api.Group("/pat", _patMw()...) _pat.POST("/create_personal_access_token_and_permission", append(_createpersonalaccesstokenandpermissionMw(), coze.CreatePersonalAccessTokenAndPermission)...) @@ -409,10 +429,21 @@ func Register(r *server.Hertz) { { _v1 := root.Group("/v1", _v1Mw()...) _v1.GET("/conversations", append(_listconversationsapiMw(), coze.ListConversationsApi)...) + _conversations := _v1.Group("/conversations", _conversationsMw()...) + _conversations.DELETE("/:conversation_id", append(_deleteconversationapiMw(), coze.DeleteConversationApi)...) + _conversations.PUT("/:conversation_id", append(_updateconversationapiMw(), coze.UpdateConversationApi)...) + { + _apps := _v1.Group("/apps", _appsMw()...) + _apps.GET("/:app_id", append(_getonlineappdataMw(), coze.GetOnlineAppData)...) + } { _bot0 := _v1.Group("/bot", _bot0Mw()...) _bot0.GET("/get_online_info", append(_getbotonlineinfoMw(), coze.GetBotOnlineInfo)...) } + { + _bots := _v1.Group("/bots", _botsMw()...) + _bots.GET("/:bot_id", append(_opengetbotinfoMw(), coze.OpenGetBotInfo)...) + } { _conversation0 := _v1.Group("/conversation", _conversation0Mw()...) _conversation0.POST("/create", append(_createconversationMw(), coze.CreateConversation)...) @@ -422,9 +453,9 @@ func Register(r *server.Hertz) { } } { - _conversations := _v1.Group("/conversations", _conversationsMw()...) + _conversations0 := _v1.Group("/conversations", _conversations0Mw()...) { - _conversation_id := _conversations.Group("/:conversation_id", _conversation_idMw()...) + _conversation_id := _conversations0.Group("/:conversation_id", _conversation_idMw()...) _conversation_id.POST("/clear", append(_clearconversationapiMw(), coze.ClearConversationApi)...) } } @@ -438,6 +469,10 @@ func Register(r *server.Hertz) { _workflow.POST("/run", append(_openapirunflowMw(), coze.OpenAPIRunFlow)...) _workflow.POST("/stream_resume", append(_openapistreamresumeflowMw(), coze.OpenAPIStreamResumeFlow)...) _workflow.POST("/stream_run", append(_openapistreamrunflowMw(), coze.OpenAPIStreamRunFlow)...) + { + _conversation1 := _workflow.Group("/conversation", _conversation1Mw()...) + _conversation1.POST("/create", append(_openapicreateconversationMw(), coze.OpenAPICreateConversation)...) + } } { _workflows := _v1.Group("/workflows", _workflowsMw()...) @@ -448,5 +483,7 @@ func Register(r *server.Hertz) { { _v3 := root.Group("/v3", _v3Mw()...) _v3.POST("/chat", append(_chatv3Mw(), coze.ChatV3)...) + _chat := _v3.Group("/chat", _chatMw()...) + _chat.POST("/cancel", append(_cancelchatapiMw(), coze.CancelChatApi)...) } } diff --git a/backend/api/router/coze/middleware.go b/backend/api/router/coze/middleware.go index f5a35a4d..271edfc9 100644 --- a/backend/api/router/coze/middleware.go +++ b/backend/api/router/coze/middleware.go @@ -1505,3 +1505,68 @@ func _upload1Mw() []app.HandlerFunc { // your code... return nil } + +func _conversation1Mw() []app.HandlerFunc { + // your code... + return nil +} + +func _openapicreateconversationMw() []app.HandlerFunc { + // your code... + return nil +} + +func _coze_web_appMw() []app.HandlerFunc { + // your code... + return nil +} + +func _impersonatecozeuserMw() []app.HandlerFunc { + // your code... + return nil +} + +func _botsMw() []app.HandlerFunc { + // your code... + return nil +} + +func _opengetbotinfoMw() []app.HandlerFunc { + // your code... + return nil +} + +func _deleteconversationapiMw() []app.HandlerFunc { + // your code... + return nil +} + +func _updateconversationapiMw() []app.HandlerFunc { + // your code... + return nil +} + +func _conversations0Mw() []app.HandlerFunc { + // your code... + return nil +} + +func _chatMw() []app.HandlerFunc { + // your code... + return nil +} + +func _cancelchatapiMw() []app.HandlerFunc { + // your code... + return nil +} + +func _appsMw() []app.HandlerFunc { + // your code... + return nil +} + +func _getonlineappdataMw() []app.HandlerFunc { + // your code... + return nil +} diff --git a/backend/application/app/app.go b/backend/application/app/app.go index c1d7e60b..dda64b08 100644 --- a/backend/application/app/app.go +++ b/backend/application/app/app.go @@ -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, diff --git a/backend/application/application.go b/backend/application/application.go index 2ddd91a8..b8e2fb5d 100644 --- a/backend/application/application.go +++ b/backend/application/application.go @@ -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 } diff --git a/backend/application/conversation/conversation.go b/backend/application/conversation/conversation.go index 8817402f..8674eefc 100644 --- a/backend/application/conversation/conversation.go +++ b/backend/application/conversation/conversation.go @@ -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 +} diff --git a/backend/application/conversation/init.go b/backend/application/conversation/init.go index f0f22db2..9c1fa4fd 100644 --- a/backend/application/conversation/init.go +++ b/backend/application/conversation/init.go @@ -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 } diff --git a/backend/application/conversation/openapi_agent_run.go b/backend/application/conversation/openapi_agent_run.go index 6a6e8df6..96c30fb4 100644 --- a/backend/application/conversation/openapi_agent_run.go +++ b/backend/application/conversation/openapi_agent_run.go @@ -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 +} diff --git a/backend/application/conversation/openapi_message.go b/backend/application/conversation/openapi_message.go index 385e0eaf..0e8282ee 100644 --- a/backend/application/conversation/openapi_message.go +++ b/backend/application/conversation/openapi_message.go @@ -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 +} diff --git a/backend/application/openauth/openapiauth.go b/backend/application/openauth/openapiauth.go index dd0c8322..1324f657 100644 --- a/backend/application/openauth/openapiauth.go +++ b/backend/application/openauth/openapiauth.go @@ -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) diff --git a/backend/application/search/resource_pack.go b/backend/application/search/resource_pack.go index 7ef269d9..03e2b0bf 100644 --- a/backend/application/search/resource_pack.go +++ b/backend/application/search/resource_pack.go @@ -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 { diff --git a/backend/application/singleagent/single_agent.go b/backend/application/singleagent/single_agent.go index b83915f4..7b0e3791 100644 --- a/backend/application/singleagent/single_agent.go +++ b/backend/application/singleagent/single_agent.go @@ -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 +} diff --git a/backend/application/upload/icon.go b/backend/application/upload/icon.go index baca6253..8bb92ee5 100644 --- a/backend/application/upload/icon.go +++ b/backend/application/upload/icon.go @@ -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 } diff --git a/backend/application/workflow/chatflow.go b/backend/application/workflow/chatflow.go new file mode 100644 index 00000000..1c2367be --- /dev/null +++ b/backend/application/workflow/chatflow.go @@ -0,0 +1,1630 @@ +/* + * 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 workflow + +import ( + "context" + + "errors" + "fmt" + "runtime/debug" + "strconv" + "strings" + "sync" + "time" + + "github.com/cloudwego/eino/schema" + + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" + workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + "github.com/coze-dev/coze-studio/backend/api/model/workflow" + "github.com/coze-dev/coze-studio/backend/application/base/ctxutil" + crossagentrun "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agentrun" + crossconversation "github.com/coze-dev/coze-studio/backend/crossdomain/contract/conversation" + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" + crossupload "github.com/coze-dev/coze-studio/backend/crossdomain/contract/upload" + agententity "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" + "github.com/coze-dev/coze-studio/backend/domain/upload/service" + "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/pkg/errorx" + "github.com/coze-dev/coze-studio/backend/pkg/lang/maps" + "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/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/sonic" + "github.com/coze-dev/coze-studio/backend/pkg/taskgroup" + "github.com/coze-dev/coze-studio/backend/types/consts" + "github.com/coze-dev/coze-studio/backend/types/errno" +) + +const ( + userRole = "user" + assistantRole = "assistant" + cardTemplate = ` +{ + "elements": { + "root": { + "id": "root", + "name": "Root", + "type": "@flowpd/cici-components/PageContainer", + "props": { + "backgroundColor": "grey", + "containerPadding": 16, + "containerRowGap": 12 + }, + "children": [ + "OpfZnYNHby", + "70zV0Jp5vy" + ], + "directives": { + + } + }, + "OpfZnYNHby": { + "id": "OpfZnYNHby", + "name": "FlowpdCiciComponentsColumnLayout", + "type": "@flowpd/cici-components/ColumnLayout", + "props": { + "backgroundColor": "transparent", + "layoutColumnGap": 4, + "layoutPaddingGap": 2, + "borderRadius": 0, + "enableClickEvent": false, + "action": "enableUrl", + "Columns": [ + { + "type": "slot", + "children": [ + "KPa0BqoODo" + ], + "config": { + "width": "weighted", + "weight": 1, + "vertical": "top", + "horizontal": "left", + "columnElementGap": 4, + "columnElementPadding": 2, + "enableClickEvent": false + } + } + ] + }, + "children": [ + + ], + "directives": { + "repeat": { + "type": "expression", + "value": "{{5fJt3qKpSz}}", + "replaceMap": { + "5fJt3qKpSz": "list" + } + } + } + }, + "KPa0BqoODo": { + "id": "KPa0BqoODo", + "name": "FlowpdCiciComponentsInput", + "type": "@flowpd/cici-components/Input", + "props": { + "enableLabel": true, + "label": { + "type": "expression", + "value": "{{item.name}}" + }, + "placeholder": "Please enter content.", + "maxLengthEnabled": false, + "maxLength": 140, + "required": false, + "enableSendIcon": true, + "actionType": "enableMessage", + "disableAfterAction": true, + "message": { + "type": "expression", + "value": "{{KPa0BqoODo_value}}" + } + }, + "children": [ + + ], + "directives": { + + } + }, + "70zV0Jp5vy": { + "id": "70zV0Jp5vy", + "name": "FlowpdCiciComponentsColumnLayout", + "type": "@flowpd/cici-components/ColumnLayout", + "props": { + "backgroundColor": "transparent", + "layoutColumnGap": 4, + "layoutPaddingGap": 2, + "borderRadius": 0, + "enableClickEvent": false, + "action": "enableUrl", + "Columns": [ + { + "type": "slot", + "children": [ + "mH5BNaFTl1" + ], + "config": { + "width": "weighted", + "weight": 1, + "vertical": "top", + "horizontal": "right", + "columnElementGap": 4, + "columnElementPadding": 2, + "enableClickEvent": false + } + } + ] + }, + "children": [ + + ], + "directives": { + + } + }, + "mH5BNaFTl1": { + "id": "mH5BNaFTl1", + "name": "FlowpdCiciComponentsButton", + "type": "@flowpd/cici-components/Button", + "props": { + "content": "Button", + "type": "primary", + "size": "small", + "width": "hug", + "widthPx": 160, + "textAlign": "center", + "enableLines": false, + "lines": 1, + "positionStyle": { + "type": "default" + }, + "actionType": "enableMessage", + "disableAfterAction": true, + "message": { + "type": "expression", + "value": "{{KPa0BqoODo_value}}" + } + }, + "children": [ + + ], + "directives": { + + } + } + }, + "rootID": "root", + "variables": { + "5fJt3qKpSz": { + "id": "5fJt3qKpSz", + "name": "list", + "defaultValue": [ + + ] + } + }, + "actions": { + + } +}` +) + +type inputCard struct { + Elements any `json:"elements"` + RootID string `json:"rootID"` + Variables map[string]any `json:"variables"` +} + +func defaultCard() *inputCard { + card := &inputCard{} + _ = sonic.UnmarshalString(cardTemplate, card) + return card +} + +func (w *ApplicationService) CreateApplicationConversationDef(ctx context.Context, req *workflow.CreateProjectConversationDefRequest) (resp *workflow.CreateProjectConversationDefResponse, err error) { + defer func() { + if panicErr := recover(); panicErr != nil { + err = safego.NewPanicErr(panicErr, debug.Stack()) + } + + if err != nil { + err = vo.WrapIfNeeded(errno.ErrConversationOfAppOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + }() + + var ( + spaceID = mustParseInt64(req.GetSpaceID()) + appID = mustParseInt64(req.GetProjectID()) + userID = ctxutil.MustGetUIDFromCtx(ctx) + ) + + if err := checkUserSpace(ctx, userID, spaceID); err != nil { + return nil, err + } + + uniqueID, err := GetWorkflowDomainSVC().CreateDraftConversationTemplate(ctx, &vo.CreateConversationTemplateMeta{ + AppID: appID, + SpaceID: spaceID, + Name: req.GetConversationName(), + UserID: userID, + }) + if err != nil { + return nil, err + } + + return &workflow.CreateProjectConversationDefResponse{ + UniqueID: strconv.FormatInt(uniqueID, 10), + SpaceID: req.GetSpaceID(), + }, err +} + +func (w *ApplicationService) UpdateApplicationConversationDef(ctx context.Context, req *workflow.UpdateProjectConversationDefRequest) (resp *workflow.UpdateProjectConversationDefResponse, err error) { + defer func() { + if panicErr := recover(); panicErr != nil { + err = safego.NewPanicErr(panicErr, debug.Stack()) + } + + if err != nil { + err = vo.WrapIfNeeded(errno.ErrConversationOfAppOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + }() + var ( + spaceID = mustParseInt64(req.GetSpaceID()) + templateID = mustParseInt64(req.GetUniqueID()) + appID = mustParseInt64(req.GetProjectID()) + userID = ctxutil.MustGetUIDFromCtx(ctx) + ) + + if err := checkUserSpace(ctx, userID, spaceID); err != nil { + return nil, err + } + + err = GetWorkflowDomainSVC().UpdateDraftConversationTemplateName(ctx, appID, userID, templateID, req.GetConversationName()) + if err != nil { + return nil, err + } + return &workflow.UpdateProjectConversationDefResponse{}, err +} + +func (w *ApplicationService) DeleteApplicationConversationDef(ctx context.Context, req *workflow.DeleteProjectConversationDefRequest) (resp *workflow.DeleteProjectConversationDefResponse, err error) { + defer func() { + if panicErr := recover(); panicErr != nil { + err = safego.NewPanicErr(panicErr, debug.Stack()) + } + + if err != nil { + err = vo.WrapIfNeeded(errno.ErrConversationOfAppOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + }() + var ( + appID = mustParseInt64(req.GetProjectID()) + templateID = mustParseInt64(req.GetUniqueID()) + ) + if err := checkUserSpace(ctx, ctxutil.MustGetUIDFromCtx(ctx), mustParseInt64(req.GetSpaceID())); err != nil { + return nil, err + } + if req.GetCheckOnly() { + wfs, err := GetWorkflowDomainSVC().CheckWorkflowsToReplace(ctx, appID, templateID) + if err != nil { + return nil, err + } + resp = &workflow.DeleteProjectConversationDefResponse{NeedReplace: make([]*workflow.Workflow, 0)} + for _, wf := range wfs { + resp.NeedReplace = append(resp.NeedReplace, &workflow.Workflow{ + Name: wf.Name, + URL: wf.IconURL, + WorkflowID: strconv.FormatInt(wf.ID, 10), + }) + } + return resp, nil + } + + wfID2ConversationName, err := maps.TransformKeyWithErrorCheck(req.GetReplace(), func(k1 string) (int64, error) { + return strconv.ParseInt(k1, 10, 64) + }) + + rowsAffected, err := GetWorkflowDomainSVC().DeleteDraftConversationTemplate(ctx, templateID, wfID2ConversationName) + if err != nil { + return nil, err + } + if rowsAffected > 0 { + return &workflow.DeleteProjectConversationDefResponse{ + Success: true, + }, err + } + + rowsAffected, err = GetWorkflowDomainSVC().DeleteDynamicConversation(ctx, vo.Draft, templateID) + if err != nil { + return nil, err + } + + if rowsAffected == 0 { + return nil, fmt.Errorf("delete conversation failed") + } + + return &workflow.DeleteProjectConversationDefResponse{ + Success: true, + }, nil + +} + +func (w *ApplicationService) ListApplicationConversationDef(ctx context.Context, req *workflow.ListProjectConversationRequest) (resp *workflow.ListProjectConversationResponse, err error) { + defer func() { + if panicErr := recover(); panicErr != nil { + err = safego.NewPanicErr(panicErr, debug.Stack()) + } + + if err != nil { + err = vo.WrapIfNeeded(errno.ErrConversationOfAppOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + }() + var connectorID int64 + if len(req.GetConnectorID()) != 0 { + connectorID = mustParseInt64(req.GetConnectorID()) + } else { + connectorID = consts.CozeConnectorID + } + var ( + page = mustParseInt64(ternary.IFElse(req.GetCursor() == "", "0", req.GetCursor())) + size = req.GetLimit() + userID = ctxutil.MustGetUIDFromCtx(ctx) + spaceID = mustParseInt64(req.GetSpaceID()) + appID = mustParseInt64(req.GetProjectID()) + version = req.ProjectVersion + listConversationMeta = vo.ListConversationMeta{ + APPID: appID, + UserID: userID, + ConnectorID: connectorID, + } + ) + + if err := checkUserSpace(ctx, userID, spaceID); err != nil { + return nil, err + } + + env := ternary.IFElse(req.GetCreateEnv() == workflow.CreateEnv_Draft, vo.Draft, vo.Online) + if req.GetCreateMethod() == workflow.CreateMethod_ManualCreate { + templates, err := GetWorkflowDomainSVC().ListConversationTemplate(ctx, env, &vo.ListConversationTemplatePolicy{ + AppID: appID, + Page: &vo.Page{ + Page: int32(page), + Size: int32(size), + }, + NameLike: ternary.IFElse(len(req.GetNameLike()) == 0, nil, ptr.Of(req.GetNameLike())), + Version: version, + }) + if err != nil { + return nil, err + } + + stsConversations, err := GetWorkflowDomainSVC().MGetStaticConversation(ctx, env, userID, connectorID, slices.Transform(templates, func(a *entity.ConversationTemplate) int64 { + return a.TemplateID + })) + if err != nil { + return nil, err + } + stsConversationMap := slices.ToMap(stsConversations, func(e *entity.StaticConversation) (int64, *entity.StaticConversation) { + return e.TemplateID, e + }) + + resp = &workflow.ListProjectConversationResponse{Data: make([]*workflow.ProjectConversation, 0)} + for _, tmpl := range templates { + conversationID := "" + if c, ok := stsConversationMap[tmpl.TemplateID]; ok { + conversationID = strconv.FormatInt(c.ConversationID, 10) + } + resp.Data = append(resp.Data, &workflow.ProjectConversation{ + UniqueID: strconv.FormatInt(tmpl.TemplateID, 10), + ConversationName: tmpl.Name, + ConversationID: conversationID, + }) + } + } + + if req.GetCreateMethod() == workflow.CreateMethod_NodeCreate { + dyConversations, err := GetWorkflowDomainSVC().ListDynamicConversation(ctx, env, &vo.ListConversationPolicy{ + ListConversationMeta: listConversationMeta, + Page: &vo.Page{ + Page: int32(page), + Size: int32(size), + }, + NameLike: ternary.IFElse(len(req.GetNameLike()) == 0, nil, ptr.Of(req.GetNameLike())), + }) + if err != nil { + return nil, err + } + resp = &workflow.ListProjectConversationResponse{Data: make([]*workflow.ProjectConversation, 0, len(dyConversations))} + resp.Data = append(resp.Data, slices.Transform(dyConversations, func(a *entity.DynamicConversation) *workflow.ProjectConversation { + return &workflow.ProjectConversation{ + UniqueID: strconv.FormatInt(a.ID, 10), + ConversationName: a.Name, + ConversationID: strconv.FormatInt(a.ConversationID, 10), + } + })...) + + } + + return resp, nil +} + +func (w *ApplicationService) OpenAPIChatFlowRun(ctx context.Context, req *workflow.ChatFlowRunRequest) ( + _ *schema.StreamReader[[]*workflow.ChatFlowRunResponse], err error) { + defer func() { + if panicErr := recover(); panicErr != nil { + err = safego.NewPanicErr(panicErr, debug.Stack()) + } + + if err != nil { + err = vo.WrapIfNeeded(errno.ErrWorkflowOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + }() + + if len(req.GetAdditionalMessages()) == 0 { + return nil, fmt.Errorf("additional_messages is requird") + } + + messages := req.GetAdditionalMessages() + + lastUserMessage := messages[len(req.GetAdditionalMessages())-1] + if lastUserMessage.Role != userRole { + return nil, errors.New("the role of the last day message must be user") + } + + var parameters = make(map[string]any) + if len(req.GetParameters()) > 0 { + err := sonic.UnmarshalString(req.GetParameters(), ¶meters) + if err != nil { + return nil, err + } + } + + var ( + workflowID = mustParseInt64(req.GetWorkflowID()) + isDebug = req.GetExecuteMode() == "DEBUG" + appID, agentID *int64 + resolveAppID int64 + conversationID int64 + sectionID int64 + version string + locator workflowModel.Locator + apiKeyInfo = ctxutil.GetApiAuthFromCtx(ctx) + userID = apiKeyInfo.UserID + connectorID int64 + ) + if len(req.GetConnectorID()) == 0 { + connectorID = ternary.IFElse(isDebug, consts.CozeConnectorID, apiKeyInfo.ConnectorID) + } else { + connectorID = mustParseInt64(req.GetConnectorID()) + } + + if req.IsSetAppID() { + appID = ptr.Of(mustParseInt64(req.GetAppID())) + resolveAppID = mustParseInt64(req.GetAppID()) + } + if req.IsSetBotID() { + agentID = ptr.Of(mustParseInt64(req.GetBotID())) + resolveAppID = mustParseInt64(req.GetBotID()) + } + + if appID != nil && agentID != nil { + return nil, errors.New("project_id and bot_id cannot be set at the same time") + } + + if isDebug { + locator = workflowModel.FromDraft + } else { + meta, err := GetWorkflowDomainSVC().Get(ctx, &vo.GetPolicy{ + ID: workflowID, + MetaOnly: true, + }) + if err != nil { + return nil, err + } + + if meta.LatestPublishedVersion == nil { + return nil, vo.NewError(errno.ErrWorkflowNotPublished) + } + if req.IsSetVersion() { + version = req.GetVersion() + locator = workflowModel.FromSpecificVersion + } else { + version = meta.GetLatestVersion() + locator = workflowModel.FromLatestVersion + } + } + + if req.IsSetConversationID() && !req.IsSetBotID() { + conversationID = mustParseInt64(req.GetConversationID()) + cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, conversationID) + if err != nil { + return nil, err + } + sectionID = cInfo.SectionID + + // only trust the conversation name under the app + conversationName, existed, err := GetWorkflowDomainSVC().GetConversationNameByID(ctx, ternary.IFElse(isDebug, vo.Draft, vo.Online), resolveAppID, connectorID, conversationID) + if err != nil { + return nil, err + } + if !existed { + return nil, fmt.Errorf("conversation not found") + } + parameters["CONVERSATION_NAME"] = conversationName + } else if req.IsSetConversationID() && req.IsSetBotID() { + parameters["CONVERSATION_NAME"] = "Default" + conversationID = mustParseInt64(req.GetConversationID()) + cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, conversationID) + if err != nil { + return nil, err + } + sectionID = cInfo.SectionID + } else { + conversationName, ok := parameters["CONVERSATION_NAME"].(string) + if !ok { + return nil, fmt.Errorf("conversation name is requried") + } + cID, sID, err := GetWorkflowDomainSVC().GetOrCreateConversation(ctx, ternary.IFElse(isDebug, vo.Draft, vo.Online), resolveAppID, connectorID, userID, conversationName) + if err != nil { + return nil, err + } + conversationID = cID + sectionID = sID + } + + runRecord, err := crossagentrun.DefaultSVC().Create(ctx, &agententity.AgentRunMeta{ + AgentID: resolveAppID, + ConversationID: conversationID, + UserID: strconv.FormatInt(userID, 10), + ConnectorID: connectorID, + SectionID: sectionID, + }) + if err != nil { + return nil, err + } + + roundID := runRecord.ID + + userMessage, err := toConversationMessage(ctx, resolveAppID, conversationID, userID, roundID, sectionID, message.MessageTypeQuestion, lastUserMessage) + if err != nil { + return nil, err + } + + messageClient := crossmessage.DefaultSVC() + _, err = messageClient.Create(ctx, userMessage) + if err != nil { + return nil, err + } + + info, existed, unbinding, err := GetWorkflowDomainSVC().GetConvRelatedInfo(ctx, conversationID) + if err != nil { + return nil, err + } + + userSchemaMessage, err := toSchemaMessage(ctx, lastUserMessage) + if err != nil { + return nil, err + } + + if existed { + var data = lastUserMessage.Content + if info.NodeType == entity.NodeTypeInputReceiver { + data = parserInput(lastUserMessage.Content) + } + sr, err := GetWorkflowDomainSVC().StreamResume(ctx, &entity.ResumeRequest{ + EventID: info.EventID, + ExecuteID: info.ExecID, + ResumeData: data, + }, workflowModel.ExecuteConfig{ + Operator: userID, + Mode: ternary.IFElse(isDebug, workflowModel.ExecuteModeDebug, workflowModel.ExecuteModeRelease), + ConnectorID: connectorID, + ConnectorUID: strconv.FormatInt(userID, 10), + BizType: workflowModel.BizTypeWorkflow, + }) + + if err != nil { + unErr := unbinding() + if unErr != nil { + logs.CtxErrorf(ctx, "unbinding failed, error: %v", unErr) + } + return nil, err + } + return schema.StreamReaderWithConvert(sr, w.convertToChatFlowRunResponseList(ctx, convertToChatFlowInfo{ + appID: resolveAppID, + conversationID: conversationID, + roundID: roundID, + workflowID: workflowID, + sectionID: sectionID, + unbinding: unbinding, + userMessage: userSchemaMessage, + suggestReplyInfo: req.GetSuggestReplyInfo(), + })), nil + + } + + exeCfg := workflowModel.ExecuteConfig{ + ID: mustParseInt64(req.GetWorkflowID()), + From: locator, + Version: version, + Operator: userID, + Mode: ternary.IFElse(isDebug, workflowModel.ExecuteModeDebug, workflowModel.ExecuteModeRelease), + AppID: appID, + AgentID: agentID, + ConnectorID: connectorID, + ConnectorUID: strconv.FormatInt(userID, 10), + TaskType: workflowModel.TaskTypeForeground, + SyncPattern: workflowModel.SyncPatternStream, + InputFailFast: true, + BizType: workflowModel.BizTypeWorkflow, + + ConversationID: ptr.Of(conversationID), + RoundID: ptr.Of(roundID), + InitRoundID: ptr.Of(roundID), + SectionID: ptr.Of(sectionID), + + UserMessage: userSchemaMessage, + Cancellable: isDebug, + } + + historyMessages, err := makeChatFlowHistoryMessages(ctx, resolveAppID, conversationID, userID, sectionID, connectorID, messages[:len(req.GetAdditionalMessages())-1]) + if err != nil { + return nil, err + } + + if len(historyMessages) > 0 { + g := taskgroup.NewTaskGroup(ctx, len(historyMessages)) + for _, hm := range historyMessages { + hMsg := hm + g.Go(func() error { + _, err := messageClient.Create(ctx, hMsg) + if err != nil { + return err + } + return nil + }) + } + err = g.Wait() + if err != nil { + logs.CtxWarnf(ctx, "create history message failed, err=%v", err) + } + } + parameters["USER_INPUT"], err = w.makeChatFlowUserInput(ctx, lastUserMessage) + if err != nil { + return nil, err + } + + sr, err := GetWorkflowDomainSVC().StreamExecute(ctx, exeCfg, parameters) + if err != nil { + return nil, err + } + + return schema.StreamReaderWithConvert(sr, w.convertToChatFlowRunResponseList(ctx, convertToChatFlowInfo{ + appID: resolveAppID, + conversationID: conversationID, + roundID: roundID, + workflowID: workflowID, + sectionID: sectionID, + unbinding: unbinding, + userMessage: userSchemaMessage, + suggestReplyInfo: req.GetSuggestReplyInfo(), + })), nil + +} + +func (w *ApplicationService) convertToChatFlowRunResponseList(ctx context.Context, info convertToChatFlowInfo) func(msg *entity.Message) (responses []*workflow.ChatFlowRunResponse, err error) { + var ( + appID = info.appID + conversationID = info.conversationID + roundID = info.roundID + workflowID = info.workflowID + sectionID = info.sectionID + unbinding = info.unbinding + userMessage = info.userMessage + spaceID int64 + executeID int64 + + outputCount int32 + inputCount int32 + + intermediateMessage *message.Message + + needRegeneratedMessage = true + + messageDetailID int64 + ) + + return func(msg *entity.Message) (responses []*workflow.ChatFlowRunResponse, err error) { + defer func() { + if err != nil { + if unbinding != nil { + unErr := unbinding() + if unErr != nil { + logs.CtxErrorf(ctx, "unbinding failed, error: %v", unErr) + } + } + if intermediateMessage != nil { + _, mErr := crossmessage.DefaultSVC().Create(ctx, intermediateMessage) + if mErr != nil { + logs.CtxWarnf(ctx, "create message faield, err: %v", err) + } + } + } + + }() + + if msg.StateMessage != nil { + if executeID > 0 && executeID != msg.StateMessage.ExecuteID { + return nil, schema.ErrNoValue + } + switch msg.StateMessage.Status { + case entity.WorkflowSuccess: + suggestWorkflowResponse := make([]*workflow.ChatFlowRunResponse, 0, 3) + if info.suggestReplyInfo != nil && info.suggestReplyInfo.IsSetSuggestReplyMode() && info.suggestReplyInfo.GetSuggestReplyMode() != workflow.SuggestReplyInfoMode_Disable { + sInfo := &vo.SuggestInfo{ + UserInput: userMessage, + AnswerInput: schema.AssistantMessage(intermediateMessage.Content, nil), + PersonaInput: info.suggestReplyInfo.CustomizedSuggestPrompt, + } + + suggests, err := GetWorkflowDomainSVC().Suggest(ctx, sInfo) + if err != nil { + return nil, err + } + + for index, s := range suggests { + suggestWorkflowResponse = append(suggestWorkflowResponse, &workflow.ChatFlowRunResponse{ + Event: string(vo.ChatFlowMessageCompleted), + Data: func() string { + s, _ := sonic.MarshalString(&vo.MessageDetail{ + ID: strconv.FormatInt(time.Now().UnixNano()+int64(index), 10), + ChatID: strconv.FormatInt(roundID, 10), + ConversationID: strconv.FormatInt(conversationID, 10), + SectionID: strconv.FormatInt(sectionID, 10), + BotID: strconv.FormatInt(appID, 10), + Role: string(schema.Assistant), + Type: "follow_up", + ContentType: "text", + Content: s, + }) + return s + }(), + }) + + } + } + + chatDoneEvent := &vo.ChatFlowDetail{ + ID: strconv.FormatInt(roundID, 10), + ConversationID: strconv.FormatInt(conversationID, 10), + SectionID: strconv.FormatInt(sectionID, 10), + BotID: strconv.FormatInt(appID, 10), + Status: vo.Completed, + ExecuteID: strconv.FormatInt(executeID, 10), + Usage: &vo.Usage{ + InputTokens: ptr.Of(inputCount), + OutputTokens: ptr.Of(outputCount), + TokenCount: ptr.Of(outputCount + inputCount), + }, + } + data, err := sonic.MarshalString(chatDoneEvent) + if err != nil { + return nil, err + } + + doneData, err := sonic.MarshalString(map[string]interface{}{ + "debug_url": fmt.Sprintf(workflowModel.DebugURLTpl, executeID, spaceID, workflowID), + }) + if err != nil { + return nil, err + } + + if unbinding != nil { + unErr := unbinding() + if unErr != nil { + logs.CtxErrorf(ctx, "unbinding failed, error: %v", unErr) + } + } + + return append(suggestWorkflowResponse, []*workflow.ChatFlowRunResponse{ + { + Event: string(vo.ChatFlowCompleted), + Data: data, + }, + { + Event: string(vo.ChatFlowDone), + Data: doneData, + }, + }...), nil + + case entity.WorkflowFailed: + var wfe vo.WorkflowError + if !errors.As(msg.StateMessage.LastError, &wfe) { + panic("stream run last error is not a WorkflowError") + } + + chatFailedEvent := &vo.ErrorDetail{ + Code: strconv.Itoa(int(wfe.Code())), + Msg: wfe.Msg(), + DebugUrl: wfe.DebugURL(), + } + data, err := sonic.MarshalString(chatFailedEvent) + if err != nil { + return nil, err + } + if intermediateMessage != nil { + _, err := crossmessage.DefaultSVC().Create(ctx, intermediateMessage) + if err != nil { + return nil, err + } + } + + if unbinding != nil { + unErr := unbinding() + if unErr != nil { + logs.CtxErrorf(ctx, "unbinding failed, error: %v", unErr) + } + } + + return []*workflow.ChatFlowRunResponse{ + { + Event: string(vo.ChatFlowError), + Data: data, + }, + }, err + + case entity.WorkflowCancel: + if intermediateMessage != nil { + _, err := crossmessage.DefaultSVC().Create(ctx, intermediateMessage) + if err != nil { + return nil, err + } + } + + if unbinding != nil { + unErr := unbinding() + if unErr != nil { + logs.CtxErrorf(ctx, "unbinding failed, error: %v", unErr) + } + } + + case entity.WorkflowInterrupted: + + var ( + interruptEvent = msg.StateMessage.InterruptEvent + interruptData = interruptEvent.InterruptData + msgContent string + contentType message.ContentType + ) + + if interruptEvent.EventType == entity.InterruptEventInput { + msgContent, contentType, err = renderInputCardDSL(interruptData) + if err != nil { + return nil, err + } + } else if interruptEvent.EventType == entity.InterruptEventQuestion { + msgContent, contentType, err = renderQACardDSL(interruptData) + if err != nil { + return nil, err + } + } else { + return nil, fmt.Errorf("unsupported interrupt event type: %s", interruptEvent.EventType) + } + + _, err = crossmessage.DefaultSVC().Create(ctx, &message.Message{ + AgentID: appID, + RunID: roundID, + SectionID: sectionID, + Content: msgContent, + ConversationID: conversationID, + Role: schema.Assistant, + MessageType: message.MessageTypeAnswer, + ContentType: contentType, + }) + if err != nil { + return nil, err + } + + completeData, _ := sonic.MarshalString(&vo.MessageDetail{ + ID: strconv.FormatInt(interruptEvent.ID, 10), + ChatID: strconv.FormatInt(roundID, 10), + ConversationID: strconv.FormatInt(conversationID, 10), + SectionID: strconv.FormatInt(sectionID, 10), + BotID: strconv.FormatInt(appID, 10), + Role: string(schema.Assistant), + Type: string(entity.Answer), + ContentType: string(contentType), + Content: msgContent, + }) + + if contentType == message.ContentTypeText { + responses = append(responses, &workflow.ChatFlowRunResponse{ + Event: string(vo.ChatFlowMessageDelta), + Data: completeData, + }) + } + + responses = append(responses, &workflow.ChatFlowRunResponse{ + Event: string(vo.ChatFlowMessageCompleted), + Data: completeData, + }) + + data, _ := sonic.MarshalString(&vo.ChatFlowDetail{ + ID: strconv.FormatInt(roundID, 10), + ConversationID: strconv.FormatInt(conversationID, 10), + SectionID: strconv.FormatInt(sectionID, 10), + Status: vo.RequiresAction, + ExecuteID: strconv.FormatInt(executeID, 10), + }) + + doneData, _ := sonic.MarshalString(map[string]interface{}{ + "debug_url": fmt.Sprintf(workflowModel.DebugURLTpl, executeID, spaceID, workflowID), + }) + + responses = append(responses, &workflow.ChatFlowRunResponse{ + Event: string(vo.ChatFlowRequiresAction), + Data: data, + }, &workflow.ChatFlowRunResponse{ + Event: string(vo.ChatFlowDone), + Data: doneData, + }) + + err = GetWorkflowDomainSVC().BindConvRelatedInfo(ctx, conversationID, entity.ConvRelatedInfo{ + EventID: msg.StateMessage.InterruptEvent.ID, ExecID: executeID, NodeType: msg.StateMessage.InterruptEvent.NodeType, + }) + if err != nil { + return nil, err + } + + return responses, nil + + case entity.WorkflowRunning: + executeID = msg.StateMessage.ExecuteID + spaceID = msg.StateMessage.SpaceID + + responses = make([]*workflow.ChatFlowRunResponse, 0) + + chatEvent := &vo.ChatFlowDetail{ + ID: strconv.FormatInt(roundID, 10), + ConversationID: strconv.FormatInt(conversationID, 10), + Status: vo.Created, + ExecuteID: strconv.FormatInt(executeID, 10), + SectionID: strconv.FormatInt(sectionID, 10), + } + + data, _ := sonic.MarshalString(chatEvent) + responses = append(responses, &workflow.ChatFlowRunResponse{ + Event: string(vo.ChatFlowCreated), + Data: data, + }) + + chatEvent.Status = vo.InProgress + data, _ = sonic.MarshalString(chatEvent) + responses = append(responses, &workflow.ChatFlowRunResponse{ + Event: string(vo.ChatFlowInProgress), + Data: data, + }) + return responses, nil + + default: + return nil, schema.ErrNoValue + } + } + if msg.DataMessage != nil { + if msg.Type != entity.Answer { + return nil, schema.ErrNoValue + } + if executeID > 0 && executeID != msg.DataMessage.ExecuteID { + return nil, schema.ErrNoValue + } + if msg.DataMessage.NodeType == entity.NodeTypeQuestionAnswer || msg.DataMessage.NodeType == entity.NodeTypeInputReceiver { + return nil, schema.ErrNoValue + } + dataMessage := msg.DataMessage + + if needRegeneratedMessage { + id, err := w.IDGenerator.GenID(ctx) + if err != nil { + return nil, err + } + intermediateMessage = &message.Message{ + ID: id, + AgentID: appID, + RunID: roundID, + SectionID: sectionID, + ConversationID: conversationID, + Role: schema.Assistant, + MessageType: message.MessageTypeAnswer, + ContentType: message.ContentTypeText, + } + messageDetailID = id + needRegeneratedMessage = false + + } + + intermediateMessage.Content += msg.Content + + deltaData, _ := sonic.MarshalString(&vo.MessageDetail{ + ID: strconv.FormatInt(messageDetailID, 10), + ChatID: strconv.FormatInt(roundID, 10), + ConversationID: strconv.FormatInt(conversationID, 10), + SectionID: strconv.FormatInt(sectionID, 10), + BotID: strconv.FormatInt(appID, 10), + Role: string(dataMessage.Role), + Type: string(dataMessage.Type), + ContentType: string(message.ContentTypeText), + Content: msg.Content, + }) + + if !msg.Last { + return []*workflow.ChatFlowRunResponse{ + { + Event: string(vo.ChatFlowMessageDelta), + Data: deltaData, + }, + }, nil + } + + _, err = crossmessage.DefaultSVC().Create(ctx, intermediateMessage) + if err != nil { + return nil, err + } + + completeData, _ := sonic.MarshalString(&vo.MessageDetail{ + ID: strconv.FormatInt(messageDetailID, 10), + ChatID: strconv.FormatInt(roundID, 10), + ConversationID: strconv.FormatInt(conversationID, 10), + SectionID: strconv.FormatInt(sectionID, 10), + BotID: strconv.FormatInt(appID, 10), + Role: string(dataMessage.Role), + Type: string(dataMessage.Type), + ContentType: string(message.ContentTypeText), + Content: intermediateMessage.Content, + }) + needRegeneratedMessage = true + + return []*workflow.ChatFlowRunResponse{ + { + Event: string(vo.ChatFlowMessageDelta), + Data: deltaData, + }, + { + Event: string(vo.ChatFlowMessageCompleted), + Data: completeData, + }, + }, nil + + } + + return nil, err + } +} + +func (w *ApplicationService) makeChatFlowUserInput(ctx context.Context, message *workflow.EnterMessage) (string, error) { + type content struct { + Type string `json:"type"` + FileID *string `json:"file_id"` + Text *string `json:"text"` + } + if message.ContentType == "text" { + return message.Content, nil + } else if message.ContentType == "object_string" { + contents := make([]content, 0) + err := sonic.UnmarshalString(message.Content, &contents) + if err != nil { + return "", err + } + texts := make([]string, 0) + urls := make([]string, 0) + for _, ct := range contents { + if ct.Text != nil && len(*ct.Text) > 0 { + texts = append(texts, *ct.Text) + } + if ct.FileID != nil && len(*ct.FileID) > 0 { + fileID := mustParseInt64(*ct.FileID) + file, err := crossupload.DefaultSVC().GetFile(ctx, &service.GetFileRequest{ID: fileID}) + if err != nil { + return "", err + } + if file.File == nil { + return "", fmt.Errorf("file not found") + } + urls = append(urls, file.File.Url) + } + } + + return strings.Join(append(texts, urls...), ","), nil + + } else { + return "", fmt.Errorf("invalid message ccontent type %v", message.ContentType) + } + +} +func makeChatFlowHistoryMessages(ctx context.Context, appID, conversationID, userID, sectionID, connectorID int64, messages []*workflow.EnterMessage) ([]*message.Message, error) { + + var ( + rID int64 + err error + runRecord *agententity.RunRecordMeta + ) + + historyMessages := make([]*message.Message, 0, len(messages)) + + for _, msg := range messages { + if msg.Role == userRole { + runRecord, err = crossagentrun.DefaultSVC().Create(ctx, &agententity.AgentRunMeta{ + AgentID: appID, + ConversationID: conversationID, + UserID: strconv.FormatInt(userID, 10), + ConnectorID: connectorID, + SectionID: sectionID, + }) + if err != nil { + return nil, err + } + rID = runRecord.ID + } else if msg.Role == assistantRole && rID == 0 { + continue + } else { + return nil, fmt.Errorf("invalid role type %v", msg.Role) + } + + m, err := toConversationMessage(ctx, appID, conversationID, userID, rID, sectionID, ternary.IFElse(msg.Role == userRole, message.MessageTypeQuestion, message.MessageTypeAnswer), msg) + if err != nil { + return nil, err + } + + historyMessages = append(historyMessages, m) + + } + return historyMessages, nil +} + +func (w *ApplicationService) OpenAPICreateConversation(ctx context.Context, req *workflow.CreateConversationRequest) (resp *workflow.CreateConversationResponse, err error) { + + defer func() { + if panicErr := recover(); panicErr != nil { + err = safego.NewPanicErr(panicErr, debug.Stack()) + } + if err != nil { + err = vo.WrapIfNeeded(errno.ErrWorkflowOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + }() + + var ( + appID = mustParseInt64(req.GetAppID()) + apiKeyInfo = ctxutil.GetApiAuthFromCtx(ctx) + userID = apiKeyInfo.UserID + env = ternary.IFElse(req.GetDraftMode(), vo.Draft, vo.Online) + cID int64 + //spaceID = mustParseInt64(req.GetSpaceID()) + //_ = spaceID + ) + + // todo check permission + + if !req.GetGetOrCreate() { + cID, err = GetWorkflowDomainSVC().UpdateConversation(ctx, env, appID, req.GetConnectorId(), userID, req.GetConversationMame()) + } else { + var tplExisted, dcExisted bool + var tplErr, dcErr error + var wg sync.WaitGroup + wg.Add(2) + + safego.Go(ctx, func() { + defer wg.Done() + _, tplExisted, tplErr = GetWorkflowDomainSVC().GetTemplateByName(ctx, env, appID, req.GetConversationMame()) + }) + + safego.Go(ctx, func() { + defer wg.Done() + _, dcExisted, dcErr = GetWorkflowDomainSVC().GetDynamicConversationByName(ctx, env, appID, req.GetConnectorId(), userID, req.GetConversationMame()) + }) + + wg.Wait() + + if tplErr != nil { + return nil, tplErr + } + if dcErr != nil { + return nil, dcErr + } + + if !tplExisted && !dcExisted { + return &workflow.CreateConversationResponse{ + Code: errno.ErrConversationNotFoundForOperation, + Msg: "Conversation not found. Please create a conversation before attempting to perform any related operations.", + }, nil + } + + cID, _, err = GetWorkflowDomainSVC().GetOrCreateConversation(ctx, env, appID, req.GetConnectorId(), userID, req.GetConversationMame()) + + } + if err != nil { + return nil, err + } + + cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, cID) + if err != nil { + return nil, err + } + + return &workflow.CreateConversationResponse{ + ConversationData: &workflow.ConversationData{ + Id: cID, + LastSectionID: ptr.Of(cInfo.SectionID), + }, + }, nil +} + +func toConversationMessage(ctx context.Context, appID, cid, userID, roundID, sectionID int64, messageType message.MessageType, msg *workflow.EnterMessage) (*message.Message, error) { + type content struct { + Type string `json:"type"` + FileID *string `json:"file_id"` + Text *string `json:"text"` + } + if msg.ContentType == "text" { + return &message.Message{ + Role: schema.User, + ConversationID: cid, + AgentID: appID, + RunID: roundID, + Content: msg.Content, + ContentType: message.ContentTypeText, + MessageType: messageType, + UserID: strconv.FormatInt(userID, 10), + SectionID: sectionID, + }, nil + + } else if msg.ContentType == "object_string" { + contents := make([]*content, 0) + err := sonic.UnmarshalString(msg.Content, &contents) + if err != nil { + return nil, err + } + + m := &message.Message{ + Role: schema.User, + MessageType: messageType, + ConversationID: cid, + AgentID: appID, + UserID: strconv.FormatInt(userID, 10), + RunID: roundID, + ContentType: message.ContentTypeMix, + MultiContent: make([]*message.InputMetaData, 0, len(contents)), + SectionID: sectionID, + } + + for _, ct := range contents { + if ct.Text != nil { + m.MultiContent = append(m.MultiContent, &message.InputMetaData{ + Type: message.InputTypeText, + Text: *ct.Text, + }) + } else if ct.FileID != nil { + fileID := mustParseInt64(*ct.FileID) + file, err := crossupload.DefaultSVC().GetFile(ctx, &service.GetFileRequest{ID: fileID}) + if err != nil { + return nil, err + } + if file.File == nil { + return nil, fmt.Errorf("file not found") + } + + m.MultiContent = append(m.MultiContent, &message.InputMetaData{ + Type: message.InputType(ct.Type), + FileData: []*message.FileData{ + { + Url: file.File.Url, + URI: file.File.TosURI, + Name: file.File.Name, + }, + }, + }) + } else { + return nil, fmt.Errorf("invalid input type %v", ct.Type) + } + } + return m, nil + } else { + return nil, fmt.Errorf("invalid message content type %v", msg.ContentType) + } +} + +func toSchemaMessage(ctx context.Context, msg *workflow.EnterMessage) (*schema.Message, error) { + type content struct { + Type string `json:"type"` + FileID *string `json:"file_id"` + Text *string `json:"text"` + } + if msg.ContentType == "text" { + return &schema.Message{ + Role: schema.User, + Content: msg.Content, + }, nil + + } else if msg.ContentType == "object_string" { + contents := make([]*content, 0) + err := sonic.UnmarshalString(msg.Content, &contents) + if err != nil { + return nil, err + } + m := &schema.Message{ + Role: schema.User, + MultiContent: make([]schema.ChatMessagePart, 0, len(contents)), + } + + for _, ct := range contents { + if ct.Text != nil { + if len(*ct.Text) == 0 { + continue + } + m.MultiContent = append(m.MultiContent, schema.ChatMessagePart{ + Type: schema.ChatMessagePartTypeText, + Text: *ct.Text, + }) + } else if ct.FileID != nil { + fileID := mustParseInt64(*ct.FileID) + file, err := crossupload.DefaultSVC().GetFile(ctx, &service.GetFileRequest{ID: fileID}) + if err != nil { + return nil, err + } + if file.File == nil { + return nil, fmt.Errorf("file not found") + } + switch ct.Type { + case "file": + m.MultiContent = append(m.MultiContent, schema.ChatMessagePart{ + Type: schema.ChatMessagePartTypeFileURL, + FileURL: &schema.ChatMessageFileURL{ + URL: file.File.Url, + }, + }) + case "image": + m.MultiContent = append(m.MultiContent, schema.ChatMessagePart{ + Type: schema.ChatMessagePartTypeImageURL, + ImageURL: &schema.ChatMessageImageURL{ + URL: file.File.Url, + }, + }) + case "audio": + m.MultiContent = append(m.MultiContent, schema.ChatMessagePart{ + Type: schema.ChatMessagePartTypeAudioURL, + AudioURL: &schema.ChatMessageAudioURL{ + URL: file.File.Url, + }, + }) + case "video": + m.MultiContent = append(m.MultiContent, schema.ChatMessagePart{ + Type: schema.ChatMessagePartTypeVideoURL, + VideoURL: &schema.ChatMessageVideoURL{ + URL: file.File.Url, + }, + }) + } + + } else { + return nil, fmt.Errorf("invalid input type %v", ct.Type) + } + } + return m, nil + } else { + return nil, fmt.Errorf("invalid message content type %v", msg.ContentType) + } +} + +type convertToChatFlowInfo struct { + userMessage *schema.Message + appID int64 + conversationID int64 + roundID int64 + workflowID int64 + sectionID int64 + unbinding func() error + suggestReplyInfo *workflow.SuggestReplyInfo +} + +func parserInput(inputString string) string { + result := map[string]any{} + lines := strings.Split(inputString, "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + keyValue := strings.SplitN(line, ":", 2) + if len(keyValue) == 2 { + result[keyValue[0]] = keyValue[1] + } + } + str, _ := sonic.MarshalString(result) + + return str + +} + +func renderInputCardDSL(c string) (string, message.ContentType, error) { + type contentInfo struct { + Content string `json:"content"` + } + type field struct { + Type string `json:"type"` + Name string `json:"name"` + Required bool `json:"required"` + } + type inputCard struct { + CardType int64 `json:"card_type"` + ContentType int64 `json:"content_type"` + ResponseType string `json:"response_type"` + TemplateId int64 `json:"template_id"` + TemplateURL string `json:"template_url"` + Data string `json:"data"` + XProperties map[string]string `json:"x_properties"` + } + + info := &contentInfo{} + err := sonic.UnmarshalString(c, info) + if err != nil { + return "", "", err + } + + fields := make([]*field, 0) + err = sonic.UnmarshalString(info.Content, &fields) + if err != nil { + return "", "", err + } + + iCard := defaultCard() + iCard.Variables["5fJt3qKpSz"].(map[string]any)["defaultValue"] = fields + iCardString, _ := sonic.MarshalString(iCard) + + rCard := &inputCard{ + CardType: 3, + ContentType: 50, + ResponseType: "card", + TemplateId: 7383997384420262000, + TemplateURL: "", + Data: iCardString, + } + + type props struct { + CardType string `json:"card_type"` + InputCardData []*field `json:"input_card_data"` + } + + propsString, _ := sonic.MarshalString(props{ + CardType: "INPUT", + InputCardData: fields, + }) + + rCard.XProperties = map[string]string{ + "workflow_card_info": propsString, + } + rCardString, _ := sonic.MarshalString(rCard) + + return rCardString, message.ContentTypeCard, nil + +} + +func renderQACardDSL(c string) (string, message.ContentType, error) { + type contentInfo struct { + Messages []struct { + Type string `json:"type"` + ContentType string `json:"content_type"` + Content any `json:"content"` + } `json:"messages"` + } + + info := &contentInfo{} + err := sonic.UnmarshalString(c, info) + if err != nil { + return "", "", err + } + + if len(info.Messages) == 0 { + return "", "", fmt.Errorf("no input card data") + } + + if info.Messages[0].ContentType == "text" { + return info.Messages[0].Content.(string), message.ContentTypeText, nil + } + + type field struct { + Name string `json:"name"` + } + type key struct { + Key string `json:"key"` + } + + type inputCard struct { + CardType int64 `json:"card_type"` + ContentType int64 `json:"content_type"` + ResponseType string `json:"response_type"` + TemplateId int64 `json:"template_id"` + TemplateURL string `json:"template_url"` + Data string `json:"data"` + XProperties map[string]string `json:"x_properties"` + } + iCard := defaultCard() + keys := make([]*key, 0) + fields := make([]*field, 0) + + content := info.Messages[0].Content + type contentOption struct { + Options []*field `json:"options"` + Question string `json:"question"` + } + + contentString, err := sonic.MarshalString(content) + if err != nil { + return "", "", err + } + + contentOptionInfo := &contentOption{} + err = sonic.UnmarshalString(contentString, contentOptionInfo) + if err != nil { + return "", "", err + } + + for _, op := range contentOptionInfo.Options { + keys = append(keys, &key{Key: op.Name}) + fields = append(fields, &field{Name: op.Name}) + } + + iCard.Variables["5fJt3qKpSz"].(map[string]any)["defaultValue"] = map[string]any{ + "description": contentOptionInfo.Question, + "list": keys, + } + iCardString, _ := sonic.MarshalString(iCard) + + rCard := &inputCard{ + CardType: 3, + ContentType: 50, + ResponseType: "card", + TemplateId: 7383997384420262000, + TemplateURL: "", + Data: iCardString, + } + + type props struct { + CardType string `json:"card_type"` + QuestionCardData struct { + Title string `json:"Title"` + Options []*field `json:"Options"` + } `json:"question_card_data"` + } + + propsString, _ := sonic.MarshalString(props{ + CardType: "QUESTION", + QuestionCardData: struct { + Title string `json:"Title"` + Options []*field `json:"Options"` + }{Title: contentOptionInfo.Question, Options: fields}, + }) + + rCard.XProperties = map[string]string{ + "workflow_card_info": propsString, + } + rCardString, _ := sonic.MarshalString(rCard) + + return rCardString, message.ContentTypeCard, nil + +} diff --git a/backend/application/workflow/init.go b/backend/application/workflow/init.go index 71cb3525..e7fc0487 100644 --- a/backend/application/workflow/init.go +++ b/backend/application/workflow/init.go @@ -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 diff --git a/backend/application/workflow/workflow.go b/backend/application/workflow/workflow.go index f4a4ebf2..94973b1f 100644 --- a/backend/application/workflow/workflow.go +++ b/backend/application/workflow/workflow.go @@ -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 +} diff --git a/backend/crossdomain/contract/agent/single_agent.go b/backend/crossdomain/contract/agent/single_agent.go index 20400e44..a1e6e801 100644 --- a/backend/crossdomain/contract/agent/single_agent.go +++ b/backend/crossdomain/contract/agent/single_agent.go @@ -21,17 +21,31 @@ import ( "github.com/cloudwego/eino/schema" - "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/agentrun" "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent" ) // Requests and responses must not reference domain entities and can only use models under api/model/crossdomain. type SingleAgent interface { - StreamExecute(ctx context.Context, historyMsg []*message.Message, query *message.Message, - agentRuntime *singleagent.AgentRuntime) (*schema.StreamReader[*singleagent.AgentEvent], error) + StreamExecute(ctx context.Context, + agentRuntime *AgentRuntime) (*schema.StreamReader[*singleagent.AgentEvent], error) ObtainAgentByIdentity(ctx context.Context, identity *singleagent.AgentIdentity) (*singleagent.SingleAgent, error) } +type AgentRuntime struct { + AgentVersion string + UserID string + AgentID int64 + IsDraft bool + SpaceID int64 + ConnectorID int64 + PreRetrieveTools []*agentrun.Tool + + HistoryMsg []*schema.Message + Input *schema.Message + ResumeInfo *ResumeInfo +} + type ResumeInfo = singleagent.InterruptInfo type AgentEvent = singleagent.AgentEvent diff --git a/backend/crossdomain/contract/agentrun/agent_run.go b/backend/crossdomain/contract/agentrun/agent_run.go index 10ee8741..d104cc77 100644 --- a/backend/crossdomain/contract/agentrun/agent_run.go +++ b/backend/crossdomain/contract/agentrun/agent_run.go @@ -18,10 +18,14 @@ package crossagentrun import ( "context" + + "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" ) type AgentRun interface { Delete(ctx context.Context, runID []int64) error + List(ctx context.Context, ListMeta *entity.ListRunRecordMeta) ([]*entity.RunRecordMeta, error) + Create(ctx context.Context, runRecord *entity.AgentRunMeta) (*entity.RunRecordMeta, error) } var defaultSVC AgentRun diff --git a/backend/crossdomain/contract/conversation/conversation.go b/backend/crossdomain/contract/conversation/conversation.go index 7161e86b..71ed0c73 100644 --- a/backend/crossdomain/contract/conversation/conversation.go +++ b/backend/crossdomain/contract/conversation/conversation.go @@ -20,10 +20,15 @@ import ( "context" "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/conversation" + "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity" ) +//go:generate mockgen -destination conversationmock/conversation_mock.go --package conversationmock -source conversation.go type Conversation interface { GetCurrentConversation(ctx context.Context, req *conversation.GetCurrent) (*conversation.Conversation, error) + CreateConversation(ctx context.Context, req *entity.CreateMeta) (*entity.Conversation, error) + ClearConversationHistory(ctx context.Context, req *ClearConversationHistoryReq) (*entity.NewConversationCtxResponse, error) + GetByID(ctx context.Context, id int64) (*entity.Conversation, error) } var defaultSVC Conversation @@ -35,3 +40,7 @@ func DefaultSVC() Conversation { func SetDefaultSVC(c Conversation) { defaultSVC = c } + +type ClearConversationHistoryReq struct { + ConversationID int64 +} diff --git a/backend/crossdomain/contract/conversation/conversationmock/conversation_mock.go b/backend/crossdomain/contract/conversation/conversationmock/conversation_mock.go new file mode 100644 index 00000000..28533017 --- /dev/null +++ b/backend/crossdomain/contract/conversation/conversationmock/conversation_mock.go @@ -0,0 +1,120 @@ +/* + * 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 MockGen. DO NOT EDIT. +// Source: conversation.go +// +// Generated by this command: +// +// mockgen -destination conversationmock/conversation_mock.go --package conversationmock -source conversation.go +// + +// Package conversationmock is a generated GoMock package. +package conversationmock + +import ( + context "context" + reflect "reflect" + + conversation "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/conversation" + conversation0 "github.com/coze-dev/coze-studio/backend/crossdomain/contract/conversation" + entity "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity" + gomock "go.uber.org/mock/gomock" +) + +// MockConversation is a mock of Conversation interface. +type MockConversation struct { + ctrl *gomock.Controller + recorder *MockConversationMockRecorder + isgomock struct{} +} + +// MockConversationMockRecorder is the mock recorder for MockConversation. +type MockConversationMockRecorder struct { + mock *MockConversation +} + +// NewMockConversation creates a new mock instance. +func NewMockConversation(ctrl *gomock.Controller) *MockConversation { + mock := &MockConversation{ctrl: ctrl} + mock.recorder = &MockConversationMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConversation) EXPECT() *MockConversationMockRecorder { + return m.recorder +} + +// ClearConversationHistory mocks base method. +func (m *MockConversation) ClearConversationHistory(ctx context.Context, req *conversation0.ClearConversationHistoryReq) (*entity.NewConversationCtxResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClearConversationHistory", ctx, req) + ret0, _ := ret[0].(*entity.NewConversationCtxResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClearConversationHistory indicates an expected call of ClearConversationHistory. +func (mr *MockConversationMockRecorder) ClearConversationHistory(ctx, req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearConversationHistory", reflect.TypeOf((*MockConversation)(nil).ClearConversationHistory), ctx, req) +} + +// CreateConversation mocks base method. +func (m *MockConversation) CreateConversation(ctx context.Context, req *entity.CreateMeta) (*entity.Conversation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateConversation", ctx, req) + ret0, _ := ret[0].(*entity.Conversation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateConversation indicates an expected call of CreateConversation. +func (mr *MockConversationMockRecorder) CreateConversation(ctx, req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateConversation", reflect.TypeOf((*MockConversation)(nil).CreateConversation), ctx, req) +} + +// GetByID mocks base method. +func (m *MockConversation) GetByID(ctx context.Context, id int64) (*entity.Conversation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetByID", ctx, id) + ret0, _ := ret[0].(*entity.Conversation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetByID indicates an expected call of GetByID. +func (mr *MockConversationMockRecorder) GetByID(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByID", reflect.TypeOf((*MockConversation)(nil).GetByID), ctx, id) +} + +// GetCurrentConversation mocks base method. +func (m *MockConversation) GetCurrentConversation(ctx context.Context, req *conversation.GetCurrent) (*conversation.Conversation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCurrentConversation", ctx, req) + ret0, _ := ret[0].(*conversation.Conversation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCurrentConversation indicates an expected call of GetCurrentConversation. +func (mr *MockConversationMockRecorder) GetCurrentConversation(ctx, req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentConversation", reflect.TypeOf((*MockConversation)(nil).GetCurrentConversation), ctx, req) +} diff --git a/backend/crossdomain/contract/message/message.go b/backend/crossdomain/contract/message/message.go index df44ad9f..bb317af4 100644 --- a/backend/crossdomain/contract/message/message.go +++ b/backend/crossdomain/contract/message/message.go @@ -19,14 +19,24 @@ package message import ( "context" + "github.com/cloudwego/eino/schema" "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" + "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity" ) +//go:generate mockgen -destination messagemock/message_mock.go --package messagemock -source message.go type Message interface { GetByRunIDs(ctx context.Context, conversationID int64, runIDs []int64) ([]*message.Message, error) PreCreate(ctx context.Context, msg *message.Message) (*message.Message, error) Create(ctx context.Context, msg *message.Message) (*message.Message, error) + List(ctx context.Context, meta *entity.ListMeta) (*entity.ListResult, error) + ListWithoutPair(ctx context.Context, req *entity.ListMeta) (*entity.ListResult, error) Edit(ctx context.Context, msg *message.Message) (*message.Message, error) + Delete(ctx context.Context, req *entity.DeleteMeta) error + GetMessageByID(ctx context.Context, id int64) (*entity.Message, error) + MessageList(ctx context.Context, req *MessageListRequest) (*MessageListResponse, error) + GetLatestRunIDs(ctx context.Context, req *GetLatestRunIDsRequest) ([]int64, error) + GetMessagesByRunIDs(ctx context.Context, req *GetMessagesByRunIDsRequest) (*GetMessagesByRunIDsResponse, error) } var defaultSVC Message @@ -40,3 +50,55 @@ func DefaultSVC() Message { func SetDefaultSVC(c Message) { defaultSVC = c } + +type MessageListRequest struct { + ConversationID int64 + Limit int64 + BeforeID *string + AfterID *string + UserID int64 + AppID int64 + OrderBy *string +} + +type MessageListResponse struct { + Messages []*WfMessage + FirstID string + LastID string + HasMore bool +} + +type Content struct { + Type message.InputType `json:"type"` + Text *string `json:"text,omitempty"` + Uri *string `json:"uri,omitempty"` + Url *string `json:"url,omitempty"` +} + +type WfMessage struct { + ID int64 + Role schema.RoleType `json:"role"` // user or assistant + MultiContent []*Content `json:"multi_content"` + Text *string `json:"text,omitempty"` + ContentType string `json:"content_type"` + SectionID int64 `json:"section_id"` +} + +type GetLatestRunIDsRequest struct { + ConversationID int64 + UserID int64 + AppID int64 + Rounds int64 + SectionID int64 + InitRunID *int64 +} + +type GetMessagesByRunIDsRequest struct { + ConversationID int64 + RunIDs []int64 +} + +type GetMessagesByRunIDsResponse struct { + Messages []*WfMessage + SchemaMessages []*schema.Message +} diff --git a/backend/crossdomain/contract/message/messagemock/message_mock.go b/backend/crossdomain/contract/message/messagemock/message_mock.go new file mode 100644 index 00000000..959566cc --- /dev/null +++ b/backend/crossdomain/contract/message/messagemock/message_mock.go @@ -0,0 +1,224 @@ +/* + * 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 MockGen. DO NOT EDIT. +// Source: message.go +// +// Generated by this command: +// +// mockgen -destination messagemock/message_mock.go --package messagemock -source message.go +// + +// Package messagemock is a generated GoMock package. +package messagemock + +import ( + context "context" + reflect "reflect" + + message "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" + message0 "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" + entity "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity" + gomock "go.uber.org/mock/gomock" +) + +// MockMessage is a mock of Message interface. +type MockMessage struct { + ctrl *gomock.Controller + recorder *MockMessageMockRecorder + isgomock struct{} +} + +// MockMessageMockRecorder is the mock recorder for MockMessage. +type MockMessageMockRecorder struct { + mock *MockMessage +} + +// NewMockMessage creates a new mock instance. +func NewMockMessage(ctrl *gomock.Controller) *MockMessage { + mock := &MockMessage{ctrl: ctrl} + mock.recorder = &MockMessageMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMessage) EXPECT() *MockMessageMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockMessage) Create(ctx context.Context, msg *message.Message) (*message.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", ctx, msg) + ret0, _ := ret[0].(*message.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create. +func (mr *MockMessageMockRecorder) Create(ctx, msg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockMessage)(nil).Create), ctx, msg) +} + +// Delete mocks base method. +func (m *MockMessage) Delete(ctx context.Context, req *entity.DeleteMeta) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", ctx, req) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockMessageMockRecorder) Delete(ctx, req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockMessage)(nil).Delete), ctx, req) +} + +// Edit mocks base method. +func (m *MockMessage) Edit(ctx context.Context, msg *message.Message) (*message.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Edit", ctx, msg) + ret0, _ := ret[0].(*message.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Edit indicates an expected call of Edit. +func (mr *MockMessageMockRecorder) Edit(ctx, msg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Edit", reflect.TypeOf((*MockMessage)(nil).Edit), ctx, msg) +} + +// GetByRunIDs mocks base method. +func (m *MockMessage) GetByRunIDs(ctx context.Context, conversationID int64, runIDs []int64) ([]*message.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetByRunIDs", ctx, conversationID, runIDs) + ret0, _ := ret[0].([]*message.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetByRunIDs indicates an expected call of GetByRunIDs. +func (mr *MockMessageMockRecorder) GetByRunIDs(ctx, conversationID, runIDs any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetByRunIDs", reflect.TypeOf((*MockMessage)(nil).GetByRunIDs), ctx, conversationID, runIDs) +} + +// GetLatestRunIDs mocks base method. +func (m *MockMessage) GetLatestRunIDs(ctx context.Context, req *message0.GetLatestRunIDsRequest) ([]int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLatestRunIDs", ctx, req) + ret0, _ := ret[0].([]int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLatestRunIDs indicates an expected call of GetLatestRunIDs. +func (mr *MockMessageMockRecorder) GetLatestRunIDs(ctx, req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestRunIDs", reflect.TypeOf((*MockMessage)(nil).GetLatestRunIDs), ctx, req) +} + +// GetMessageByID mocks base method. +func (m *MockMessage) GetMessageByID(ctx context.Context, id int64) (*entity.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMessageByID", ctx, id) + ret0, _ := ret[0].(*entity.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMessageByID indicates an expected call of GetMessageByID. +func (mr *MockMessageMockRecorder) GetMessageByID(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessageByID", reflect.TypeOf((*MockMessage)(nil).GetMessageByID), ctx, id) +} + +// GetMessagesByRunIDs mocks base method. +func (m *MockMessage) GetMessagesByRunIDs(ctx context.Context, req *message0.GetMessagesByRunIDsRequest) (*message0.GetMessagesByRunIDsResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMessagesByRunIDs", ctx, req) + ret0, _ := ret[0].(*message0.GetMessagesByRunIDsResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMessagesByRunIDs indicates an expected call of GetMessagesByRunIDs. +func (mr *MockMessageMockRecorder) GetMessagesByRunIDs(ctx, req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessagesByRunIDs", reflect.TypeOf((*MockMessage)(nil).GetMessagesByRunIDs), ctx, req) +} + +// List mocks base method. +func (m *MockMessage) List(ctx context.Context, meta *entity.ListMeta) (*entity.ListResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", ctx, meta) + ret0, _ := ret[0].(*entity.ListResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockMessageMockRecorder) List(ctx, meta any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockMessage)(nil).List), ctx, meta) +} + +// ListWithoutPair mocks base method. +func (m *MockMessage) ListWithoutPair(ctx context.Context, req *entity.ListMeta) (*entity.ListResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListWithoutPair", ctx, req) + ret0, _ := ret[0].(*entity.ListResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListWithoutPair indicates an expected call of ListWithoutPair. +func (mr *MockMessageMockRecorder) ListWithoutPair(ctx, req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListWithoutPair", reflect.TypeOf((*MockMessage)(nil).ListWithoutPair), ctx, req) +} + +// MessageList mocks base method. +func (m *MockMessage) MessageList(ctx context.Context, req *message0.MessageListRequest) (*message0.MessageListResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MessageList", ctx, req) + ret0, _ := ret[0].(*message0.MessageListResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MessageList indicates an expected call of MessageList. +func (mr *MockMessageMockRecorder) MessageList(ctx, req any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MessageList", reflect.TypeOf((*MockMessage)(nil).MessageList), ctx, req) +} + +// PreCreate mocks base method. +func (m *MockMessage) PreCreate(ctx context.Context, msg *message.Message) (*message.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PreCreate", ctx, msg) + ret0, _ := ret[0].(*message.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PreCreate indicates an expected call of PreCreate. +func (mr *MockMessageMockRecorder) PreCreate(ctx, msg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PreCreate", reflect.TypeOf((*MockMessage)(nil).PreCreate), ctx, msg) +} diff --git a/backend/crossdomain/contract/upload/upload.go b/backend/crossdomain/contract/upload/upload.go new file mode 100644 index 00000000..1c29e1e5 --- /dev/null +++ b/backend/crossdomain/contract/upload/upload.go @@ -0,0 +1,37 @@ +/* + * 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 crossupload + +import ( + "context" + + "github.com/coze-dev/coze-studio/backend/domain/upload/service" +) + +var defaultSVC Uploader + +type Uploader interface { + GetFile(ctx context.Context, req *service.GetFileRequest) (resp *service.GetFileResponse, err error) +} + +func SetDefaultSVC(s Uploader) { + defaultSVC = s +} + +func DefaultSVC() Uploader { + return defaultSVC +} diff --git a/backend/crossdomain/contract/workflow/workflow.go b/backend/crossdomain/contract/workflow/workflow.go index d3020aee..18bbf173 100644 --- a/backend/crossdomain/contract/workflow/workflow.go +++ b/backend/crossdomain/contract/workflow/workflow.go @@ -38,19 +38,47 @@ type Workflow interface { allInterruptEvents map[string]*workflowEntity.ToolInterruptEvent) einoCompose.Option ReleaseApplicationWorkflows(ctx context.Context, appID int64, config *ReleaseWorkflowConfig) ([]*vo.ValidateIssue, error) GetWorkflowIDsByAppID(ctx context.Context, appID int64) ([]int64, error) + SyncExecuteWorkflow(ctx context.Context, config workflowModel.ExecuteConfig, input map[string]any) (*workflowEntity.WorkflowExecution, vo.TerminatePlan, error) + StreamExecute(ctx context.Context, config workflowModel.ExecuteConfig, input map[string]any) (*schema.StreamReader[*workflowEntity.Message], error) WithExecuteConfig(cfg workflowModel.ExecuteConfig) einoCompose.Option WithMessagePipe() (compose.Option, *schema.StreamReader[*entity.Message], func()) + StreamResume(ctx context.Context, req *entity.ResumeRequest, config workflowModel.ExecuteConfig) (*schema.StreamReader[*entity.Message], error) + InitApplicationDefaultConversationTemplate(ctx context.Context, spaceID int64, appID int64, userID int64) error } type ExecuteConfig = workflowModel.ExecuteConfig type ExecuteMode = workflowModel.ExecuteMode -type NodeType = entity.NodeType -type WorkflowMessage = entity.Message +type WorkflowMessage = workflowEntity.Message + +type StateMessage = workflowEntity.StateMessage + +type NodeType = entity.NodeType +type MessageType = entity.MessageType +type InterruptEvent = workflowEntity.InterruptEvent +type EventType = workflowEntity.InterruptEventType +type ResumeRequest = entity.ResumeRequest +type WorkflowExecuteStatus = entity.WorkflowExecuteStatus + +const ( + WorkflowRunning = WorkflowExecuteStatus(entity.WorkflowRunning) + WorkflowSuccess = WorkflowExecuteStatus(entity.WorkflowSuccess) + WorkflowFailed = WorkflowExecuteStatus(entity.WorkflowFailed) + WorkflowCancel = WorkflowExecuteStatus(entity.WorkflowCancel) + WorkflowInterrupted = WorkflowExecuteStatus(entity.WorkflowInterrupted) +) + +const ( + Answer MessageType = "answer" + FunctionCall MessageType = "function_call" + ToolResponse MessageType = "tool_response" +) const ( NodeTypeOutputEmitter NodeType = "OutputEmitter" + NodeTypeInputReceiver NodeType = "InputReceiver" + NodeTypeQuestion NodeType = "QuestionAnswer" ) const ( @@ -61,6 +89,14 @@ const ( type TaskType = workflowModel.TaskType +type SyncPattern = workflowModel.SyncPattern + +const ( + SyncPatternSync SyncPattern = "sync" + SyncPatternAsync SyncPattern = "async" + SyncPatternStream SyncPattern = "stream" +) + const ( TaskTypeForeground TaskType = "foreground" TaskTypeBackground TaskType = "background" @@ -73,6 +109,14 @@ const ( BizTypeWorkflow BizType = "workflow" ) +type Locator = workflowModel.Locator + +const ( + FromDraft Locator = iota + FromSpecificVersion + FromLatestVersion +) + type ReleaseWorkflowConfig = vo.ReleaseWorkflowConfig type ToolInterruptEvent = workflowEntity.ToolInterruptEvent diff --git a/backend/crossdomain/impl/agentrun/agent_run.go b/backend/crossdomain/impl/agentrun/agent_run.go index bad0e49e..3474244d 100644 --- a/backend/crossdomain/impl/agentrun/agent_run.go +++ b/backend/crossdomain/impl/agentrun/agent_run.go @@ -20,6 +20,7 @@ import ( "context" crossagentrun "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agentrun" + "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" agentrun "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/service" ) @@ -44,3 +45,11 @@ func InitDomainService(c agentrun.Run) crossagentrun.AgentRun { func (c *impl) Delete(ctx context.Context, runID []int64) error { return c.DomainSVC.Delete(ctx, runID) } + +func (c *impl) List(ctx context.Context, meta *entity.ListRunRecordMeta) ([]*entity.RunRecordMeta, error) { + return c.DomainSVC.List(ctx, meta) +} + +func (c *impl) Create(ctx context.Context, meta *entity.AgentRunMeta) (*entity.RunRecordMeta, error) { + return c.DomainSVC.Create(ctx, meta) +} diff --git a/backend/crossdomain/impl/conversation/conversation.go b/backend/crossdomain/impl/conversation/conversation.go index 47af5999..44cd51b3 100644 --- a/backend/crossdomain/impl/conversation/conversation.go +++ b/backend/crossdomain/impl/conversation/conversation.go @@ -21,6 +21,7 @@ import ( model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/conversation" crossconversation "github.com/coze-dev/coze-studio/backend/crossdomain/contract/conversation" + "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity" conversation "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/service" ) @@ -37,6 +38,20 @@ func InitDomainService(c conversation.Conversation) crossconversation.Conversati return defaultSVC } +func (s *impl) CreateConversation(ctx context.Context, req *entity.CreateMeta) (*entity.Conversation, error) { + return s.DomainSVC.Create(ctx, req) +} + +func (s *impl) ClearConversationHistory(ctx context.Context, req *crossconversation.ClearConversationHistoryReq) (*entity.NewConversationCtxResponse, error) { + return s.DomainSVC.NewConversationCtx(ctx, &entity.NewConversationCtxRequest{ + ID: req.ConversationID, + }) +} + func (s *impl) GetCurrentConversation(ctx context.Context, req *model.GetCurrent) (*model.Conversation, error) { return s.DomainSVC.GetCurrentConversation(ctx, req) } + +func (s *impl) GetByID(ctx context.Context, id int64) (*entity.Conversation, error) { + return s.DomainSVC.GetByID(ctx, id) +} diff --git a/backend/crossdomain/impl/message/message.go b/backend/crossdomain/impl/message/message.go index d745f8ac..843857d7 100644 --- a/backend/crossdomain/impl/message/message.go +++ b/backend/crossdomain/impl/message/message.go @@ -18,9 +18,19 @@ package message import ( "context" + "fmt" + "strconv" + "github.com/cloudwego/eino/schema" model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" + crossagentrun "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agentrun" crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" + agententity "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" + "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity" + "github.com/coze-dev/coze-studio/backend/domain/workflow" + "github.com/coze-dev/coze-studio/backend/pkg/lang/ptr" + "github.com/coze-dev/coze-studio/backend/pkg/sonic" + message "github.com/coze-dev/coze-studio/backend/domain/conversation/message/service" ) @@ -38,6 +48,96 @@ func InitDomainService(c message.Message) crossmessage.Message { return defaultSVC } +func (c *impl) MessageList(ctx context.Context, req *crossmessage.MessageListRequest) (*crossmessage.MessageListResponse, error) { + lm := &entity.ListMeta{ + ConversationID: req.ConversationID, + Limit: int(req.Limit), // Since the value of limit is checked inside the node, the type cast here is safe + UserID: strconv.FormatInt(req.UserID, 10), + AgentID: req.AppID, + OrderBy: req.OrderBy, + } + if req.BeforeID != nil { + lm.Cursor, _ = strconv.ParseInt(*req.BeforeID, 10, 64) + lm.Direction = entity.ScrollPageDirectionNext + } + if req.AfterID != nil { + lm.Cursor, _ = strconv.ParseInt(*req.AfterID, 10, 64) + lm.Direction = entity.ScrollPageDirectionPrev + } + lm.MessageType = []*model.MessageType{ptr.Of(model.MessageTypeQuestion), ptr.Of(model.MessageTypeAnswer)} + + lr, err := c.DomainSVC.ListWithoutPair(ctx, lm) + if err != nil { + return nil, err + } + + response := &crossmessage.MessageListResponse{ + HasMore: lr.HasMore, + } + + if lr.PrevCursor > 0 { + response.FirstID = strconv.FormatInt(lr.PrevCursor, 10) + } + if lr.NextCursor > 0 { + response.LastID = strconv.FormatInt(lr.NextCursor, 10) + } + if len(lr.Messages) == 0 { + return response, nil + } + messages, _, err := convertToConvAndSchemaMessage(ctx, lr.Messages) + if err != nil { + return nil, err + } + response.Messages = messages + return response, nil +} + +func (c *impl) GetLatestRunIDs(ctx context.Context, req *crossmessage.GetLatestRunIDsRequest) ([]int64, error) { + listMeta := &agententity.ListRunRecordMeta{ + ConversationID: req.ConversationID, + AgentID: req.AppID, + Limit: int32(req.Rounds), + SectionID: req.SectionID, + } + + if req.InitRunID != nil { + listMeta.BeforeID = *req.InitRunID + } + + runRecords, err := crossagentrun.DefaultSVC().List(ctx, listMeta) + if err != nil { + return nil, err + } + runIDs := make([]int64, 0, len(runRecords)) + for _, record := range runRecords { + runIDs = append(runIDs, record.ID) + } + return runIDs, nil +} + +func (c *impl) GetMessagesByRunIDs(ctx context.Context, req *crossmessage.GetMessagesByRunIDsRequest) (*crossmessage.GetMessagesByRunIDsResponse, error) { + responseMessages, err := c.DomainSVC.GetByRunIDs(ctx, req.ConversationID, req.RunIDs) + if err != nil { + return nil, err + } + // only returns messages of type user/assistant/system role type + messages := make([]*model.Message, 0, len(responseMessages)) + for _, m := range responseMessages { + if m.Role == schema.User || m.Role == schema.System || m.Role == schema.Assistant { + messages = append(messages, m) + } + } + + convMessages, scMessages, err := convertToConvAndSchemaMessage(ctx, messages) + if err != nil { + return nil, err + } + return &crossmessage.GetMessagesByRunIDsResponse{ + Messages: convMessages, + SchemaMessages: scMessages, + }, nil +} + func (c *impl) GetByRunIDs(ctx context.Context, conversationID int64, runIDs []int64) ([]*model.Message, error) { return c.DomainSVC.GetByRunIDs(ctx, conversationID, runIDs) } @@ -53,3 +153,115 @@ func (c *impl) Edit(ctx context.Context, msg *model.Message) (*model.Message, er func (c *impl) PreCreate(ctx context.Context, msg *model.Message) (*model.Message, error) { return c.DomainSVC.PreCreate(ctx, msg) } + +func (c *impl) List(ctx context.Context, lm *entity.ListMeta) (*entity.ListResult, error) { + return c.DomainSVC.List(ctx, lm) +} + +func (c *impl) Delete(ctx context.Context, req *entity.DeleteMeta) error { + return c.DomainSVC.Delete(ctx, req) +} + +func (c *impl) GetMessageByID(ctx context.Context, id int64) (*entity.Message, error) { + return c.DomainSVC.GetByID(ctx, id) +} + +func (c *impl) ListWithoutPair(ctx context.Context, req *entity.ListMeta) (*entity.ListResult, error) { + return c.DomainSVC.ListWithoutPair(ctx, req) +} + +func convertToConvAndSchemaMessage(ctx context.Context, msgs []*entity.Message) ([]*crossmessage.WfMessage, []*schema.Message, error) { + messages := make([]*schema.Message, 0) + convMessages := make([]*crossmessage.WfMessage, 0) + for _, m := range msgs { + msg := &schema.Message{} + err := sonic.UnmarshalString(m.ModelContent, msg) + if err != nil { + return nil, nil, err + } + msg.Role = m.Role + + covMsg := &crossmessage.WfMessage{ + ID: m.ID, + Role: m.Role, + ContentType: string(m.ContentType), + SectionID: m.SectionID, + } + + if len(msg.MultiContent) == 0 { + covMsg.Text = ptr.Of(msg.Content) + } else { + covMsg.MultiContent = make([]*crossmessage.Content, 0, len(msg.MultiContent)) + for _, part := range msg.MultiContent { + switch part.Type { + case schema.ChatMessagePartTypeText: + covMsg.MultiContent = append(covMsg.MultiContent, &crossmessage.Content{ + Type: model.InputTypeText, + Text: ptr.Of(part.Text), + }) + + case schema.ChatMessagePartTypeImageURL: + if part.ImageURL != nil { + part.ImageURL.URL, err = workflow.GetRepository().GetObjectUrl(ctx, part.ImageURL.URI) + if err != nil { + return nil, nil, err + } + covMsg.MultiContent = append(covMsg.MultiContent, &crossmessage.Content{ + Uri: ptr.Of(part.ImageURL.URI), + Type: model.InputTypeImage, + Url: ptr.Of(part.ImageURL.URL), + }) + } + + case schema.ChatMessagePartTypeFileURL: + + if part.FileURL != nil { + part.FileURL.URL, err = workflow.GetRepository().GetObjectUrl(ctx, part.FileURL.URI) + if err != nil { + return nil, nil, err + } + + covMsg.MultiContent = append(covMsg.MultiContent, &crossmessage.Content{ + Uri: ptr.Of(part.FileURL.URI), + Type: model.InputTypeFile, + Url: ptr.Of(part.FileURL.URL), + }) + + } + + case schema.ChatMessagePartTypeAudioURL: + if part.AudioURL != nil { + part.AudioURL.URL, err = workflow.GetRepository().GetObjectUrl(ctx, part.AudioURL.URI) + if err != nil { + return nil, nil, err + } + covMsg.MultiContent = append(covMsg.MultiContent, &crossmessage.Content{ + Uri: ptr.Of(part.AudioURL.URI), + Type: model.InputTypeAudio, + Url: ptr.Of(part.AudioURL.URL), + }) + + } + case schema.ChatMessagePartTypeVideoURL: + if part.VideoURL != nil { + part.VideoURL.URL, err = workflow.GetRepository().GetObjectUrl(ctx, part.VideoURL.URI) + if err != nil { + return nil, nil, err + } + covMsg.MultiContent = append(covMsg.MultiContent, &crossmessage.Content{ + Uri: ptr.Of(part.VideoURL.URI), + Type: model.InputTypeVideo, + Url: ptr.Of(part.VideoURL.URL), + }) + } + default: + return nil, nil, fmt.Errorf("unknown part type: %s", part.Type) + } + } + } + + messages = append(messages, msg) + convMessages = append(convMessages, covMsg) + } + return convMessages, messages, nil +} diff --git a/backend/crossdomain/impl/message/message_test.go b/backend/crossdomain/impl/message/message_test.go new file mode 100644 index 00000000..f68a547e --- /dev/null +++ b/backend/crossdomain/impl/message/message_test.go @@ -0,0 +1,362 @@ +/* + * 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 message + +import ( + "context" + "testing" + + "github.com/cloudwego/eino/schema" + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" + "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity" + "github.com/coze-dev/coze-studio/backend/domain/workflow" + "github.com/coze-dev/coze-studio/backend/infra/contract/storage" + "github.com/coze-dev/coze-studio/backend/pkg/lang/ptr" + "github.com/coze-dev/coze-studio/backend/pkg/sonic" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type mockWorkflowRepo struct { + workflow.Repository +} + +func (m *mockWorkflowRepo) GetObjectUrl(ctx context.Context, uri string, opts ...storage.GetOptFn) (string, error) { + return uri, nil +} + +func Test_convertToConvAndSchemaMessage(t *testing.T) { + workflow.SetRepository(&mockWorkflowRepo{}) + + sm1, err := sonic.MarshalString(&schema.Message{Content: "hello"}) + require.NoError(t, err) + + sm2, err := sonic.MarshalString(&schema.Message{MultiContent: []schema.ChatMessagePart{{Type: schema.ChatMessagePartTypeFileURL, FileURL: &schema.ChatMessageFileURL{URI: "f_uri_1"}}}}) + require.NoError(t, err) + + sm3, err := sonic.MarshalString(&schema.Message{MultiContent: []schema.ChatMessagePart{{Type: schema.ChatMessagePartTypeText, Text: "hello"}, {Type: schema.ChatMessagePartTypeFileURL, FileURL: &schema.ChatMessageFileURL{URI: "f_uri_2"}}}}) + require.NoError(t, err) + + sm4, err := sonic.MarshalString(&schema.Message{MultiContent: []schema.ChatMessagePart{{Type: schema.ChatMessagePartTypeFileURL, FileURL: &schema.ChatMessageFileURL{URI: "f_uri_3"}}, {Type: schema.ChatMessagePartTypeFileURL, FileURL: &schema.ChatMessageFileURL{URI: "f_uri_4"}}}}) + require.NoError(t, err) + + sm5, err := sonic.MarshalString(&schema.Message{Content: ""}) + require.NoError(t, err) + + sm6, err := sonic.MarshalString(&schema.Message{MultiContent: []schema.ChatMessagePart{{Type: schema.ChatMessagePartTypeImageURL, ImageURL: &schema.ChatMessageImageURL{URI: "image_uri_5"}}}}) + require.NoError(t, err) + + sm7, err := sonic.MarshalString(&schema.Message{MultiContent: []schema.ChatMessagePart{{Type: schema.ChatMessagePartTypeImageURL, ImageURL: &schema.ChatMessageImageURL{URI: "file_id_6"}}, {Type: schema.ChatMessagePartTypeImageURL, ImageURL: &schema.ChatMessageImageURL{URI: "file_id_7"}}}}) + require.NoError(t, err) + + sm8, err := sonic.MarshalString(&schema.Message{MultiContent: []schema.ChatMessagePart{{Type: schema.ChatMessagePartTypeText, Text: "hello"}, {Type: schema.ChatMessagePartTypeImageURL, ImageURL: &schema.ChatMessageImageURL{URI: "file_id_8"}}, {Type: schema.ChatMessagePartTypeFileURL, FileURL: &schema.ChatMessageFileURL{URI: "file_id_9"}}}}) + require.NoError(t, err) + + type args struct { + msgs []*entity.Message + } + type want struct { + convMsgs []*crossmessage.WfMessage + schemaMsgs []*schema.Message + } + tests := []struct { + name string + args args + want want + wantErr bool + }{ + { + name: "pure text", + args: args{ + msgs: []*entity.Message{ + { + ID: 1, + Role: schema.User, + ContentType: "text", + ModelContent: sm1, + }, + }, + }, + want: want{ + convMsgs: []*crossmessage.WfMessage{ + { + ID: 1, + Role: schema.User, + ContentType: "text", + Text: ptr.Of("hello"), + }, + }, + schemaMsgs: []*schema.Message{ + { + Role: schema.User, + Content: "hello", + }, + }, + }, + }, + { + name: "pure file", + args: args{ + msgs: []*entity.Message{ + { + ID: 2, + Role: schema.User, + ContentType: "file", + ModelContent: sm2, + }, + }, + }, + want: want{ + convMsgs: []*crossmessage.WfMessage{ + { + ID: 2, + Role: schema.User, + ContentType: "file", + MultiContent: []*crossmessage.Content{ + {Type: message.InputTypeFile, Uri: ptr.Of("f_uri_1"), Url: ptr.Of("f_uri_1")}, + }, + }, + }, + schemaMsgs: []*schema.Message{ + { + Role: schema.User, + MultiContent: []schema.ChatMessagePart{ + {Type: schema.ChatMessagePartTypeFileURL, FileURL: &schema.ChatMessageFileURL{URI: "f_uri_1", URL: "f_uri_1"}}, + }, + }, + }, + }, + }, + { + name: "text and file", + args: args{ + msgs: []*entity.Message{ + { + ID: 3, + Role: schema.User, + ContentType: "text_file", + ModelContent: sm3, + }, + }, + }, + want: want{ + convMsgs: []*crossmessage.WfMessage{ + { + ID: 3, + Role: schema.User, + ContentType: "text_file", + MultiContent: []*crossmessage.Content{ + {Type: message.InputTypeText, Text: ptr.Of("hello")}, + {Type: message.InputTypeFile, Uri: ptr.Of("f_uri_2"), Url: ptr.Of("f_uri_2")}, + }, + }, + }, + schemaMsgs: []*schema.Message{ + { + Role: schema.User, + MultiContent: []schema.ChatMessagePart{ + {Type: schema.ChatMessagePartTypeText, Text: "hello"}, + {Type: schema.ChatMessagePartTypeFileURL, FileURL: &schema.ChatMessageFileURL{URI: "f_uri_2", URL: "f_uri_2"}}, + }, + }, + }, + }, + }, + { + name: "multiple files", + args: args{ + msgs: []*entity.Message{ + { + ID: 4, + Role: schema.User, + ContentType: "file", + ModelContent: sm4, + }, + }, + }, + want: want{ + convMsgs: []*crossmessage.WfMessage{ + { + ID: 4, + Role: schema.User, + ContentType: "file", + MultiContent: []*crossmessage.Content{ + {Type: message.InputTypeFile, Uri: ptr.Of("f_uri_3"), Url: ptr.Of("f_uri_3")}, + {Type: message.InputTypeFile, Uri: ptr.Of("f_uri_4"), Url: ptr.Of("f_uri_4")}, + }, + }, + }, + schemaMsgs: []*schema.Message{ + { + Role: schema.User, + MultiContent: []schema.ChatMessagePart{ + {Type: schema.ChatMessagePartTypeFileURL, FileURL: &schema.ChatMessageFileURL{URI: "f_uri_3", URL: "f_uri_3"}}, + {Type: schema.ChatMessagePartTypeFileURL, FileURL: &schema.ChatMessageFileURL{URI: "f_uri_4", URL: "f_uri_4"}}, + }, + }, + }, + }, + }, + { + name: "empty text", + args: args{ + msgs: []*entity.Message{ + { + ID: 5, + Role: schema.User, + ContentType: "text", + ModelContent: sm5, + }, + }, + }, + want: want{ + convMsgs: []*crossmessage.WfMessage{ + { + ID: 5, + Role: schema.User, + ContentType: "text", + Text: ptr.Of(""), + }, + }, + schemaMsgs: []*schema.Message{ + { + Role: schema.User, + Content: "", + }, + }, + }, + }, + { + name: "pure image", + args: args{ + msgs: []*entity.Message{ + { + ID: 6, + Role: schema.User, + ContentType: "image", + ModelContent: sm6, + }, + }, + }, + want: want{ + convMsgs: []*crossmessage.WfMessage{ + { + ID: 6, + Role: schema.User, + ContentType: "image", + MultiContent: []*crossmessage.Content{ + {Type: message.InputTypeImage, Uri: ptr.Of("image_uri_5"), Url: ptr.Of("image_uri_5")}, + }, + }, + }, + schemaMsgs: []*schema.Message{ + { + Role: schema.User, + MultiContent: []schema.ChatMessagePart{ + {Type: schema.ChatMessagePartTypeImageURL, ImageURL: &schema.ChatMessageImageURL{URI: "image_uri_5", URL: "image_uri_5"}}, + }, + }, + }, + }, + }, + { + name: "multiple images", + args: args{ + msgs: []*entity.Message{ + { + ID: 7, + Role: schema.User, + ContentType: "image", + ModelContent: sm7, + }, + }, + }, + want: want{ + convMsgs: []*crossmessage.WfMessage{ + { + ID: 7, + Role: schema.User, + ContentType: "image", + MultiContent: []*crossmessage.Content{ + {Type: message.InputTypeImage, Uri: ptr.Of("file_id_6"), Url: ptr.Of("file_id_6")}, + {Type: message.InputTypeImage, Uri: ptr.Of("file_id_7"), Url: ptr.Of("file_id_7")}, + }, + }, + }, + schemaMsgs: []*schema.Message{ + { + Role: schema.User, + MultiContent: []schema.ChatMessagePart{ + {Type: schema.ChatMessagePartTypeImageURL, ImageURL: &schema.ChatMessageImageURL{URI: "file_id_6", URL: "file_id_6"}}, + {Type: schema.ChatMessagePartTypeImageURL, ImageURL: &schema.ChatMessageImageURL{URI: "file_id_7", URL: "file_id_7"}}, + }, + }, + }, + }, + }, + { + name: "mixed content", + args: args{ + msgs: []*entity.Message{ + { + ID: 8, + Role: schema.User, + ContentType: "mix", + ModelContent: sm8, + }, + }, + }, + want: want{ + convMsgs: []*crossmessage.WfMessage{ + { + ID: 8, + Role: schema.User, + ContentType: "mix", + MultiContent: []*crossmessage.Content{ + {Type: message.InputTypeText, Text: ptr.Of("hello")}, + {Type: message.InputTypeImage, Uri: ptr.Of("file_id_8"), Url: ptr.Of("file_id_8")}, + {Type: message.InputTypeFile, Uri: ptr.Of("file_id_9"), Url: ptr.Of("file_id_9")}, + }, + }, + }, + schemaMsgs: []*schema.Message{ + { + Role: schema.User, + MultiContent: []schema.ChatMessagePart{ + {Type: schema.ChatMessagePartTypeText, Text: "hello"}, + {Type: schema.ChatMessagePartTypeImageURL, ImageURL: &schema.ChatMessageImageURL{URI: "file_id_8", URL: "file_id_8"}}, + {Type: schema.ChatMessagePartTypeFileURL, FileURL: &schema.ChatMessageFileURL{URI: "file_id_9", URL: "file_id_9"}}, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + convMsgs, schemaMsgs, err := convertToConvAndSchemaMessage(context.Background(), tt.args.msgs) + if tt.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want.convMsgs, convMsgs) + assert.Equal(t, tt.want.schemaMsgs, schemaMsgs) + }) + } +} diff --git a/backend/crossdomain/impl/singleagent/single_agent.go b/backend/crossdomain/impl/singleagent/single_agent.go index 7f1163cc..d349cb96 100644 --- a/backend/crossdomain/impl/singleagent/single_agent.go +++ b/backend/crossdomain/impl/singleagent/single_agent.go @@ -18,17 +18,13 @@ package agent import ( "context" - "encoding/json" "github.com/cloudwego/eino/schema" "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/agentrun" - "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent" crossagent "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agent" singleagent "github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/service" - "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity" - "github.com/coze-dev/coze-studio/backend/infra/contract/imagex" "github.com/coze-dev/coze-studio/backend/pkg/lang/conv" "github.com/coze-dev/coze-studio/backend/pkg/lang/slices" "github.com/coze-dev/coze-studio/backend/pkg/logs" @@ -38,49 +34,34 @@ var defaultSVC crossagent.SingleAgent type impl struct { DomainSVC singleagent.SingleAgent - ImagexSVC imagex.ImageX } -func InitDomainService(c singleagent.SingleAgent, imagexClient imagex.ImageX) crossagent.SingleAgent { +func InitDomainService(c singleagent.SingleAgent) crossagent.SingleAgent { defaultSVC = &impl{ DomainSVC: c, - ImagexSVC: imagexClient, } return defaultSVC } -func (c *impl) StreamExecute(ctx context.Context, historyMsg []*message.Message, - query *message.Message, agentRuntime *model.AgentRuntime, +func (c *impl) StreamExecute(ctx context.Context, agentRuntime *crossagent.AgentRuntime, ) (*schema.StreamReader[*model.AgentEvent], error) { - historyMsg = c.historyPairs(historyMsg) - - singleAgentStreamExecReq := c.buildSingleAgentStreamExecuteReq(ctx, historyMsg, query, agentRuntime) + singleAgentStreamExecReq := c.buildSingleAgentStreamExecuteReq(ctx, agentRuntime) streamEvent, err := c.DomainSVC.StreamExecute(ctx, singleAgentStreamExecReq) logs.CtxInfof(ctx, "agent StreamExecute req:%v, streamEvent:%v, err:%v", conv.DebugJsonToStr(singleAgentStreamExecReq), streamEvent, err) return streamEvent, err } -func (c *impl) buildSingleAgentStreamExecuteReq(ctx context.Context, historyMsg []*message.Message, - input *message.Message, agentRuntime *model.AgentRuntime, +func (c *impl) buildSingleAgentStreamExecuteReq(ctx context.Context, agentRuntime *crossagent.AgentRuntime, ) *model.ExecuteRequest { - identity := c.buildIdentity(input, agentRuntime) - inputBuild := c.buildSchemaMessage(ctx, []*message.Message{input}) - var inputSM *schema.Message - if len(inputBuild) > 0 { - inputSM = inputBuild[0] - } - history := c.buildSchemaMessage(ctx, historyMsg) - - resumeInfo := c.checkResumeInfo(ctx, historyMsg) return &model.ExecuteRequest{ - Identity: identity, - Input: inputSM, - History: history, - UserID: input.UserID, + Identity: c.buildIdentity(agentRuntime), + Input: agentRuntime.Input, + History: agentRuntime.HistoryMsg, + UserID: agentRuntime.UserID, PreCallTools: slices.Transform(agentRuntime.PreRetrieveTools, func(tool *agentrun.Tool) *agentrun.ToolsRetriever { return &agentrun.ToolsRetriever{ PluginID: tool.PluginID, @@ -98,141 +79,19 @@ func (c *impl) buildSingleAgentStreamExecuteReq(ctx context.Context, historyMsg }(tool.Type), } }), - - ResumeInfo: resumeInfo, + ResumeInfo: agentRuntime.ResumeInfo, } } -func (c *impl) historyPairs(historyMsg []*message.Message) []*message.Message { - - fcMsgPairs := make(map[int64][]*message.Message) - for _, one := range historyMsg { - if one.MessageType != message.MessageTypeFunctionCall && one.MessageType != message.MessageTypeToolResponse { - continue - } - if _, ok := fcMsgPairs[one.RunID]; !ok { - fcMsgPairs[one.RunID] = []*message.Message{one} - } else { - fcMsgPairs[one.RunID] = append(fcMsgPairs[one.RunID], one) - } - } - - var historyAfterPairs []*message.Message - for _, value := range historyMsg { - if value.MessageType == message.MessageTypeFunctionCall { - if len(fcMsgPairs[value.RunID])%2 == 0 { - historyAfterPairs = append(historyAfterPairs, value) - } - } else { - historyAfterPairs = append(historyAfterPairs, value) - } - } - return historyAfterPairs - -} -func (c *impl) checkResumeInfo(_ context.Context, historyMsg []*message.Message) *crossagent.ResumeInfo { - - var resumeInfo *crossagent.ResumeInfo - for i := len(historyMsg) - 1; i >= 0; i-- { - if historyMsg[i].MessageType == message.MessageTypeQuestion { - break - } - if historyMsg[i].MessageType == message.MessageTypeVerbose { - if historyMsg[i].Ext[string(entity.ExtKeyResumeInfo)] != "" { - err := json.Unmarshal([]byte(historyMsg[i].Ext[string(entity.ExtKeyResumeInfo)]), &resumeInfo) - if err != nil { - return nil - } - } - } - } - - return resumeInfo -} - -func (c *impl) buildSchemaMessage(ctx context.Context, msgs []*message.Message) []*schema.Message { - schemaMessage := make([]*schema.Message, 0, len(msgs)) - - for _, msgOne := range msgs { - if msgOne.ModelContent == "" { - continue - } - if msgOne.MessageType == message.MessageTypeVerbose || msgOne.MessageType == message.MessageTypeFlowUp { - continue - } - var sm *schema.Message - err := json.Unmarshal([]byte(msgOne.ModelContent), &sm) - if err != nil { - continue - } - if len(sm.ReasoningContent) > 0 { - sm.ReasoningContent = "" - } - - schemaMessage = append(schemaMessage, c.parseMessageURI(ctx, sm)) - } - - return schemaMessage -} - -func (c *impl) parseMessageURI(ctx context.Context, mcMsg *schema.Message) *schema.Message { - if mcMsg.MultiContent == nil { - return mcMsg - } - for k, one := range mcMsg.MultiContent { - switch one.Type { - case schema.ChatMessagePartTypeImageURL: - - if one.ImageURL.URI != "" { - url, err := c.ImagexSVC.GetResourceURL(ctx, one.ImageURL.URI) - if err == nil { - mcMsg.MultiContent[k].ImageURL.URL = url.URL - } - } - case schema.ChatMessagePartTypeFileURL: - if one.FileURL.URI != "" { - url, err := c.ImagexSVC.GetResourceURL(ctx, one.FileURL.URI) - if err == nil { - mcMsg.MultiContent[k].FileURL.URL = url.URL - } - } - case schema.ChatMessagePartTypeAudioURL: - if one.AudioURL.URI != "" { - url, err := c.ImagexSVC.GetResourceURL(ctx, one.AudioURL.URI) - if err == nil { - mcMsg.MultiContent[k].AudioURL.URL = url.URL - } - } - case schema.ChatMessagePartTypeVideoURL: - if one.VideoURL.URI != "" { - url, err := c.ImagexSVC.GetResourceURL(ctx, one.VideoURL.URI) - if err == nil { - mcMsg.MultiContent[k].VideoURL.URL = url.URL - } - } - } - } - return mcMsg -} - -func (c *impl) buildIdentity(input *message.Message, agentRuntime *model.AgentRuntime) *model.AgentIdentity { +func (c *impl) buildIdentity(agentRuntime *crossagent.AgentRuntime) *model.AgentIdentity { return &model.AgentIdentity{ - AgentID: input.AgentID, + AgentID: agentRuntime.AgentID, Version: agentRuntime.AgentVersion, IsDraft: agentRuntime.IsDraft, ConnectorID: agentRuntime.ConnectorID, } } -func (c *impl) GetSingleAgent(ctx context.Context, agentID int64, version string) (agent *model.SingleAgent, err error) { - agentInfo, err := c.DomainSVC.GetSingleAgent(ctx, agentID, version) - if err != nil { - return nil, err - } - - return agentInfo.SingleAgent, nil -} - func (c *impl) ObtainAgentByIdentity(ctx context.Context, identity *model.AgentIdentity) (*model.SingleAgent, error) { agentInfo, err := c.DomainSVC.ObtainAgentByIdentity(ctx, identity) if err != nil { diff --git a/backend/crossdomain/impl/upload/upload.go b/backend/crossdomain/impl/upload/upload.go new file mode 100644 index 00000000..345bebc3 --- /dev/null +++ b/backend/crossdomain/impl/upload/upload.go @@ -0,0 +1,42 @@ +/* + * 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 upload + +import ( + "context" + + crossupload "github.com/coze-dev/coze-studio/backend/crossdomain/contract/upload" + "github.com/coze-dev/coze-studio/backend/domain/upload/service" +) + +var defaultSVC crossupload.Uploader + +type impl struct { + DomainSVC service.UploadService +} + +func InitDomainService(c service.UploadService) crossupload.Uploader { + defaultSVC = &impl{ + DomainSVC: c, + } + + return defaultSVC +} + +func (c *impl) GetFile(ctx context.Context, req *service.GetFileRequest) (resp *service.GetFileResponse, err error) { + return c.DomainSVC.GetFile(ctx, req) +} diff --git a/backend/crossdomain/impl/workflow/workflow.go b/backend/crossdomain/impl/workflow/workflow.go index bcb4b1fc..2f0f93f1 100644 --- a/backend/crossdomain/impl/workflow/workflow.go +++ b/backend/crossdomain/impl/workflow/workflow.go @@ -18,7 +18,6 @@ package workflow import ( "context" - "github.com/cloudwego/eino/compose" einoCompose "github.com/cloudwego/eino/compose" "github.com/cloudwego/eino/schema" @@ -66,6 +65,17 @@ func (i *impl) WithExecuteConfig(cfg workflowModel.ExecuteConfig) einoCompose.Op return i.DomainSVC.WithExecuteConfig(cfg) } +func (i *impl) StreamResume(ctx context.Context, req *entity.ResumeRequest, config workflowModel.ExecuteConfig) (*schema.StreamReader[*entity.Message], error) { + return i.DomainSVC.StreamResume(ctx, req, config) +} +func (i *impl) StreamExecute(ctx context.Context, config workflowModel.ExecuteConfig, input map[string]any) (*schema.StreamReader[*workflowEntity.Message], error) { + return i.DomainSVC.StreamExecute(ctx, config, input) +} + +func (i *impl) InitApplicationDefaultConversationTemplate(ctx context.Context, spaceID int64, appID int64, userID int64) error { + return i.DomainSVC.InitApplicationDefaultConversationTemplate(ctx, spaceID, appID, userID) +} + func (i *impl) WithMessagePipe() (compose.Option, *schema.StreamReader[*entity.Message], func()) { return i.DomainSVC.WithMessagePipe() } diff --git a/backend/domain/agent/singleagent/internal/dal/model/single_agent_draft.gen.go b/backend/domain/agent/singleagent/internal/dal/model/single_agent_draft.gen.go index 8a879f00..6769c45c 100644 --- a/backend/domain/agent/singleagent/internal/dal/model/single_agent_draft.gen.go +++ b/backend/domain/agent/singleagent/internal/dal/model/single_agent_draft.gen.go @@ -50,7 +50,9 @@ type SingleAgentDraft struct { JumpConfig *bot_common.JumpConfig `gorm:"column:jump_config;comment:Jump Configuration;serializer:json" json:"jump_config"` // Jump Configuration BackgroundImageInfoList []*bot_common.BackgroundImageInfo `gorm:"column:background_image_info_list;comment:Background image;serializer:json" json:"background_image_info_list"` // Background image DatabaseConfig []*bot_common.Database `gorm:"column:database_config;comment:Agent Database Base Configuration;serializer:json" json:"database_config"` // Agent Database Base Configuration + BotMode int32 `gorm:"column:bot_mode;not null;comment:mod,0:single mode 2:chatflow mode" json:"bot_mode"` // mod,0:single mode 2:chatflow mode ShortcutCommand []string `gorm:"column:shortcut_command;comment:shortcut command;serializer:json" json:"shortcut_command"` // shortcut command + LayoutInfo *bot_common.LayoutInfo `gorm:"column:layout_info;comment:chatflow layout info;serializer:json" json:"layout_info"` // chatflow layout info } // TableName SingleAgentDraft's table name diff --git a/backend/domain/agent/singleagent/internal/dal/model/single_agent_version.gen.go b/backend/domain/agent/singleagent/internal/dal/model/single_agent_version.gen.go index 6fce8009..d4820f33 100644 --- a/backend/domain/agent/singleagent/internal/dal/model/single_agent_version.gen.go +++ b/backend/domain/agent/singleagent/internal/dal/model/single_agent_version.gen.go @@ -52,7 +52,9 @@ type SingleAgentVersion struct { Version string `gorm:"column:version;not null;comment:Agent Version" json:"version"` // Agent Version BackgroundImageInfoList []*bot_common.BackgroundImageInfo `gorm:"column:background_image_info_list;comment:Background image;serializer:json" json:"background_image_info_list"` // Background image DatabaseConfig []*bot_common.Database `gorm:"column:database_config;comment:Agent Database Base Configuration;serializer:json" json:"database_config"` // Agent Database Base Configuration + BotMode int32 `gorm:"column:bot_mode;not null;comment:mod,0:single mode 2:chatflow mode" json:"bot_mode"` // mod,0:single mode 2:chatflow mode ShortcutCommand []string `gorm:"column:shortcut_command;comment:shortcut command;serializer:json" json:"shortcut_command"` // shortcut command + LayoutInfo *bot_common.LayoutInfo `gorm:"column:layout_info;comment:chatflow layout info;serializer:json" json:"layout_info"` // chatflow layout info } // TableName SingleAgentVersion's table name diff --git a/backend/domain/agent/singleagent/internal/dal/query/single_agent_draft.gen.go b/backend/domain/agent/singleagent/internal/dal/query/single_agent_draft.gen.go index 0047e133..854c0fed 100644 --- a/backend/domain/agent/singleagent/internal/dal/query/single_agent_draft.gen.go +++ b/backend/domain/agent/singleagent/internal/dal/query/single_agent_draft.gen.go @@ -1,3 +1,19 @@ +/* + * 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 gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. @@ -48,7 +64,9 @@ func newSingleAgentDraft(db *gorm.DB, opts ...gen.DOOption) singleAgentDraft { _singleAgentDraft.JumpConfig = field.NewField(tableName, "jump_config") _singleAgentDraft.BackgroundImageInfoList = field.NewField(tableName, "background_image_info_list") _singleAgentDraft.DatabaseConfig = field.NewField(tableName, "database_config") + _singleAgentDraft.BotMode = field.NewInt32(tableName, "bot_mode") _singleAgentDraft.ShortcutCommand = field.NewField(tableName, "shortcut_command") + _singleAgentDraft.LayoutInfo = field.NewField(tableName, "layout_info") _singleAgentDraft.fillFieldMap() @@ -81,7 +99,9 @@ type singleAgentDraft struct { JumpConfig field.Field // Jump Configuration BackgroundImageInfoList field.Field // Background image DatabaseConfig field.Field // Agent Database Base Configuration + BotMode field.Int32 // mod,0:single mode 2:chatflow mode ShortcutCommand field.Field // shortcut command + LayoutInfo field.Field // chatflow layout info fieldMap map[string]field.Expr } @@ -119,7 +139,9 @@ func (s *singleAgentDraft) updateTableName(table string) *singleAgentDraft { s.JumpConfig = field.NewField(table, "jump_config") s.BackgroundImageInfoList = field.NewField(table, "background_image_info_list") s.DatabaseConfig = field.NewField(table, "database_config") + s.BotMode = field.NewInt32(table, "bot_mode") s.ShortcutCommand = field.NewField(table, "shortcut_command") + s.LayoutInfo = field.NewField(table, "layout_info") s.fillFieldMap() @@ -136,7 +158,7 @@ func (s *singleAgentDraft) GetFieldByName(fieldName string) (field.OrderExpr, bo } func (s *singleAgentDraft) fillFieldMap() { - s.fieldMap = make(map[string]field.Expr, 22) + s.fieldMap = make(map[string]field.Expr, 24) s.fieldMap["id"] = s.ID s.fieldMap["agent_id"] = s.AgentID s.fieldMap["creator_id"] = s.CreatorID @@ -158,7 +180,9 @@ func (s *singleAgentDraft) fillFieldMap() { s.fieldMap["jump_config"] = s.JumpConfig s.fieldMap["background_image_info_list"] = s.BackgroundImageInfoList s.fieldMap["database_config"] = s.DatabaseConfig + s.fieldMap["bot_mode"] = s.BotMode s.fieldMap["shortcut_command"] = s.ShortcutCommand + s.fieldMap["layout_info"] = s.LayoutInfo } func (s singleAgentDraft) clone(db *gorm.DB) singleAgentDraft { diff --git a/backend/domain/agent/singleagent/internal/dal/query/single_agent_version.gen.go b/backend/domain/agent/singleagent/internal/dal/query/single_agent_version.gen.go index ee2869a8..214e7ff2 100644 --- a/backend/domain/agent/singleagent/internal/dal/query/single_agent_version.gen.go +++ b/backend/domain/agent/singleagent/internal/dal/query/single_agent_version.gen.go @@ -1,3 +1,19 @@ +/* + * 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 gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. @@ -50,7 +66,9 @@ func newSingleAgentVersion(db *gorm.DB, opts ...gen.DOOption) singleAgentVersion _singleAgentVersion.Version = field.NewString(tableName, "version") _singleAgentVersion.BackgroundImageInfoList = field.NewField(tableName, "background_image_info_list") _singleAgentVersion.DatabaseConfig = field.NewField(tableName, "database_config") + _singleAgentVersion.BotMode = field.NewInt32(tableName, "bot_mode") _singleAgentVersion.ShortcutCommand = field.NewField(tableName, "shortcut_command") + _singleAgentVersion.LayoutInfo = field.NewField(tableName, "layout_info") _singleAgentVersion.fillFieldMap() @@ -85,7 +103,9 @@ type singleAgentVersion struct { Version field.String // Agent Version BackgroundImageInfoList field.Field // Background image DatabaseConfig field.Field // Agent Database Base Configuration + BotMode field.Int32 // mod,0:single mode 2:chatflow mode ShortcutCommand field.Field // shortcut command + LayoutInfo field.Field // chatflow layout info fieldMap map[string]field.Expr } @@ -125,7 +145,9 @@ func (s *singleAgentVersion) updateTableName(table string) *singleAgentVersion { s.Version = field.NewString(table, "version") s.BackgroundImageInfoList = field.NewField(table, "background_image_info_list") s.DatabaseConfig = field.NewField(table, "database_config") + s.BotMode = field.NewInt32(table, "bot_mode") s.ShortcutCommand = field.NewField(table, "shortcut_command") + s.LayoutInfo = field.NewField(table, "layout_info") s.fillFieldMap() @@ -142,7 +164,7 @@ func (s *singleAgentVersion) GetFieldByName(fieldName string) (field.OrderExpr, } func (s *singleAgentVersion) fillFieldMap() { - s.fieldMap = make(map[string]field.Expr, 24) + s.fieldMap = make(map[string]field.Expr, 26) s.fieldMap["id"] = s.ID s.fieldMap["agent_id"] = s.AgentID s.fieldMap["creator_id"] = s.CreatorID @@ -166,7 +188,9 @@ func (s *singleAgentVersion) fillFieldMap() { s.fieldMap["version"] = s.Version s.fieldMap["background_image_info_list"] = s.BackgroundImageInfoList s.fieldMap["database_config"] = s.DatabaseConfig + s.fieldMap["bot_mode"] = s.BotMode s.fieldMap["shortcut_command"] = s.ShortcutCommand + s.fieldMap["layout_info"] = s.LayoutInfo } func (s singleAgentVersion) clone(db *gorm.DB) singleAgentVersion { diff --git a/backend/domain/agent/singleagent/internal/dal/single_agent_draft.go b/backend/domain/agent/singleagent/internal/dal/single_agent_draft.go index 90b03265..29ad1112 100644 --- a/backend/domain/agent/singleagent/internal/dal/single_agent_draft.go +++ b/backend/domain/agent/singleagent/internal/dal/single_agent_draft.go @@ -22,7 +22,9 @@ import ( "gorm.io/gorm" + "github.com/coze-dev/coze-studio/backend/api/model/app/bot_common" "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent" + "github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/entity" "github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/internal/dal/model" "github.com/coze-dev/coze-studio/backend/domain/agent/singleagent/internal/dal/query" @@ -118,7 +120,7 @@ func (sa *SingleAgentDraftDAO) Update(ctx context.Context, agentInfo *entity.Sin po := sa.singleAgentDraftDo2Po(agentInfo) singleAgentDAOModel := sa.dbQuery.SingleAgentDraft - _, err = singleAgentDAOModel.Where(singleAgentDAOModel.AgentID.Eq(agentInfo.AgentID)).Updates(po) + err = singleAgentDAOModel.Where(singleAgentDAOModel.AgentID.Eq(agentInfo.AgentID)).Save(po) if err != nil { return errorx.WrapByCode(err, errno.ErrAgentUpdateCode) } @@ -156,6 +158,8 @@ func (sa *SingleAgentDraftDAO) singleAgentDraftPo2Do(po *model.SingleAgentDraft) BackgroundImageInfoList: po.BackgroundImageInfoList, Database: po.DatabaseConfig, ShortcutCommand: po.ShortcutCommand, + BotMode: bot_common.BotMode(po.BotMode), + LayoutInfo: po.LayoutInfo, }, } } @@ -183,5 +187,7 @@ func (sa *SingleAgentDraftDAO) singleAgentDraftDo2Po(do *entity.SingleAgent) *mo BackgroundImageInfoList: do.BackgroundImageInfoList, DatabaseConfig: do.Database, ShortcutCommand: do.ShortcutCommand, + BotMode: int32(do.BotMode), + LayoutInfo: do.LayoutInfo, } } diff --git a/backend/domain/agent/singleagent/repository/repository.go b/backend/domain/agent/singleagent/repository/repository.go index 245a6f3d..60e0ad0d 100644 --- a/backend/domain/agent/singleagent/repository/repository.go +++ b/backend/domain/agent/singleagent/repository/repository.go @@ -47,7 +47,6 @@ type SingleAgentDraftRepo interface { Delete(ctx context.Context, spaceID, agentID int64) (err error) Update(ctx context.Context, agentInfo *entity.SingleAgent) (err error) Save(ctx context.Context, agentInfo *entity.SingleAgent) (err error) - GetDisplayInfo(ctx context.Context, userID, agentID int64) (*entity.AgentDraftDisplayInfo, error) UpdateDisplayInfo(ctx context.Context, userID int64, e *entity.AgentDraftDisplayInfo) error } diff --git a/backend/domain/app/entity/connector.go b/backend/domain/app/entity/connector.go index 961d5529..21956430 100644 --- a/backend/domain/app/entity/connector.go +++ b/backend/domain/app/entity/connector.go @@ -22,6 +22,7 @@ import ( ) var ConnectorIDWhiteList = []int64{ + consts.WebSDKConnectorID, consts.APIConnectorID, } diff --git a/backend/domain/app/service/publish_app.go b/backend/domain/app/service/publish_app.go index a0883e31..ddbf7c79 100644 --- a/backend/domain/app/service/publish_app.go +++ b/backend/domain/app/service/publish_app.go @@ -75,7 +75,7 @@ func (a *appServiceImpl) publishByConnectors(ctx context.Context, recordID int64 for cid := range req.ConnectorPublishConfigs { connectorIDs = append(connectorIDs, cid) } - failedResources, err := a.packResources(ctx, req.APPID, req.Version, connectorIDs) + failedResources, err := a.packResources(ctx, req.APPID, req.Version, connectorIDs, req.ConnectorPublishConfigs) if err != nil { return false, err } @@ -92,7 +92,7 @@ func (a *appServiceImpl) publishByConnectors(ctx context.Context, recordID int64 for cid := range req.ConnectorPublishConfigs { switch cid { - case commonConsts.APIConnectorID: + case commonConsts.APIConnectorID, commonConsts.WebSDKConnectorID: updateSuccessErr := a.APPRepo.UpdateConnectorPublishStatus(ctx, recordID, entity.ConnectorPublishStatusOfSuccess) if updateSuccessErr == nil { continue @@ -172,7 +172,8 @@ func (a *appServiceImpl) createPublishVersion(ctx context.Context, req *PublishA return recordID, nil } -func (a *appServiceImpl) packResources(ctx context.Context, appID int64, version string, connectorIDs []int64) (failedResources []*entity.PackResourceFailedInfo, err error) { +func (a *appServiceImpl) packResources(ctx context.Context, appID int64, version string, connectorIDs []int64, pConfig map[int64]entity.PublishConfig) (failedResources []*entity.PackResourceFailedInfo, err error) { + failedPlugins, allDraftPlugins, err := a.packPlugins(ctx, appID, version) if err != nil { return nil, err diff --git a/backend/domain/app/service/service_impl.go b/backend/domain/app/service/service_impl.go index 60a42093..33e42586 100644 --- a/backend/domain/app/service/service_impl.go +++ b/backend/domain/app/service/service_impl.go @@ -25,10 +25,13 @@ import ( connectorModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/connector" databaseModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/database" knowledgeModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/knowledge" + crossconnector "github.com/coze-dev/coze-studio/backend/crossdomain/contract/connector" crossdatabase "github.com/coze-dev/coze-studio/backend/crossdomain/contract/database" crossknowledge "github.com/coze-dev/coze-studio/backend/crossdomain/contract/knowledge" crossplugin "github.com/coze-dev/coze-studio/backend/crossdomain/contract/plugin" + crossworkflow "github.com/coze-dev/coze-studio/backend/crossdomain/contract/workflow" + "github.com/coze-dev/coze-studio/backend/domain/app/entity" "github.com/coze-dev/coze-studio/backend/domain/app/repository" "github.com/coze-dev/coze-studio/backend/infra/contract/idgen" @@ -67,6 +70,11 @@ func (a *appServiceImpl) CreateDraftAPP(ctx context.Context, req *CreateDraftAPP return 0, errorx.Wrapf(err, "CreateDraftAPP failed, spaceID=%d", req.SpaceID) } + err = crossworkflow.DefaultSVC().InitApplicationDefaultConversationTemplate(ctx, req.SpaceID, appID, req.OwnerID) + if err != nil { + return 0, err + } + return appID, nil } diff --git a/backend/domain/connector/service/connector_impl.go b/backend/domain/connector/service/connector_impl.go index 1be5b14e..90d5b8c3 100644 --- a/backend/domain/connector/service/connector_impl.go +++ b/backend/domain/connector/service/connector_impl.go @@ -41,7 +41,7 @@ func NewService(tos storage.Storage) Connector { var i18n2ConnectorDesc = map[i18n.Locale]map[int64]string{ i18n.LocaleEN: { - consts.WebSDKConnectorID: "Deploy the bot as a Web SDK", + consts.WebSDKConnectorID: "Deploy your project to the Chat SDK. This publishing method is supported only for projects that have created a conversation flow, please refer to [Installation Guidelines](coze://web-sdk-guide) for installation methods.", consts.APIConnectorID: "Supports OAuth 2.0 and personal access tokens", consts.CozeConnectorID: "Coze", }, @@ -54,7 +54,7 @@ func (c *connectorImpl) AllConnectorInfo(ctx context.Context) []*entity.Connecto ID: consts.WebSDKConnectorID, Name: "Chat SDK", URI: "default_icon/connector-chat-sdk.jpg", - Desc: "将Bot部署为Web SDK", + Desc: "将项目部署到Chat SDK。仅创建过对话流的项目支持该发布方式,安装方式请查看[安装指引](coze://web-sdk-guide)", }, }, { diff --git a/backend/domain/conversation/agentrun/entity/run_record.go b/backend/domain/conversation/agentrun/entity/run_record.go index e9e6f769..d0872c23 100644 --- a/backend/domain/conversation/agentrun/entity/run_record.go +++ b/backend/domain/conversation/agentrun/entity/run_record.go @@ -43,6 +43,7 @@ type RunRecordMeta struct { ChatRequest *string `json:"chat_message"` CompletedAt int64 `json:"completed_at"` FailedAt int64 `json:"failed_at"` + CreatorID int64 `json:"creator_id"` } type ChunkRunItem = RunRecordMeta @@ -158,3 +159,18 @@ type ModelAnswerEvent struct { Message *schema.Message Err error } + +type ListRunRecordMeta struct { + ConversationID int64 `json:"conversation_id"` + AgentID int64 `json:"agent_id"` + SectionID int64 `json:"section_id"` + Limit int32 `json:"limit"` + OrderBy string `json:"order_by"` //desc asc + BeforeID int64 `json:"before_id"` + AfterID int64 `json:"after_id"` +} + +type CancelRunMeta struct { + ConversationID int64 `json:"conversation_id"` + RunID int64 `json:"run_id"` +} diff --git a/backend/domain/conversation/agentrun/internal/agent_info.go b/backend/domain/conversation/agentrun/internal/agent_info.go new file mode 100644 index 00000000..c3d824d3 --- /dev/null +++ b/backend/domain/conversation/agentrun/internal/agent_info.go @@ -0,0 +1,45 @@ +/* + * 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 internal + +import ( + "context" + + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent" + crossagent "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agent" + "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" + "github.com/coze-dev/coze-studio/backend/pkg/lang/ptr" +) + +func getAgentHistoryRounds(agentInfo *singleagent.SingleAgent) int32 { + var conversationTurns int32 = entity.ConversationTurnsDefault + if agentInfo != nil && agentInfo.ModelInfo != nil && agentInfo.ModelInfo.ShortMemoryPolicy != nil && ptr.From(agentInfo.ModelInfo.ShortMemoryPolicy.HistoryRound) > 0 { + conversationTurns = ptr.From(agentInfo.ModelInfo.ShortMemoryPolicy.HistoryRound) + } + return conversationTurns +} + +func getAgentInfo(ctx context.Context, agentID int64, isDraft bool) (*singleagent.SingleAgent, error) { + agentInfo, err := crossagent.DefaultSVC().ObtainAgentByIdentity(ctx, &singleagent.AgentIdentity{ + AgentID: agentID, + IsDraft: isDraft, + }) + if err != nil { + return nil, err + } + + return agentInfo, nil +} diff --git a/backend/domain/conversation/agentrun/internal/chatflow_run.go b/backend/domain/conversation/agentrun/internal/chatflow_run.go new file mode 100644 index 00000000..4120a769 --- /dev/null +++ b/backend/domain/conversation/agentrun/internal/chatflow_run.go @@ -0,0 +1,215 @@ +/* + * 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 internal + +import ( + "bytes" + "context" + "errors" + "io" + "strconv" + "sync" + + "github.com/cloudwego/eino/schema" + + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/agentrun" + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" + crossworkflow "github.com/coze-dev/coze-studio/backend/crossdomain/contract/workflow" + "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" + msgEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity" + "github.com/coze-dev/coze-studio/backend/infra/contract/imagex" + "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/logs" + "github.com/coze-dev/coze-studio/backend/pkg/safego" + "github.com/coze-dev/coze-studio/backend/types/errno" +) + +func (art *AgentRuntime) ChatflowRun(ctx context.Context, imagex imagex.ImageX) (err error) { + + mh := &MesssageEventHanlder{ + sw: art.SW, + messageEvent: art.MessageEvent, + } + resumeInfo := parseResumeInfo(ctx, art.GetHistory()) + wfID, _ := strconv.ParseInt(art.GetAgentInfo().LayoutInfo.WorkflowId, 10, 64) + + if wfID == 0 { + mh.handlerErr(ctx, errorx.New(errno.ErrAgentRunWorkflowNotFound)) + return + } + var wfStreamer *schema.StreamReader[*crossworkflow.WorkflowMessage] + + executeConfig := crossworkflow.ExecuteConfig{ + ID: wfID, + ConnectorID: art.GetRunMeta().ConnectorID, + ConnectorUID: art.GetRunMeta().UserID, + AgentID: ptr.Of(art.GetRunMeta().AgentID), + Mode: crossworkflow.ExecuteModeRelease, + BizType: crossworkflow.BizTypeAgent, + SyncPattern: crossworkflow.SyncPatternStream, + From: crossworkflow.FromLatestVersion, + } + + if resumeInfo != nil { + wfStreamer, err = crossworkflow.DefaultSVC().StreamResume(ctx, &crossworkflow.ResumeRequest{ + ResumeData: concatWfInput(art), + EventID: resumeInfo.ChatflowInterrupt.InterruptEvent.ID, + ExecuteID: resumeInfo.ChatflowInterrupt.ExecuteID, + }, executeConfig) + } else { + executeConfig.ConversationID = &art.GetRunMeta().ConversationID + executeConfig.SectionID = &art.GetRunMeta().SectionID + executeConfig.InitRoundID = &art.RunRecord.ID + executeConfig.RoundID = &art.RunRecord.ID + executeConfig.UserMessage = transMessageToSchemaMessage(ctx, []*msgEntity.Message{art.GetInput()}, imagex)[0] + executeConfig.MaxHistoryRounds = ptr.Of(getAgentHistoryRounds(art.GetAgentInfo())) + wfStreamer, err = crossworkflow.DefaultSVC().StreamExecute(ctx, executeConfig, map[string]any{ + "USER_INPUT": concatWfInput(art), + }) + } + if err != nil { + return err + } + + var wg sync.WaitGroup + wg.Add(1) + safego.Go(ctx, func() { + defer wg.Done() + art.pullWfStream(ctx, wfStreamer, mh) + }) + wg.Wait() + return err +} + +func concatWfInput(rtDependence *AgentRuntime) string { + var input string + for _, content := range rtDependence.RunMeta.Content { + if content.Type == message.InputTypeText { + input = content.Text + "," + input + } else { + for _, file := range content.FileData { + input += file.Url + "," + } + } + } + return input +} + +func (art *AgentRuntime) pullWfStream(ctx context.Context, events *schema.StreamReader[*crossworkflow.WorkflowMessage], mh *MesssageEventHanlder) { + + fullAnswerContent := bytes.NewBuffer([]byte{}) + var usage *msgEntity.UsageExt + + preAnswerMsg, cErr := preCreateAnswer(ctx, art) + + if cErr != nil { + return + } + + var preMsgIsFinish = false + var lastAnswerMsg *entity.ChunkMessageItem + + for { + st, re := events.Recv() + if re != nil { + if errors.Is(re, io.EOF) { + + if lastAnswerMsg != nil && usage != nil { + art.SetUsage(&agentrun.Usage{ + LlmPromptTokens: usage.InputTokens, + LlmCompletionTokens: usage.OutputTokens, + LlmTotalTokens: usage.TotalCount, + }) + _ = mh.handlerWfUsage(ctx, lastAnswerMsg, usage) + } + + finishErr := mh.handlerFinalAnswerFinish(ctx, art) + if finishErr != nil { + logs.CtxErrorf(ctx, "handlerFinalAnswerFinish error: %v", finishErr) + return + } + return + } + logs.CtxErrorf(ctx, "pullWfStream Recv error: %v", re) + mh.handlerErr(ctx, re) + return + } + if st == nil { + continue + } + if st.StateMessage != nil { + if st.StateMessage.Status == crossworkflow.WorkflowFailed { + mh.handlerErr(ctx, st.StateMessage.LastError) + continue + } + if st.StateMessage.Usage != nil { + usage = &msgEntity.UsageExt{ + InputTokens: st.StateMessage.Usage.InputTokens, + OutputTokens: st.StateMessage.Usage.OutputTokens, + TotalCount: st.StateMessage.Usage.InputTokens + st.StateMessage.Usage.OutputTokens, + } + } + + if st.StateMessage.InterruptEvent != nil { // interrupt + mh.handlerWfInterruptMsg(ctx, st.StateMessage, art) + continue + } + + } + + if st.DataMessage == nil { + continue + } + + switch st.DataMessage.Type { + case crossworkflow.Answer: + + // input node & question node skip + if st.DataMessage != nil && (st.DataMessage.NodeType == crossworkflow.NodeTypeInputReceiver || st.DataMessage.NodeType == crossworkflow.NodeTypeQuestion) { + break + } + + if preMsgIsFinish { + preAnswerMsg, cErr = preCreateAnswer(ctx, art) + if cErr != nil { + return + } + preMsgIsFinish = false + } + if st.DataMessage.Content != "" { + fullAnswerContent.WriteString(st.DataMessage.Content) + } + + sendAnswerMsg := buildSendMsg(ctx, preAnswerMsg, false, art) + sendAnswerMsg.Content = st.DataMessage.Content + + mh.messageEvent.SendMsgEvent(entity.RunEventMessageDelta, sendAnswerMsg, mh.sw) + + if st.DataMessage.Last { + preMsgIsFinish = true + sendAnswerMsg := buildSendMsg(ctx, preAnswerMsg, false, art) + sendAnswerMsg.Content = fullAnswerContent.String() + fullAnswerContent.Reset() + hfErr := mh.handlerAnswer(ctx, sendAnswerMsg, usage, art, preAnswerMsg) + if hfErr != nil { + return + } + lastAnswerMsg = sendAnswerMsg + } + } + } +} diff --git a/backend/domain/conversation/agentrun/internal/dal/dao.go b/backend/domain/conversation/agentrun/internal/dal/dao.go index 0b125a93..1e553568 100644 --- a/backend/domain/conversation/agentrun/internal/dal/dao.go +++ b/backend/domain/conversation/agentrun/internal/dal/dao.go @@ -19,6 +19,8 @@ package dal import ( "context" "encoding/json" + "strconv" + "strings" "time" "gorm.io/gorm" @@ -27,6 +29,7 @@ import ( "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal/dal/model" "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal/dal/query" "github.com/coze-dev/coze-studio/backend/infra/contract/idgen" + "github.com/coze-dev/coze-studio/backend/pkg/lang/slices" "github.com/coze-dev/coze-studio/backend/pkg/logs" ) @@ -59,8 +62,12 @@ func (dao *RunRecordDAO) Create(ctx context.Context, runMeta *entity.AgentRunMet return dao.buildPo2Do(createPO), nil } -func (dao *RunRecordDAO) GetByID(ctx context.Context, id int64) (*model.RunRecord, error) { - return dao.query.RunRecord.WithContext(ctx).Where(dao.query.RunRecord.ID.Eq(id)).First() +func (dao *RunRecordDAO) GetByID(ctx context.Context, id int64) (*entity.RunRecordMeta, error) { + po, err := dao.query.RunRecord.WithContext(ctx).Where(dao.query.RunRecord.ID.Eq(id)).First() + if err != nil { + return nil, err + } + return dao.buildPo2Do(po), nil } func (dao *RunRecordDAO) UpdateByID(ctx context.Context, id int64, updateMeta *entity.UpdateMeta) error { @@ -106,20 +113,40 @@ func (dao *RunRecordDAO) Delete(ctx context.Context, id []int64) error { return err } -func (dao *RunRecordDAO) List(ctx context.Context, conversationID int64, sectionID int64, limit int32) ([]*model.RunRecord, error) { - logs.CtxInfof(ctx, "list run record req:%v, sectionID:%v, limit:%v", conversationID, sectionID, limit) +func (dao *RunRecordDAO) List(ctx context.Context, meta *entity.ListRunRecordMeta) ([]*entity.RunRecordMeta, error) { + logs.CtxInfof(ctx, "list run record req:%v, sectionID:%v, limit:%v", meta.ConversationID, meta.SectionID, meta.Limit) m := dao.query.RunRecord - do := m.WithContext(ctx).Where(m.ConversationID.Eq(conversationID)).Debug().Where(m.Status.NotIn(string(entity.RunStatusDeleted))) - - if sectionID > 0 { - do = do.Where(m.SectionID.Eq(sectionID)) + do := m.WithContext(ctx).Where(m.ConversationID.Eq(meta.ConversationID)).Debug().Where(m.Status.NotIn(string(entity.RunStatusDeleted))) + if meta.BeforeID > 0 { + runRecord, err := m.Where(m.ID.Eq(meta.BeforeID)).First() + if err != nil { + return nil, err + } + do = do.Where(m.CreatedAt.Lt(runRecord.CreatedAt)) } - if limit > 0 { - do = do.Limit(int(limit)) + if meta.AfterID > 0 { + runRecord, err := m.Where(m.ID.Eq(meta.AfterID)).First() + if err != nil { + return nil, err + } + do = do.Where(m.CreatedAt.Gt(runRecord.CreatedAt)) + } + if meta.SectionID > 0 { + do = do.Where(m.SectionID.Eq(meta.SectionID)) + } + if meta.Limit > 0 { + do = do.Limit(int(meta.Limit)) + } + if strings.ToLower(meta.OrderBy) == "asc" { + do = do.Order(m.CreatedAt.Asc()) + } else { + do = do.Order(m.CreatedAt.Desc()) } - runRecords, err := do.Order(m.CreatedAt.Desc()).Find() - return runRecords, err + runRecords, err := do.Find() + return slices.Transform(runRecords, func(item *model.RunRecord) *entity.RunRecordMeta { + return dao.buildPo2Do(item) + }), err } func (dao *RunRecordDAO) buildCreatePO(ctx context.Context, runMeta *entity.AgentRunMeta) (*model.RunRecord, error) { @@ -135,7 +162,10 @@ func (dao *RunRecordDAO) buildCreatePO(ctx context.Context, runMeta *entity.Agen } timeNow := time.Now().UnixMilli() - + creatorID, err := strconv.ParseInt(runMeta.UserID, 10, 64) + if err != nil { + return nil, err + } return &model.RunRecord{ ID: runID, ConversationID: runMeta.ConversationID, @@ -145,6 +175,7 @@ func (dao *RunRecordDAO) buildCreatePO(ctx context.Context, runMeta *entity.Agen ChatRequest: string(reqOrigin), UserID: runMeta.UserID, CreatedAt: timeNow, + CreatorID: creatorID, }, nil } @@ -161,7 +192,21 @@ func (dao *RunRecordDAO) buildPo2Do(po *model.RunRecord) *entity.RunRecordMeta { CompletedAt: po.CompletedAt, FailedAt: po.FailedAt, Usage: po.Usage, + CreatorID: po.CreatorID, } return runMeta } + +func (dao *RunRecordDAO) Cancel(ctx context.Context, meta *entity.CancelRunMeta) (*entity.RunRecordMeta, error) { + + m := dao.query.RunRecord + _, err := m.WithContext(ctx).Where(m.ID.Eq(meta.RunID)).UpdateColumns(map[string]interface{}{ + "updated_at": time.Now().UnixMilli(), + "status": entity.RunEventCancelled, + }) + if err != nil { + return nil, err + } + return dao.GetByID(ctx, meta.RunID) +} diff --git a/backend/domain/conversation/agentrun/internal/event.go b/backend/domain/conversation/agentrun/internal/event.go deleted file mode 100644 index bcbdbb9c..00000000 --- a/backend/domain/conversation/agentrun/internal/event.go +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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 internal - -import ( - "github.com/cloudwego/eino/schema" - - "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" -) - -type Event struct { -} - -func NewEvent() *Event { - return &Event{} -} - -func (e *Event) buildMessageEvent(runEvent entity.RunEvent, chunkMsgItem *entity.ChunkMessageItem) *entity.AgentRunResponse { - return &entity.AgentRunResponse{ - Event: runEvent, - ChunkMessageItem: chunkMsgItem, - } -} - -func (e *Event) buildRunEvent(runEvent entity.RunEvent, chunkRunItem *entity.ChunkRunItem) *entity.AgentRunResponse { - return &entity.AgentRunResponse{ - Event: runEvent, - ChunkRunItem: chunkRunItem, - } -} - -func (e *Event) buildErrEvent(runEvent entity.RunEvent, err *entity.RunError) *entity.AgentRunResponse { - return &entity.AgentRunResponse{ - Event: runEvent, - Error: err, - } -} - -func (e *Event) buildStreamDoneEvent() *entity.AgentRunResponse { - - return &entity.AgentRunResponse{ - Event: entity.RunEventStreamDone, - } -} - -func (e *Event) SendRunEvent(runEvent entity.RunEvent, runItem *entity.ChunkRunItem, sw *schema.StreamWriter[*entity.AgentRunResponse]) { - resp := e.buildRunEvent(runEvent, runItem) - sw.Send(resp, nil) -} - -func (e *Event) SendMsgEvent(runEvent entity.RunEvent, messageItem *entity.ChunkMessageItem, sw *schema.StreamWriter[*entity.AgentRunResponse]) { - resp := e.buildMessageEvent(runEvent, messageItem) - sw.Send(resp, nil) -} - -func (e *Event) SendErrEvent(runEvent entity.RunEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], err *entity.RunError) { - resp := e.buildErrEvent(runEvent, err) - sw.Send(resp, nil) -} - -func (e *Event) SendStreamDoneEvent(sw *schema.StreamWriter[*entity.AgentRunResponse]) { - resp := e.buildStreamDoneEvent() - sw.Send(resp, nil) -} diff --git a/backend/domain/conversation/agentrun/internal/message_builder.go b/backend/domain/conversation/agentrun/internal/message_builder.go new file mode 100644 index 00000000..6891f5a8 --- /dev/null +++ b/backend/domain/conversation/agentrun/internal/message_builder.go @@ -0,0 +1,512 @@ +/* + * 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 internal + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "time" + + "github.com/cloudwego/eino/schema" + + messageModel "github.com/coze-dev/coze-studio/backend/api/model/conversation/message" + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent" + crossagent "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agent" + "github.com/coze-dev/coze-studio/backend/infra/contract/imagex" + + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" + crossworkflow "github.com/coze-dev/coze-studio/backend/crossdomain/contract/workflow" + "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" + msgEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity" + + "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/types/errno" +) + +func buildSendMsg(_ context.Context, msg *msgEntity.Message, isFinish bool, rtDependence *AgentRuntime) *entity.ChunkMessageItem { + + copyMap := make(map[string]string) + for k, v := range msg.Ext { + copyMap[k] = v + } + + return &entity.ChunkMessageItem{ + ID: msg.ID, + ConversationID: msg.ConversationID, + SectionID: msg.SectionID, + AgentID: msg.AgentID, + Content: msg.Content, + Role: entity.RoleTypeAssistant, + ContentType: msg.ContentType, + MessageType: msg.MessageType, + ReplyID: rtDependence.GetQuestionMsgID(), + Type: msg.MessageType, + CreatedAt: msg.CreatedAt, + UpdatedAt: msg.UpdatedAt, + RunID: rtDependence.GetRunRecord().ID, + Ext: copyMap, + IsFinish: isFinish, + ReasoningContent: ptr.Of(msg.ReasoningContent), + } +} + +func buildKnowledge(_ context.Context, chunk *entity.AgentRespEvent) *msgEntity.VerboseInfo { + var recallDatas []msgEntity.RecallDataInfo + for _, kOne := range chunk.Knowledge { + recallDatas = append(recallDatas, msgEntity.RecallDataInfo{ + Slice: kOne.Content, + Meta: msgEntity.MetaInfo{ + Dataset: msgEntity.DatasetInfo{ + ID: kOne.MetaData["dataset_id"].(string), + Name: kOne.MetaData["dataset_name"].(string), + }, + Document: msgEntity.DocumentInfo{ + ID: kOne.MetaData["document_id"].(string), + Name: kOne.MetaData["document_name"].(string), + }, + }, + Score: kOne.Score(), + }) + } + + verboseData := &msgEntity.VerboseData{ + Chunks: recallDatas, + OriReq: "", + StatusCode: 0, + } + data, err := json.Marshal(verboseData) + if err != nil { + return nil + } + knowledgeInfo := &msgEntity.VerboseInfo{ + MessageType: string(entity.MessageSubTypeKnowledgeCall), + Data: string(data), + } + return knowledgeInfo +} + +func buildBotStateExt(arm *entity.AgentRunMeta) *msgEntity.BotStateExt { + agentID := strconv.FormatInt(arm.AgentID, 10) + botStateExt := &msgEntity.BotStateExt{ + AgentID: agentID, + AgentName: arm.Name, + Awaiting: agentID, + BotID: agentID, + } + + return botStateExt +} + +type irMsg struct { + Type string `json:"type,omitempty"` + ContentType string `json:"content_type"` + Content any `json:"content"` // either optionContent or string + ID string `json:"id,omitempty"` +} + +func parseInterruptData(_ context.Context, interruptData *singleagent.InterruptInfo) (string, message.ContentType, error) { + + defaultContentType := message.ContentTypeText + switch interruptData.InterruptType { + case singleagent.InterruptEventType_OauthPlugin: + data := interruptData.AllToolInterruptData[interruptData.ToolCallID].ToolNeedOAuth.Message + return data, defaultContentType, nil + case singleagent.InterruptEventType_Question: + data := interruptData.AllWfInterruptData[interruptData.ToolCallID].InterruptData + return processQuestionInterruptData(data) + case singleagent.InterruptEventType_InputNode: + data := interruptData.AllWfInterruptData[interruptData.ToolCallID].InterruptData + return processInputNodeInterruptData(data) + case singleagent.InterruptEventType_WorkflowLLM: + toolInterruptEvent := interruptData.AllWfInterruptData[interruptData.ToolCallID].ToolInterruptEvent + data := toolInterruptEvent.InterruptData + if singleagent.InterruptEventType(toolInterruptEvent.EventType) == singleagent.InterruptEventType_InputNode { + return processInputNodeInterruptData(data) + } + if singleagent.InterruptEventType(toolInterruptEvent.EventType) == singleagent.InterruptEventType_Question { + return processQuestionInterruptData(data) + } + return "", defaultContentType, errorx.New(errno.ErrUnknowInterruptType) + + } + return "", defaultContentType, errorx.New(errno.ErrUnknowInterruptType) +} + +func processQuestionInterruptData(data string) (string, message.ContentType, error) { + defaultContentType := message.ContentTypeText + var iData map[string][]*irMsg + err := json.Unmarshal([]byte(data), &iData) + if err != nil { + return "", defaultContentType, err + } + if len(iData["messages"]) == 0 { + return "", defaultContentType, errorx.New(errno.ErrInterruptDataEmpty) + } + interruptMsg := iData["messages"][0] + + if interruptMsg.ContentType == "text" { + return interruptMsg.Content.(string), defaultContentType, nil + } else if interruptMsg.ContentType == "option" || interruptMsg.ContentType == "form_schema" { + iMarshalData, err := json.Marshal(interruptMsg) + if err != nil { + return "", defaultContentType, err + } + return string(iMarshalData), message.ContentTypeCard, nil + } + return "", defaultContentType, errorx.New(errno.ErrUnknowInterruptType) +} + +func processInputNodeInterruptData(data string) (string, message.ContentType, error) { + return data, message.ContentTypeCard, nil +} + +func handlerUsage(meta *schema.ResponseMeta) *msgEntity.UsageExt { + if meta == nil || meta.Usage == nil { + return nil + } + + return &msgEntity.UsageExt{ + TotalCount: int64(meta.Usage.TotalTokens), + InputTokens: int64(meta.Usage.PromptTokens), + OutputTokens: int64(meta.Usage.CompletionTokens), + } +} + +func preCreateAnswer(ctx context.Context, rtDependence *AgentRuntime) (*msgEntity.Message, error) { + arm := rtDependence.RunMeta + msgMeta := &msgEntity.Message{ + ConversationID: arm.ConversationID, + RunID: rtDependence.RunRecord.ID, + AgentID: arm.AgentID, + SectionID: arm.SectionID, + UserID: arm.UserID, + Role: schema.Assistant, + MessageType: message.MessageTypeAnswer, + ContentType: message.ContentTypeText, + Ext: arm.Ext, + } + + if arm.Ext == nil { + msgMeta.Ext = map[string]string{} + } + + botStateExt := buildBotStateExt(arm) + bseString, err := json.Marshal(botStateExt) + if err != nil { + return nil, err + } + + if _, ok := msgMeta.Ext[string(msgEntity.MessageExtKeyBotState)]; !ok { + msgMeta.Ext[string(msgEntity.MessageExtKeyBotState)] = string(bseString) + } + + msgMeta.Ext = arm.Ext + return crossmessage.DefaultSVC().PreCreate(ctx, msgMeta) +} + +func buildAgentMessage2Create(ctx context.Context, chunk *entity.AgentRespEvent, messageType message.MessageType, rtDependence *AgentRuntime) *message.Message { + arm := rtDependence.GetRunMeta() + msg := &msgEntity.Message{ + ConversationID: arm.ConversationID, + RunID: rtDependence.RunRecord.ID, + AgentID: arm.AgentID, + SectionID: arm.SectionID, + UserID: arm.UserID, + MessageType: messageType, + } + buildExt := map[string]string{} + + timeCost := fmt.Sprintf("%.1f", float64(time.Since(rtDependence.GetStartTime()).Milliseconds())/1000.00) + + switch messageType { + case message.MessageTypeQuestion: + msg.Role = schema.User + msg.ContentType = arm.ContentType + for _, content := range arm.Content { + if content.Type == message.InputTypeText { + msg.Content = content.Text + break + } + } + msg.MultiContent = arm.Content + buildExt = arm.Ext + + msg.DisplayContent = arm.DisplayContent + case message.MessageTypeAnswer, message.MessageTypeToolAsAnswer: + msg.Role = schema.Assistant + msg.ContentType = message.ContentTypeText + + case message.MessageTypeToolResponse: + msg.Role = schema.Assistant + msg.ContentType = message.ContentTypeText + msg.Content = chunk.ToolsMessage[0].Content + + buildExt[string(msgEntity.MessageExtKeyTimeCost)] = timeCost + modelContent := chunk.ToolsMessage[0] + mc, err := json.Marshal(modelContent) + if err == nil { + msg.ModelContent = string(mc) + } + + case message.MessageTypeKnowledge: + msg.Role = schema.Assistant + msg.ContentType = message.ContentTypeText + + knowledgeContent := buildKnowledge(ctx, chunk) + if knowledgeContent != nil { + knInfo, err := json.Marshal(knowledgeContent) + if err == nil { + msg.Content = string(knInfo) + } + } + + buildExt[string(msgEntity.MessageExtKeyTimeCost)] = timeCost + + modelContent := chunk.Knowledge + mc, err := json.Marshal(modelContent) + if err == nil { + msg.ModelContent = string(mc) + } + + case message.MessageTypeFunctionCall: + msg.Role = schema.Assistant + msg.ContentType = message.ContentTypeText + + if len(chunk.FuncCall.ToolCalls) > 0 { + toolCall := chunk.FuncCall.ToolCalls[0] + toolCalling, err := json.Marshal(toolCall) + if err == nil { + msg.Content = string(toolCalling) + } + buildExt[string(msgEntity.MessageExtKeyPlugin)] = toolCall.Function.Name + buildExt[string(msgEntity.MessageExtKeyToolName)] = toolCall.Function.Name + buildExt[string(msgEntity.MessageExtKeyTimeCost)] = timeCost + + modelContent := chunk.FuncCall + mc, err := json.Marshal(modelContent) + if err == nil { + msg.ModelContent = string(mc) + } + } + case message.MessageTypeFlowUp: + msg.Role = schema.Assistant + msg.ContentType = message.ContentTypeText + msg.Content = chunk.Suggest.Content + + case message.MessageTypeVerbose: + msg.Role = schema.Assistant + msg.ContentType = message.ContentTypeText + + d := &entity.Data{ + FinishReason: 0, + FinData: "", + } + dByte, _ := json.Marshal(d) + afc := &entity.AnswerFinshContent{ + MsgType: entity.MessageSubTypeGenerateFinish, + Data: string(dByte), + } + afcMarshal, _ := json.Marshal(afc) + msg.Content = string(afcMarshal) + case message.MessageTypeInterrupt: + msg.Role = schema.Assistant + msg.MessageType = message.MessageTypeVerbose + msg.ContentType = message.ContentTypeText + + afc := &entity.AnswerFinshContent{ + MsgType: entity.MessageSubTypeInterrupt, + Data: "", + } + afcMarshal, _ := json.Marshal(afc) + msg.Content = string(afcMarshal) + + // Add ext to save to context_message + interruptByte, err := json.Marshal(chunk.Interrupt) + if err == nil { + buildExt[string(msgEntity.ExtKeyResumeInfo)] = string(interruptByte) + } + buildExt[string(msgEntity.ExtKeyToolCallsIDs)] = chunk.Interrupt.ToolCallID + rc := &messageModel.RequiredAction{ + Type: "submit_tool_outputs", + SubmitToolOutputs: &messageModel.SubmitToolOutputs{}, + } + msg.RequiredAction = rc + rcExtByte, err := json.Marshal(rc) + if err == nil { + buildExt[string(msgEntity.ExtKeyRequiresAction)] = string(rcExtByte) + } + } + + if messageType != message.MessageTypeQuestion { + botStateExt := buildBotStateExt(arm) + bseString, err := json.Marshal(botStateExt) + if err == nil { + buildExt[string(msgEntity.MessageExtKeyBotState)] = string(bseString) + } + } + msg.Ext = buildExt + return msg +} + +func handlerWfInterruptEvent(_ context.Context, interruptEventData *crossworkflow.InterruptEvent) (string, message.ContentType, error) { + defaultContentType := message.ContentTypeText + switch singleagent.InterruptEventType(interruptEventData.EventType) { + case singleagent.InterruptEventType_OauthPlugin: + + case singleagent.InterruptEventType_Question: + data := interruptEventData.InterruptData + return processQuestionInterruptData(data) + case singleagent.InterruptEventType_InputNode: + data := interruptEventData.InterruptData + return processInputNodeInterruptData(data) + case singleagent.InterruptEventType_WorkflowLLM: + data := interruptEventData.ToolInterruptEvent.InterruptData + if singleagent.InterruptEventType(interruptEventData.EventType) == singleagent.InterruptEventType_InputNode { + return processInputNodeInterruptData(data) + } + if singleagent.InterruptEventType(interruptEventData.EventType) == singleagent.InterruptEventType_Question { + return processQuestionInterruptData(data) + } + return "", defaultContentType, errorx.New(errno.ErrUnknowInterruptType) + } + return "", defaultContentType, errorx.New(errno.ErrUnknowInterruptType) +} + +func historyPairs(historyMsg []*message.Message) []*message.Message { + + fcMsgPairs := make(map[int64][]*message.Message) + for _, one := range historyMsg { + if one.MessageType != message.MessageTypeFunctionCall && one.MessageType != message.MessageTypeToolResponse { + continue + } + if _, ok := fcMsgPairs[one.RunID]; !ok { + fcMsgPairs[one.RunID] = []*message.Message{one} + } else { + fcMsgPairs[one.RunID] = append(fcMsgPairs[one.RunID], one) + } + } + + var historyAfterPairs []*message.Message + for _, value := range historyMsg { + if value.MessageType == message.MessageTypeFunctionCall { + if len(fcMsgPairs[value.RunID])%2 == 0 { + historyAfterPairs = append(historyAfterPairs, value) + } + } else { + historyAfterPairs = append(historyAfterPairs, value) + } + } + return historyAfterPairs + +} + +func transMessageToSchemaMessage(ctx context.Context, msgs []*message.Message, imagexClient imagex.ImageX) []*schema.Message { + schemaMessage := make([]*schema.Message, 0, len(msgs)) + + for _, msgOne := range msgs { + if msgOne.ModelContent == "" { + continue + } + if msgOne.MessageType == message.MessageTypeVerbose || msgOne.MessageType == message.MessageTypeFlowUp { + continue + } + var sm *schema.Message + err := json.Unmarshal([]byte(msgOne.ModelContent), &sm) + if err != nil { + continue + } + if len(sm.ReasoningContent) > 0 { + sm.ReasoningContent = "" + } + schemaMessage = append(schemaMessage, parseMessageURI(ctx, sm, imagexClient)) + } + + return schemaMessage +} + +func parseMessageURI(ctx context.Context, mcMsg *schema.Message, imagexClient imagex.ImageX) *schema.Message { + if mcMsg.MultiContent == nil { + return mcMsg + } + for k, one := range mcMsg.MultiContent { + switch one.Type { + case schema.ChatMessagePartTypeImageURL: + + if one.ImageURL.URI != "" { + url, err := imagexClient.GetResourceURL(ctx, one.ImageURL.URI) + if err == nil { + mcMsg.MultiContent[k].ImageURL.URL = url.URL + } + } + case schema.ChatMessagePartTypeFileURL: + if one.FileURL.URI != "" { + url, err := imagexClient.GetResourceURL(ctx, one.FileURL.URI) + if err == nil { + mcMsg.MultiContent[k].FileURL.URL = url.URL + } + } + case schema.ChatMessagePartTypeAudioURL: + if one.AudioURL.URI != "" { + url, err := imagexClient.GetResourceURL(ctx, one.AudioURL.URI) + if err == nil { + mcMsg.MultiContent[k].AudioURL.URL = url.URL + } + } + case schema.ChatMessagePartTypeVideoURL: + if one.VideoURL.URI != "" { + url, err := imagexClient.GetResourceURL(ctx, one.VideoURL.URI) + if err == nil { + mcMsg.MultiContent[k].VideoURL.URL = url.URL + } + } + } + } + return mcMsg +} + +func parseResumeInfo(_ context.Context, historyMsg []*message.Message) *crossagent.ResumeInfo { + + var resumeInfo *crossagent.ResumeInfo + for i := len(historyMsg) - 1; i >= 0; i-- { + if historyMsg[i].MessageType == message.MessageTypeQuestion { + break + } + if historyMsg[i].MessageType == message.MessageTypeVerbose { + if historyMsg[i].Ext[string(msgEntity.ExtKeyResumeInfo)] != "" { + err := json.Unmarshal([]byte(historyMsg[i].Ext[string(msgEntity.ExtKeyResumeInfo)]), &resumeInfo) + if err != nil { + return nil + } + } + } + } + return resumeInfo +} + +func buildSendRunRecord(_ context.Context, runRecord *entity.RunRecordMeta, runStatus entity.RunStatus) *entity.ChunkRunItem { + return &entity.ChunkRunItem{ + ID: runRecord.ID, + ConversationID: runRecord.ConversationID, + AgentID: runRecord.AgentID, + SectionID: runRecord.SectionID, + Status: runStatus, + CreatedAt: runRecord.CreatedAt, + } +} diff --git a/backend/domain/conversation/agentrun/internal/message_event.go b/backend/domain/conversation/agentrun/internal/message_event.go new file mode 100644 index 00000000..7a7f9528 --- /dev/null +++ b/backend/domain/conversation/agentrun/internal/message_event.go @@ -0,0 +1,428 @@ +/* + * 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 internal + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "strconv" + "strings" + "time" + + "github.com/cloudwego/eino/schema" + "github.com/mohae/deepcopy" + + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" + + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/agentrun" + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent" + crossworkflow "github.com/coze-dev/coze-studio/backend/crossdomain/contract/workflow" + "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" + msgEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity" + "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/types/consts" + "github.com/coze-dev/coze-studio/backend/types/errno" +) + +type Event struct { +} + +func NewMessageEvent() *Event { + return &Event{} +} + +func (e *Event) buildMessageEvent(runEvent entity.RunEvent, chunkMsgItem *entity.ChunkMessageItem) *entity.AgentRunResponse { + return &entity.AgentRunResponse{ + Event: runEvent, + ChunkMessageItem: chunkMsgItem, + } +} + +func (e *Event) buildRunEvent(runEvent entity.RunEvent, chunkRunItem *entity.ChunkRunItem) *entity.AgentRunResponse { + return &entity.AgentRunResponse{ + Event: runEvent, + ChunkRunItem: chunkRunItem, + } +} + +func (e *Event) buildErrEvent(runEvent entity.RunEvent, err *entity.RunError) *entity.AgentRunResponse { + return &entity.AgentRunResponse{ + Event: runEvent, + Error: err, + } +} + +func (e *Event) buildStreamDoneEvent() *entity.AgentRunResponse { + + return &entity.AgentRunResponse{ + Event: entity.RunEventStreamDone, + } +} + +func (e *Event) SendRunEvent(runEvent entity.RunEvent, runItem *entity.ChunkRunItem, sw *schema.StreamWriter[*entity.AgentRunResponse]) { + resp := e.buildRunEvent(runEvent, runItem) + sw.Send(resp, nil) +} + +func (e *Event) SendMsgEvent(runEvent entity.RunEvent, messageItem *entity.ChunkMessageItem, sw *schema.StreamWriter[*entity.AgentRunResponse]) { + resp := e.buildMessageEvent(runEvent, messageItem) + sw.Send(resp, nil) +} + +func (e *Event) SendErrEvent(runEvent entity.RunEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], err *entity.RunError) { + resp := e.buildErrEvent(runEvent, err) + sw.Send(resp, nil) +} + +func (e *Event) SendStreamDoneEvent(sw *schema.StreamWriter[*entity.AgentRunResponse]) { + resp := e.buildStreamDoneEvent() + sw.Send(resp, nil) +} + +type MesssageEventHanlder struct { + messageEvent *Event + sw *schema.StreamWriter[*entity.AgentRunResponse] +} + +func (mh *MesssageEventHanlder) handlerErr(_ context.Context, err error) { + + var errMsg string + var statusErr errorx.StatusError + if errors.As(err, &statusErr) { + errMsg = statusErr.Msg() + } else { + if strings.ToLower(os.Getenv(consts.RunMode)) != "debug" { + errMsg = "Internal Server Error" + } else { + errMsg = errorx.ErrorWithoutStack(err) + } + } + + mh.messageEvent.SendErrEvent(entity.RunEventError, mh.sw, &entity.RunError{ + Code: errno.ErrAgentRun, + Msg: errMsg, + }) +} + +func (mh *MesssageEventHanlder) handlerAckMessage(_ context.Context, input *msgEntity.Message) error { + sendMsg := &entity.ChunkMessageItem{ + ID: input.ID, + ConversationID: input.ConversationID, + SectionID: input.SectionID, + AgentID: input.AgentID, + Role: entity.RoleType(input.Role), + MessageType: message.MessageTypeAck, + ReplyID: input.ID, + Content: input.Content, + ContentType: message.ContentTypeText, + IsFinish: true, + } + + mh.messageEvent.SendMsgEvent(entity.RunEventAck, sendMsg, mh.sw) + + return nil +} + +func (mh *MesssageEventHanlder) handlerFunctionCall(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error { + cm := buildAgentMessage2Create(ctx, chunk, message.MessageTypeFunctionCall, rtDependence) + + cmData, err := crossmessage.DefaultSVC().Create(ctx, cm) + if err != nil { + return err + } + + sendMsg := buildSendMsg(ctx, cmData, true, rtDependence) + + mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, mh.sw) + return nil +} + +func (mh *MesssageEventHanlder) handlerTooResponse(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime, preToolResponseMsg *msgEntity.Message, toolResponseMsgContent string) error { + + cm := buildAgentMessage2Create(ctx, chunk, message.MessageTypeToolResponse, rtDependence) + + var cmData *message.Message + var err error + + if preToolResponseMsg != nil { + cm.ID = preToolResponseMsg.ID + cm.CreatedAt = preToolResponseMsg.CreatedAt + cm.UpdatedAt = preToolResponseMsg.UpdatedAt + if len(toolResponseMsgContent) > 0 { + cm.Content = toolResponseMsgContent + "\n" + cm.Content + } + } + + cmData, err = crossmessage.DefaultSVC().Create(ctx, cm) + if err != nil { + return err + } + + sendMsg := buildSendMsg(ctx, cmData, true, rtDependence) + + mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, mh.sw) + + return nil +} + +func (mh *MesssageEventHanlder) handlerSuggest(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error { + cm := buildAgentMessage2Create(ctx, chunk, message.MessageTypeFlowUp, rtDependence) + + cmData, err := crossmessage.DefaultSVC().Create(ctx, cm) + if err != nil { + return err + } + + sendMsg := buildSendMsg(ctx, cmData, true, rtDependence) + + mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, mh.sw) + + return nil +} + +func (mh *MesssageEventHanlder) handlerKnowledge(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error { + cm := buildAgentMessage2Create(ctx, chunk, message.MessageTypeKnowledge, rtDependence) + cmData, err := crossmessage.DefaultSVC().Create(ctx, cm) + if err != nil { + return err + } + + sendMsg := buildSendMsg(ctx, cmData, true, rtDependence) + + mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, mh.sw) + return nil +} + +func (mh *MesssageEventHanlder) handlerAnswer(ctx context.Context, msg *entity.ChunkMessageItem, usage *msgEntity.UsageExt, rtDependence *AgentRuntime, preAnswerMsg *msgEntity.Message) error { + + if len(msg.Content) == 0 && len(ptr.From(msg.ReasoningContent)) == 0 { + return nil + } + + msg.IsFinish = true + + if msg.Ext == nil { + msg.Ext = map[string]string{} + } + if usage != nil { + msg.Ext[string(msgEntity.MessageExtKeyToken)] = strconv.FormatInt(usage.TotalCount, 10) + msg.Ext[string(msgEntity.MessageExtKeyInputTokens)] = strconv.FormatInt(usage.InputTokens, 10) + msg.Ext[string(msgEntity.MessageExtKeyOutputTokens)] = strconv.FormatInt(usage.OutputTokens, 10) + + rtDependence.Usage = &agentrun.Usage{ + LlmPromptTokens: usage.InputTokens, + LlmCompletionTokens: usage.OutputTokens, + LlmTotalTokens: usage.TotalCount, + } + } + + if _, ok := msg.Ext[string(msgEntity.MessageExtKeyTimeCost)]; !ok { + msg.Ext[string(msgEntity.MessageExtKeyTimeCost)] = fmt.Sprintf("%.1f", float64(time.Since(rtDependence.GetStartTime()).Milliseconds())/1000.00) + } + + buildModelContent := &schema.Message{ + Role: schema.Assistant, + Content: msg.Content, + } + + mc, err := json.Marshal(buildModelContent) + if err != nil { + return err + } + preAnswerMsg.Content = msg.Content + preAnswerMsg.ReasoningContent = ptr.From(msg.ReasoningContent) + preAnswerMsg.Ext = msg.Ext + preAnswerMsg.ContentType = msg.ContentType + preAnswerMsg.ModelContent = string(mc) + preAnswerMsg.CreatedAt = 0 + preAnswerMsg.UpdatedAt = 0 + + _, err = crossmessage.DefaultSVC().Create(ctx, preAnswerMsg) + if err != nil { + return err + } + mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, msg, mh.sw) + + return nil +} + +func (mh *MesssageEventHanlder) handlerFinalAnswerFinish(ctx context.Context, rtDependence *AgentRuntime) error { + cm := buildAgentMessage2Create(ctx, nil, message.MessageTypeVerbose, rtDependence) + cmData, err := crossmessage.DefaultSVC().Create(ctx, cm) + if err != nil { + return err + } + + sendMsg := buildSendMsg(ctx, cmData, true, rtDependence) + + mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, mh.sw) + return nil +} + +func (mh *MesssageEventHanlder) handlerInterruptVerbose(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime) error { + cm := buildAgentMessage2Create(ctx, chunk, message.MessageTypeInterrupt, rtDependence) + cmData, err := crossmessage.DefaultSVC().Create(ctx, cm) + if err != nil { + return err + } + + sendMsg := buildSendMsg(ctx, cmData, true, rtDependence) + + mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, mh.sw) + return nil +} + +func (mh *MesssageEventHanlder) handlerWfUsage(ctx context.Context, msg *entity.ChunkMessageItem, usage *msgEntity.UsageExt) error { + + if msg.Ext == nil { + msg.Ext = map[string]string{} + } + if usage != nil { + msg.Ext[string(msgEntity.MessageExtKeyToken)] = strconv.FormatInt(usage.TotalCount, 10) + msg.Ext[string(msgEntity.MessageExtKeyInputTokens)] = strconv.FormatInt(usage.InputTokens, 10) + msg.Ext[string(msgEntity.MessageExtKeyOutputTokens)] = strconv.FormatInt(usage.OutputTokens, 10) + } + + _, err := crossmessage.DefaultSVC().Edit(ctx, &msgEntity.Message{ + ID: msg.ID, + Ext: msg.Ext, + }) + if err != nil { + return err + } + + mh.messageEvent.SendMsgEvent(entity.RunEventMessageCompleted, msg, mh.sw) + return nil +} + +func (mh *MesssageEventHanlder) handlerInterrupt(ctx context.Context, chunk *entity.AgentRespEvent, rtDependence *AgentRuntime, firstAnswerMsg *msgEntity.Message, reasoningContent string) error { + interruptData, cType, err := parseInterruptData(ctx, chunk.Interrupt) + if err != nil { + return err + } + preMsg, err := preCreateAnswer(ctx, rtDependence) + if err != nil { + return err + } + deltaAnswer := &entity.ChunkMessageItem{ + ID: preMsg.ID, + ConversationID: preMsg.ConversationID, + SectionID: preMsg.SectionID, + RunID: preMsg.RunID, + AgentID: preMsg.AgentID, + Role: entity.RoleType(preMsg.Role), + Content: interruptData, + MessageType: preMsg.MessageType, + ContentType: cType, + ReplyID: preMsg.RunID, + Ext: preMsg.Ext, + IsFinish: false, + } + + mh.messageEvent.SendMsgEvent(entity.RunEventMessageDelta, deltaAnswer, mh.sw) + finalAnswer := deepcopy.Copy(deltaAnswer).(*entity.ChunkMessageItem) + if len(reasoningContent) > 0 && firstAnswerMsg == nil { + finalAnswer.ReasoningContent = ptr.Of(reasoningContent) + } + usage := func() *msgEntity.UsageExt { + if rtDependence.GetUsage() != nil { + return &msgEntity.UsageExt{ + TotalCount: rtDependence.GetUsage().LlmTotalTokens, + InputTokens: rtDependence.GetUsage().LlmPromptTokens, + OutputTokens: rtDependence.GetUsage().LlmCompletionTokens, + } + } + return nil + } + + err = mh.handlerAnswer(ctx, finalAnswer, usage(), rtDependence, preMsg) + if err != nil { + return err + } + + err = mh.handlerInterruptVerbose(ctx, chunk, rtDependence) + if err != nil { + return err + } + return nil +} + +func (mh *MesssageEventHanlder) handlerWfInterruptMsg(ctx context.Context, stateMsg *crossworkflow.StateMessage, rtDependence *AgentRuntime) { + interruptData, cType, err := handlerWfInterruptEvent(ctx, stateMsg.InterruptEvent) + if err != nil { + return + } + preMsg, err := preCreateAnswer(ctx, rtDependence) + if err != nil { + return + } + deltaAnswer := &entity.ChunkMessageItem{ + ID: preMsg.ID, + ConversationID: preMsg.ConversationID, + SectionID: preMsg.SectionID, + RunID: preMsg.RunID, + AgentID: preMsg.AgentID, + Role: entity.RoleType(preMsg.Role), + Content: interruptData, + MessageType: preMsg.MessageType, + ContentType: cType, + ReplyID: preMsg.RunID, + Ext: preMsg.Ext, + IsFinish: false, + } + + mh.messageEvent.SendMsgEvent(entity.RunEventMessageDelta, deltaAnswer, mh.sw) + finalAnswer := deepcopy.Copy(deltaAnswer).(*entity.ChunkMessageItem) + + err = mh.handlerAnswer(ctx, finalAnswer, nil, rtDependence, preMsg) + if err != nil { + return + } + + err = mh.handlerInterruptVerbose(ctx, &entity.AgentRespEvent{ + EventType: message.MessageTypeInterrupt, + Interrupt: &singleagent.InterruptInfo{ + + InterruptType: singleagent.InterruptEventType(stateMsg.InterruptEvent.EventType), + InterruptID: strconv.FormatInt(stateMsg.InterruptEvent.ID, 10), + ChatflowInterrupt: stateMsg, + }, + }, rtDependence) + if err != nil { + return + } +} + +func (mh *MesssageEventHanlder) HandlerInput(ctx context.Context, rtDependence *AgentRuntime) (*msgEntity.Message, error) { + msgMeta := buildAgentMessage2Create(ctx, nil, message.MessageTypeQuestion, rtDependence) + + cm, err := crossmessage.DefaultSVC().Create(ctx, msgMeta) + if err != nil { + return nil, err + } + + ackErr := mh.handlerAckMessage(ctx, cm) + if ackErr != nil { + return msgMeta, ackErr + } + return cm, nil +} diff --git a/backend/domain/conversation/agentrun/internal/run.go b/backend/domain/conversation/agentrun/internal/run.go new file mode 100644 index 00000000..ac27624f --- /dev/null +++ b/backend/domain/conversation/agentrun/internal/run.go @@ -0,0 +1,214 @@ +/* + * 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 internal + +import ( + "context" + "time" + + "github.com/cloudwego/eino/schema" + + "github.com/coze-dev/coze-studio/backend/api/model/app/bot_common" + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/agentrun" + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent" + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" + "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" + "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/repository" + msgEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity" + "github.com/coze-dev/coze-studio/backend/infra/contract/imagex" + "github.com/coze-dev/coze-studio/backend/pkg/logs" + "github.com/coze-dev/coze-studio/backend/types/errno" +) + +type AgentRuntime struct { + RunRecord *entity.RunRecordMeta + AgentInfo *singleagent.SingleAgent + QuestionMsgID int64 + RunMeta *entity.AgentRunMeta + StartTime time.Time + Input *msgEntity.Message + HistoryMsg []*msgEntity.Message + Usage *agentrun.Usage + SW *schema.StreamWriter[*entity.AgentRunResponse] + + RunProcess *RunProcess + RunRecordRepo repository.RunRecordRepo + ImagexClient imagex.ImageX + MessageEvent *Event +} + +func (rd *AgentRuntime) SetRunRecord(runRecord *entity.RunRecordMeta) { + rd.RunRecord = runRecord +} + +func (rd *AgentRuntime) GetRunRecord() *entity.RunRecordMeta { + return rd.RunRecord +} + +func (rd *AgentRuntime) SetUsage(usage *agentrun.Usage) { + rd.Usage = usage +} +func (rd *AgentRuntime) GetUsage() *agentrun.Usage { + return rd.Usage +} + +func (rd *AgentRuntime) SetRunMeta(arm *entity.AgentRunMeta) { + rd.RunMeta = arm +} +func (rd *AgentRuntime) GetRunMeta() *entity.AgentRunMeta { + return rd.RunMeta +} +func (rd *AgentRuntime) SetAgentInfo(agentInfo *singleagent.SingleAgent) { + rd.AgentInfo = agentInfo +} +func (rd *AgentRuntime) GetAgentInfo() *singleagent.SingleAgent { + return rd.AgentInfo +} +func (rd *AgentRuntime) SetQuestionMsgID(msgID int64) { + rd.QuestionMsgID = msgID +} +func (rd *AgentRuntime) GetQuestionMsgID() int64 { + return rd.QuestionMsgID +} +func (rd *AgentRuntime) SetStartTime(t time.Time) { + rd.StartTime = t +} +func (rd *AgentRuntime) GetStartTime() time.Time { + return rd.StartTime +} +func (rd *AgentRuntime) SetInput(input *msgEntity.Message) { + rd.Input = input +} +func (rd *AgentRuntime) GetInput() *msgEntity.Message { + return rd.Input +} + +func (rd *AgentRuntime) SetHistoryMsg(histroyMsg []*msgEntity.Message) { + rd.HistoryMsg = histroyMsg +} + +func (rd *AgentRuntime) GetHistory() []*msgEntity.Message { + return rd.HistoryMsg +} + +func (art *AgentRuntime) Run(ctx context.Context) (err error) { + + agentInfo, err := getAgentInfo(ctx, art.GetRunMeta().AgentID, art.GetRunMeta().IsDraft) + if err != nil { + return + } + + art.SetAgentInfo(agentInfo) + + history, err := art.getHistory(ctx) + if err != nil { + return + } + + runRecord, err := art.createRunRecord(ctx) + + if err != nil { + return + } + + art.SetRunRecord(runRecord) + art.SetHistoryMsg(history) + + defer func() { + srRecord := buildSendRunRecord(ctx, runRecord, entity.RunStatusCompleted) + if err != nil { + srRecord.Error = &entity.RunError{ + Code: errno.ErrConversationAgentRunError, + Msg: err.Error(), + } + art.RunProcess.StepToFailed(ctx, srRecord, art.SW) + return + } + art.RunProcess.StepToComplete(ctx, srRecord, art.SW, art.GetUsage()) + }() + mh := &MesssageEventHanlder{ + messageEvent: art.MessageEvent, + sw: art.SW, + } + input, err := mh.HandlerInput(ctx, art) + if err != nil { + return + } + art.SetInput(input) + + art.SetQuestionMsgID(input.ID) + + if art.GetAgentInfo().BotMode == bot_common.BotMode_WorkflowMode { + err = art.ChatflowRun(ctx, art.ImagexClient) + } else { + err = art.AgentStreamExecute(ctx, art.ImagexClient) + } + return +} + +func (art *AgentRuntime) getHistory(ctx context.Context) ([]*msgEntity.Message, error) { + + conversationTurns := getAgentHistoryRounds(art.GetAgentInfo()) + + runRecordList, err := art.RunRecordRepo.List(ctx, &entity.ListRunRecordMeta{ + ConversationID: art.GetRunMeta().ConversationID, + SectionID: art.GetRunMeta().SectionID, + Limit: conversationTurns, + }) + if err != nil { + return nil, err + } + + if len(runRecordList) == 0 { + return nil, nil + } + runIDS := concactRunID(runRecordList) + history, err := crossmessage.DefaultSVC().GetByRunIDs(ctx, art.GetRunMeta().ConversationID, runIDS) + if err != nil { + return nil, err + } + + return history, nil +} + +func concactRunID(rr []*entity.RunRecordMeta) []int64 { + ids := make([]int64, 0, len(rr)) + for _, c := range rr { + ids = append(ids, c.ID) + } + + return ids +} + +func (art *AgentRuntime) createRunRecord(ctx context.Context) (*entity.RunRecordMeta, error) { + runPoData, err := art.RunRecordRepo.Create(ctx, art.GetRunMeta()) + if err != nil { + logs.CtxErrorf(ctx, "RunRecordRepo.Create error: %v", err) + return nil, err + } + + srRecord := buildSendRunRecord(ctx, runPoData, entity.RunStatusCreated) + + art.RunProcess.StepToCreate(ctx, srRecord, art.SW) + + err = art.RunProcess.StepToInProgress(ctx, srRecord, art.SW) + if err != nil { + logs.CtxErrorf(ctx, "runProcess.StepToInProgress error: %v", err) + return nil, err + } + return runPoData, nil +} diff --git a/backend/domain/conversation/agentrun/internal/run_process.go b/backend/domain/conversation/agentrun/internal/run_process_event.go similarity index 97% rename from backend/domain/conversation/agentrun/internal/run_process.go rename to backend/domain/conversation/agentrun/internal/run_process_event.go index 8c1a7455..d5eb6733 100644 --- a/backend/domain/conversation/agentrun/internal/run_process.go +++ b/backend/domain/conversation/agentrun/internal/run_process_event.go @@ -30,8 +30,8 @@ import ( ) type RunProcess struct { - event *Event - + event *Event + SW *schema.StreamWriter[*entity.AgentRunResponse] RunRecordRepo repository.RunRecordRepo } @@ -115,7 +115,6 @@ func (r *RunProcess) StepToFailed(ctx context.Context, srRecord *entity.ChunkRun Code: srRecord.Error.Code, Msg: srRecord.Error.Msg, }) - return } func (r *RunProcess) StepToDone(sw *schema.StreamWriter[*entity.AgentRunResponse]) { diff --git a/backend/domain/conversation/agentrun/internal/singleagent_run.go b/backend/domain/conversation/agentrun/internal/singleagent_run.go new file mode 100644 index 00000000..147ef050 --- /dev/null +++ b/backend/domain/conversation/agentrun/internal/singleagent_run.go @@ -0,0 +1,450 @@ +/* + * 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 internal + +import ( + "bytes" + "context" + "errors" + "io" + "sync" + + "github.com/cloudwego/eino/schema" + "github.com/mohae/deepcopy" + + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/agentrun" + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent" + crossagent "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agent" + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" + "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" + msgEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity" + "github.com/coze-dev/coze-studio/backend/infra/contract/imagex" + "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/logs" + "github.com/coze-dev/coze-studio/backend/pkg/safego" + "github.com/coze-dev/coze-studio/backend/types/errno" +) + +func (art *AgentRuntime) AgentStreamExecute(ctx context.Context, imagex imagex.ImageX) (err error) { + mainChan := make(chan *entity.AgentRespEvent, 100) + + ar := &crossagent.AgentRuntime{ + AgentVersion: art.GetRunMeta().Version, + SpaceID: art.GetRunMeta().SpaceID, + AgentID: art.GetRunMeta().AgentID, + IsDraft: art.GetRunMeta().IsDraft, + UserID: art.GetRunMeta().UserID, + ConnectorID: art.GetRunMeta().ConnectorID, + PreRetrieveTools: art.GetRunMeta().PreRetrieveTools, + Input: transMessageToSchemaMessage(ctx, []*msgEntity.Message{art.GetInput()}, imagex)[0], + HistoryMsg: transMessageToSchemaMessage(ctx, historyPairs(art.GetHistory()), imagex), + ResumeInfo: parseResumeInfo(ctx, art.GetHistory()), + } + + streamer, err := crossagent.DefaultSVC().StreamExecute(ctx, ar) + if err != nil { + return errors.New(errorx.ErrorWithoutStack(err)) + } + + var wg sync.WaitGroup + wg.Add(2) + safego.Go(ctx, func() { + defer wg.Done() + art.pull(ctx, mainChan, streamer) + }) + + safego.Go(ctx, func() { + defer wg.Done() + art.push(ctx, mainChan) + }) + + wg.Wait() + + return err +} + +func (art *AgentRuntime) push(ctx context.Context, mainChan chan *entity.AgentRespEvent) { + + mh := &MesssageEventHanlder{ + sw: art.SW, + messageEvent: art.MessageEvent, + } + + var err error + defer func() { + if err != nil { + logs.CtxErrorf(ctx, "run.push error: %v", err) + mh.handlerErr(ctx, err) + } + }() + + reasoningContent := bytes.NewBuffer([]byte{}) + + var firstAnswerMsg *msgEntity.Message + var reasoningMsg *msgEntity.Message + isSendFinishAnswer := false + var preToolResponseMsg *msgEntity.Message + toolResponseMsgContent := bytes.NewBuffer([]byte{}) + for { + chunk, ok := <-mainChan + if !ok || chunk == nil { + return + } + + if chunk.Err != nil { + if errors.Is(chunk.Err, io.EOF) { + if !isSendFinishAnswer { + isSendFinishAnswer = true + if firstAnswerMsg != nil && len(reasoningContent.String()) > 0 { + art.saveReasoningContent(ctx, firstAnswerMsg, reasoningContent.String()) + reasoningContent.Reset() + } + + finishErr := mh.handlerFinalAnswerFinish(ctx, art) + if finishErr != nil { + err = finishErr + return + } + } + return + } + mh.handlerErr(ctx, chunk.Err) + return + } + + switch chunk.EventType { + case message.MessageTypeFunctionCall: + if chunk.FuncCall != nil && chunk.FuncCall.ResponseMeta != nil { + if usage := handlerUsage(chunk.FuncCall.ResponseMeta); usage != nil { + art.SetUsage(&agentrun.Usage{ + LlmPromptTokens: usage.InputTokens, + LlmCompletionTokens: usage.OutputTokens, + LlmTotalTokens: usage.TotalCount, + }) + } + } + err = mh.handlerFunctionCall(ctx, chunk, art) + if err != nil { + return + } + + if preToolResponseMsg == nil { + var cErr error + preToolResponseMsg, cErr = preCreateAnswer(ctx, art) + if cErr != nil { + err = cErr + return + } + } + case message.MessageTypeToolResponse: + err = mh.handlerTooResponse(ctx, chunk, art, preToolResponseMsg, toolResponseMsgContent.String()) + if err != nil { + return + } + preToolResponseMsg = nil // reset + case message.MessageTypeKnowledge: + err = mh.handlerKnowledge(ctx, chunk, art) + if err != nil { + return + } + case message.MessageTypeToolMidAnswer: + fullMidAnswerContent := bytes.NewBuffer([]byte{}) + var usage *msgEntity.UsageExt + toolMidAnswerMsg, cErr := preCreateAnswer(ctx, art) + + if cErr != nil { + err = cErr + return + } + + var preMsgIsFinish = false + for { + streamMsg, receErr := chunk.ToolMidAnswer.Recv() + if receErr != nil { + if errors.Is(receErr, io.EOF) { + break + } + err = receErr + return + } + if preMsgIsFinish { + toolMidAnswerMsg, cErr = preCreateAnswer(ctx, art) + if cErr != nil { + err = cErr + return + } + preMsgIsFinish = false + } + if streamMsg == nil { + continue + } + if firstAnswerMsg == nil && len(streamMsg.Content) > 0 { + if reasoningMsg != nil { + toolMidAnswerMsg = deepcopy.Copy(reasoningMsg).(*msgEntity.Message) + } + firstAnswerMsg = deepcopy.Copy(toolMidAnswerMsg).(*msgEntity.Message) + } + + if streamMsg.Extra != nil { + if val, ok := streamMsg.Extra["workflow_node_name"]; ok && val != nil { + toolMidAnswerMsg.Ext["message_title"] = val.(string) + } + } + + sendMidAnswerMsg := buildSendMsg(ctx, toolMidAnswerMsg, false, art) + sendMidAnswerMsg.Content = streamMsg.Content + toolResponseMsgContent.WriteString(streamMsg.Content) + fullMidAnswerContent.WriteString(streamMsg.Content) + + art.MessageEvent.SendMsgEvent(entity.RunEventMessageDelta, sendMidAnswerMsg, art.SW) + + if streamMsg != nil && streamMsg.ResponseMeta != nil { + usage = handlerUsage(streamMsg.ResponseMeta) + } + + if streamMsg.Extra["is_finish"] == true { + preMsgIsFinish = true + sendMidAnswerMsg := buildSendMsg(ctx, toolMidAnswerMsg, false, art) + sendMidAnswerMsg.Content = fullMidAnswerContent.String() + fullMidAnswerContent.Reset() + hfErr := mh.handlerAnswer(ctx, sendMidAnswerMsg, usage, art, toolMidAnswerMsg) + if hfErr != nil { + err = hfErr + return + } + } + } + + case message.MessageTypeToolAsAnswer: + var usage *msgEntity.UsageExt + fullContent := bytes.NewBuffer([]byte{}) + toolAsAnswerMsg, cErr := preCreateAnswer(ctx, art) + if cErr != nil { + err = cErr + return + } + if firstAnswerMsg == nil { + firstAnswerMsg = toolAsAnswerMsg + } + + for { + streamMsg, receErr := chunk.ToolAsAnswer.Recv() + if receErr != nil { + if errors.Is(receErr, io.EOF) { + + answer := buildSendMsg(ctx, toolAsAnswerMsg, false, art) + answer.Content = fullContent.String() + hfErr := mh.handlerAnswer(ctx, answer, usage, art, toolAsAnswerMsg) + if hfErr != nil { + err = hfErr + return + } + break + } + err = receErr + return + } + + if streamMsg != nil && streamMsg.ResponseMeta != nil { + usage = handlerUsage(streamMsg.ResponseMeta) + } + sendMsg := buildSendMsg(ctx, toolAsAnswerMsg, false, art) + fullContent.WriteString(streamMsg.Content) + sendMsg.Content = streamMsg.Content + art.MessageEvent.SendMsgEvent(entity.RunEventMessageDelta, sendMsg, art.SW) + } + + case message.MessageTypeAnswer: + fullContent := bytes.NewBuffer([]byte{}) + var usage *msgEntity.UsageExt + var isToolCalls = false + var modelAnswerMsg *msgEntity.Message + for { + streamMsg, receErr := chunk.ModelAnswer.Recv() + if receErr != nil { + if errors.Is(receErr, io.EOF) { + + if isToolCalls { + break + } + if modelAnswerMsg == nil { + break + } + answer := buildSendMsg(ctx, modelAnswerMsg, false, art) + answer.Content = fullContent.String() + hfErr := mh.handlerAnswer(ctx, answer, usage, art, modelAnswerMsg) + if hfErr != nil { + err = hfErr + return + } + break + } + err = receErr + return + } + + if streamMsg != nil && len(streamMsg.ToolCalls) > 0 { + isToolCalls = true + } + + if streamMsg != nil && streamMsg.ResponseMeta != nil { + usage = handlerUsage(streamMsg.ResponseMeta) + } + + if streamMsg != nil && len(streamMsg.ReasoningContent) == 0 && len(streamMsg.Content) == 0 { + continue + } + + if len(streamMsg.ReasoningContent) > 0 { + if reasoningMsg == nil { + reasoningMsg, err = preCreateAnswer(ctx, art) + if err != nil { + return + } + } + + sendReasoningMsg := buildSendMsg(ctx, reasoningMsg, false, art) + reasoningContent.WriteString(streamMsg.ReasoningContent) + sendReasoningMsg.ReasoningContent = ptr.Of(streamMsg.ReasoningContent) + art.MessageEvent.SendMsgEvent(entity.RunEventMessageDelta, sendReasoningMsg, art.SW) + } + if len(streamMsg.Content) > 0 { + + if modelAnswerMsg == nil { + modelAnswerMsg, err = preCreateAnswer(ctx, art) + if err != nil { + return + } + if firstAnswerMsg == nil { + if reasoningMsg != nil { + modelAnswerMsg.ID = reasoningMsg.ID + } + firstAnswerMsg = modelAnswerMsg + } + } + + sendAnswerMsg := buildSendMsg(ctx, modelAnswerMsg, false, art) + fullContent.WriteString(streamMsg.Content) + sendAnswerMsg.Content = streamMsg.Content + art.MessageEvent.SendMsgEvent(entity.RunEventMessageDelta, sendAnswerMsg, art.SW) + } + } + + case message.MessageTypeFlowUp: + if isSendFinishAnswer { + + if firstAnswerMsg != nil && len(reasoningContent.String()) > 0 { + art.saveReasoningContent(ctx, firstAnswerMsg, reasoningContent.String()) + } + + isSendFinishAnswer = true + finishErr := mh.handlerFinalAnswerFinish(ctx, art) + if finishErr != nil { + err = finishErr + return + } + } + + err = mh.handlerSuggest(ctx, chunk, art) + if err != nil { + return + } + + case message.MessageTypeInterrupt: + err = mh.handlerInterrupt(ctx, chunk, art, firstAnswerMsg, reasoningContent.String()) + if err != nil { + return + } + } + } +} + +func (art *AgentRuntime) pull(_ context.Context, mainChan chan *entity.AgentRespEvent, events *schema.StreamReader[*crossagent.AgentEvent]) { + defer func() { + close(mainChan) + }() + + for { + rm, re := events.Recv() + if re != nil { + errChunk := &entity.AgentRespEvent{ + Err: re, + } + mainChan <- errChunk + return + } + + eventType, tErr := transformEventMap(rm.EventType) + + if tErr != nil { + errChunk := &entity.AgentRespEvent{ + Err: tErr, + } + mainChan <- errChunk + return + } + + respChunk := &entity.AgentRespEvent{ + EventType: eventType, + ModelAnswer: rm.ChatModelAnswer, + ToolsMessage: rm.ToolsMessage, + FuncCall: rm.FuncCall, + Knowledge: rm.Knowledge, + Suggest: rm.Suggest, + Interrupt: rm.Interrupt, + + ToolMidAnswer: rm.ToolMidAnswer, + ToolAsAnswer: rm.ToolAsChatModelAnswer, + } + + mainChan <- respChunk + } +} + +func transformEventMap(eventType singleagent.EventType) (message.MessageType, error) { + var eType message.MessageType + switch eventType { + case singleagent.EventTypeOfFuncCall: + return message.MessageTypeFunctionCall, nil + case singleagent.EventTypeOfKnowledge: + return message.MessageTypeKnowledge, nil + case singleagent.EventTypeOfToolsMessage: + return message.MessageTypeToolResponse, nil + case singleagent.EventTypeOfChatModelAnswer: + return message.MessageTypeAnswer, nil + case singleagent.EventTypeOfToolsAsChatModelStream: + return message.MessageTypeToolAsAnswer, nil + case singleagent.EventTypeOfToolMidAnswer: + return message.MessageTypeToolMidAnswer, nil + case singleagent.EventTypeOfSuggest: + return message.MessageTypeFlowUp, nil + case singleagent.EventTypeOfInterrupt: + return message.MessageTypeInterrupt, nil + } + return eType, errorx.New(errno.ErrReplyUnknowEventType) +} + +func (art *AgentRuntime) saveReasoningContent(ctx context.Context, firstAnswerMsg *msgEntity.Message, reasoningContent string) { + _, err := crossmessage.DefaultSVC().Edit(ctx, &message.Message{ + ID: firstAnswerMsg.ID, + ReasoningContent: reasoningContent, + }) + if err != nil { + logs.CtxInfof(ctx, "save reasoning content failed, err: %v", err) + } +} diff --git a/backend/domain/conversation/agentrun/repository/repository.go b/backend/domain/conversation/agentrun/repository/repository.go index a9dc4351..7dd39d36 100644 --- a/backend/domain/conversation/agentrun/repository/repository.go +++ b/backend/domain/conversation/agentrun/repository/repository.go @@ -23,7 +23,6 @@ import ( "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal/dal" - "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal/dal/model" "github.com/coze-dev/coze-studio/backend/infra/contract/idgen" ) @@ -34,8 +33,9 @@ func NewRunRecordRepo(db *gorm.DB, idGen idgen.IDGenerator) RunRecordRepo { type RunRecordRepo interface { Create(ctx context.Context, runMeta *entity.AgentRunMeta) (*entity.RunRecordMeta, error) - GetByID(ctx context.Context, id int64) (*entity.RunRecord, error) + GetByID(ctx context.Context, id int64) (*entity.RunRecordMeta, error) + Cancel(ctx context.Context, req *entity.CancelRunMeta) (*entity.RunRecordMeta, error) Delete(ctx context.Context, id []int64) error UpdateByID(ctx context.Context, id int64, update *entity.UpdateMeta) error - List(ctx context.Context, conversationID int64, sectionID int64, limit int32) ([]*model.RunRecord, error) + List(ctx context.Context, meta *entity.ListRunRecordMeta) ([]*entity.RunRecordMeta, error) } diff --git a/backend/domain/conversation/agentrun/service/agent_run.go b/backend/domain/conversation/agentrun/service/agent_run.go index 585243f6..53737fae 100644 --- a/backend/domain/conversation/agentrun/service/agent_run.go +++ b/backend/domain/conversation/agentrun/service/agent_run.go @@ -26,6 +26,9 @@ import ( type Run interface { AgentRun(ctx context.Context, req *entity.AgentRunMeta) (*schema.StreamReader[*entity.AgentRunResponse], error) - Delete(ctx context.Context, runID []int64) error + Create(ctx context.Context, runRecord *entity.AgentRunMeta) (*entity.RunRecordMeta, error) + List(ctx context.Context, ListMeta *entity.ListRunRecordMeta) ([]*entity.RunRecordMeta, error) + GetByID(ctx context.Context, runID int64) (*entity.RunRecordMeta, error) + Cancel(ctx context.Context, req *entity.CancelRunMeta) (*entity.RunRecordMeta, error) } diff --git a/backend/domain/conversation/agentrun/service/agent_run_impl.go b/backend/domain/conversation/agentrun/service/agent_run_impl.go index 32b28f7b..8d12d172 100644 --- a/backend/domain/conversation/agentrun/service/agent_run_impl.go +++ b/backend/domain/conversation/agentrun/service/agent_run_impl.go @@ -17,68 +17,32 @@ package agentrun import ( - "bytes" "context" - "encoding/json" - "errors" - "fmt" - "io" - "os" "runtime/debug" - "strconv" - "strings" - "sync" "time" "github.com/cloudwego/eino/schema" - "github.com/mohae/deepcopy" - messageModel "github.com/coze-dev/coze-studio/backend/api/model/conversation/message" - "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/agentrun" - "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" - "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent" - crossagent "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agent" - crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal" - "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal/dal/model" "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/repository" - msgEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity" - "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/infra/contract/imagex" "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/types/consts" - "github.com/coze-dev/coze-studio/backend/types/errno" ) type runImpl struct { Components - - runProcess *internal.RunProcess - runEvent *internal.Event -} - -type runtimeDependence struct { - runID int64 - agentInfo *singleagent.SingleAgent - questionMsgID int64 - runMeta *entity.AgentRunMeta - startTime time.Time - - usage *agentrun.Usage } type Components struct { RunRecordRepo repository.RunRecordRepo + ImagexSVC imagex.ImageX } func NewService(c *Components) Run { return &runImpl{ Components: *c, - runEvent: internal.NewEvent(), - runProcess: internal.NewRunProcess(c.RunRecordRepo), } } @@ -92,1105 +56,38 @@ func (c *runImpl) AgentRun(ctx context.Context, arm *entity.AgentRunMeta) (*sche } }() - rtDependence := &runtimeDependence{ - runMeta: arm, - startTime: time.Now(), + art := &internal.AgentRuntime{ + StartTime: time.Now(), + RunMeta: arm, + SW: sw, + MessageEvent: internal.NewMessageEvent(), + RunProcess: internal.NewRunProcess(c.RunRecordRepo), + RunRecordRepo: c.RunRecordRepo, + ImagexClient: c.ImagexSVC, } - safego.Go(ctx, func() { defer sw.Close() - _ = c.run(ctx, sw, rtDependence) + _ = art.Run(ctx) }) return sr, nil } -func (c *runImpl) run(ctx context.Context, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) (err error) { - - agentInfo, err := c.handlerAgent(ctx, rtDependence) - if err != nil { - return - } - - rtDependence.agentInfo = agentInfo - - history, err := c.handlerHistory(ctx, rtDependence) - if err != nil { - return - } - - runRecord, err := c.createRunRecord(ctx, sw, rtDependence) - - if err != nil { - return - } - rtDependence.runID = runRecord.ID - defer func() { - srRecord := c.buildSendRunRecord(ctx, runRecord, entity.RunStatusCompleted) - if err != nil { - srRecord.Error = &entity.RunError{ - Code: errno.ErrConversationAgentRunError, - Msg: err.Error(), - } - c.runProcess.StepToFailed(ctx, srRecord, sw) - return - } - c.runProcess.StepToComplete(ctx, srRecord, sw, rtDependence.usage) - }() - - input, err := c.handlerInput(ctx, sw, rtDependence) - if err != nil { - return - } - - rtDependence.questionMsgID = input.ID - - err = c.handlerStreamExecute(ctx, sw, history, input, rtDependence) - return -} - -func (c *runImpl) handlerAgent(ctx context.Context, rtDependence *runtimeDependence) (*singleagent.SingleAgent, error) { - agentInfo, err := crossagent.DefaultSVC().ObtainAgentByIdentity(ctx, &singleagent.AgentIdentity{ - AgentID: rtDependence.runMeta.AgentID, - IsDraft: rtDependence.runMeta.IsDraft, - }) - if err != nil { - return nil, err - } - - return agentInfo, nil -} - -func (c *runImpl) handlerStreamExecute(ctx context.Context, sw *schema.StreamWriter[*entity.AgentRunResponse], historyMsg []*msgEntity.Message, input *msgEntity.Message, rtDependence *runtimeDependence) (err error) { - mainChan := make(chan *entity.AgentRespEvent, 100) - - ar := &singleagent.AgentRuntime{ - AgentVersion: rtDependence.runMeta.Version, - SpaceID: rtDependence.runMeta.SpaceID, - IsDraft: rtDependence.runMeta.IsDraft, - ConnectorID: rtDependence.runMeta.ConnectorID, - PreRetrieveTools: rtDependence.runMeta.PreRetrieveTools, - } - - streamer, err := crossagent.DefaultSVC().StreamExecute(ctx, historyMsg, input, ar) - if err != nil { - return errors.New(errorx.ErrorWithoutStack(err)) - } - - var wg sync.WaitGroup - wg.Add(2) - safego.Go(ctx, func() { - defer wg.Done() - c.pull(ctx, mainChan, streamer) - }) - - safego.Go(ctx, func() { - defer wg.Done() - c.push(ctx, mainChan, sw, rtDependence) - }) - - wg.Wait() - - return err -} - -func transformEventMap(eventType singleagent.EventType) (message.MessageType, error) { - var eType message.MessageType - switch eventType { - case singleagent.EventTypeOfFuncCall: - return message.MessageTypeFunctionCall, nil - case singleagent.EventTypeOfKnowledge: - return message.MessageTypeKnowledge, nil - case singleagent.EventTypeOfToolsMessage: - return message.MessageTypeToolResponse, nil - case singleagent.EventTypeOfChatModelAnswer: - return message.MessageTypeAnswer, nil - case singleagent.EventTypeOfToolsAsChatModelStream: - return message.MessageTypeToolAsAnswer, nil - case singleagent.EventTypeOfToolMidAnswer: - return message.MessageTypeToolMidAnswer, nil - case singleagent.EventTypeOfSuggest: - return message.MessageTypeFlowUp, nil - case singleagent.EventTypeOfInterrupt: - return message.MessageTypeInterrupt, nil - } - return eType, errorx.New(errno.ErrReplyUnknowEventType) -} - -func (c *runImpl) buildAgentMessage2Create(ctx context.Context, chunk *entity.AgentRespEvent, messageType message.MessageType, rtDependence *runtimeDependence) *message.Message { - arm := rtDependence.runMeta - msg := &msgEntity.Message{ - ConversationID: arm.ConversationID, - RunID: rtDependence.runID, - AgentID: arm.AgentID, - SectionID: arm.SectionID, - UserID: arm.UserID, - MessageType: messageType, - } - buildExt := map[string]string{} - - timeCost := fmt.Sprintf("%.1f", float64(time.Since(rtDependence.startTime).Milliseconds())/1000.00) - - switch messageType { - case message.MessageTypeQuestion: - msg.Role = schema.User - msg.ContentType = arm.ContentType - for _, content := range arm.Content { - if content.Type == message.InputTypeText { - msg.Content = content.Text - break - } - } - msg.MultiContent = arm.Content - buildExt = arm.Ext - - msg.DisplayContent = arm.DisplayContent - case message.MessageTypeAnswer, message.MessageTypeToolAsAnswer: - msg.Role = schema.Assistant - msg.ContentType = message.ContentTypeText - - case message.MessageTypeToolResponse: - msg.Role = schema.Assistant - msg.ContentType = message.ContentTypeText - msg.Content = chunk.ToolsMessage[0].Content - - buildExt[string(msgEntity.MessageExtKeyTimeCost)] = timeCost - modelContent := chunk.ToolsMessage[0] - mc, err := json.Marshal(modelContent) - if err == nil { - msg.ModelContent = string(mc) - } - - case message.MessageTypeKnowledge: - msg.Role = schema.Assistant - msg.ContentType = message.ContentTypeText - - knowledgeContent := c.buildKnowledge(ctx, chunk) - if knowledgeContent != nil { - knInfo, err := json.Marshal(knowledgeContent) - if err == nil { - msg.Content = string(knInfo) - } - } - - buildExt[string(msgEntity.MessageExtKeyTimeCost)] = timeCost - - modelContent := chunk.Knowledge - mc, err := json.Marshal(modelContent) - if err == nil { - msg.ModelContent = string(mc) - } - - case message.MessageTypeFunctionCall: - msg.Role = schema.Assistant - msg.ContentType = message.ContentTypeText - - if len(chunk.FuncCall.ToolCalls) > 0 { - toolCall := chunk.FuncCall.ToolCalls[0] - toolCalling, err := json.Marshal(toolCall) - if err == nil { - msg.Content = string(toolCalling) - } - buildExt[string(msgEntity.MessageExtKeyPlugin)] = toolCall.Function.Name - buildExt[string(msgEntity.MessageExtKeyToolName)] = toolCall.Function.Name - buildExt[string(msgEntity.MessageExtKeyTimeCost)] = timeCost - - modelContent := chunk.FuncCall - mc, err := json.Marshal(modelContent) - if err == nil { - msg.ModelContent = string(mc) - } - } - case message.MessageTypeFlowUp: - msg.Role = schema.Assistant - msg.ContentType = message.ContentTypeText - msg.Content = chunk.Suggest.Content - - case message.MessageTypeVerbose: - msg.Role = schema.Assistant - msg.ContentType = message.ContentTypeText - - d := &entity.Data{ - FinishReason: 0, - FinData: "", - } - dByte, _ := json.Marshal(d) - afc := &entity.AnswerFinshContent{ - MsgType: entity.MessageSubTypeGenerateFinish, - Data: string(dByte), - } - afcMarshal, _ := json.Marshal(afc) - msg.Content = string(afcMarshal) - case message.MessageTypeInterrupt: - msg.Role = schema.Assistant - msg.MessageType = message.MessageTypeVerbose - msg.ContentType = message.ContentTypeText - - afc := &entity.AnswerFinshContent{ - MsgType: entity.MessageSubTypeInterrupt, - Data: "", - } - afcMarshal, _ := json.Marshal(afc) - msg.Content = string(afcMarshal) - - // Add ext to save to context_message - interruptByte, err := json.Marshal(chunk.Interrupt) - if err == nil { - buildExt[string(msgEntity.ExtKeyResumeInfo)] = string(interruptByte) - } - buildExt[string(msgEntity.ExtKeyToolCallsIDs)] = chunk.Interrupt.ToolCallID - rc := &messageModel.RequiredAction{ - Type: "submit_tool_outputs", - SubmitToolOutputs: &messageModel.SubmitToolOutputs{}, - } - msg.RequiredAction = rc - rcExtByte, err := json.Marshal(rc) - if err == nil { - buildExt[string(msgEntity.ExtKeyRequiresAction)] = string(rcExtByte) - } - } - - if messageType != message.MessageTypeQuestion { - botStateExt := c.buildBotStateExt(arm) - bseString, err := json.Marshal(botStateExt) - if err == nil { - buildExt[string(msgEntity.MessageExtKeyBotState)] = string(bseString) - } - } - msg.Ext = buildExt - return msg -} - -func (c *runImpl) handlerHistory(ctx context.Context, rtDependence *runtimeDependence) ([]*msgEntity.Message, error) { - - conversationTurns := entity.ConversationTurnsDefault - - if rtDependence.agentInfo != nil && rtDependence.agentInfo.ModelInfo != nil && rtDependence.agentInfo.ModelInfo.ShortMemoryPolicy != nil && ptr.From(rtDependence.agentInfo.ModelInfo.ShortMemoryPolicy.HistoryRound) > 0 { - conversationTurns = ptr.From(rtDependence.agentInfo.ModelInfo.ShortMemoryPolicy.HistoryRound) - } - - runRecordList, err := c.RunRecordRepo.List(ctx, rtDependence.runMeta.ConversationID, rtDependence.runMeta.SectionID, conversationTurns) - if err != nil { - return nil, err - } - - if len(runRecordList) == 0 { - return nil, nil - } - - runIDS := c.getRunID(runRecordList) - - history, err := crossmessage.DefaultSVC().GetByRunIDs(ctx, rtDependence.runMeta.ConversationID, runIDS) - if err != nil { - return nil, err - } - - return history, nil -} - -func (c *runImpl) getRunID(rr []*model.RunRecord) []int64 { - ids := make([]int64, 0, len(rr)) - for _, c := range rr { - ids = append(ids, c.ID) - } - - return ids -} - -func (c *runImpl) createRunRecord(ctx context.Context, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) (*entity.RunRecordMeta, error) { - runPoData, err := c.RunRecordRepo.Create(ctx, rtDependence.runMeta) - if err != nil { - logs.CtxErrorf(ctx, "RunRecordRepo.Create error: %v", err) - return nil, err - } - - srRecord := c.buildSendRunRecord(ctx, runPoData, entity.RunStatusCreated) - - c.runProcess.StepToCreate(ctx, srRecord, sw) - - err = c.runProcess.StepToInProgress(ctx, srRecord, sw) - if err != nil { - logs.CtxErrorf(ctx, "runProcess.StepToInProgress error: %v", err) - return nil, err - } - - return runPoData, nil -} - -func (c *runImpl) handlerInput(ctx context.Context, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) (*msgEntity.Message, error) { - msgMeta := c.buildAgentMessage2Create(ctx, nil, message.MessageTypeQuestion, rtDependence) - - cm, err := crossmessage.DefaultSVC().Create(ctx, msgMeta) - if err != nil { - return nil, err - } - - ackErr := c.handlerAckMessage(ctx, cm, sw) - if ackErr != nil { - return msgMeta, ackErr - } - return cm, nil -} - -func (c *runImpl) pull(_ context.Context, mainChan chan *entity.AgentRespEvent, events *schema.StreamReader[*crossagent.AgentEvent]) { - defer func() { - close(mainChan) - }() - - for { - rm, re := events.Recv() - if re != nil { - errChunk := &entity.AgentRespEvent{ - Err: re, - } - mainChan <- errChunk - return - } - - eventType, tErr := transformEventMap(rm.EventType) - - if tErr != nil { - errChunk := &entity.AgentRespEvent{ - Err: tErr, - } - mainChan <- errChunk - return - } - - respChunk := &entity.AgentRespEvent{ - EventType: eventType, - ModelAnswer: rm.ChatModelAnswer, - ToolsMessage: rm.ToolsMessage, - FuncCall: rm.FuncCall, - Knowledge: rm.Knowledge, - Suggest: rm.Suggest, - Interrupt: rm.Interrupt, - - ToolMidAnswer: rm.ToolMidAnswer, - ToolAsAnswer: rm.ToolAsChatModelAnswer, - } - - mainChan <- respChunk - } -} - -func (c *runImpl) push(ctx context.Context, mainChan chan *entity.AgentRespEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) { - - var err error - defer func() { - if err != nil { - logs.CtxErrorf(ctx, "run.push error: %v", err) - c.handlerErr(ctx, err, sw) - } - }() - - reasoningContent := bytes.NewBuffer([]byte{}) - - var firstAnswerMsg *msgEntity.Message - var reasoningMsg *msgEntity.Message - isSendFinishAnswer := false - var preToolResponseMsg *msgEntity.Message - toolResponseMsgContent := bytes.NewBuffer([]byte{}) - for { - chunk, ok := <-mainChan - if !ok || chunk == nil { - return - } - logs.CtxInfof(ctx, "hanlder event:%v,err:%v", conv.DebugJsonToStr(chunk), chunk.Err) - if chunk.Err != nil { - if errors.Is(chunk.Err, io.EOF) { - if !isSendFinishAnswer { - isSendFinishAnswer = true - if firstAnswerMsg != nil && len(reasoningContent.String()) > 0 { - c.saveReasoningContent(ctx, firstAnswerMsg, reasoningContent.String()) - reasoningContent.Reset() - } - - finishErr := c.handlerFinalAnswerFinish(ctx, sw, rtDependence) - if finishErr != nil { - err = finishErr - return - } - } - return - } - c.handlerErr(ctx, chunk.Err, sw) - return - } - - switch chunk.EventType { - case message.MessageTypeFunctionCall: - err = c.handlerFunctionCall(ctx, chunk, sw, rtDependence) - if err != nil { - return - } - - if preToolResponseMsg == nil { - var cErr error - preToolResponseMsg, cErr = c.PreCreateAnswer(ctx, rtDependence) - if cErr != nil { - err = cErr - return - } - } - case message.MessageTypeToolResponse: - err = c.handlerTooResponse(ctx, chunk, sw, rtDependence, preToolResponseMsg, toolResponseMsgContent.String()) - if err != nil { - return - } - preToolResponseMsg = nil // reset - case message.MessageTypeKnowledge: - err = c.handlerKnowledge(ctx, chunk, sw, rtDependence) - if err != nil { - return - } - case message.MessageTypeToolMidAnswer: - fullMidAnswerContent := bytes.NewBuffer([]byte{}) - var usage *msgEntity.UsageExt - toolMidAnswerMsg, cErr := c.PreCreateAnswer(ctx, rtDependence) - - if cErr != nil { - err = cErr - return - } - - var preMsgIsFinish = false - for { - streamMsg, receErr := chunk.ToolMidAnswer.Recv() - if receErr != nil { - if errors.Is(receErr, io.EOF) { - break - } - err = receErr - return - } - if preMsgIsFinish { - toolMidAnswerMsg, cErr = c.PreCreateAnswer(ctx, rtDependence) - if cErr != nil { - err = cErr - return - } - preMsgIsFinish = false - } - if streamMsg == nil { - continue - } - if firstAnswerMsg == nil && len(streamMsg.Content) > 0 { - if reasoningMsg != nil { - toolMidAnswerMsg = deepcopy.Copy(reasoningMsg).(*msgEntity.Message) - } - firstAnswerMsg = deepcopy.Copy(toolMidAnswerMsg).(*msgEntity.Message) - } - - if streamMsg.Extra != nil { - if val, ok := streamMsg.Extra["workflow_node_name"]; ok && val != nil { - toolMidAnswerMsg.Ext["message_title"] = val.(string) - } - } - - sendMidAnswerMsg := c.buildSendMsg(ctx, toolMidAnswerMsg, false, rtDependence) - sendMidAnswerMsg.Content = streamMsg.Content - toolResponseMsgContent.WriteString(streamMsg.Content) - fullMidAnswerContent.WriteString(streamMsg.Content) - - c.runEvent.SendMsgEvent(entity.RunEventMessageDelta, sendMidAnswerMsg, sw) - - if streamMsg != nil && streamMsg.ResponseMeta != nil { - usage = c.handlerUsage(streamMsg.ResponseMeta) - } - - if streamMsg.Extra["is_finish"] == true { - preMsgIsFinish = true - sendMidAnswerMsg := c.buildSendMsg(ctx, toolMidAnswerMsg, false, rtDependence) - sendMidAnswerMsg.Content = fullMidAnswerContent.String() - fullMidAnswerContent.Reset() - hfErr := c.handlerAnswer(ctx, sendMidAnswerMsg, sw, usage, rtDependence, toolMidAnswerMsg) - if hfErr != nil { - err = hfErr - return - } - } - } - - case message.MessageTypeToolAsAnswer: - var usage *msgEntity.UsageExt - fullContent := bytes.NewBuffer([]byte{}) - toolAsAnswerMsg, cErr := c.PreCreateAnswer(ctx, rtDependence) - if cErr != nil { - err = cErr - return - } - if firstAnswerMsg == nil { - firstAnswerMsg = toolAsAnswerMsg - } - - for { - streamMsg, receErr := chunk.ToolAsAnswer.Recv() - if receErr != nil { - if errors.Is(receErr, io.EOF) { - - answer := c.buildSendMsg(ctx, toolAsAnswerMsg, false, rtDependence) - answer.Content = fullContent.String() - hfErr := c.handlerAnswer(ctx, answer, sw, usage, rtDependence, toolAsAnswerMsg) - if hfErr != nil { - err = hfErr - return - } - break - } - err = receErr - return - } - - if streamMsg != nil && streamMsg.ResponseMeta != nil { - usage = c.handlerUsage(streamMsg.ResponseMeta) - } - sendMsg := c.buildSendMsg(ctx, toolAsAnswerMsg, false, rtDependence) - fullContent.WriteString(streamMsg.Content) - sendMsg.Content = streamMsg.Content - c.runEvent.SendMsgEvent(entity.RunEventMessageDelta, sendMsg, sw) - } - - case message.MessageTypeAnswer: - fullContent := bytes.NewBuffer([]byte{}) - var usage *msgEntity.UsageExt - var isToolCalls = false - var modelAnswerMsg *msgEntity.Message - for { - streamMsg, receErr := chunk.ModelAnswer.Recv() - if receErr != nil { - if errors.Is(receErr, io.EOF) { - - if isToolCalls { - break - } - if modelAnswerMsg == nil { - break - } - answer := c.buildSendMsg(ctx, modelAnswerMsg, false, rtDependence) - answer.Content = fullContent.String() - hfErr := c.handlerAnswer(ctx, answer, sw, usage, rtDependence, modelAnswerMsg) - if hfErr != nil { - err = hfErr - return - } - break - } - err = receErr - return - } - - if streamMsg != nil && len(streamMsg.ToolCalls) > 0 { - isToolCalls = true - } - - if streamMsg != nil && streamMsg.ResponseMeta != nil { - usage = c.handlerUsage(streamMsg.ResponseMeta) - } - - if streamMsg != nil && len(streamMsg.ReasoningContent) == 0 && len(streamMsg.Content) == 0 { - continue - } - - if len(streamMsg.ReasoningContent) > 0 { - if reasoningMsg == nil { - reasoningMsg, err = c.PreCreateAnswer(ctx, rtDependence) - if err != nil { - return - } - } - - sendReasoningMsg := c.buildSendMsg(ctx, reasoningMsg, false, rtDependence) - reasoningContent.WriteString(streamMsg.ReasoningContent) - sendReasoningMsg.ReasoningContent = ptr.Of(streamMsg.ReasoningContent) - c.runEvent.SendMsgEvent(entity.RunEventMessageDelta, sendReasoningMsg, sw) - } - if len(streamMsg.Content) > 0 { - - if modelAnswerMsg == nil { - modelAnswerMsg, err = c.PreCreateAnswer(ctx, rtDependence) - if err != nil { - return - } - if firstAnswerMsg == nil { - if reasoningMsg != nil { - modelAnswerMsg.ID = reasoningMsg.ID - } - firstAnswerMsg = modelAnswerMsg - } - } - - sendAnswerMsg := c.buildSendMsg(ctx, modelAnswerMsg, false, rtDependence) - fullContent.WriteString(streamMsg.Content) - sendAnswerMsg.Content = streamMsg.Content - c.runEvent.SendMsgEvent(entity.RunEventMessageDelta, sendAnswerMsg, sw) - } - } - - case message.MessageTypeFlowUp: - if isSendFinishAnswer { - - if firstAnswerMsg != nil && len(reasoningContent.String()) > 0 { - c.saveReasoningContent(ctx, firstAnswerMsg, reasoningContent.String()) - } - - isSendFinishAnswer = true - finishErr := c.handlerFinalAnswerFinish(ctx, sw, rtDependence) - if finishErr != nil { - err = finishErr - return - } - } - - err = c.handlerSuggest(ctx, chunk, sw, rtDependence) - if err != nil { - return - } - - case message.MessageTypeInterrupt: - err = c.handlerInterrupt(ctx, chunk, sw, rtDependence, firstAnswerMsg, reasoningContent.String()) - if err != nil { - return - } - } - } -} - -func (c *runImpl) saveReasoningContent(ctx context.Context, firstAnswerMsg *msgEntity.Message, reasoningContent string) { - _, err := crossmessage.DefaultSVC().Edit(ctx, &message.Message{ - ID: firstAnswerMsg.ID, - ReasoningContent: reasoningContent, - }) - if err != nil { - logs.CtxInfof(ctx, "save reasoning content failed, err: %v", err) - } -} - -func (c *runImpl) handlerInterrupt(ctx context.Context, chunk *entity.AgentRespEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence, firstAnswerMsg *msgEntity.Message, reasoningCOntent string) error { - interruptData, cType, err := c.parseInterruptData(ctx, chunk.Interrupt) - if err != nil { - return err - } - preMsg, err := c.PreCreateAnswer(ctx, rtDependence) - if err != nil { - return err - } - deltaAnswer := &entity.ChunkMessageItem{ - ID: preMsg.ID, - ConversationID: preMsg.ConversationID, - SectionID: preMsg.SectionID, - RunID: preMsg.RunID, - AgentID: preMsg.AgentID, - Role: entity.RoleType(preMsg.Role), - Content: interruptData, - MessageType: preMsg.MessageType, - ContentType: cType, - ReplyID: preMsg.RunID, - Ext: preMsg.Ext, - IsFinish: false, - } - - c.runEvent.SendMsgEvent(entity.RunEventMessageDelta, deltaAnswer, sw) - finalAnswer := deepcopy.Copy(deltaAnswer).(*entity.ChunkMessageItem) - if len(reasoningCOntent) > 0 && firstAnswerMsg == nil { - finalAnswer.ReasoningContent = ptr.Of(reasoningCOntent) - } - err = c.handlerAnswer(ctx, finalAnswer, sw, nil, rtDependence, preMsg) - if err != nil { - return err - } - - err = c.handlerInterruptVerbose(ctx, chunk, sw, rtDependence) - if err != nil { - return err - } - return nil -} - -func (c *runImpl) parseInterruptData(_ context.Context, interruptData *singleagent.InterruptInfo) (string, message.ContentType, error) { - - type msg struct { - Type string `json:"type,omitempty"` - ContentType string `json:"content_type"` - Content any `json:"content"` // either optionContent or string - ID string `json:"id,omitempty"` - } - - defaultContentType := message.ContentTypeText - switch interruptData.InterruptType { - case singleagent.InterruptEventType_OauthPlugin: - data := interruptData.AllToolInterruptData[interruptData.ToolCallID].ToolNeedOAuth.Message - return data, defaultContentType, nil - case singleagent.InterruptEventType_Question: - var iData map[string][]*msg - err := json.Unmarshal([]byte(interruptData.AllWfInterruptData[interruptData.ToolCallID].InterruptData), &iData) - if err != nil { - return "", defaultContentType, err - } - if len(iData["messages"]) == 0 { - return "", defaultContentType, errorx.New(errno.ErrInterruptDataEmpty) - } - interruptMsg := iData["messages"][0] - - if interruptMsg.ContentType == "text" { - return interruptMsg.Content.(string), defaultContentType, nil - } else if interruptMsg.ContentType == "option" || interruptMsg.ContentType == "form_schema" { - iMarshalData, err := json.Marshal(interruptMsg) - if err != nil { - return "", defaultContentType, err - } - return string(iMarshalData), message.ContentTypeCard, nil - } - case singleagent.InterruptEventType_InputNode: - data := interruptData.AllWfInterruptData[interruptData.ToolCallID].InterruptData - return data, message.ContentTypeCard, nil - case singleagent.InterruptEventType_WorkflowLLM: - toolInterruptEvent := interruptData.AllWfInterruptData[interruptData.ToolCallID].ToolInterruptEvent - data := toolInterruptEvent.InterruptData - if singleagent.InterruptEventType(toolInterruptEvent.EventType) == singleagent.InterruptEventType_InputNode { - return data, message.ContentTypeCard, nil - } - if singleagent.InterruptEventType(toolInterruptEvent.EventType) == singleagent.InterruptEventType_Question { - var iData map[string][]*msg - err := json.Unmarshal([]byte(data), &iData) - if err != nil { - return "", defaultContentType, err - } - if len(iData["messages"]) == 0 { - return "", defaultContentType, errorx.New(errno.ErrInterruptDataEmpty) - } - interruptMsg := iData["messages"][0] - - if interruptMsg.ContentType == "text" { - return interruptMsg.Content.(string), defaultContentType, nil - } else if interruptMsg.ContentType == "option" || interruptMsg.ContentType == "form_schema" { - iMarshalData, err := json.Marshal(interruptMsg) - if err != nil { - return "", defaultContentType, err - } - return string(iMarshalData), message.ContentTypeCard, nil - } - } - return "", defaultContentType, errorx.New(errno.ErrUnknowInterruptType) - - } - return "", defaultContentType, errorx.New(errno.ErrUnknowInterruptType) -} - -func (c *runImpl) handlerUsage(meta *schema.ResponseMeta) *msgEntity.UsageExt { - if meta == nil || meta.Usage == nil { - return nil - } - - return &msgEntity.UsageExt{ - TotalCount: int64(meta.Usage.TotalTokens), - InputTokens: int64(meta.Usage.PromptTokens), - OutputTokens: int64(meta.Usage.CompletionTokens), - } -} - -func (c *runImpl) handlerErr(_ context.Context, err error, sw *schema.StreamWriter[*entity.AgentRunResponse]) { - - errMsg := errorx.ErrorWithoutStack(err) - if strings.ToLower(os.Getenv(consts.RunMode)) != "debug" { - var statusErr errorx.StatusError - if errors.As(err, &statusErr) { - errMsg = statusErr.Msg() - } else { - errMsg = "Internal Server Error" - } - } - c.runEvent.SendErrEvent(entity.RunEventError, sw, &entity.RunError{ - Code: errno.ErrAgentRun, - Msg: errMsg, - }) -} - -func (c *runImpl) PreCreateAnswer(ctx context.Context, rtDependence *runtimeDependence) (*msgEntity.Message, error) { - arm := rtDependence.runMeta - msgMeta := &msgEntity.Message{ - ConversationID: arm.ConversationID, - RunID: rtDependence.runID, - AgentID: arm.AgentID, - SectionID: arm.SectionID, - UserID: arm.UserID, - Role: schema.Assistant, - MessageType: message.MessageTypeAnswer, - ContentType: message.ContentTypeText, - Ext: arm.Ext, - } - - if arm.Ext == nil { - msgMeta.Ext = map[string]string{} - } - - botStateExt := c.buildBotStateExt(arm) - bseString, err := json.Marshal(botStateExt) - if err != nil { - return nil, err - } - - if _, ok := msgMeta.Ext[string(msgEntity.MessageExtKeyBotState)]; !ok { - msgMeta.Ext[string(msgEntity.MessageExtKeyBotState)] = string(bseString) - } - - msgMeta.Ext = arm.Ext - return crossmessage.DefaultSVC().PreCreate(ctx, msgMeta) -} - -func (c *runImpl) handlerAnswer(ctx context.Context, msg *entity.ChunkMessageItem, sw *schema.StreamWriter[*entity.AgentRunResponse], usage *msgEntity.UsageExt, rtDependence *runtimeDependence, preAnswerMsg *msgEntity.Message) error { - - if len(msg.Content) == 0 && len(ptr.From(msg.ReasoningContent)) == 0 { - return nil - } - - msg.IsFinish = true - - if msg.Ext == nil { - msg.Ext = map[string]string{} - } - if usage != nil { - msg.Ext[string(msgEntity.MessageExtKeyToken)] = strconv.FormatInt(usage.TotalCount, 10) - msg.Ext[string(msgEntity.MessageExtKeyInputTokens)] = strconv.FormatInt(usage.InputTokens, 10) - msg.Ext[string(msgEntity.MessageExtKeyOutputTokens)] = strconv.FormatInt(usage.OutputTokens, 10) - - rtDependence.usage = &agentrun.Usage{ - LlmPromptTokens: usage.InputTokens, - LlmCompletionTokens: usage.OutputTokens, - LlmTotalTokens: usage.TotalCount, - } - } - - if _, ok := msg.Ext[string(msgEntity.MessageExtKeyTimeCost)]; !ok { - msg.Ext[string(msgEntity.MessageExtKeyTimeCost)] = fmt.Sprintf("%.1f", float64(time.Since(rtDependence.startTime).Milliseconds())/1000.00) - } - - buildModelContent := &schema.Message{ - Role: schema.Assistant, - Content: msg.Content, - } - - mc, err := json.Marshal(buildModelContent) - if err != nil { - return err - } - preAnswerMsg.Content = msg.Content - preAnswerMsg.ReasoningContent = ptr.From(msg.ReasoningContent) - preAnswerMsg.Ext = msg.Ext - preAnswerMsg.ContentType = msg.ContentType - preAnswerMsg.ModelContent = string(mc) - preAnswerMsg.CreatedAt = 0 - preAnswerMsg.UpdatedAt = 0 - - _, err = crossmessage.DefaultSVC().Create(ctx, preAnswerMsg) - if err != nil { - return err - } - c.runEvent.SendMsgEvent(entity.RunEventMessageCompleted, msg, sw) - - return nil -} - -func (c *runImpl) buildBotStateExt(arm *entity.AgentRunMeta) *msgEntity.BotStateExt { - agentID := strconv.FormatInt(arm.AgentID, 10) - botStateExt := &msgEntity.BotStateExt{ - AgentID: agentID, - AgentName: arm.Name, - Awaiting: agentID, - BotID: agentID, - } - - return botStateExt -} - -func (c *runImpl) handlerFunctionCall(ctx context.Context, chunk *entity.AgentRespEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) error { - cm := c.buildAgentMessage2Create(ctx, chunk, message.MessageTypeFunctionCall, rtDependence) - - cmData, err := crossmessage.DefaultSVC().Create(ctx, cm) - if err != nil { - return err - } - - sendMsg := c.buildSendMsg(ctx, cmData, true, rtDependence) - - c.runEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, sw) - return nil -} - -func (c *runImpl) handlerAckMessage(_ context.Context, input *msgEntity.Message, sw *schema.StreamWriter[*entity.AgentRunResponse]) error { - sendMsg := &entity.ChunkMessageItem{ - ID: input.ID, - ConversationID: input.ConversationID, - SectionID: input.SectionID, - AgentID: input.AgentID, - Role: entity.RoleType(input.Role), - MessageType: message.MessageTypeAck, - ReplyID: input.ID, - Content: input.Content, - ContentType: message.ContentTypeText, - IsFinish: true, - } - - c.runEvent.SendMsgEvent(entity.RunEventAck, sendMsg, sw) - - return nil -} - -func (c *runImpl) handlerTooResponse(ctx context.Context, chunk *entity.AgentRespEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence, preToolResponseMsg *msgEntity.Message, toolResponseMsgContent string) error { - - cm := c.buildAgentMessage2Create(ctx, chunk, message.MessageTypeToolResponse, rtDependence) - - var cmData *message.Message - var err error - - if preToolResponseMsg != nil { - cm.ID = preToolResponseMsg.ID - cm.CreatedAt = preToolResponseMsg.CreatedAt - cm.UpdatedAt = preToolResponseMsg.UpdatedAt - if len(toolResponseMsgContent) > 0 { - cm.Content = toolResponseMsgContent + "\n" + cm.Content - } - } - - cmData, err = crossmessage.DefaultSVC().Create(ctx, cm) - if err != nil { - return err - } - - sendMsg := c.buildSendMsg(ctx, cmData, true, rtDependence) - - c.runEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, sw) - - return nil -} - -func (c *runImpl) handlerSuggest(ctx context.Context, chunk *entity.AgentRespEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) error { - cm := c.buildAgentMessage2Create(ctx, chunk, message.MessageTypeFlowUp, rtDependence) - - cmData, err := crossmessage.DefaultSVC().Create(ctx, cm) - if err != nil { - return err - } - - sendMsg := c.buildSendMsg(ctx, cmData, true, rtDependence) - - c.runEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, sw) - - return nil -} - -func (c *runImpl) handlerKnowledge(ctx context.Context, chunk *entity.AgentRespEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) error { - cm := c.buildAgentMessage2Create(ctx, chunk, message.MessageTypeKnowledge, rtDependence) - cmData, err := crossmessage.DefaultSVC().Create(ctx, cm) - if err != nil { - return err - } - - sendMsg := c.buildSendMsg(ctx, cmData, true, rtDependence) - - c.runEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, sw) - return nil -} - -func (c *runImpl) buildKnowledge(_ context.Context, chunk *entity.AgentRespEvent) *msgEntity.VerboseInfo { - var recallDatas []msgEntity.RecallDataInfo - for _, kOne := range chunk.Knowledge { - recallDatas = append(recallDatas, msgEntity.RecallDataInfo{ - Slice: kOne.Content, - Meta: msgEntity.MetaInfo{ - Dataset: msgEntity.DatasetInfo{ - ID: kOne.MetaData["dataset_id"].(string), - Name: kOne.MetaData["dataset_name"].(string), - }, - Document: msgEntity.DocumentInfo{ - ID: kOne.MetaData["document_id"].(string), - Name: kOne.MetaData["document_name"].(string), - }, - }, - Score: kOne.Score(), - }) - } - - verboseData := &msgEntity.VerboseData{ - Chunks: recallDatas, - OriReq: "", - StatusCode: 0, - } - data, err := json.Marshal(verboseData) - if err != nil { - return nil - } - knowledgeInfo := &msgEntity.VerboseInfo{ - MessageType: string(entity.MessageSubTypeKnowledgeCall), - Data: string(data), - } - return knowledgeInfo -} - -func (c *runImpl) handlerFinalAnswerFinish(ctx context.Context, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) error { - cm := c.buildAgentMessage2Create(ctx, nil, message.MessageTypeVerbose, rtDependence) - cmData, err := crossmessage.DefaultSVC().Create(ctx, cm) - if err != nil { - return err - } - - sendMsg := c.buildSendMsg(ctx, cmData, true, rtDependence) - - c.runEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, sw) - return nil -} - -func (c *runImpl) handlerInterruptVerbose(ctx context.Context, chunk *entity.AgentRespEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) error { - cm := c.buildAgentMessage2Create(ctx, chunk, message.MessageTypeInterrupt, rtDependence) - cmData, err := crossmessage.DefaultSVC().Create(ctx, cm) - if err != nil { - return err - } - - sendMsg := c.buildSendMsg(ctx, cmData, true, rtDependence) - - c.runEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, sw) - return nil -} - -func (c *runImpl) buildSendMsg(_ context.Context, msg *msgEntity.Message, isFinish bool, rtDependence *runtimeDependence) *entity.ChunkMessageItem { - - copyMap := make(map[string]string) - for k, v := range msg.Ext { - copyMap[k] = v - } - - return &entity.ChunkMessageItem{ - ID: msg.ID, - ConversationID: msg.ConversationID, - SectionID: msg.SectionID, - AgentID: msg.AgentID, - Content: msg.Content, - Role: entity.RoleTypeAssistant, - ContentType: msg.ContentType, - MessageType: msg.MessageType, - ReplyID: rtDependence.questionMsgID, - Type: msg.MessageType, - CreatedAt: msg.CreatedAt, - UpdatedAt: msg.UpdatedAt, - RunID: rtDependence.runID, - Ext: copyMap, - IsFinish: isFinish, - ReasoningContent: ptr.Of(msg.ReasoningContent), - } -} - -func (c *runImpl) buildSendRunRecord(_ context.Context, runRecord *entity.RunRecordMeta, runStatus entity.RunStatus) *entity.ChunkRunItem { - return &entity.ChunkRunItem{ - ID: runRecord.ID, - ConversationID: runRecord.ConversationID, - AgentID: runRecord.AgentID, - SectionID: runRecord.SectionID, - Status: runStatus, - CreatedAt: runRecord.CreatedAt, - } -} - func (c *runImpl) Delete(ctx context.Context, runID []int64) error { return c.RunRecordRepo.Delete(ctx, runID) } + +func (c *runImpl) List(ctx context.Context, meta *entity.ListRunRecordMeta) ([]*entity.RunRecordMeta, error) { + return c.RunRecordRepo.List(ctx, meta) +} + +func (c *runImpl) Create(ctx context.Context, runRecord *entity.AgentRunMeta) (*entity.RunRecordMeta, error) { + return c.RunRecordRepo.Create(ctx, runRecord) +} +func (c *runImpl) Cancel(ctx context.Context, req *entity.CancelRunMeta) (*entity.RunRecordMeta, error) { + return c.RunRecordRepo.Cancel(ctx, req) +} + +func (c *runImpl) GetByID(ctx context.Context, runID int64) (*entity.RunRecordMeta, error) { + return c.RunRecordRepo.GetByID(ctx, runID) +} diff --git a/backend/domain/conversation/agentrun/service/agent_run_test.go b/backend/domain/conversation/agentrun/service/agent_run_test.go index ce414f56..e054321c 100644 --- a/backend/domain/conversation/agentrun/service/agent_run_test.go +++ b/backend/domain/conversation/agentrun/service/agent_run_test.go @@ -17,7 +17,18 @@ package agentrun import ( + "context" "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + + "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" + "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal/dal/model" + "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/repository" + mock "github.com/coze-dev/coze-studio/backend/internal/mock/infra/contract/idgen" + "github.com/coze-dev/coze-studio/backend/internal/mock/infra/contract/orm" ) func TestAgentRun(t *testing.T) { @@ -97,3 +108,158 @@ func TestAgentRun(t *testing.T) { // assert.NoError(t, err) } + +func TestRunImpl_List(t *testing.T) { + ctx := context.Background() + mockDBGen := orm.NewMockDB() + mockDBGen.AddTable(&model.RunRecord{}).AddRows( + &model.RunRecord{ + ID: 1, + ConversationID: 123, + AgentID: 456, + SectionID: 789, + UserID: "123456", + CreatedAt: time.Now().Unix(), + }, + &model.RunRecord{ + ID: 2, + ConversationID: 123, + AgentID: 456, + SectionID: 789, + UserID: "123456", + CreatedAt: time.Now().Unix() + 1, + }, &model.RunRecord{ + ID: 3, + ConversationID: 123, + AgentID: 456, + SectionID: 789, + UserID: "123456", + CreatedAt: time.Now().Unix() + 2, + }, &model.RunRecord{ + ID: 4, + ConversationID: 123, + AgentID: 456, + SectionID: 789, + UserID: "123456", + CreatedAt: time.Now().Unix() + 3, + }, &model.RunRecord{ + ID: 5, + ConversationID: 123, + AgentID: 456, + SectionID: 789, + UserID: "123456", + CreatedAt: time.Now().Unix() + 4, + }, + &model.RunRecord{ + ID: 6, + ConversationID: 123, + AgentID: 456, + SectionID: 789, + UserID: "123456", + CreatedAt: time.Now().Unix() + 5, + }, &model.RunRecord{ + ID: 7, + ConversationID: 123, + AgentID: 456, + SectionID: 789, + UserID: "123456", + CreatedAt: time.Now().Unix() + 6, + }, &model.RunRecord{ + ID: 8, + ConversationID: 123, + AgentID: 456, + SectionID: 789, + UserID: "123456", + CreatedAt: time.Now().Unix() + 7, + }, &model.RunRecord{ + ID: 9, + ConversationID: 123, + AgentID: 456, + SectionID: 789, + UserID: "123456", + CreatedAt: time.Now().Unix() + 8, + }, + ) + mockDB, err := mockDBGen.DB() + assert.NoError(t, err) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockIDGen := mock.NewMockIDGenerator(ctrl) + + runRecordRepo := repository.NewRunRecordRepo(mockDB, mockIDGen) + + service := &runImpl{ + Components: Components{ + RunRecordRepo: runRecordRepo, + }, + } + + t.Run("list success", func(t *testing.T) { + + meta := &entity.ListRunRecordMeta{ + ConversationID: 123, + AgentID: 456, + SectionID: 789, + Limit: 10, + OrderBy: "desc", + } + + result, err := service.List(ctx, meta) + // check result + assert.NoError(t, err) + assert.Len(t, result, 9) + assert.Equal(t, int64(123), result[0].ConversationID) + assert.Equal(t, int64(456), result[0].AgentID) + }) + + t.Run("empty list", func(t *testing.T) { + meta := &entity.ListRunRecordMeta{ + ConversationID: 999, // + Limit: 10, + OrderBy: "desc", + } + + // check result + result, err := service.List(ctx, meta) + assert.NoError(t, err) + assert.Empty(t, result) + }) + + t.Run("search with before id", func(t *testing.T) { + + meta := &entity.ListRunRecordMeta{ + ConversationID: 123, + SectionID: 789, + AgentID: 456, + BeforeID: 5, + Limit: 3, + OrderBy: "desc", + } + + result, err := service.List(ctx, meta) + + // check result + assert.NoError(t, err) + assert.Len(t, result, 3) + assert.Equal(t, int64(4), result[0].ID) + }) + t.Run("search with after id and limit", func(t *testing.T) { + + meta := &entity.ListRunRecordMeta{ + ConversationID: 123, + SectionID: 789, + AgentID: 456, + AfterID: 5, + Limit: 3, + OrderBy: "desc", + } + + result, err := service.List(ctx, meta) + + // check result + assert.NoError(t, err) + assert.Len(t, result, 3) + assert.Equal(t, int64(9), result[0].ID) + + }) +} diff --git a/backend/domain/conversation/conversation/entity/conversation.go b/backend/domain/conversation/conversation/entity/conversation.go index 3a8d1771..ba20e789 100644 --- a/backend/domain/conversation/conversation/entity/conversation.go +++ b/backend/domain/conversation/conversation/entity/conversation.go @@ -24,6 +24,7 @@ import ( type Conversation = conversation.Conversation type CreateMeta struct { + Name string `json:"name"` AgentID int64 `json:"agent_id"` UserID int64 `json:"user_id"` ConnectorID int64 `json:"connector_id"` @@ -50,3 +51,8 @@ type ListMeta struct { Limit int `json:"limit"` Page int `json:"page"` } + +type UpdateMeta struct { + ID int64 `json:"id"` + Name string `json:"name"` +} diff --git a/backend/domain/conversation/conversation/internal/dal/dao.go b/backend/domain/conversation/conversation/internal/dal/dao.go index e86a79e4..eb8ff84e 100644 --- a/backend/domain/conversation/conversation/internal/dal/dao.go +++ b/backend/domain/conversation/conversation/internal/dal/dao.go @@ -107,6 +107,20 @@ func (dao *ConversationDAO) Delete(ctx context.Context, id int64) (int64, error) return updateRes.RowsAffected, err } +func (dao *ConversationDAO) Update(ctx context.Context, req *entity.UpdateMeta) (*entity.Conversation, error) { + updateColumn := make(map[string]interface{}) + updateColumn[dao.query.Conversation.UpdatedAt.ColumnName().String()] = time.Now().UnixMilli() + if len(req.Name) > 0 { + updateColumn[dao.query.Conversation.Name.ColumnName().String()] = req.Name + } + + _, err := dao.query.Conversation.WithContext(ctx).Where(dao.query.Conversation.ID.Eq(req.ID)).UpdateColumns(updateColumn) + if err != nil { + return nil, err + } + return dao.GetByID(ctx, req.ID) +} + func (dao *ConversationDAO) Get(ctx context.Context, userID int64, agentID int64, scene int32, connectorID int64) (*entity.Conversation, error) { po, err := dao.query.Conversation.WithContext(ctx).Debug(). Where(dao.query.Conversation.CreatorID.Eq(userID)). @@ -133,13 +147,15 @@ func (dao *ConversationDAO) List(ctx context.Context, userID int64, agentID int6 do = do.Where(dao.query.Conversation.CreatorID.Eq(userID)). Where(dao.query.Conversation.AgentID.Eq(agentID)). Where(dao.query.Conversation.Scene.Eq(scene)). - Where(dao.query.Conversation.ConnectorID.Eq(connectorID)) + Where(dao.query.Conversation.ConnectorID.Eq(connectorID)). + Where(dao.query.Conversation.Status.Eq(int32(conversation.ConversationStatusNormal))) do = do.Offset((page - 1) * limit) if limit > 0 { do = do.Limit(int(limit) + 1) } + do = do.Order(dao.query.Conversation.CreatedAt.Desc()) poList, err := do.Find() @@ -173,6 +189,7 @@ func (dao *ConversationDAO) conversationDO2PO(ctx context.Context, conversation Ext: conversation.Ext, CreatedAt: time.Now().UnixMilli(), UpdatedAt: time.Now().UnixMilli(), + Name: conversation.Name, } } @@ -188,6 +205,7 @@ func (dao *ConversationDAO) conversationPO2DO(ctx context.Context, c *model.Conv Ext: c.Ext, CreatedAt: c.CreatedAt, UpdatedAt: c.UpdatedAt, + Name: c.Name, } } @@ -204,6 +222,7 @@ func (dao *ConversationDAO) conversationBatchPO2DO(ctx context.Context, conversa Ext: c.Ext, CreatedAt: c.CreatedAt, UpdatedAt: c.UpdatedAt, + Name: c.Name, } }) } diff --git a/backend/domain/conversation/conversation/internal/dal/model/conversation.gen.go b/backend/domain/conversation/conversation/internal/dal/model/conversation.gen.go index e72ffcc1..6838dd20 100644 --- a/backend/domain/conversation/conversation/internal/dal/model/conversation.gen.go +++ b/backend/domain/conversation/conversation/internal/dal/model/conversation.gen.go @@ -1,3 +1,19 @@ +/* + * 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 gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. @@ -9,6 +25,7 @@ const TableNameConversation = "conversation" // Conversation conversation info record type Conversation struct { ID int64 `gorm:"column:id;primaryKey;autoIncrement:true;comment:id" json:"id"` // id + Name string `gorm:"column:name;not null;comment:conversation name" json:"name"` // conversation name ConnectorID int64 `gorm:"column:connector_id;not null;comment:Publish Connector ID" json:"connector_id"` // Publish Connector ID AgentID int64 `gorm:"column:agent_id;not null;comment:agent_id" json:"agent_id"` // agent_id Scene int32 `gorm:"column:scene;not null;comment:conversation scene" json:"scene"` // conversation scene diff --git a/backend/domain/conversation/conversation/internal/dal/query/conversation.gen.go b/backend/domain/conversation/conversation/internal/dal/query/conversation.gen.go index f4772674..a2755e0e 100644 --- a/backend/domain/conversation/conversation/internal/dal/query/conversation.gen.go +++ b/backend/domain/conversation/conversation/internal/dal/query/conversation.gen.go @@ -1,3 +1,19 @@ +/* + * 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 gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. @@ -28,6 +44,7 @@ func newConversation(db *gorm.DB, opts ...gen.DOOption) conversation { tableName := _conversation.conversationDo.TableName() _conversation.ALL = field.NewAsterisk(tableName) _conversation.ID = field.NewInt64(tableName, "id") + _conversation.Name = field.NewString(tableName, "name") _conversation.ConnectorID = field.NewInt64(tableName, "connector_id") _conversation.AgentID = field.NewInt64(tableName, "agent_id") _conversation.Scene = field.NewInt32(tableName, "scene") @@ -49,6 +66,7 @@ type conversation struct { ALL field.Asterisk ID field.Int64 // id + Name field.String // conversation name ConnectorID field.Int64 // Publish Connector ID AgentID field.Int64 // agent_id Scene field.Int32 // conversation scene @@ -75,6 +93,7 @@ func (c conversation) As(alias string) *conversation { func (c *conversation) updateTableName(table string) *conversation { c.ALL = field.NewAsterisk(table) c.ID = field.NewInt64(table, "id") + c.Name = field.NewString(table, "name") c.ConnectorID = field.NewInt64(table, "connector_id") c.AgentID = field.NewInt64(table, "agent_id") c.Scene = field.NewInt32(table, "scene") @@ -100,8 +119,9 @@ func (c *conversation) GetFieldByName(fieldName string) (field.OrderExpr, bool) } func (c *conversation) fillFieldMap() { - c.fieldMap = make(map[string]field.Expr, 10) + c.fieldMap = make(map[string]field.Expr, 11) c.fieldMap["id"] = c.ID + c.fieldMap["name"] = c.Name c.fieldMap["connector_id"] = c.ConnectorID c.fieldMap["agent_id"] = c.AgentID c.fieldMap["scene"] = c.Scene diff --git a/backend/domain/conversation/conversation/repository/repository.go b/backend/domain/conversation/conversation/repository/repository.go index 348d7fd9..f21a1481 100644 --- a/backend/domain/conversation/conversation/repository/repository.go +++ b/backend/domain/conversation/conversation/repository/repository.go @@ -35,6 +35,7 @@ type ConversationRepo interface { GetByID(ctx context.Context, id int64) (*entity.Conversation, error) UpdateSection(ctx context.Context, id int64) (int64, error) Get(ctx context.Context, userID int64, agentID int64, scene int32, connectorID int64) (*entity.Conversation, error) + Update(ctx context.Context, req *entity.UpdateMeta) (*entity.Conversation, error) Delete(ctx context.Context, id int64) (int64, error) List(ctx context.Context, userID int64, agentID int64, connectorID int64, scene int32, limit int, page int) ([]*entity.Conversation, bool, error) } diff --git a/backend/domain/conversation/conversation/service/conversation.go b/backend/domain/conversation/conversation/service/conversation.go index f0e27fd3..edc70eb9 100644 --- a/backend/domain/conversation/conversation/service/conversation.go +++ b/backend/domain/conversation/conversation/service/conversation.go @@ -29,4 +29,5 @@ type Conversation interface { GetCurrentConversation(ctx context.Context, req *entity.GetCurrent) (*entity.Conversation, error) Delete(ctx context.Context, id int64) error List(ctx context.Context, req *entity.ListMeta) ([]*entity.Conversation, bool, error) + Update(ctx context.Context, req *entity.UpdateMeta) (*entity.Conversation, error) } diff --git a/backend/domain/conversation/conversation/service/conversation_impl.go b/backend/domain/conversation/conversation/service/conversation_impl.go index cb27c16c..61f914f2 100644 --- a/backend/domain/conversation/conversation/service/conversation_impl.go +++ b/backend/domain/conversation/conversation/service/conversation_impl.go @@ -101,6 +101,11 @@ func (c *conversationImpl) Delete(ctx context.Context, id int64) error { return nil } +func (c *conversationImpl) Update(ctx context.Context, req *entity.UpdateMeta) (*entity.Conversation, error) { + // get conversation + return c.ConversationRepo.Update(ctx, req) +} + func (c *conversationImpl) List(ctx context.Context, req *entity.ListMeta) ([]*entity.Conversation, bool, error) { conversationList, hasMore, err := c.ConversationRepo.List(ctx, req.UserID, req.AgentID, req.ConnectorID, int32(req.Scene), req.Limit, req.Page) diff --git a/backend/domain/conversation/message/entity/message.go b/backend/domain/conversation/message/entity/message.go index f26eec55..7930f6e4 100644 --- a/backend/domain/conversation/message/entity/message.go +++ b/backend/domain/conversation/message/entity/message.go @@ -16,19 +16,22 @@ package entity -import "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" +import ( + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" +) type Message = message.Message type ListMeta struct { - ConversationID int64 `json:"conversation_id"` - RunID []*int64 `json:"run_id"` - UserID string `json:"user_id"` - AgentID int64 `json:"agent_id"` - OrderBy *string `json:"order_by"` - Limit int `json:"limit"` - Cursor int64 `json:"cursor"` // message id - Direction ScrollPageDirection `json:"direction"` // "prev" "Next" + ConversationID int64 `json:"conversation_id"` + RunID []*int64 `json:"run_id"` + UserID string `json:"user_id"` + AgentID int64 `json:"agent_id"` + OrderBy *string `json:"order_by"` + Limit int `json:"limit"` + Cursor int64 `json:"cursor"` // message id + Direction ScrollPageDirection `json:"direction"` // "prev" "Next" + MessageType []*message.MessageType `json:"message_type"` } type ListResult struct { @@ -45,8 +48,9 @@ type GetByRunIDsRequest struct { } type DeleteMeta struct { - MessageIDs []int64 `json:"message_ids"` - RunIDs []int64 `json:"run_ids"` + ConversationID *int64 `json:"conversation_id"` + MessageIDs []int64 `json:"message_ids"` + RunIDs []int64 `json:"run_ids"` } type BrokenMeta struct { diff --git a/backend/domain/conversation/message/internal/dal/message.go b/backend/domain/conversation/message/internal/dal/message.go index d1842ee9..739978d7 100644 --- a/backend/domain/conversation/message/internal/dal/message.go +++ b/backend/domain/conversation/message/internal/dal/message.go @@ -31,6 +31,7 @@ import ( "github.com/coze-dev/coze-studio/backend/domain/conversation/message/internal/dal/query" "github.com/coze-dev/coze-studio/backend/infra/contract/idgen" "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/pkg/sonic" "github.com/coze-dev/coze-studio/backend/types/errno" @@ -71,27 +72,41 @@ func (dao *MessageDAO) Create(ctx context.Context, msg *entity.Message) (*entity return dao.messagePO2DO(poData), nil } -func (dao *MessageDAO) List(ctx context.Context, conversationID int64, limit int, cursor int64, direction entity.ScrollPageDirection, messageType *message.MessageType) ([]*entity.Message, bool, error) { +func (dao *MessageDAO) List(ctx context.Context, listMeta *entity.ListMeta) ([]*entity.Message, bool, error) { m := dao.query.Message - do := m.WithContext(ctx).Debug().Where(m.ConversationID.Eq(conversationID)).Where(m.Status.Eq(int32(entity.MessageStatusAvailable))) + do := m.WithContext(ctx).Debug().Where(m.ConversationID.Eq(listMeta.ConversationID)).Where(m.Status.Eq(int32(entity.MessageStatusAvailable))) - if messageType != nil { - do = do.Where(m.MessageType.Eq(string(*messageType))) + if len(listMeta.RunID) > 0 { + do = do.Where(m.RunID.In(slices.Transform(listMeta.RunID, func(t *int64) int64 { + return *t + })...)) + } + if len(listMeta.MessageType) > 0 { + do = do.Where(m.MessageType.In(slices.Transform(listMeta.MessageType, func(t *message.MessageType) string { + return string(*t) + })...)) } - if limit > 0 { - do = do.Limit(int(limit) + 1) + if listMeta.Limit > 0 { + do = do.Limit(int(listMeta.Limit) + 1) } - if cursor > 0 { - if direction == entity.ScrollPageDirectionPrev { - do = do.Where(m.CreatedAt.Lt(cursor)) - } else { - do = do.Where(m.CreatedAt.Gt(cursor)) + if listMeta.Cursor > 0 { + msg, err := m.Where(m.ID.Eq(listMeta.Cursor)).First() + if err != nil { + return nil, false, err } + if listMeta.Direction == entity.ScrollPageDirectionPrev { + do = do.Where(m.CreatedAt.Lt(msg.CreatedAt)) + do = do.Order(m.CreatedAt.Desc()) + } else { + do = do.Where(m.CreatedAt.Gt(msg.CreatedAt)) + do = do.Order(m.CreatedAt.Asc()) + } + } else { + do = do.Order(m.CreatedAt.Desc()) } - do = do.Order(m.CreatedAt.Desc()) messageList, err := do.Find() var hasMore bool @@ -103,9 +118,9 @@ func (dao *MessageDAO) List(ctx context.Context, conversationID int64, limit int return nil, false, err } - if len(messageList) > limit { + if len(messageList) > int(listMeta.Limit) { hasMore = true - messageList = messageList[:limit] + messageList = messageList[:int(listMeta.Limit)] } return dao.batchMessagePO2DO(messageList), hasMore, nil @@ -113,7 +128,8 @@ func (dao *MessageDAO) List(ctx context.Context, conversationID int64, limit int func (dao *MessageDAO) GetByRunIDs(ctx context.Context, runIDs []int64, orderBy string) ([]*entity.Message, error) { m := dao.query.Message - do := m.WithContext(ctx).Debug().Where(m.RunID.In(runIDs...)) + do := m.WithContext(ctx).Debug().Where(m.RunID.In(runIDs...)).Where(m.Status.Eq(int32(entity.MessageStatusAvailable))) + if orderBy == "DESC" { do = do.Order(m.CreatedAt.Desc()) } else { @@ -133,19 +149,37 @@ func (dao *MessageDAO) GetByRunIDs(ctx context.Context, runIDs []int64, orderBy func (dao *MessageDAO) Edit(ctx context.Context, msgID int64, msg *message.Message) (int64, error) { m := dao.query.Message - columns := dao.buildEditColumns(msg) + + originMsg, err := dao.GetByID(ctx, msgID) + if originMsg == nil { + return 0, errorx.New(errno.ErrRecordNotFound) + } + if err != nil { + return 0, err + } + + columns := dao.buildEditColumns(msg, originMsg) do, err := m.WithContext(ctx).Where(m.ID.Eq(msgID)).UpdateColumns(columns) if err != nil { return 0, err } + if do.RowsAffected == 0 { + return 0, errorx.New(errno.ErrRecordNotFound) + } + return do.RowsAffected, nil } -func (dao *MessageDAO) buildEditColumns(msg *message.Message) map[string]interface{} { +func (dao *MessageDAO) buildEditColumns(msg *message.Message, originMsg *entity.Message) map[string]interface{} { columns := make(map[string]interface{}) table := dao.query.Message if msg.Content != "" { + msg.Role = originMsg.Role columns[table.Content.ColumnName().String()] = msg.Content + modelContent, err := dao.buildModelContent(msg) + if err == nil { + columns[table.ModelContent.ColumnName().String()] = modelContent + } } if msg.MessageType != "" { columns[table.MessageType.ColumnName().String()] = msg.MessageType @@ -170,6 +204,11 @@ func (dao *MessageDAO) buildEditColumns(msg *message.Message) map[string]interfa columns[table.UpdatedAt.ColumnName().String()] = time.Now().UnixMilli() if msg.Ext != nil { + if originMsg.Ext != nil { + for k, v := range originMsg.Ext { + msg.Ext[k] = v + } + } ext, err := sonic.MarshalString(msg.Ext) if err == nil { columns[table.Ext.ColumnName().String()] = ext @@ -192,8 +231,8 @@ func (dao *MessageDAO) GetByID(ctx context.Context, msgID int64) (*entity.Messag return dao.messagePO2DO(po), nil } -func (dao *MessageDAO) Delete(ctx context.Context, msgIDs []int64, runIDs []int64) error { - if len(msgIDs) == 0 && len(runIDs) == 0 { +func (dao *MessageDAO) Delete(ctx context.Context, delMeta *entity.DeleteMeta) error { + if len(delMeta.MessageIDs) == 0 && len(delMeta.RunIDs) == 0 { return nil } @@ -202,11 +241,14 @@ func (dao *MessageDAO) Delete(ctx context.Context, msgIDs []int64, runIDs []int6 m := dao.query.Message do := m.WithContext(ctx) - if len(runIDs) > 0 { - do = do.Where(m.RunID.In(runIDs...)) + if len(delMeta.RunIDs) > 0 { + do = do.Where(m.RunID.In(delMeta.RunIDs...)) } - if len(msgIDs) > 0 { - do = do.Where(m.ID.In(msgIDs...)) + if len(delMeta.MessageIDs) > 0 { + do = do.Where(m.ID.In(delMeta.MessageIDs...)) + } + if delMeta.ConversationID != nil && ptr.From(delMeta.ConversationID) > 0 { + do = do.Where(m.ConversationID.Eq(*delMeta.ConversationID)) } _, err := do.UpdateColumns(&updateColumns) return err @@ -284,6 +326,9 @@ func (dao *MessageDAO) buildModelContent(msgDO *entity.Message) (string, error) var multiContent []schema.ChatMessagePart for _, contentData := range msgDO.MultiContent { if contentData.Type == message.InputTypeText { + if len(msgDO.Content) == 0 && len(contentData.Text) > 0 { + msgDO.Content = contentData.Text + } continue } one := schema.ChatMessagePart{} diff --git a/backend/domain/conversation/message/repository/repository.go b/backend/domain/conversation/message/repository/repository.go index b595e9c0..11796436 100644 --- a/backend/domain/conversation/message/repository/repository.go +++ b/backend/domain/conversation/message/repository/repository.go @@ -34,10 +34,9 @@ func NewMessageRepo(db *gorm.DB, idGen idgen.IDGenerator) MessageRepo { type MessageRepo interface { PreCreate(ctx context.Context, msg *entity.Message) (*entity.Message, error) Create(ctx context.Context, msg *entity.Message) (*entity.Message, error) - List(ctx context.Context, conversationID int64, limit int, cursor int64, - direction entity.ScrollPageDirection, messageType *message.MessageType) ([]*entity.Message, bool, error) + List(ctx context.Context, listMeta *entity.ListMeta) ([]*entity.Message, bool, error) GetByRunIDs(ctx context.Context, runIDs []int64, orderBy string) ([]*entity.Message, error) Edit(ctx context.Context, msgID int64, message *message.Message) (int64, error) GetByID(ctx context.Context, msgID int64) (*entity.Message, error) - Delete(ctx context.Context, msgIDs []int64, runIDs []int64) error + Delete(ctx context.Context, delMeta *entity.DeleteMeta) error } diff --git a/backend/domain/conversation/message/service/message.go b/backend/domain/conversation/message/service/message.go index d1398e67..8cab1258 100644 --- a/backend/domain/conversation/message/service/message.go +++ b/backend/domain/conversation/message/service/message.go @@ -24,6 +24,7 @@ import ( type Message interface { List(ctx context.Context, req *entity.ListMeta) (*entity.ListResult, error) + ListWithoutPair(ctx context.Context, req *entity.ListMeta) (*entity.ListResult, error) PreCreate(ctx context.Context, req *entity.Message) (*entity.Message, error) Create(ctx context.Context, req *entity.Message) (*entity.Message, error) GetByRunIDs(ctx context.Context, conversationID int64, runIDs []int64) ([]*entity.Message, error) diff --git a/backend/domain/conversation/message/service/message_impl.go b/backend/domain/conversation/message/service/message_impl.go index edeec508..833ac2d3 100644 --- a/backend/domain/conversation/message/service/message_impl.go +++ b/backend/domain/conversation/message/service/message_impl.go @@ -18,6 +18,7 @@ package message import ( "context" + "sort" "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity" @@ -51,9 +52,9 @@ func (m *messageImpl) Create(ctx context.Context, msg *entity.Message) (*entity. func (m *messageImpl) List(ctx context.Context, req *entity.ListMeta) (*entity.ListResult, error) { resp := &entity.ListResult{} - + req.MessageType = []*message.MessageType{ptr.Of(message.MessageTypeQuestion)} // get message with query - messageList, hasMore, err := m.MessageRepo.List(ctx, req.ConversationID, req.Limit, req.Cursor, req.Direction, ptr.Of(message.MessageTypeQuestion)) + messageList, hasMore, err := m.MessageRepo.List(ctx, req) if err != nil { return resp, err } @@ -62,8 +63,11 @@ func (m *messageImpl) List(ctx context.Context, req *entity.ListMeta) (*entity.L resp.HasMore = hasMore if len(messageList) > 0 { - resp.PrevCursor = messageList[len(messageList)-1].CreatedAt - resp.NextCursor = messageList[0].CreatedAt + sort.Slice(messageList, func(i, j int) bool { + return messageList[i].CreatedAt > messageList[j].CreatedAt + }) + resp.PrevCursor = messageList[len(messageList)-1].ID + resp.NextCursor = messageList[0].ID var runIDs []int64 for _, m := range messageList { @@ -82,6 +86,23 @@ func (m *messageImpl) List(ctx context.Context, req *entity.ListMeta) (*entity.L return resp, nil } +func (m *messageImpl) ListWithoutPair(ctx context.Context, req *entity.ListMeta) (*entity.ListResult, error) { + resp := &entity.ListResult{} + messageList, hasMore, err := m.MessageRepo.List(ctx, req) + if err != nil { + return resp, err + } + resp.Direction = req.Direction + resp.HasMore = hasMore + resp.Messages = messageList + if len(messageList) > 0 { + resp.PrevCursor = messageList[0].ID + resp.NextCursor = messageList[len(messageList)-1].ID + } + + return resp, nil +} + func (m *messageImpl) GetByRunIDs(ctx context.Context, conversationID int64, runIDs []int64) ([]*entity.Message, error) { return m.MessageRepo.GetByRunIDs(ctx, runIDs, "ASC") } @@ -96,7 +117,7 @@ func (m *messageImpl) Edit(ctx context.Context, req *entity.Message) (*entity.Me } func (m *messageImpl) Delete(ctx context.Context, req *entity.DeleteMeta) error { - return m.MessageRepo.Delete(ctx, req.MessageIDs, req.RunIDs) + return m.MessageRepo.Delete(ctx, req) } func (m *messageImpl) GetByID(ctx context.Context, id int64) (*entity.Message, error) { diff --git a/backend/domain/conversation/message/service/message_test.go b/backend/domain/conversation/message/service/message_test.go index 50451327..721e1ef1 100644 --- a/backend/domain/conversation/message/service/message_test.go +++ b/backend/domain/conversation/message/service/message_test.go @@ -18,6 +18,7 @@ package message import ( "context" + "encoding/json" "testing" "time" @@ -145,20 +146,26 @@ func TestCreateMessage(t *testing.T) { func TestEditMessage(t *testing.T) { ctx := context.Background() mockDBGen := orm.NewMockDB() - + extData := map[string]string{ + "test": "test", + } + ext, _ := json.Marshal(extData) mockDBGen.AddTable(&model.Message{}). AddRows( &model.Message{ ID: 1, ConversationID: 1, UserID: "1", + Role: string(schema.User), RunID: 123, }, &model.Message{ ID: 2, ConversationID: 1, UserID: "1", + Role: string(schema.User), RunID: 124, + Ext: string(ext), }, ) @@ -177,7 +184,7 @@ func TestEditMessage(t *testing.T) { Url: "https://xxxxx.xxxx/file", Name: "test_file", } - content := []*message.InputMetaData{ + _ = []*message.InputMetaData{ { Type: message.InputTypeText, Text: "解析图片中的内容", @@ -197,56 +204,293 @@ func TestEditMessage(t *testing.T) { } resp, err := NewService(components).Edit(ctx, &entity.Message{ - ID: 2, - Content: "test edit message", - MultiContent: content, + ID: 2, + Content: "test edit message", + Ext: map[string]string{"newext": "true"}, + + // MultiContent: content, }) _ = resp - msOne, err := NewService(components).GetByRunIDs(ctx, 1, []int64{124}) + msg, err := NewService(components).GetByID(ctx, 2) assert.NoError(t, err) - assert.Equal(t, int64(124), msOne[0].RunID) + assert.Equal(t, int64(2), msg.ID) + assert.Equal(t, "test edit message", msg.Content) + var modelContent *schema.Message + err = json.Unmarshal([]byte(msg.ModelContent), &modelContent) + assert.NoError(t, err) + + assert.Equal(t, "test edit message", modelContent.Content) + + assert.Equal(t, "true", msg.Ext["newext"]) } -func TestGetByRunIDs(t *testing.T) { +//func TestGetByRunIDs(t *testing.T) { +// ctx := context.Background() +// +// mockDBGen := orm.NewMockDB() +// +// mockDBGen.AddTable(&model.Message{}). +// AddRows( +// &model.Message{ +// ID: 1, +// ConversationID: 1, +// UserID: "1", +// RunID: 123, +// Content: "test content123", +// }, +// &model.Message{ +// ID: 2, +// ConversationID: 1, +// UserID: "1", +// Content: "test content124", +// RunID: 124, +// }, +// &model.Message{ +// ID: 3, +// ConversationID: 1, +// UserID: "1", +// Content: "test content124", +// RunID: 124, +// }, +// ) +// mockDB, err := mockDBGen.DB() +// assert.NoError(t, err) +// components := &Components{ +// MessageRepo: repository.NewMessageRepo(mockDB, nil), +// } +// +// resp, err := NewService(components).GetByRunIDs(ctx, 1, []int64{124}) +// +// assert.NoError(t, err) +// +// assert.Len(t, resp, 2) +//} + +func TestListWithoutPair(t *testing.T) { ctx := context.Background() + t.Run("success_with_messages", func(t *testing.T) { + mockDBGen := orm.NewMockDB() - mockDBGen := orm.NewMockDB() + mockDBGen.AddTable(&model.Message{}). + AddRows( + &model.Message{ + ID: 1, + ConversationID: 100, + UserID: "user123", + RunID: 200, + Content: "Hello", + MessageType: string(message.MessageTypeAnswer), + Status: 1, // MessageStatusAvailable + CreatedAt: time.Now().UnixMilli(), + }, + &model.Message{ + ID: 2, + ConversationID: 100, + UserID: "user123", + RunID: 201, + Content: "World", + MessageType: string(message.MessageTypeAnswer), + Status: 1, // MessageStatusAvailable + CreatedAt: time.Now().UnixMilli(), + }, + ) - mockDBGen.AddTable(&model.Message{}). - AddRows( - &model.Message{ - ID: 1, - ConversationID: 1, - UserID: "1", - RunID: 123, - Content: "test content123", - }, - &model.Message{ - ID: 2, - ConversationID: 1, - UserID: "1", - Content: "test content124", - RunID: 124, - }, - &model.Message{ - ID: 3, - ConversationID: 1, - UserID: "1", - Content: "test content124", - RunID: 124, - }, - ) - mockDB, err := mockDBGen.DB() - assert.NoError(t, err) - components := &Components{ - MessageRepo: repository.NewMessageRepo(mockDB, nil), - } + mockDB, err := mockDBGen.DB() + assert.NoError(t, err) - resp, err := NewService(components).GetByRunIDs(ctx, 1, []int64{124}) + components := &Components{ + MessageRepo: repository.NewMessageRepo(mockDB, nil), + } - assert.NoError(t, err) + req := &entity.ListMeta{ + ConversationID: 100, + UserID: "user123", + Limit: 10, + Direction: entity.ScrollPageDirectionNext, + } - assert.Len(t, resp, 2) + resp, err := NewService(components).ListWithoutPair(ctx, req) + assert.NoError(t, err) + assert.NotNil(t, resp) + assert.Equal(t, entity.ScrollPageDirectionNext, resp.Direction) + assert.False(t, resp.HasMore) + assert.Len(t, resp.Messages, 2) + assert.Equal(t, "Hello", resp.Messages[0].Content) + assert.Equal(t, "World", resp.Messages[1].Content) + }) + + t.Run("empty_result", func(t *testing.T) { + mockDBGen := orm.NewMockDB() + mockDBGen.AddTable(&model.Message{}) + + mockDB, err := mockDBGen.DB() + assert.NoError(t, err) + + components := &Components{ + MessageRepo: repository.NewMessageRepo(mockDB, nil), + } + + req := &entity.ListMeta{ + ConversationID: 999, + UserID: "user123", + Limit: 10, + Direction: entity.ScrollPageDirectionNext, + } + + resp, err := NewService(components).ListWithoutPair(ctx, req) + assert.NoError(t, err) + assert.NotNil(t, resp) + assert.Equal(t, entity.ScrollPageDirectionNext, resp.Direction) + assert.False(t, resp.HasMore) + assert.Len(t, resp.Messages, 0) + }) + + t.Run("pagination_has_more", func(t *testing.T) { + mockDBGen := orm.NewMockDB() + + mockDBGen.AddTable(&model.Message{}). + AddRows( + &model.Message{ + ID: 1, + ConversationID: 100, + UserID: "user123", + RunID: 200, + Content: "Message 1", + MessageType: string(message.MessageTypeAnswer), + Status: 1, + CreatedAt: time.Now().UnixMilli() - 3000, + }, + &model.Message{ + ID: 2, + ConversationID: 100, + UserID: "user123", + RunID: 201, + Content: "Message 2", + MessageType: string(message.MessageTypeAnswer), + Status: 1, + CreatedAt: time.Now().UnixMilli() - 2000, + }, + &model.Message{ + ID: 3, + ConversationID: 100, + UserID: "user123", + RunID: 202, + Content: "Message 3", + MessageType: string(message.MessageTypeAnswer), + Status: 1, + CreatedAt: time.Now().UnixMilli() - 1000, + }, + ) + + mockDB, err := mockDBGen.DB() + assert.NoError(t, err) + + components := &Components{ + MessageRepo: repository.NewMessageRepo(mockDB, nil), + } + + req := &entity.ListMeta{ + ConversationID: 100, + UserID: "user123", + Limit: 2, + Direction: entity.ScrollPageDirectionNext, + } + + resp, err := NewService(components).ListWithoutPair(ctx, req) + assert.NoError(t, err) + assert.NotNil(t, resp) + assert.Equal(t, entity.ScrollPageDirectionNext, resp.Direction) + assert.True(t, resp.HasMore) + assert.Len(t, resp.Messages, 2) + }) + + t.Run("direction_prev", func(t *testing.T) { + mockDBGen := orm.NewMockDB() + + mockDBGen.AddTable(&model.Message{}). + AddRows( + &model.Message{ + ID: 1, + ConversationID: 100, + UserID: "user123", + RunID: 200, + Content: "Test message", + MessageType: string(message.MessageTypeAnswer), + Status: 1, + CreatedAt: time.Now().UnixMilli(), + }, + ) + + mockDB, err := mockDBGen.DB() + assert.NoError(t, err) + + components := &Components{ + MessageRepo: repository.NewMessageRepo(mockDB, nil), + } + + req := &entity.ListMeta{ + ConversationID: 100, + UserID: "user123", + Limit: 10, + Direction: entity.ScrollPageDirectionPrev, + } + + resp, err := NewService(components).ListWithoutPair(ctx, req) + assert.NoError(t, err) + assert.NotNil(t, resp) + assert.Equal(t, entity.ScrollPageDirectionPrev, resp.Direction) + assert.False(t, resp.HasMore) + assert.Len(t, resp.Messages, 1) + }) + + t.Run("with_message_type_filter", func(t *testing.T) { + mockDBGen := orm.NewMockDB() + + mockDBGen.AddTable(&model.Message{}). + AddRows( + &model.Message{ + ID: 1, + ConversationID: 100, + UserID: "user123", + RunID: 200, + Content: "Answer message", + MessageType: string(message.MessageTypeAnswer), + Status: 1, + CreatedAt: time.Now().UnixMilli(), + }, + &model.Message{ + ID: 2, + ConversationID: 100, + UserID: "user123", + RunID: 201, + Content: "Question message", + MessageType: string(message.MessageTypeQuestion), + Status: 1, + CreatedAt: time.Now().UnixMilli(), + }, + ) + + mockDB, err := mockDBGen.DB() + assert.NoError(t, err) + + components := &Components{ + MessageRepo: repository.NewMessageRepo(mockDB, nil), + } + + req := &entity.ListMeta{ + ConversationID: 100, + UserID: "user123", + Limit: 10, + Direction: entity.ScrollPageDirectionNext, + MessageType: []*message.MessageType{&[]message.MessageType{message.MessageTypeAnswer}[0]}, + } + + resp, err := NewService(components).ListWithoutPair(ctx, req) + assert.NoError(t, err) + assert.NotNil(t, resp) + assert.Len(t, resp.Messages, 1) + assert.Equal(t, "Answer message", resp.Messages[0].Content) + }) } diff --git a/backend/domain/openauth/openapiauth/entity/api_auth.go b/backend/domain/openauth/openapiauth/entity/api_auth.go index 1ae87d25..3033119d 100644 --- a/backend/domain/openauth/openapiauth/entity/api_auth.go +++ b/backend/domain/openauth/openapiauth/entity/api_auth.go @@ -32,6 +32,7 @@ type CreateApiKey struct { Name string `json:"name"` Expire int64 `json:"expire"` UserID int64 `json:"user_id"` + AkType AkType `json:"ak_type"` } type DeleteApiKey struct { diff --git a/backend/domain/openauth/openapiauth/entity/consts.go b/backend/domain/openauth/openapiauth/entity/consts.go new file mode 100644 index 00000000..f98f54cf --- /dev/null +++ b/backend/domain/openauth/openapiauth/entity/consts.go @@ -0,0 +1,24 @@ +/* + * 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 + +type AkType int32 + +const ( + AkTypeCustomer AkType = 0 + AkTypeTemporary AkType = 1 +) diff --git a/backend/domain/openauth/openapiauth/internal/dal/api_key.go b/backend/domain/openauth/openapiauth/internal/dal/api_key.go index 51f1b19c..3c41c92f 100644 --- a/backend/domain/openauth/openapiauth/internal/dal/api_key.go +++ b/backend/domain/openauth/openapiauth/internal/dal/api_key.go @@ -72,6 +72,7 @@ func (a *ApiKeyDAO) doToPo(ctx context.Context, do *entity.CreateApiKey) (*model Name: do.Name, ExpiredAt: do.Expire, UserID: do.UserID, + AkType: int32(do.AkType), CreatedAt: time.Now().Unix(), } return po, nil @@ -119,7 +120,7 @@ func (a *ApiKeyDAO) FindByKey(ctx context.Context, key string) (*model.APIKey, e func (a *ApiKeyDAO) List(ctx context.Context, userID int64, limit int, page int) ([]*model.APIKey, bool, error) { do := a.dbQuery.APIKey.WithContext(ctx).Where(a.dbQuery.APIKey.UserID.Eq(userID)) - + do = do.Where(a.dbQuery.APIKey.AkType.Eq(int32(entity.AkTypeCustomer))) do = do.Offset((page - 1) * limit).Limit(limit + 1) list, err := do.Order(a.dbQuery.APIKey.CreatedAt.Desc()).Find() diff --git a/backend/domain/openauth/openapiauth/internal/dal/model/api_key.gen.go b/backend/domain/openauth/openapiauth/internal/dal/model/api_key.gen.go index 887ebee1..773c9d97 100644 --- a/backend/domain/openauth/openapiauth/internal/dal/model/api_key.gen.go +++ b/backend/domain/openauth/openapiauth/internal/dal/model/api_key.gen.go @@ -1,3 +1,19 @@ +/* + * 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 gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. @@ -17,6 +33,7 @@ type APIKey struct { CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:Create Time in Milliseconds" json:"created_at"` // Create Time in Milliseconds UpdatedAt int64 `gorm:"column:updated_at;not null;autoUpdateTime:milli;comment:Update Time in Milliseconds" json:"updated_at"` // Update Time in Milliseconds LastUsedAt int64 `gorm:"column:last_used_at;not null;comment:Used Time in Milliseconds" json:"last_used_at"` // Used Time in Milliseconds + AkType int32 `gorm:"column:ak_type;not null;comment:api key type" json:"ak_type"` // api key type } // TableName APIKey's table name diff --git a/backend/domain/openauth/openapiauth/internal/dal/query/api_key.gen.go b/backend/domain/openauth/openapiauth/internal/dal/query/api_key.gen.go index 7e9247c4..8724e6f2 100644 --- a/backend/domain/openauth/openapiauth/internal/dal/query/api_key.gen.go +++ b/backend/domain/openauth/openapiauth/internal/dal/query/api_key.gen.go @@ -1,3 +1,19 @@ +/* + * 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 gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. // Code generated by gorm.io/gen. DO NOT EDIT. @@ -36,6 +52,7 @@ func newAPIKey(db *gorm.DB, opts ...gen.DOOption) aPIKey { _aPIKey.CreatedAt = field.NewInt64(tableName, "created_at") _aPIKey.UpdatedAt = field.NewInt64(tableName, "updated_at") _aPIKey.LastUsedAt = field.NewInt64(tableName, "last_used_at") + _aPIKey.AkType = field.NewInt32(tableName, "ak_type") _aPIKey.fillFieldMap() @@ -56,6 +73,7 @@ type aPIKey struct { CreatedAt field.Int64 // Create Time in Milliseconds UpdatedAt field.Int64 // Update Time in Milliseconds LastUsedAt field.Int64 // Used Time in Milliseconds + AkType field.Int32 // api key type fieldMap map[string]field.Expr } @@ -81,6 +99,7 @@ func (a *aPIKey) updateTableName(table string) *aPIKey { a.CreatedAt = field.NewInt64(table, "created_at") a.UpdatedAt = field.NewInt64(table, "updated_at") a.LastUsedAt = field.NewInt64(table, "last_used_at") + a.AkType = field.NewInt32(table, "ak_type") a.fillFieldMap() @@ -97,7 +116,7 @@ func (a *aPIKey) GetFieldByName(fieldName string) (field.OrderExpr, bool) { } func (a *aPIKey) fillFieldMap() { - a.fieldMap = make(map[string]field.Expr, 9) + a.fieldMap = make(map[string]field.Expr, 10) a.fieldMap["id"] = a.ID a.fieldMap["api_key"] = a.APIKey a.fieldMap["name"] = a.Name @@ -107,6 +126,7 @@ func (a *aPIKey) fillFieldMap() { a.fieldMap["created_at"] = a.CreatedAt a.fieldMap["updated_at"] = a.UpdatedAt a.fieldMap["last_used_at"] = a.LastUsedAt + a.fieldMap["ak_type"] = a.AkType } func (a aPIKey) clone(db *gorm.DB) aPIKey { diff --git a/backend/domain/upload/entity/file.go b/backend/domain/upload/entity/file.go new file mode 100644 index 00000000..446c4074 --- /dev/null +++ b/backend/domain/upload/entity/file.go @@ -0,0 +1,46 @@ +/* + * 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 + +type File struct { + ID int64 `json:"id"` + Name string `json:"name"` + FileSize int64 `json:"file_size"` + TosURI string `json:"tos_uri"` + Status FileStatus `json:"status"` + Comment string `json:"comment"` + Source FileSource `json:"source"` + CreatorID string `json:"creator_id"` + CozeAccountID int64 `json:"coze_account_id"` + ContentType string `json:"content_type"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` + Url string `json:"url"` +} + +type FileStatus int32 + +const ( + FileStatusInvalid FileStatus = 0 + FileStatusValid FileStatus = 1 +) + +type FileSource int32 + +const ( + FileSourceAPI FileSource = 1 +) diff --git a/backend/domain/upload/internal/dal/dao/files.go b/backend/domain/upload/internal/dal/dao/files.go new file mode 100644 index 00000000..63bba119 --- /dev/null +++ b/backend/domain/upload/internal/dal/dao/files.go @@ -0,0 +1,113 @@ +/* + * 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 dao + +import ( + "context" + + "gorm.io/gorm" + + "github.com/coze-dev/coze-studio/backend/domain/upload/entity" + "github.com/coze-dev/coze-studio/backend/domain/upload/internal/dal/model" + "github.com/coze-dev/coze-studio/backend/domain/upload/internal/dal/query" + "github.com/coze-dev/coze-studio/backend/pkg/lang/slices" +) + +type FilesDAO struct { + DB *gorm.DB + Query *query.Query +} + +func NewFilesDAO(db *gorm.DB) *FilesDAO { + return &FilesDAO{ + DB: db, + Query: query.Use(db), + } +} + +func (dao *FilesDAO) Create(ctx context.Context, file *entity.File) error { + f := dao.fromEntityToModel(file) + return dao.Query.Files.WithContext(ctx).Create(f) +} + +func (dao *FilesDAO) BatchCreate(ctx context.Context, files []*entity.File) error { + if len(files) == 0 { + return nil + } + return dao.Query.Files.WithContext(ctx).CreateInBatches(slices.Transform(files, dao.fromEntityToModel), len(files)) +} + +func (dao *FilesDAO) Delete(ctx context.Context, id int64) error { + _, err := dao.Query.Files.WithContext(ctx).Where(dao.Query.Files.ID.Eq(id)).Delete() + return err +} + +func (dao *FilesDAO) GetByID(ctx context.Context, id int64) (*entity.File, error) { + file, err := dao.Query.Files.WithContext(ctx).Where(dao.Query.Files.ID.Eq(id)).First() + if err != nil { + return nil, err + } + return dao.fromModelToEntity(file), nil +} + +func (dao *FilesDAO) MGetByIDs(ctx context.Context, ids []int64) ([]*entity.File, error) { + if len(ids) == 0 { + return nil, nil + } + files, err := dao.Query.Files.WithContext(ctx).Where(dao.Query.Files.ID.In(ids...)).Find() + if err != nil { + return nil, err + } + return slices.Transform(files, dao.fromModelToEntity), nil +} + +func (dao *FilesDAO) fromModelToEntity(model *model.Files) *entity.File { + if model == nil { + return nil + } + return &entity.File{ + ID: model.ID, + Name: model.Name, + FileSize: model.FileSize, + TosURI: model.TosURI, + Status: entity.FileStatus(model.Status), + Comment: model.Comment, + Source: entity.FileSource(model.Source), + CreatorID: model.CreatorID, + CozeAccountID: model.CozeAccountID, + ContentType: model.ContentType, + CreatedAt: model.CreatedAt, + UpdatedAt: model.UpdatedAt, + } +} + +func (dao *FilesDAO) fromEntityToModel(entity *entity.File) *model.Files { + return &model.Files{ + ID: entity.ID, + Name: entity.Name, + FileSize: entity.FileSize, + TosURI: entity.TosURI, + Status: int32(entity.Status), + Comment: entity.Comment, + Source: int32(entity.Source), + CreatorID: entity.CreatorID, + CozeAccountID: entity.CozeAccountID, + ContentType: entity.ContentType, + CreatedAt: entity.CreatedAt, + UpdatedAt: entity.UpdatedAt, + } +} diff --git a/backend/domain/upload/internal/dal/model/files.gen.go b/backend/domain/upload/internal/dal/model/files.gen.go new file mode 100644 index 00000000..f42e8abf --- /dev/null +++ b/backend/domain/upload/internal/dal/model/files.gen.go @@ -0,0 +1,49 @@ +/* + * 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 gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package model + +import ( + "gorm.io/gorm" +) + +const TableNameFiles = "files" + +// Files file resource table +type Files struct { + ID int64 `gorm:"column:id;primaryKey;comment:id" json:"id"` // id + Name string `gorm:"column:name;not null;comment:file name" json:"name"` // file name + FileSize int64 `gorm:"column:file_size;not null;comment:file size" json:"file_size"` // file size + TosURI string `gorm:"column:tos_uri;not null;comment:TOS URI" json:"tos_uri"` // TOS URI + Status int32 `gorm:"column:status;not null;comment:status,0invalid,1valid" json:"status"` // status,0invalid,1valid + Comment string `gorm:"column:comment;not null;comment:file comment" json:"comment"` // file comment + Source int32 `gorm:"column:source;not null;comment:source:1 from API," json:"source"` // source:1 from API, + CreatorID string `gorm:"column:creator_id;not null;comment:creator id" json:"creator_id"` // creator id + ContentType string `gorm:"column:content_type;not null;comment:content type" json:"content_type"` // content type + CozeAccountID int64 `gorm:"column:coze_account_id;not null;comment:coze account id" json:"coze_account_id"` // coze account id + CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:Create Time in Milliseconds" json:"created_at"` // Create Time in Milliseconds + UpdatedAt int64 `gorm:"column:updated_at;not null;autoUpdateTime:milli;comment:Update Time in Milliseconds" json:"updated_at"` // Update Time in Milliseconds + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:Delete Time" json:"deleted_at"` // Delete Time +} + +// TableName Files's table name +func (*Files) TableName() string { + return TableNameFiles +} diff --git a/backend/domain/upload/internal/dal/query/files.gen.go b/backend/domain/upload/internal/dal/query/files.gen.go new file mode 100644 index 00000000..c6b9333c --- /dev/null +++ b/backend/domain/upload/internal/dal/query/files.gen.go @@ -0,0 +1,445 @@ +/* + * 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 gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/coze-dev/coze-studio/backend/domain/upload/internal/dal/model" +) + +func newFiles(db *gorm.DB, opts ...gen.DOOption) files { + _files := files{} + + _files.filesDo.UseDB(db, opts...) + _files.filesDo.UseModel(&model.Files{}) + + tableName := _files.filesDo.TableName() + _files.ALL = field.NewAsterisk(tableName) + _files.ID = field.NewInt64(tableName, "id") + _files.Name = field.NewString(tableName, "name") + _files.FileSize = field.NewInt64(tableName, "file_size") + _files.TosURI = field.NewString(tableName, "tos_uri") + _files.Status = field.NewInt32(tableName, "status") + _files.Comment = field.NewString(tableName, "comment") + _files.Source = field.NewInt32(tableName, "source") + _files.CreatorID = field.NewString(tableName, "creator_id") + _files.ContentType = field.NewString(tableName, "content_type") + _files.CozeAccountID = field.NewInt64(tableName, "coze_account_id") + _files.CreatedAt = field.NewInt64(tableName, "created_at") + _files.UpdatedAt = field.NewInt64(tableName, "updated_at") + _files.DeletedAt = field.NewField(tableName, "deleted_at") + + _files.fillFieldMap() + + return _files +} + +// files file resource table +type files struct { + filesDo + + ALL field.Asterisk + ID field.Int64 // id + Name field.String // file name + FileSize field.Int64 // file size + TosURI field.String // TOS URI + Status field.Int32 // status,0invalid,1valid + Comment field.String // file comment + Source field.Int32 // source:1 from API, + CreatorID field.String // creator id + ContentType field.String // content type + CozeAccountID field.Int64 // coze account id + CreatedAt field.Int64 // Create Time in Milliseconds + UpdatedAt field.Int64 // Update Time in Milliseconds + DeletedAt field.Field // Delete Time + + fieldMap map[string]field.Expr +} + +func (f files) Table(newTableName string) *files { + f.filesDo.UseTable(newTableName) + return f.updateTableName(newTableName) +} + +func (f files) As(alias string) *files { + f.filesDo.DO = *(f.filesDo.As(alias).(*gen.DO)) + return f.updateTableName(alias) +} + +func (f *files) updateTableName(table string) *files { + f.ALL = field.NewAsterisk(table) + f.ID = field.NewInt64(table, "id") + f.Name = field.NewString(table, "name") + f.FileSize = field.NewInt64(table, "file_size") + f.TosURI = field.NewString(table, "tos_uri") + f.Status = field.NewInt32(table, "status") + f.Comment = field.NewString(table, "comment") + f.Source = field.NewInt32(table, "source") + f.CreatorID = field.NewString(table, "creator_id") + f.ContentType = field.NewString(table, "content_type") + f.CozeAccountID = field.NewInt64(table, "coze_account_id") + f.CreatedAt = field.NewInt64(table, "created_at") + f.UpdatedAt = field.NewInt64(table, "updated_at") + f.DeletedAt = field.NewField(table, "deleted_at") + + f.fillFieldMap() + + return f +} + +func (f *files) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := f.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (f *files) fillFieldMap() { + f.fieldMap = make(map[string]field.Expr, 13) + f.fieldMap["id"] = f.ID + f.fieldMap["name"] = f.Name + f.fieldMap["file_size"] = f.FileSize + f.fieldMap["tos_uri"] = f.TosURI + f.fieldMap["status"] = f.Status + f.fieldMap["comment"] = f.Comment + f.fieldMap["source"] = f.Source + f.fieldMap["creator_id"] = f.CreatorID + f.fieldMap["content_type"] = f.ContentType + f.fieldMap["coze_account_id"] = f.CozeAccountID + f.fieldMap["created_at"] = f.CreatedAt + f.fieldMap["updated_at"] = f.UpdatedAt + f.fieldMap["deleted_at"] = f.DeletedAt +} + +func (f files) clone(db *gorm.DB) files { + f.filesDo.ReplaceConnPool(db.Statement.ConnPool) + return f +} + +func (f files) replaceDB(db *gorm.DB) files { + f.filesDo.ReplaceDB(db) + return f +} + +type filesDo struct{ gen.DO } + +type IFilesDo interface { + gen.SubQuery + Debug() IFilesDo + WithContext(ctx context.Context) IFilesDo + WithResult(fc func(tx gen.Dao)) gen.ResultInfo + ReplaceDB(db *gorm.DB) + ReadDB() IFilesDo + WriteDB() IFilesDo + As(alias string) gen.Dao + Session(config *gorm.Session) IFilesDo + Columns(cols ...field.Expr) gen.Columns + Clauses(conds ...clause.Expression) IFilesDo + Not(conds ...gen.Condition) IFilesDo + Or(conds ...gen.Condition) IFilesDo + Select(conds ...field.Expr) IFilesDo + Where(conds ...gen.Condition) IFilesDo + Order(conds ...field.Expr) IFilesDo + Distinct(cols ...field.Expr) IFilesDo + Omit(cols ...field.Expr) IFilesDo + Join(table schema.Tabler, on ...field.Expr) IFilesDo + LeftJoin(table schema.Tabler, on ...field.Expr) IFilesDo + RightJoin(table schema.Tabler, on ...field.Expr) IFilesDo + Group(cols ...field.Expr) IFilesDo + Having(conds ...gen.Condition) IFilesDo + Limit(limit int) IFilesDo + Offset(offset int) IFilesDo + Count() (count int64, err error) + Scopes(funcs ...func(gen.Dao) gen.Dao) IFilesDo + Unscoped() IFilesDo + Create(values ...*model.Files) error + CreateInBatches(values []*model.Files, batchSize int) error + Save(values ...*model.Files) error + First() (*model.Files, error) + Take() (*model.Files, error) + Last() (*model.Files, error) + Find() ([]*model.Files, error) + FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Files, err error) + FindInBatches(result *[]*model.Files, batchSize int, fc func(tx gen.Dao, batch int) error) error + Pluck(column field.Expr, dest interface{}) error + Delete(...*model.Files) (info gen.ResultInfo, err error) + Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + Updates(value interface{}) (info gen.ResultInfo, err error) + UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + UpdateColumns(value interface{}) (info gen.ResultInfo, err error) + UpdateFrom(q gen.SubQuery) gen.Dao + Attrs(attrs ...field.AssignExpr) IFilesDo + Assign(attrs ...field.AssignExpr) IFilesDo + Joins(fields ...field.RelationField) IFilesDo + Preload(fields ...field.RelationField) IFilesDo + FirstOrInit() (*model.Files, error) + FirstOrCreate() (*model.Files, error) + FindByPage(offset int, limit int) (result []*model.Files, count int64, err error) + ScanByPage(result interface{}, offset int, limit int) (count int64, err error) + Scan(result interface{}) (err error) + Returning(value interface{}, columns ...string) IFilesDo + UnderlyingDB() *gorm.DB + schema.Tabler +} + +func (f filesDo) Debug() IFilesDo { + return f.withDO(f.DO.Debug()) +} + +func (f filesDo) WithContext(ctx context.Context) IFilesDo { + return f.withDO(f.DO.WithContext(ctx)) +} + +func (f filesDo) ReadDB() IFilesDo { + return f.Clauses(dbresolver.Read) +} + +func (f filesDo) WriteDB() IFilesDo { + return f.Clauses(dbresolver.Write) +} + +func (f filesDo) Session(config *gorm.Session) IFilesDo { + return f.withDO(f.DO.Session(config)) +} + +func (f filesDo) Clauses(conds ...clause.Expression) IFilesDo { + return f.withDO(f.DO.Clauses(conds...)) +} + +func (f filesDo) Returning(value interface{}, columns ...string) IFilesDo { + return f.withDO(f.DO.Returning(value, columns...)) +} + +func (f filesDo) Not(conds ...gen.Condition) IFilesDo { + return f.withDO(f.DO.Not(conds...)) +} + +func (f filesDo) Or(conds ...gen.Condition) IFilesDo { + return f.withDO(f.DO.Or(conds...)) +} + +func (f filesDo) Select(conds ...field.Expr) IFilesDo { + return f.withDO(f.DO.Select(conds...)) +} + +func (f filesDo) Where(conds ...gen.Condition) IFilesDo { + return f.withDO(f.DO.Where(conds...)) +} + +func (f filesDo) Order(conds ...field.Expr) IFilesDo { + return f.withDO(f.DO.Order(conds...)) +} + +func (f filesDo) Distinct(cols ...field.Expr) IFilesDo { + return f.withDO(f.DO.Distinct(cols...)) +} + +func (f filesDo) Omit(cols ...field.Expr) IFilesDo { + return f.withDO(f.DO.Omit(cols...)) +} + +func (f filesDo) Join(table schema.Tabler, on ...field.Expr) IFilesDo { + return f.withDO(f.DO.Join(table, on...)) +} + +func (f filesDo) LeftJoin(table schema.Tabler, on ...field.Expr) IFilesDo { + return f.withDO(f.DO.LeftJoin(table, on...)) +} + +func (f filesDo) RightJoin(table schema.Tabler, on ...field.Expr) IFilesDo { + return f.withDO(f.DO.RightJoin(table, on...)) +} + +func (f filesDo) Group(cols ...field.Expr) IFilesDo { + return f.withDO(f.DO.Group(cols...)) +} + +func (f filesDo) Having(conds ...gen.Condition) IFilesDo { + return f.withDO(f.DO.Having(conds...)) +} + +func (f filesDo) Limit(limit int) IFilesDo { + return f.withDO(f.DO.Limit(limit)) +} + +func (f filesDo) Offset(offset int) IFilesDo { + return f.withDO(f.DO.Offset(offset)) +} + +func (f filesDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IFilesDo { + return f.withDO(f.DO.Scopes(funcs...)) +} + +func (f filesDo) Unscoped() IFilesDo { + return f.withDO(f.DO.Unscoped()) +} + +func (f filesDo) Create(values ...*model.Files) error { + if len(values) == 0 { + return nil + } + return f.DO.Create(values) +} + +func (f filesDo) CreateInBatches(values []*model.Files, batchSize int) error { + return f.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (f filesDo) Save(values ...*model.Files) error { + if len(values) == 0 { + return nil + } + return f.DO.Save(values) +} + +func (f filesDo) First() (*model.Files, error) { + if result, err := f.DO.First(); err != nil { + return nil, err + } else { + return result.(*model.Files), nil + } +} + +func (f filesDo) Take() (*model.Files, error) { + if result, err := f.DO.Take(); err != nil { + return nil, err + } else { + return result.(*model.Files), nil + } +} + +func (f filesDo) Last() (*model.Files, error) { + if result, err := f.DO.Last(); err != nil { + return nil, err + } else { + return result.(*model.Files), nil + } +} + +func (f filesDo) Find() ([]*model.Files, error) { + result, err := f.DO.Find() + return result.([]*model.Files), err +} + +func (f filesDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Files, err error) { + buf := make([]*model.Files, 0, batchSize) + err = f.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (f filesDo) FindInBatches(result *[]*model.Files, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return f.DO.FindInBatches(result, batchSize, fc) +} + +func (f filesDo) Attrs(attrs ...field.AssignExpr) IFilesDo { + return f.withDO(f.DO.Attrs(attrs...)) +} + +func (f filesDo) Assign(attrs ...field.AssignExpr) IFilesDo { + return f.withDO(f.DO.Assign(attrs...)) +} + +func (f filesDo) Joins(fields ...field.RelationField) IFilesDo { + for _, _f := range fields { + f = *f.withDO(f.DO.Joins(_f)) + } + return &f +} + +func (f filesDo) Preload(fields ...field.RelationField) IFilesDo { + for _, _f := range fields { + f = *f.withDO(f.DO.Preload(_f)) + } + return &f +} + +func (f filesDo) FirstOrInit() (*model.Files, error) { + if result, err := f.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*model.Files), nil + } +} + +func (f filesDo) FirstOrCreate() (*model.Files, error) { + if result, err := f.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*model.Files), nil + } +} + +func (f filesDo) FindByPage(offset int, limit int) (result []*model.Files, count int64, err error) { + result, err = f.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = f.Offset(-1).Limit(-1).Count() + return +} + +func (f filesDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = f.Count() + if err != nil { + return + } + + err = f.Offset(offset).Limit(limit).Scan(result) + return +} + +func (f filesDo) Scan(result interface{}) (err error) { + return f.DO.Scan(result) +} + +func (f filesDo) Delete(models ...*model.Files) (result gen.ResultInfo, err error) { + return f.DO.Delete(models) +} + +func (f *filesDo) withDO(do gen.Dao) *filesDo { + f.DO = *do.(*gen.DO) + return f +} diff --git a/backend/domain/upload/internal/dal/query/gen.go b/backend/domain/upload/internal/dal/query/gen.go new file mode 100644 index 00000000..bd21f9bf --- /dev/null +++ b/backend/domain/upload/internal/dal/query/gen.go @@ -0,0 +1,119 @@ +/* + * 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 gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + "database/sql" + + "gorm.io/gorm" + + "gorm.io/gen" + + "gorm.io/plugin/dbresolver" +) + +var ( + Q = new(Query) + Files *files +) + +func SetDefault(db *gorm.DB, opts ...gen.DOOption) { + *Q = *Use(db, opts...) + Files = &Q.Files +} + +func Use(db *gorm.DB, opts ...gen.DOOption) *Query { + return &Query{ + db: db, + Files: newFiles(db, opts...), + } +} + +type Query struct { + db *gorm.DB + + Files files +} + +func (q *Query) Available() bool { return q.db != nil } + +func (q *Query) clone(db *gorm.DB) *Query { + return &Query{ + db: db, + Files: q.Files.clone(db), + } +} + +func (q *Query) ReadDB() *Query { + return q.ReplaceDB(q.db.Clauses(dbresolver.Read)) +} + +func (q *Query) WriteDB() *Query { + return q.ReplaceDB(q.db.Clauses(dbresolver.Write)) +} + +func (q *Query) ReplaceDB(db *gorm.DB) *Query { + return &Query{ + db: db, + Files: q.Files.replaceDB(db), + } +} + +type queryCtx struct { + Files IFilesDo +} + +func (q *Query) WithContext(ctx context.Context) *queryCtx { + return &queryCtx{ + Files: q.Files.WithContext(ctx), + } +} + +func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error { + return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...) +} + +func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx { + tx := q.db.Begin(opts...) + return &QueryTx{Query: q.clone(tx), Error: tx.Error} +} + +type QueryTx struct { + *Query + Error error +} + +func (q *QueryTx) Commit() error { + return q.db.Commit().Error +} + +func (q *QueryTx) Rollback() error { + return q.db.Rollback().Error +} + +func (q *QueryTx) SavePoint(name string) error { + return q.db.SavePoint(name).Error +} + +func (q *QueryTx) RollbackTo(name string) error { + return q.db.RollbackTo(name).Error +} diff --git a/backend/domain/upload/repository/repository.go b/backend/domain/upload/repository/repository.go new file mode 100644 index 00000000..98d83e33 --- /dev/null +++ b/backend/domain/upload/repository/repository.go @@ -0,0 +1,39 @@ +/* + * 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 repository + +import ( + "context" + + "gorm.io/gorm" + + "github.com/coze-dev/coze-studio/backend/domain/upload/entity" + "github.com/coze-dev/coze-studio/backend/domain/upload/internal/dal/dao" +) + +func NewFilesRepo(db *gorm.DB) FilesRepo { + return dao.NewFilesDAO(db) +} + +//go:generate mockgen -destination ../internal/mock/dal/dao/knowledge_document.go --package dao -source knowledge_document.go +type FilesRepo interface { + Create(ctx context.Context, file *entity.File) error + BatchCreate(ctx context.Context, files []*entity.File) error + Delete(ctx context.Context, id int64) error + GetByID(ctx context.Context, id int64) (*entity.File, error) + MGetByIDs(ctx context.Context, ids []int64) ([]*entity.File, error) +} diff --git a/backend/domain/upload/service/interface.go b/backend/domain/upload/service/interface.go new file mode 100644 index 00000000..d602d77f --- /dev/null +++ b/backend/domain/upload/service/interface.go @@ -0,0 +1,60 @@ +/* + * 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 service + +import ( + "context" + + "github.com/coze-dev/coze-studio/backend/domain/upload/entity" +) + +type UploadService interface { + UploadFile(ctx context.Context, req *UploadFileRequest) (resp *UploadFileResponse, err error) + UploadFiles(ctx context.Context, req *UploadFilesRequest) (resp *UploadFilesResponse, err error) + GetFiles(ctx context.Context, req *GetFilesRequest) (resp *GetFilesResponse, err error) + GetFile(ctx context.Context, req *GetFileRequest) (resp *GetFileResponse, err error) +} + +type UploadFileRequest struct { + File *entity.File `json:"file"` +} +type UploadFileResponse struct { + File *entity.File `json:"file"` +} +type UploadFilesRequest struct { + Files []*entity.File `json:"files"` +} + +type UploadFilesResponse struct { + Files []*entity.File `json:"files"` +} + +type GetFilesRequest struct { + IDs []int64 `json:"ids"` +} + +type GetFilesResponse struct { + Files []*entity.File `json:"files"` +} + +type GetFileRequest struct { + ID int64 `json:"id"` +} + +type GetFileResponse struct { + File *entity.File `json:"file"` +} diff --git a/backend/domain/upload/service/service.go b/backend/domain/upload/service/service.go new file mode 100644 index 00000000..9c43626d --- /dev/null +++ b/backend/domain/upload/service/service.go @@ -0,0 +1,97 @@ +/* + * 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 service + +import ( + "context" + + "gorm.io/gorm" + + "github.com/coze-dev/coze-studio/backend/domain/upload/repository" + "github.com/coze-dev/coze-studio/backend/infra/contract/idgen" + "github.com/coze-dev/coze-studio/backend/infra/impl/storage" + "github.com/coze-dev/coze-studio/backend/pkg/errorx" + "github.com/coze-dev/coze-studio/backend/types/errno" +) + +type uploadSVC struct { + fileRepo repository.FilesRepo + idgen idgen.IDGenerator + oss storage.Storage +} + +func NewUploadSVC(db *gorm.DB, idgen idgen.IDGenerator, oss storage.Storage) UploadService { + return &uploadSVC{fileRepo: repository.NewFilesRepo(db), idgen: idgen, oss: oss} +} + +func (u *uploadSVC) UploadFile(ctx context.Context, req *UploadFileRequest) (resp *UploadFileResponse, err error) { + resp = &UploadFileResponse{} + if req.File.ID == 0 { + req.File.ID, err = u.idgen.GenID(ctx) + if err != nil { + return nil, errorx.New(errno.ErrIDGenError) + } + } + err = u.fileRepo.Create(ctx, req.File) + if err != nil { + return nil, errorx.WrapByCode(err, errno.ErrUploadSystemErrorCode) + } + resp.File = req.File + return +} + +func (u *uploadSVC) UploadFiles(ctx context.Context, req *UploadFilesRequest) (resp *UploadFilesResponse, err error) { + resp = &UploadFilesResponse{} + for _, file := range req.Files { + if file.ID == 0 { + file.ID, err = u.idgen.GenID(ctx) + if err != nil { + return nil, errorx.New(errno.ErrIDGenError) + } + } + } + err = u.fileRepo.BatchCreate(ctx, req.Files) + if err != nil { + return nil, errorx.WrapByCode(err, errno.ErrUploadSystemErrorCode) + } + resp.Files = req.Files + return +} + +func (u *uploadSVC) GetFiles(ctx context.Context, req *GetFilesRequest) (resp *GetFilesResponse, err error) { + resp = &GetFilesResponse{} + resp.Files, err = u.fileRepo.MGetByIDs(ctx, req.IDs) + if err != nil { + return nil, errorx.WrapByCode(err, errno.ErrUploadSystemErrorCode) + } + return +} + +func (u *uploadSVC) GetFile(ctx context.Context, req *GetFileRequest) (resp *GetFileResponse, err error) { + resp = &GetFileResponse{} + resp.File, err = u.fileRepo.GetByID(ctx, req.ID) + if err != nil { + return nil, errorx.WrapByCode(err, errno.ErrUploadSystemErrorCode) + } + if resp.File != nil { + url, err := u.oss.GetObjectUrl(ctx, resp.File.TosURI) + if err == nil { + resp.File.Url = url + } + } + return +} diff --git a/backend/domain/workflow/component_interface.go b/backend/domain/workflow/component_interface.go index 3a8e3bcf..9ebc924f 100644 --- a/backend/domain/workflow/component_interface.go +++ b/backend/domain/workflow/component_interface.go @@ -24,6 +24,7 @@ import ( "github.com/cloudwego/eino/schema" workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + conventity "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity" "github.com/coze-dev/coze-studio/backend/domain/workflow/config" "github.com/coze-dev/coze-studio/backend/domain/workflow/entity" "github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo" @@ -55,12 +56,41 @@ type AsTool interface { allInterruptEvents map[string]*entity.ToolInterruptEvent) compose.Option } +type ChatFlowRole interface { + CreateChatFlowRole(ctx context.Context, role *vo.ChatFlowRoleCreate) (int64, error) + UpdateChatFlowRole(ctx context.Context, workflowID int64, role *vo.ChatFlowRoleUpdate) error + GetChatFlowRole(ctx context.Context, workflowID int64, version string) (*entity.ChatFlowRole, error) + DeleteChatFlowRole(ctx context.Context, id int64, workflowID int64) error + PublishChatFlowRole(ctx context.Context, policy *vo.PublishRolePolicy) error +} + +type Conversation interface { + CreateDraftConversationTemplate(ctx context.Context, template *vo.CreateConversationTemplateMeta) (int64, error) + UpdateDraftConversationTemplateName(ctx context.Context, appID int64, userID int64, templateID int64, name string) error + DeleteDraftConversationTemplate(ctx context.Context, templateID int64, wfID2ConversationName map[int64]string) (int64, error) + CheckWorkflowsToReplace(ctx context.Context, appID int64, templateID int64) ([]*entity.Workflow, error) + DeleteDynamicConversation(ctx context.Context, env vo.Env, templateID int64) (int64, error) + ListConversationTemplate(ctx context.Context, env vo.Env, policy *vo.ListConversationTemplatePolicy) ([]*entity.ConversationTemplate, error) + MGetStaticConversation(ctx context.Context, env vo.Env, userID, connectorID int64, templateIDs []int64) ([]*entity.StaticConversation, error) + ListDynamicConversation(ctx context.Context, env vo.Env, policy *vo.ListConversationPolicy) ([]*entity.DynamicConversation, error) + ReleaseConversationTemplate(ctx context.Context, appID int64, version string) error + InitApplicationDefaultConversationTemplate(ctx context.Context, spaceID int64, appID int64, userID int64) error + GetOrCreateConversation(ctx context.Context, env vo.Env, appID, connectorID, userID int64, conversationName string) (int64, int64, error) + UpdateConversation(ctx context.Context, env vo.Env, appID, connectorID, userID int64, conversationName string) (int64, error) + GetTemplateByName(ctx context.Context, env vo.Env, appID int64, templateName string) (*entity.ConversationTemplate, bool, error) + GetDynamicConversationByName(ctx context.Context, env vo.Env, appID, connectorID, userID int64, name string) (*entity.DynamicConversation, bool, error) + GetConversationNameByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (string, bool, error) +} + type InterruptEventStore interface { SaveInterruptEvents(ctx context.Context, wfExeID int64, events []*entity.InterruptEvent) error GetFirstInterruptEvent(ctx context.Context, wfExeID int64) (*entity.InterruptEvent, bool, error) UpdateFirstInterruptEvent(ctx context.Context, wfExeID int64, event *entity.InterruptEvent) error PopFirstInterruptEvent(ctx context.Context, wfExeID int64) (*entity.InterruptEvent, bool, error) ListInterruptEvents(ctx context.Context, wfExeID int64) ([]*entity.InterruptEvent, error) + + BindConvRelatedInfo(ctx context.Context, convID int64, info entity.ConvRelatedInfo) error + GetConvRelatedInfo(ctx context.Context, convID int64) (*entity.ConvRelatedInfo, bool, func() error, error) } type CancelSignalStore interface { @@ -93,6 +123,33 @@ type ToolFromWorkflow interface { GetWorkflow() *entity.Workflow } +type ConversationIDGenerator func(ctx context.Context, appID int64, userID, connectorID int64) (*conventity.Conversation, error) + +type ConversationRepository interface { + CreateDraftConversationTemplate(ctx context.Context, template *vo.CreateConversationTemplateMeta) (int64, error) + UpdateDraftConversationTemplateName(ctx context.Context, templateID int64, name string) error + DeleteDraftConversationTemplate(ctx context.Context, templateID int64) (int64, error) + GetConversationTemplate(ctx context.Context, env vo.Env, policy vo.GetConversationTemplatePolicy) (*entity.ConversationTemplate, bool, error) + DeleteDynamicConversation(ctx context.Context, env vo.Env, id int64) (int64, error) + ListConversationTemplate(ctx context.Context, env vo.Env, policy *vo.ListConversationTemplatePolicy) ([]*entity.ConversationTemplate, error) + MGetStaticConversation(ctx context.Context, env vo.Env, userID, connectorID int64, templateIDs []int64) ([]*entity.StaticConversation, error) + GetOrCreateStaticConversation(ctx context.Context, env vo.Env, idGen ConversationIDGenerator, meta *vo.CreateStaticConversation) (int64, int64, bool, error) + GetOrCreateDynamicConversation(ctx context.Context, env vo.Env, idGen ConversationIDGenerator, meta *vo.CreateDynamicConversation) (int64, int64, bool, error) + GetDynamicConversationByName(ctx context.Context, env vo.Env, appID, connectorID, userID int64, name string) (*entity.DynamicConversation, bool, error) + GetStaticConversationByTemplateID(ctx context.Context, env vo.Env, userID, connectorID, templateID int64) (*entity.StaticConversation, bool, error) + ListDynamicConversation(ctx context.Context, env vo.Env, policy *vo.ListConversationPolicy) ([]*entity.DynamicConversation, error) + BatchCreateOnlineConversationTemplate(ctx context.Context, templates []*entity.ConversationTemplate, version string) error + UpdateDynamicConversationNameByID(ctx context.Context, env vo.Env, templateID int64, name string) error + UpdateStaticConversation(ctx context.Context, env vo.Env, templateID int64, connectorID int64, userID int64, newConversationID int64) error + UpdateDynamicConversation(ctx context.Context, env vo.Env, conversationID, newConversationID int64) error + CopyTemplateConversationByAppID(ctx context.Context, appID int64, toAppID int64) error + GetStaticConversationByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (string, bool, error) + GetDynamicConversationByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (*entity.DynamicConversation, bool, error) +} type WorkflowConfig interface { GetNodeOfCodeConfig() *config.NodeOfCodeConfig } + +type Suggester interface { + Suggest(ctx context.Context, input *vo.SuggestInfo) ([]string, error) +} diff --git a/backend/domain/workflow/config/workflow_config.go b/backend/domain/workflow/config/workflow_config.go index f18f30d1..76faffca 100644 --- a/backend/domain/workflow/config/workflow_config.go +++ b/backend/domain/workflow/config/workflow_config.go @@ -20,7 +20,7 @@ type WorkflowConfig struct { NodeOfCodeConfig *NodeOfCodeConfig `yaml:"NodeOfCodeConfig"` } -func (w WorkflowConfig) GetNodeOfCodeConfig() *NodeOfCodeConfig { +func (w *WorkflowConfig) GetNodeOfCodeConfig() *NodeOfCodeConfig { return w.NodeOfCodeConfig } diff --git a/backend/domain/workflow/entity/chatflow_role.go b/backend/domain/workflow/entity/chatflow_role.go new file mode 100644 index 00000000..4c0de973 --- /dev/null +++ b/backend/domain/workflow/entity/chatflow_role.go @@ -0,0 +1,37 @@ +/* + * 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 + +import "time" + +type ChatFlowRole struct { + ID int64 + WorkflowID int64 + ConnectorID int64 + Name string + Description string + Version string + AvatarUri string + BackgroundImageInfo string + OnboardingInfo string + SuggestReplyInfo string + AudioConfig string + UserInputConfig string + CreatorID int64 + CreatedAt time.Time + UpdatedAt time.Time +} diff --git a/backend/domain/workflow/entity/conversation.go b/backend/domain/workflow/entity/conversation.go new file mode 100644 index 00000000..5710958c --- /dev/null +++ b/backend/domain/workflow/entity/conversation.go @@ -0,0 +1,39 @@ +/* + * 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 + +type ConversationTemplate struct { + SpaceID int64 + AppID int64 + Name string + TemplateID int64 +} + +type StaticConversation struct { + UserID int64 + ConnectorID int64 + TemplateID int64 + ConversationID int64 +} + +type DynamicConversation struct { + ID int64 + UserID int64 + ConnectorID int64 + ConversationID int64 + Name string +} diff --git a/backend/domain/workflow/entity/interrupt_event.go b/backend/domain/workflow/entity/interrupt_event.go index e2c4e606..4eab9eb0 100644 --- a/backend/domain/workflow/entity/interrupt_event.go +++ b/backend/domain/workflow/entity/interrupt_event.go @@ -74,3 +74,9 @@ type ToolInterruptEvent struct { ExecuteID int64 *InterruptEvent } + +type ConvRelatedInfo struct { + EventID int64 + ExecID int64 + NodeType NodeType +} diff --git a/backend/domain/workflow/entity/node_meta.go b/backend/domain/workflow/entity/node_meta.go index 91a6e10b..59ad20b2 100644 --- a/backend/domain/workflow/entity/node_meta.go +++ b/backend/domain/workflow/entity/node_meta.go @@ -144,8 +144,11 @@ const ( NodeTypeCodeRunner NodeType = "CodeRunner" NodeTypePlugin NodeType = "Plugin" NodeTypeCreateConversation NodeType = "CreateConversation" + NodeTypeConversationList NodeType = "ConversationList" NodeTypeMessageList NodeType = "MessageList" - NodeTypeClearMessage NodeType = "ClearMessage" + NodeTypeCreateMessage NodeType = "CreateMessage" + NodeTypeEditMessage NodeType = "EditMessage" + NodeTypeDeleteMessage NodeType = "DeleteMessage" NodeTypeLambda NodeType = "Lambda" NodeTypeLLM NodeType = "LLM" NodeTypeSelector NodeType = "Selector" @@ -153,6 +156,10 @@ const ( NodeTypeSubWorkflow NodeType = "SubWorkflow" NodeTypeJsonSerialization NodeType = "JsonSerialization" NodeTypeJsonDeserialization NodeType = "JsonDeserialization" + NodeTypeConversationUpdate NodeType = "ConversationUpdate" + NodeTypeConversationDelete NodeType = "ConversationDelete" + NodeTypeClearConversationHistory NodeType = "ClearConversationHistory" + NodeTypeConversationHistory NodeType = "ConversationHistory" NodeTypeComment NodeType = "Comment" ) @@ -272,6 +279,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{ PostFillNil: true, InputSourceAware: true, MayUseChatModel: true, + UseCtxCache: true, }, EnUSName: "LLM", EnUSDescription: "Invoke the large language model, generate responses using variables and prompt words.", @@ -324,6 +332,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{ ExecutableMeta: ExecutableMeta{ PreFillZero: true, PostFillNil: true, + UseCtxCache: true, }, EnUSName: "Knowledge retrieval", EnUSDescription: "In the selected knowledge, the best matching information is recalled based on the input variable and returned as an Array.", @@ -487,6 +496,7 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{ PreFillZero: true, PostFillNil: true, MayUseChatModel: true, + UseCtxCache: true, }, EnUSName: "Intent recognition", EnUSDescription: "Used for recognizing the intent in user input and matching it with preset intent options.", @@ -593,7 +603,6 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{ Color: "#F2B600", IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-List.jpeg", SupportBatch: false, - Disabled: true, ExecutableMeta: ExecutableMeta{ PreFillZero: true, PostFillNil: true, @@ -601,16 +610,15 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{ EnUSName: "Query message list", EnUSDescription: "Used to query the message list", }, - NodeTypeClearMessage: { + NodeTypeClearConversationHistory: { ID: 38, - Key: NodeTypeClearMessage, - Name: "清除上下文", - Category: "conversation_history", + Key: NodeTypeClearConversationHistory, + Name: "清空会话历史", + Category: "conversation_history", // Mapped from cate_list Desc: "用于清空会话历史,清空后LLM看到的会话历史为空", Color: "#F2B600", IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-Delete.jpeg", - SupportBatch: false, - Disabled: true, + SupportBatch: false, // supportBatch: 1 ExecutableMeta: ExecutableMeta{ PreFillZero: true, PostFillNil: true, @@ -627,7 +635,6 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{ Color: "#F2B600", IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-Create.jpeg", SupportBatch: false, - Disabled: true, ExecutableMeta: ExecutableMeta{ PreFillZero: true, PostFillNil: true, @@ -730,6 +737,118 @@ var NodeTypeMetas = map[NodeType]*NodeTypeMeta{ EnUSName: "Add Data", EnUSDescription: "Add new data records to the table, and insert them into the database after the user enters the data content", }, + NodeTypeConversationUpdate: { + ID: 51, + Name: "修改会话", + Key: NodeTypeConversationUpdate, + Category: "conversation_management", + Desc: "用于修改会话的名字", + Color: "#F2B600", + IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-编辑会话.jpg", + SupportBatch: false, + ExecutableMeta: ExecutableMeta{ + PreFillZero: true, + PostFillNil: true, + }, + EnUSName: "Edit Conversation", + EnUSDescription: "Used to modify the name of a conversation.", + }, + + NodeTypeConversationDelete: { + ID: 52, + Name: "删除会话", + Key: NodeTypeConversationDelete, + Category: "conversation_management", + Desc: "用于删除会话", + Color: "#F2B600", + IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-删除会话.jpg", + SupportBatch: false, + ExecutableMeta: ExecutableMeta{ + PreFillZero: true, + PostFillNil: true, + }, + EnUSName: "Delete Conversation", + EnUSDescription: "Used to delete a conversation.", + }, + NodeTypeConversationList: { + ID: 53, + Name: "查询会话列表", + Key: NodeTypeConversationList, + Category: "conversation_management", + Desc: "用于查询所有会话,包含静态会话、动态会话", + Color: "#F2B600", + IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-查询会话.jpg", + SupportBatch: false, + ExecutableMeta: ExecutableMeta{ + PostFillNil: true, + }, + EnUSName: "Query Conversation List", + EnUSDescription: "Used to query all conversations, including static conversations and dynamic conversations", + }, + NodeTypeConversationHistory: { + ID: 54, + Name: "查询会话历史", + Key: NodeTypeConversationHistory, + Category: "conversation_history", // Mapped from cate_list + Desc: "用于查询会话历史,返回LLM可见的会话消息", + Color: "#F2B600", + IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-查询会话历史.jpg", + SupportBatch: false, + ExecutableMeta: ExecutableMeta{ + PreFillZero: true, + PostFillNil: true, + }, + EnUSName: "Query Conversation History", + EnUSDescription: "Used to query conversation history, returns conversation messages visible to the LLM", + }, + NodeTypeCreateMessage: { + ID: 55, + Name: "创建消息", + Key: NodeTypeCreateMessage, + Category: "message", + Desc: "用于创建消息", + Color: "#F2B600", + IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-创建消息.jpg", + SupportBatch: false, // supportBatch: 1 + ExecutableMeta: ExecutableMeta{ + PreFillZero: true, + PostFillNil: true, + }, + EnUSName: "Create message", + EnUSDescription: "Used to create messages", + }, + NodeTypeEditMessage: { + ID: 56, + Name: "修改消息", + Key: NodeTypeEditMessage, + Category: "message", + Desc: "用于修改消息", + Color: "#F2B600", + IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-修改消息.jpg", + SupportBatch: false, // supportBatch: 1 + ExecutableMeta: ExecutableMeta{ + PreFillZero: true, + PostFillNil: true, + }, + EnUSName: "Edit message", + EnUSDescription: "Used to edit messages", + }, + NodeTypeDeleteMessage: { + ID: 57, + Name: "删除消息", + Key: NodeTypeDeleteMessage, + Category: "message", + Desc: "用于删除消息", + Color: "#F2B600", + IconURL: "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-删除消息.jpg", + SupportBatch: false, // supportBatch: 1 + ExecutableMeta: ExecutableMeta{ + PreFillZero: true, + PostFillNil: true, + }, + EnUSName: "Delete message", + EnUSDescription: "Used to delete messages", + }, NodeTypeJsonSerialization: { // ID is the unique identifier of this node type. Used in various front-end APIs. ID: 58, diff --git a/backend/domain/workflow/entity/vo/canvas.go b/backend/domain/workflow/entity/vo/canvas.go index 14bf6356..e9373e2d 100644 --- a/backend/domain/workflow/entity/vo/canvas.go +++ b/backend/domain/workflow/entity/vo/canvas.go @@ -17,6 +17,8 @@ package vo import ( + "fmt" + model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/modelmgr" "github.com/coze-dev/coze-studio/backend/api/model/workflow" "github.com/coze-dev/coze-studio/backend/pkg/i18n" @@ -108,7 +110,8 @@ type Data struct { type Inputs struct { // InputParameters are the fields defined by user for this particular node. InputParameters []*Param `json:"inputParameters"` - + // ChatHistorySetting configures the chat history setting for this node in chatflow mode. + ChatHistorySetting *ChatHistorySetting `json:"chatHistorySetting,omitempty"` // SettingOnError configures common error handling strategy for nodes. // NOTE: enable in frontend node's form first. SettingOnError *SettingOnError `json:"settingOnError,omitempty"` @@ -432,9 +435,8 @@ type DatabaseInfo struct { } type IntentDetector struct { - ChatHistorySetting *ChatHistorySetting `json:"chatHistorySetting,omitempty"` - Intents []*Intent `json:"intents,omitempty"` - Mode string `json:"mode,omitempty"` + Intents []*Intent `json:"intents,omitempty"` + Mode string `json:"mode,omitempty"` } type ChatHistorySetting struct { EnableChatHistory bool `json:"enableChatHistory,omitempty"` @@ -826,6 +828,133 @@ const defaultEnUSInitCanvasJsonSchema = `{ } }` +const defaultZhCNInitCanvasJsonSchemaChat = `{ + "nodes": [{ + "id": "100001", + "type": "1", + "meta": { + "position": { + "x": 0, + "y": 0 + } + }, + "data": { + "outputs": [{ + "type": "string", + "name": "USER_INPUT", + "required": true + }, { + "type": "string", + "name": "CONVERSATION_NAME", + "required": false, + "description": "本次请求绑定的会话,会自动写入消息、会从该会话读对话历史。", + "defaultValue": "%s" + }], + "nodeMeta": { + "title": "开始", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start.png", + "description": "工作流的起始节点,用于设定启动工作流需要的信息", + "subTitle": "" + } + } + }, { + "id": "900001", + "type": "2", + "meta": { + "position": { + "x": 1000, + "y": 0 + } + }, + "data": { + "nodeMeta": { + "title": "结束", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End.png", + "description": "工作流的最终节点,用于返回工作流运行后的结果信息", + "subTitle": "" + }, + "inputs": { + "terminatePlan": "useAnswerContent", + "streamingOutput": true, + "inputParameters": [{ + "name": "output", + "input": { + "type": "string", + "value": { + "type": "ref" + } + } + }] + } + } + }] +}` +const defaultEnUSInitCanvasJsonSchemaChat = `{ + "nodes": [{ + "id": "100001", + "type": "1", + "meta": { + "position": { + "x": 0, + "y": 0 + } + }, + "data": { + "outputs": [{ + "type": "string", + "name": "USER_INPUT", + "required": true + }, { + "type": "string", + "name": "CONVERSATION_NAME", + "required": false, + "description": "The conversation bound to this request will automatically write messages and read conversation history from that conversation.", + "defaultValue": "%s" + }], + "nodeMeta": { + "title": "Start", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start.png", + "description": "The starting node of the workflow, used to set the information needed to initiate the workflow.", + "subTitle": "" + } + } + }, { + "id": "900001", + "type": "2", + "meta": { + "position": { + "x": 1000, + "y": 0 + } + }, + "data": { + "nodeMeta": { + "title": "End", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End.png", + "description": "The final node of the workflow, used to return the result information after the workflow runs.", + "subTitle": "" + }, + "inputs": { + "terminatePlan": "useAnswerContent", + "streamingOutput": true, + "inputParameters": [{ + "name": "output", + "input": { + "type": "string", + "value": { + "type": "ref" + } + } + }] + } + } + }] +}` + func GetDefaultInitCanvasJsonSchema(locale i18n.Locale) string { return ternary.IFElse(locale == i18n.LocaleEN, defaultEnUSInitCanvasJsonSchema, defaultZhCNInitCanvasJsonSchema) } + +func GetDefaultInitCanvasJsonSchemaChat(locale i18n.Locale, name string) string { + return ternary.IFElse(locale == i18n.LocaleEN, fmt.Sprintf(defaultEnUSInitCanvasJsonSchemaChat, name), fmt.Sprintf(defaultZhCNInitCanvasJsonSchemaChat, name)) +} diff --git a/backend/domain/workflow/entity/vo/chat_flow_role.go b/backend/domain/workflow/entity/vo/chat_flow_role.go new file mode 100644 index 00000000..9927c4f2 --- /dev/null +++ b/backend/domain/workflow/entity/vo/chat_flow_role.go @@ -0,0 +1,48 @@ +/* + * 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 vo + +type ChatFlowRoleCreate struct { + WorkflowID int64 + CreatorID int64 + Name string + Description string + AvatarUri string + BackgroundImageInfo string + OnboardingInfo string + SuggestReplyInfo string + AudioConfig string + UserInputConfig string +} + +type ChatFlowRoleUpdate struct { + WorkflowID int64 + Name *string + Description *string + AvatarUri *string + BackgroundImageInfo *string + OnboardingInfo *string + SuggestReplyInfo *string + AudioConfig *string + UserInputConfig *string +} + +type PublishRolePolicy struct { + WorkflowID int64 + CreatorID int64 + Version string +} diff --git a/backend/domain/workflow/entity/vo/chatflow.go b/backend/domain/workflow/entity/vo/chatflow.go new file mode 100644 index 00000000..133b7a6f --- /dev/null +++ b/backend/domain/workflow/entity/vo/chatflow.go @@ -0,0 +1,84 @@ +/* + * 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 vo + +import "github.com/cloudwego/eino/schema" + +type ChatFlowEvent string + +const ( + ChatFlowCreated ChatFlowEvent = "conversation.chat.created" + ChatFlowInProgress ChatFlowEvent = "conversation.chat.in_progress" + ChatFlowCompleted ChatFlowEvent = "conversation.chat.completed" + ChatFlowFailed ChatFlowEvent = "conversation.chat.failed" + ChatFlowRequiresAction ChatFlowEvent = "conversation.chat.requires_action" + ChatFlowError ChatFlowEvent = "error" + ChatFlowDone ChatFlowEvent = "done" + ChatFlowMessageDelta ChatFlowEvent = "conversation.message.delta" + ChatFlowMessageCompleted ChatFlowEvent = "conversation.message.completed" +) + +type Usage struct { + TokenCount *int32 `form:"token_count" json:"token_count,omitempty"` + OutputTokens *int32 `form:"output_count" json:"output_count,omitempty"` + InputTokens *int32 `form:"input_count" json:"input_count,omitempty"` +} + +type Status string + +const ( + Created Status = "created" + InProgress Status = "in_progress" + Completed Status = "completed" + Failed Status = "failed" + RequiresAction Status = "requires_action" + Canceled Status = "canceled" +) + +type ChatFlowDetail struct { + ID string `json:"id,omitempty"` + ConversationID string `json:"conversation_id,omitempty"` + BotID string `json:"bot_id,omitempty"` + Status Status `json:"status,omitempty"` + Usage *Usage `json:"usage,omitempty"` + ExecuteID string `json:"execute_id,omitempty"` + SectionID string `json:"section_id"` +} + +type MessageDetail struct { + ID string `json:"id"` + ChatID string `json:"chat_id"` + ConversationID string `json:"conversation_id"` + BotID string `json:"bot_id"` + Role string `json:"role"` + Type string `json:"type"` + Content string `json:"content"` + ContentType string `json:"content_type"` + SectionID string `json:"section_id"` +} + +type ErrorDetail struct { + Code string `form:"code,required" json:"code,required"` + Msg string `form:"msg,required" json:"msg,required"` + DebugUrl string `form:"debug_url" json:"debug_url,omitempty"` +} + +type SuggestInfo struct { + UserInput *schema.Message `json:"user_input,omitempty"` + AnswerInput *schema.Message `json:"answer,omitempty"` + PersonaInput *string `json:"persona_input,omitempty"` +} diff --git a/backend/domain/workflow/entity/vo/conversation.go b/backend/domain/workflow/entity/vo/conversation.go new file mode 100644 index 00000000..1ed5c739 --- /dev/null +++ b/backend/domain/workflow/entity/vo/conversation.go @@ -0,0 +1,74 @@ +/* + * 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 vo + +type Env string + +const ( + Draft Env = "draft" + Online Env = "online" +) + +type CreateConversationTemplateMeta struct { + UserID int64 + AppID int64 + SpaceID int64 + Name string +} + +type GetConversationTemplatePolicy struct { + AppID *int64 + Name *string + Version *string + TemplateID *int64 +} + +type ListConversationTemplatePolicy struct { + AppID int64 + Page *Page + NameLike *string + Version *string +} + +type ListConversationMeta struct { + APPID int64 + UserID int64 + ConnectorID int64 +} + +type ListConversationPolicy struct { + ListConversationMeta + + Page *Page + NameLike *string + Version *string +} + +type CreateStaticConversation struct { + AppID int64 + UserID int64 + ConnectorID int64 + + TemplateID int64 +} +type CreateDynamicConversation struct { + AppID int64 + UserID int64 + ConnectorID int64 + + Name string +} diff --git a/backend/domain/workflow/entity/vo/meta.go b/backend/domain/workflow/entity/vo/meta.go index a8897525..4bdb5878 100644 --- a/backend/domain/workflow/entity/vo/meta.go +++ b/backend/domain/workflow/entity/vo/meta.go @@ -68,6 +68,7 @@ type MetaUpdate struct { IconURI *string HasPublished *bool LatestPublishedVersion *string + WorkflowMode *Mode } type MetaQuery struct { @@ -80,4 +81,5 @@ type MetaQuery struct { LibOnly bool NeedTotalNumber bool DescByUpdate bool + Mode *workflow.WorkflowMode } diff --git a/backend/domain/workflow/entity/vo/workflow_publish.go b/backend/domain/workflow/entity/vo/workflow_publish.go index c5a45fd8..354d7ff7 100644 --- a/backend/domain/workflow/entity/vo/workflow_publish.go +++ b/backend/domain/workflow/entity/vo/workflow_publish.go @@ -21,4 +21,5 @@ type ReleaseWorkflowConfig struct { PluginIDs []int64 ConnectorIDs []int64 + WorkflowIDs []int64 } diff --git a/backend/domain/workflow/interface.go b/backend/domain/workflow/interface.go index 09153095..7bcd2b93 100644 --- a/backend/domain/workflow/interface.go +++ b/backend/domain/workflow/interface.go @@ -26,6 +26,7 @@ import ( "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" + "github.com/coze-dev/coze-studio/backend/infra/contract/storage" ) //go:generate mockgen -destination ../../internal/mock/domain/workflow/interface.go --package mockWorkflow -source interface.go @@ -39,12 +40,15 @@ type Service interface { Publish(ctx context.Context, policy *vo.PublishPolicy) (err error) UpdateMeta(ctx context.Context, id int64, metaUpdate *vo.MetaUpdate) (err error) CopyWorkflow(ctx context.Context, workflowID int64, policy vo.CopyWorkflowPolicy) (*entity.Workflow, error) + WorkflowSchemaCheck(ctx context.Context, wf *entity.Workflow, checks []workflow.CheckType) ([]*workflow.CheckResult, error) QueryNodeProperties(ctx context.Context, id int64) (map[string]*vo.NodeProperty, error) // only draft ValidateTree(ctx context.Context, id int64, validateConfig vo.ValidateTreeConfig) ([]*workflow.ValidateTreeInfo, error) GetWorkflowReference(ctx context.Context, id int64) (map[int64]*vo.Meta, error) + GetWorkflowVersionsByConnector(ctx context.Context, connectorID, workflowID int64, limit int) ([]string, error) + Executable AsTool @@ -53,17 +57,29 @@ type Service interface { DuplicateWorkflowsByAppID(ctx context.Context, sourceAPPID, targetAppID int64, related vo.ExternalResourceRelated) ([]*entity.Workflow, error) GetWorkflowDependenceResource(ctx context.Context, workflowID int64) (*vo.DependenceResource, error) SyncRelatedWorkflowResources(ctx context.Context, appID int64, relatedWorkflows map[int64]entity.IDVersionPair, related vo.ExternalResourceRelated) error + + ChatFlowRole + Conversation + + BindConvRelatedInfo(ctx context.Context, convID int64, info entity.ConvRelatedInfo) error + GetConvRelatedInfo(ctx context.Context, convID int64) (*entity.ConvRelatedInfo, bool, func() error, error) + Suggest(ctx context.Context, input *vo.SuggestInfo) ([]string, error) } type Repository interface { CreateMeta(ctx context.Context, meta *vo.Meta) (int64, error) CreateVersion(ctx context.Context, id int64, info *vo.VersionInfo, newRefs map[entity.WorkflowReferenceKey]struct{}) (err error) CreateOrUpdateDraft(ctx context.Context, id int64, draft *vo.DraftInfo) error + CreateChatFlowRoleConfig(ctx context.Context, chatFlowRole *entity.ChatFlowRole) (int64, error) + UpdateChatFlowRoleConfig(ctx context.Context, workflowID int64, chatFlowRole *vo.ChatFlowRoleUpdate) error + GetChatFlowRoleConfig(ctx context.Context, workflowID int64, version string) (*entity.ChatFlowRole, error, bool) + DeleteChatFlowRoleConfig(ctx context.Context, id int64, workflowID int64) error Delete(ctx context.Context, id int64) error MDelete(ctx context.Context, ids []int64) error GetMeta(ctx context.Context, id int64) (*vo.Meta, error) UpdateMeta(ctx context.Context, id int64, metaUpdate *vo.MetaUpdate) error - GetVersion(ctx context.Context, id int64, version string) (*vo.VersionInfo, error) + GetVersion(ctx context.Context, id int64, version string) (*vo.VersionInfo, bool, error) + GetVersionListByConnectorAndWorkflowID(ctx context.Context, connectorID, workflowID int64, limit int) ([]string, error) GetEntity(ctx context.Context, policy *vo.GetPolicy) (*entity.Workflow, error) @@ -95,12 +111,15 @@ type Repository interface { IsApplicationConnectorWorkflowVersion(ctx context.Context, connectorID, workflowID int64, version string) (b bool, err error) + GetObjectUrl(ctx context.Context, objectKey string, opts ...storage.GetOptFn) (string, error) + compose.CheckPointStore idgen.IDGenerator GetKnowledgeRecallChatModel() model.BaseChatModel - + ConversationRepository WorkflowConfig + Suggester } var repositorySingleton Repository diff --git a/backend/domain/workflow/internal/canvas/adaptor/to_schema.go b/backend/domain/workflow/internal/canvas/adaptor/to_schema.go index a4bc63f7..d82f03dd 100644 --- a/backend/domain/workflow/internal/canvas/adaptor/to_schema.go +++ b/backend/domain/workflow/internal/canvas/adaptor/to_schema.go @@ -34,6 +34,7 @@ import ( "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes/batch" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes/code" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes/conversation" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes/database" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes/emitter" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes/entry" @@ -674,6 +675,36 @@ func RegisterAllNodeAdaptors() { nodes.RegisterNodeAdaptor(entity.NodeTypeLLM, func() nodes.NodeAdaptor { return &llm.Config{} }) + nodes.RegisterNodeAdaptor(entity.NodeTypeCreateConversation, func() nodes.NodeAdaptor { + return &conversation.CreateConversationConfig{} + }) + nodes.RegisterNodeAdaptor(entity.NodeTypeConversationUpdate, func() nodes.NodeAdaptor { + return &conversation.UpdateConversationConfig{} + }) + nodes.RegisterNodeAdaptor(entity.NodeTypeConversationDelete, func() nodes.NodeAdaptor { + return &conversation.DeleteConversationConfig{} + }) + nodes.RegisterNodeAdaptor(entity.NodeTypeConversationList, func() nodes.NodeAdaptor { + return &conversation.ConversationListConfig{} + }) + nodes.RegisterNodeAdaptor(entity.NodeTypeConversationHistory, func() nodes.NodeAdaptor { + return &conversation.ConversationHistoryConfig{} + }) + nodes.RegisterNodeAdaptor(entity.NodeTypeClearConversationHistory, func() nodes.NodeAdaptor { + return &conversation.ClearConversationHistoryConfig{} + }) + nodes.RegisterNodeAdaptor(entity.NodeTypeMessageList, func() nodes.NodeAdaptor { + return &conversation.MessageListConfig{} + }) + nodes.RegisterNodeAdaptor(entity.NodeTypeCreateMessage, func() nodes.NodeAdaptor { + return &conversation.CreateMessageConfig{} + }) + nodes.RegisterNodeAdaptor(entity.NodeTypeEditMessage, func() nodes.NodeAdaptor { + return &conversation.EditMessageConfig{} + }) + nodes.RegisterNodeAdaptor(entity.NodeTypeDeleteMessage, func() nodes.NodeAdaptor { + return &conversation.DeleteMessageConfig{} + }) // register branch adaptors nodes.RegisterBranchAdaptor(entity.NodeTypeSelector, func() nodes.BranchAdaptor { diff --git a/backend/domain/workflow/internal/canvas/examples/chatflow/new_chatflow.json b/backend/domain/workflow/internal/canvas/examples/chatflow/new_chatflow.json new file mode 100644 index 00000000..241a9759 --- /dev/null +++ b/backend/domain/workflow/internal/canvas/examples/chatflow/new_chatflow.json @@ -0,0 +1,93 @@ +{ + "nodes": [ + { + "id": "100001", + "type": "1", + "meta": { + "position": { + "x": 13.818572856225469, + "y": -37.20384999753011 + } + }, + "data": { + "nodeMeta": { + "title": "开始", + "description": "工作流的起始节点,用于设定启动工作流需要的信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg", + "subTitle": "" + }, + "settings": null, + "version": "", + "outputs": [ + { + "type": "string", + "name": "USER_INPUT", + "required": false + }, + { + "type": "string", + "name": "CONVERSATION_NAME", + "required": false, + "description": "本次请求绑定的会话,会自动写入消息、会从该会话读对话历史。", + "defaultValue": "Default" + }, + { + "type": "string", + "name": "input", + "required": false + } + ], + "trigger_parameters": [] + } + }, + { + "id": "900001", + "type": "2", + "meta": { + "position": { + "x": 642.9671427865745, + "y": -37.20384999753011 + } + }, + "data": { + "nodeMeta": { + "description": "工作流的最终节点,用于返回工作流运行后的结果信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End-v2.jpg", + "subTitle": "", + "title": "结束" + }, + "inputs": { + "terminatePlan": "returnVariables", + "inputParameters": [ + { + "name": "output", + "input": { + "type": "string", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "100001", + "name": "input" + }, + "rawMeta": { + "type": 1 + } + } + } + } + ] + } + } + } + ], + "edges": [ + { + "sourceNodeID": "100001", + "targetNodeID": "900001" + } + ], + "versions": { + "loop": "v2" + } +} diff --git a/backend/domain/workflow/internal/canvas/examples/chatflow/start_exit.json b/backend/domain/workflow/internal/canvas/examples/chatflow/start_exit.json new file mode 100644 index 00000000..8ebd8edf --- /dev/null +++ b/backend/domain/workflow/internal/canvas/examples/chatflow/start_exit.json @@ -0,0 +1,89 @@ +{ + "nodes": [{ + "blocks": [], + "data": { + "nodeMeta": { + "description": "工作流的起始节点,用于设定启动工作流需要的信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg", + "subTitle": "", + "title": "开始" + }, + "outputs": [{ + "name": "USER_INPUT", + "required": false, + "type": "string" + }, { + "defaultValue": "Default", + "description": "本次请求绑定的会话,会自动写入消息、会从该会话读对话历史。", + "name": "CONVERSATION_NAME", + "required": false, + "type": "string" + }], + "trigger_parameters": [] + }, + "edges": null, + "id": "100001", + "meta": { + "position": { + "x": 0, + "y": 0 + } + }, + "type": "1" + }, { + "blocks": [], + "data": { + "inputs": { + "content": { + "type": "string", + "value": { + "content": "{{output}}", + "type": "literal" + } + }, + "inputParameters": [{ + "input": { + "type": "string", + "value": { + "content": { + "blockID": "100001", + "name": "USER_INPUT", + "source": "block-output" + }, + "rawMeta": { + "type": 1 + }, + "type": "ref" + } + }, + "name": "output" + }], + "streamingOutput": true, + "terminatePlan": "useAnswerContent" + }, + "nodeMeta": { + "description": "工作流的最终节点,用于返回工作流运行后的结果信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End-v2.jpg", + "subTitle": "", + "title": "结束" + } + }, + "edges": null, + "id": "900001", + "meta": { + "position": { + "x": 1000, + "y": 0 + } + }, + "type": "2" + }], + "edges": [{ + "sourceNodeID": "100001", + "targetNodeID": "900001", + "sourcePortID": "" + }], + "versions": { + "loop": "v2" + } +} \ No newline at end of file diff --git a/backend/domain/workflow/internal/canvas/examples/conversation_manager/conversation_list.json b/backend/domain/workflow/internal/canvas/examples/conversation_manager/conversation_list.json new file mode 100644 index 00000000..1743c4e1 --- /dev/null +++ b/backend/domain/workflow/internal/canvas/examples/conversation_manager/conversation_list.json @@ -0,0 +1,193 @@ +{ + "nodes": [{ + "blocks": [], + "data": { + "nodeMeta": { + "description": "工作流的起始节点,用于设定启动工作流需要的信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg", + "subTitle": "", + "title": "开始" + }, + "outputs": [{ + "name": "USER_INPUT", + "required": false, + "type": "string" + }, { + "defaultValue": "Default", + "description": "本次请求绑定的会话,会自动写入消息、会从该会话读对话历史。", + "name": "CONVERSATION_NAME", + "required": false, + "type": "string" + }], + "trigger_parameters": [] + }, + "edges": null, + "id": "100001", + "meta": { + "position": { + "x": 0, + "y": 0 + } + }, + "type": "1" + }, { + "blocks": [], + "data": { + "inputs": { + "content": { + "type": "string", + "value": { + "content": "{{output}}", + "type": "literal" + } + }, + "inputParameters": [{ + "input": { + "schema": { + "schema": [{ + "name": "conversationName", + "type": "string" + }, { + "name": "conversationId", + "type": "string" + }], + "type": "object" + }, + "type": "list", + "value": { + "content": { + "blockID": "107363", + "name": "conversationList", + "source": "block-output" + }, + "rawMeta": { + "type": 103 + }, + "type": "ref" + } + }, + "name": "output" + }], + "streamingOutput": true, + "terminatePlan": "useAnswerContent" + }, + "nodeMeta": { + "description": "工作流的最终节点,用于返回工作流运行后的结果信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End-v2.jpg", + "subTitle": "", + "title": "结束" + } + }, + "edges": null, + "id": "900001", + "meta": { + "position": { + "x": 1058, + "y": -13 + } + }, + "type": "2" + }, { + "blocks": [], + "data": { + "inputs": { + "inputParameters": [] + }, + "nodeMeta": { + "description": "用于查询所有会话,包含静态会话、动态会话", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-查询会话.jpg", + "mainColor": "#F2B600", + "subTitle": "查询会话列表", + "title": "查询会话列表" + }, + "outputs": [{ + "name": "conversationList", + "schema": { + "schema": [{ + "name": "conversationName", + "type": "string" + }, { + "name": "conversationId", + "type": "string" + }], + "type": "object" + }, + "type": "list" + }] + }, + "edges": null, + "id": "107363", + "meta": { + "position": { + "x": 561, + "y": 186 + } + }, + "type": "53" + }, { + "blocks": [], + "data": { + "inputs": { + "inputParameters": [{ + "input": { + "type": "string", + "value": { + "content": { + "blockID": "100001", + "name": "CONVERSATION_NAME", + "source": "block-output" + }, + "rawMeta": { + "type": 1 + }, + "type": "ref" + } + }, + "name": "conversationName" + }] + }, + "nodeMeta": { + "description": "用于创建会话", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-Create.jpeg", + "mainColor": "#F2B600", + "subTitle": "创建会话", + "title": "创建会话" + }, + "outputs": [{ + "name": "isSuccess", + "type": "boolean" + }, { + "name": "isExisted", + "type": "boolean" + }, { + "name": "conversationId", + "type": "string" + }] + }, + "edges": null, + "id": "110245", + "meta": { + "position": { + "x": 487, + "y": -196 + } + }, + "type": "39" + }], + "edges": [{ + "sourceNodeID": "100001", + "targetNodeID": "110245", + "sourcePortID": "" + }, { + "sourceNodeID": "107363", + "targetNodeID": "900001", + "sourcePortID": "" + }, { + "sourceNodeID": "110245", + "targetNodeID": "107363", + "sourcePortID": "" + }], + "versions": { + "loop": "v2" + } +} \ No newline at end of file diff --git a/backend/domain/workflow/internal/canvas/examples/conversation_manager/create_conversation.json b/backend/domain/workflow/internal/canvas/examples/conversation_manager/create_conversation.json new file mode 100644 index 00000000..366d27ae --- /dev/null +++ b/backend/domain/workflow/internal/canvas/examples/conversation_manager/create_conversation.json @@ -0,0 +1,137 @@ +{ + "nodes": [ + { + "id": "100001", + "type": "1", + "meta": { + "position": { + "x": 180, + "y": 13.700000000000003 + } + }, + "data": { + "nodeMeta": { + "description": "工作流的起始节点,用于设定启动工作流需要的信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg", + "subTitle": "", + "title": "开始" + }, + "outputs": [ + { + "type": "string", + "name": "input", + "required": false + } + ], + "trigger_parameters": [] + } + }, + { + "id": "900001", + "type": "2", + "meta": { + "position": { + "x": 1100, + "y": 0.7000000000000028 + } + }, + "data": { + "nodeMeta": { + "description": "工作流的最终节点,用于返回工作流运行后的结果信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End-v2.jpg", + "subTitle": "", + "title": "结束" + }, + "inputs": { + "terminatePlan": "returnVariables", + "inputParameters": [ + { + "name": "output", + "input": { + "type": "string", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "163698", + "name": "conversationId" + }, + "rawMeta": { + "type": 1 + } + } + } + } + ] + } + } + }, + { + "id": "163698", + "type": "39", + "meta": { + "position": { + "x": 640, + "y": 0 + } + }, + "data": { + "outputs": [ + { + "type": "boolean", + "name": "isSuccess" + }, + { + "type": "boolean", + "name": "isExisted" + }, + { + "type": "string", + "name": "conversationId" + } + ], + "nodeMeta": { + "title": "创建会话", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-Create.jpeg", + "description": "用于创建会话", + "mainColor": "#F2B600", + "subTitle": "创建会话" + }, + "inputs": { + "inputParameters": [ + { + "name": "conversationName", + "input": { + "type": "string", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "100001", + "name": "input" + }, + "rawMeta": { + "type": 1 + } + } + } + } + ] + } + } + } + ], + "edges": [ + { + "sourceNodeID": "100001", + "targetNodeID": "163698" + }, + { + "sourceNodeID": "163698", + "targetNodeID": "900001" + } + ], + "versions": { + "loop": "v2" + } +} diff --git a/backend/domain/workflow/internal/canvas/examples/conversation_manager/delete_conversation.json b/backend/domain/workflow/internal/canvas/examples/conversation_manager/delete_conversation.json new file mode 100644 index 00000000..17edaf8e --- /dev/null +++ b/backend/domain/workflow/internal/canvas/examples/conversation_manager/delete_conversation.json @@ -0,0 +1,129 @@ +{ + "nodes": [ + { + "id": "100001", + "type": "1", + "meta": { + "position": { + "x": -13.523809523809522, + "y": -25.294372294372295 + } + }, + "data": { + "nodeMeta": { + "description": "工作流的起始节点,用于设定启动工作流需要的信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg", + "subTitle": "", + "title": "开始" + }, + "outputs": [ + { + "type": "string", + "name": "input", + "required": false + } + ], + "trigger_parameters": [] + } + }, + { + "id": "900001", + "type": "2", + "meta": { + "position": { + "x": 890.3549783549786, + "y": -71.48917748917748 + } + }, + "data": { + "nodeMeta": { + "description": "工作流的最终节点,用于返回工作流运行后的结果信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End-v2.jpg", + "subTitle": "", + "title": "结束" + }, + "inputs": { + "terminatePlan": "returnVariables", + "inputParameters": [ + { + "name": "output", + "input": { + "type": "boolean", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "118024", + "name": "isSuccess" + }, + "rawMeta": { + "type": 3 + } + } + } + } + ] + } + } + }, + { + "id": "118024", + "type": "52", + "meta": { + "position": { + "x": 423.6623376623378, + "y": -126.39999999999999 + } + }, + "data": { + "outputs": [ + { + "type": "boolean", + "name": "isSuccess" + } + ], + "nodeMeta": { + "title": "删除会话", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-删除会话.jpg", + "description": "用于删除会话", + "mainColor": "#F2B600", + "subTitle": "删除会话" + }, + "inputs": { + "inputParameters": [ + { + "name": "conversationName", + "input": { + "type": "string", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "100001", + "name": "input" + }, + "rawMeta": { + "type": 1 + } + } + } + } + ] + } + } + } + ], + "edges": [ + { + "sourceNodeID": "100001", + "targetNodeID": "118024" + }, + { + "sourceNodeID": "118024", + "targetNodeID": "900001" + } + ], + "versions": { + "loop": "v2" + } +} diff --git a/backend/domain/workflow/internal/canvas/examples/conversation_manager/update_conversation.json b/backend/domain/workflow/internal/canvas/examples/conversation_manager/update_conversation.json new file mode 100644 index 00000000..a26714b0 --- /dev/null +++ b/backend/domain/workflow/internal/canvas/examples/conversation_manager/update_conversation.json @@ -0,0 +1,191 @@ +{ + "nodes": [ + { + "id": "100001", + "type": "1", + "meta": { + "position": { + "x": -243.67931247880136, + "y": -233.598184501318 + } + }, + "data": { + "nodeMeta": { + "description": "工作流的起始节点,用于设定启动工作流需要的信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg", + "subTitle": "", + "title": "开始" + }, + "outputs": [ + { + "type": "string", + "name": "input", + "required": false + } + ], + "trigger_parameters": [] + } + }, + { + "id": "900001", + "type": "2", + "meta": { + "position": { + "x": 911.2952705396514, + "y": -331.2250749763467 + } + }, + "data": { + "nodeMeta": { + "description": "工作流的最终节点,用于返回工作流运行后的结果信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End-v2.jpg", + "subTitle": "", + "title": "结束" + }, + "inputs": { + "terminatePlan": "returnVariables", + "inputParameters": [ + { + "name": "output", + "input": { + "value": { + "type": "object_ref" + }, + "type": "object", + "schema": [ + { + "name": "isSuccess", + "input": { + "type": "boolean", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "122336", + "name": "isSuccess" + }, + "rawMeta": { + "type": 3 + } + } + } + }, + { + "name": "isExisted", + "input": { + "type": "boolean", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "122336", + "name": "isExisted" + }, + "rawMeta": { + "type": 3 + } + } + } + }, + { + "name": "conversationId", + "input": { + "type": "string", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "122336", + "name": "conversationId" + }, + "rawMeta": { + "type": 1 + } + } + } + } + ] + } + } + ] + } + } + }, + { + "id": "122336", + "type": "51", + "meta": { + "position": { + "x": 343.08704991877585, + "y": -462.38794621339696 + } + }, + "data": { + "outputs": [ + { + "type": "boolean", + "name": "isSuccess" + }, + { + "type": "boolean", + "name": "isExisted" + }, + { + "type": "string", + "name": "conversationId" + } + ], + "nodeMeta": { + "title": "修改会话", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-编辑会话.jpg", + "description": "用于修改会话的名字", + "mainColor": "#F2B600", + "subTitle": "修改会话" + }, + "inputs": { + "inputParameters": [ + { + "name": "conversationName", + "input": { + "type": "string", + "value": { + "type": "literal", + "content": "template_v1", + "rawMeta": { + "type": 1 + } + } + } + }, + { + "name": "newConversationName", + "input": { + "type": "string", + "value": { + "type": "literal", + "content": "new", + "rawMeta": { + "type": 1 + } + } + } + } + ] + } + } + } + ], + "edges": [ + { + "sourceNodeID": "100001", + "targetNodeID": "122336" + }, + { + "sourceNodeID": "122336", + "targetNodeID": "900001" + } + ], + "versions": { + "loop": "v2" + } +} diff --git a/backend/domain/workflow/internal/canvas/examples/conversation_manager/update_dynamic_conversation.json b/backend/domain/workflow/internal/canvas/examples/conversation_manager/update_dynamic_conversation.json new file mode 100644 index 00000000..444ac1a6 --- /dev/null +++ b/backend/domain/workflow/internal/canvas/examples/conversation_manager/update_dynamic_conversation.json @@ -0,0 +1,262 @@ +{ + "nodes": [ + { + "id": "100001", + "type": "1", + "meta": { + "position": { + "x": 180, + "y": 13.700000000000003 + } + }, + "data": { + "nodeMeta": { + "description": "工作流的起始节点,用于设定启动工作流需要的信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg", + "subTitle": "", + "title": "开始" + }, + "outputs": [ + { + "type": "string", + "name": "input", + "required": true + }, + { + "type": "string", + "name": "new_name", + "required": true + } + ], + "trigger_parameters": [] + } + }, + { + "id": "900001", + "type": "2", + "meta": { + "position": { + "x": 1560, + "y": 0.7000000000000028 + } + }, + "data": { + "nodeMeta": { + "description": "工作流的最终节点,用于返回工作流运行后的结果信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End-v2.jpg", + "subTitle": "", + "title": "结束" + }, + "inputs": { + "terminatePlan": "returnVariables", + "inputParameters": [ + { + "name": "obj", + "input": { + "value": { + "type": "object_ref" + }, + "type": "object", + "schema": [ + { + "name": "isSuccess", + "input": { + "type": "boolean", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "193175", + "name": "isSuccess" + }, + "rawMeta": { + "type": 3 + } + } + } + }, + { + "name": "isExisted", + "input": { + "type": "boolean", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "193175", + "name": "isExisted" + }, + "rawMeta": { + "type": 3 + } + } + } + }, + { + "name": "conversationId", + "input": { + "type": "string", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "193175", + "name": "conversationId" + }, + "rawMeta": { + "type": 1 + } + } + } + } + ] + } + } + ] + } + } + }, + { + "id": "139551", + "type": "39", + "meta": { + "position": { + "x": 627.929589270746, + "y": -36.21123218776195 + } + }, + "data": { + "outputs": [ + { + "type": "boolean", + "name": "isSuccess" + }, + { + "type": "boolean", + "name": "isExisted" + }, + { + "type": "string", + "name": "conversationId" + } + ], + "nodeMeta": { + "title": "创建会话", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-Create.jpeg", + "description": "用于创建会话", + "mainColor": "#F2B600", + "subTitle": "创建会话" + }, + "inputs": { + "inputParameters": [ + { + "name": "conversationName", + "input": { + "type": "string", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "100001", + "name": "input" + }, + "rawMeta": { + "type": 1 + } + } + } + } + ] + } + } + }, + { + "id": "193175", + "type": "51", + "meta": { + "position": { + "x": 1100, + "y": 0 + } + }, + "data": { + "outputs": [ + { + "type": "boolean", + "name": "isSuccess" + }, + { + "type": "boolean", + "name": "isExisted" + }, + { + "type": "string", + "name": "conversationId" + } + ], + "nodeMeta": { + "title": "修改会话", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-编辑会话.jpg", + "description": "用于修改会话的名字", + "mainColor": "#F2B600", + "subTitle": "修改会话" + }, + "inputs": { + "inputParameters": [ + { + "name": "conversationName", + "input": { + "type": "string", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "100001", + "name": "input" + }, + "rawMeta": { + "type": 1 + } + } + } + }, + { + "name": "newConversationName", + "input": { + "type": "string", + "value": { + "type": "ref", + "content": { + "source": "block-output", + "blockID": "100001", + "name": "new_name" + }, + "rawMeta": { + "type": 1 + } + } + } + } + ] + } + } + } + ], + "edges": [ + { + "sourceNodeID": "100001", + "targetNodeID": "139551" + }, + { + "sourceNodeID": "193175", + "targetNodeID": "900001" + }, + { + "sourceNodeID": "139551", + "targetNodeID": "193175" + } + ], + "versions": { + "loop": "v2" + } +} diff --git a/backend/domain/workflow/internal/canvas/examples/message/create_message.json b/backend/domain/workflow/internal/canvas/examples/message/create_message.json new file mode 100644 index 00000000..01e7abf5 --- /dev/null +++ b/backend/domain/workflow/internal/canvas/examples/message/create_message.json @@ -0,0 +1,234 @@ +{ + "nodes": [{ + "blocks": [], + "data": { + "nodeMeta": { + "description": "工作流的起始节点,用于设定启动工作流需要的信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg", + "subTitle": "", + "title": "开始" + }, + "outputs": [{ + "name": "USER_INPUT", + "required": false, + "type": "string" + }, { + "defaultValue": "Default", + "description": "本次请求绑定的会话,会自动写入消息、会从该会话读对话历史。", + "name": "CONVERSATION_NAME", + "required": false, + "type": "string" + }], + "trigger_parameters": [] + }, + "edges": null, + "id": "100001", + "meta": { + "position": { + "x": 0, + "y": 0 + } + }, + "type": "1" + }, { + "blocks": [], + "data": { + "inputs": { + "inputParameters": [{ + "input": { + "type": "boolean", + "value": { + "content": { + "blockID": "195185", + "name": "isSuccess", + "source": "block-output" + }, + "rawMeta": { + "type": 3 + }, + "type": "ref" + } + }, + "name": "output" + }, { + "input": { + "type": "string", + "value": { + "content": { + "blockID": "195185", + "name": "message.messageId", + "source": "block-output" + }, + "rawMeta": { + "type": 1 + }, + "type": "ref" + } + }, + "name": "mID" + }], + "terminatePlan": "returnVariables" + }, + "nodeMeta": { + "description": "工作流的最终节点,用于返回工作流运行后的结果信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End-v2.jpg", + "subTitle": "", + "title": "结束" + } + }, + "edges": null, + "id": "900001", + "meta": { + "position": { + "x": 1000, + "y": 0 + } + }, + "type": "2" + }, { + "blocks": [], + "data": { + "inputs": { + "inputParameters": [{ + "input": { + "type": "string", + "value": { + "content": { + "blockID": "100001", + "name": "CONVERSATION_NAME", + "source": "block-output" + }, + "rawMeta": { + "type": 1 + }, + "type": "ref" + } + }, + "name": "conversationName" + }, { + "input": { + "type": "string", + "value": { + "content": "user", + "type": "literal" + } + }, + "name": "role" + }, { + "input": { + "type": "string", + "value": { + "content": "1", + "rawMeta": { + "type": 1 + }, + "type": "literal" + } + }, + "name": "content" + }] + }, + "nodeMeta": { + "description": "用于创建消息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-创建消息.jpg", + "mainColor": "#F2B600", + "subTitle": "创建消息", + "title": "创建消息" + }, + "outputs": [{ + "name": "isSuccess", + "type": "boolean" + }, { + "name": "message", + "schema": [{ + "name": "messageId", + "type": "string" + }, { + "name": "role", + "type": "string" + }, { + "name": "contentType", + "type": "string" + }, { + "name": "content", + "type": "string" + }], + "type": "object" + }] + }, + "edges": null, + "id": "195185", + "meta": { + "position": { + "x": 482, + "y": -13 + } + }, + "type": "55" + }, { + "blocks": [], + "data": { + "inputs": { + "inputParameters": [{ + "input": { + "type": "string", + "value": { + "content": { + "blockID": "100001", + "name": "CONVERSATION_NAME", + "source": "block-output" + }, + "rawMeta": { + "type": 1 + }, + "type": "ref" + } + }, + "name": "conversationName" + }] + }, + "nodeMeta": { + "description": "用于创建会话", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-Create.jpeg", + "mainColor": "#F2B600", + "subTitle": "创建会话", + "title": "创建会话" + }, + "outputs": [{ + "name": "isSuccess", + "type": "boolean" + }, { + "name": "isExisted", + "type": "boolean" + }, { + "name": "conversationId", + "type": "string" + }] + }, + "edges": null, + "id": "121849", + "meta": { + "position": { + "x": 302, + "y": -236 + } + }, + "type": "39" + }], + "edges": [{ + "sourceNodeID": "100001", + "targetNodeID": "121849", + "sourcePortID": "" + }, { + "sourceNodeID": "195185", + "targetNodeID": "900001", + "sourcePortID": "" + }, { + "sourceNodeID": "121849", + "targetNodeID": "195185", + "sourcePortID": "" + }], + "versions": { + "loop": "v2" + } +} \ No newline at end of file diff --git a/backend/domain/workflow/internal/canvas/examples/message/message_list.json b/backend/domain/workflow/internal/canvas/examples/message/message_list.json new file mode 100644 index 00000000..1bf19186 --- /dev/null +++ b/backend/domain/workflow/internal/canvas/examples/message/message_list.json @@ -0,0 +1,310 @@ +{ + "nodes": [{ + "blocks": [], + "data": { + "nodeMeta": { + "description": "工作流的起始节点,用于设定启动工作流需要的信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Start-v2.jpg", + "subTitle": "", + "title": "开始" + }, + "outputs": [{ + "name": "USER_INPUT", + "required": false, + "type": "string" + }, { + "defaultValue": "Default", + "description": "本次请求绑定的会话,会自动写入消息、会从该会话读对话历史。", + "name": "CONVERSATION_NAME", + "required": false, + "type": "string" + }], + "trigger_parameters": [] + }, + "edges": null, + "id": "100001", + "meta": { + "position": { + "x": 0, + "y": 0 + } + }, + "type": "1" + }, { + "blocks": [], + "data": { + "inputs": { + "inputParameters": [{ + "input": { + "schema": { + "schema": [{ + "name": "messageId", + "type": "string" + }, { + "name": "role", + "type": "string" + }, { + "name": "contentType", + "type": "string" + }, { + "name": "content", + "type": "string" + }], + "type": "object" + }, + "type": "list", + "value": { + "content": { + "blockID": "132703", + "name": "messageList", + "source": "block-output" + }, + "rawMeta": { + "type": 103 + }, + "type": "ref" + } + }, + "name": "output" + }], + "terminatePlan": "returnVariables" + }, + "nodeMeta": { + "description": "工作流的最终节点,用于返回工作流运行后的结果信息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-End-v2.jpg", + "subTitle": "", + "title": "结束" + } + }, + "edges": null, + "id": "900001", + "meta": { + "position": { + "x": 1000, + "y": 0 + } + }, + "type": "2" + }, { + "blocks": [], + "data": { + "inputs": { + "inputParameters": [{ + "input": { + "type": "string", + "value": { + "content": { + "blockID": "100001", + "name": "CONVERSATION_NAME", + "source": "block-output" + }, + "rawMeta": { + "type": 1 + }, + "type": "ref" + } + }, + "name": "conversationName" + }] + }, + "nodeMeta": { + "description": "用于查询消息列表", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-List.jpeg", + "mainColor": "#F2B600", + "subTitle": "查询消息列表", + "title": "查询消息列表" + }, + "outputs": [{ + "name": "messageList", + "schema": { + "schema": [{ + "name": "messageId", + "type": "string" + }, { + "name": "role", + "type": "string" + }, { + "name": "contentType", + "type": "string" + }, { + "name": "content", + "type": "string" + }], + "type": "object" + }, + "type": "list" + }, { + "name": "firstId", + "type": "string" + }, { + "name": "lastId", + "type": "string" + }, { + "name": "hasMore", + "type": "boolean" + }] + }, + "edges": null, + "id": "132703", + "meta": { + "position": { + "x": 514, + "y": 96 + } + }, + "type": "37" + }, { + "blocks": [], + "data": { + "inputs": { + "inputParameters": [{ + "input": { + "type": "string", + "value": { + "content": { + "blockID": "100001", + "name": "CONVERSATION_NAME", + "source": "block-output" + }, + "rawMeta": { + "type": 1 + }, + "type": "ref" + } + }, + "name": "conversationName" + }] + }, + "nodeMeta": { + "description": "用于创建会话", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-Conversation-Create.jpeg", + "mainColor": "#F2B600", + "subTitle": "创建会话", + "title": "创建会话" + }, + "outputs": [{ + "name": "isSuccess", + "type": "boolean" + }, { + "name": "isExisted", + "type": "boolean" + }, { + "name": "conversationId", + "type": "string" + }] + }, + "edges": null, + "id": "166724", + "meta": { + "position": { + "x": 323, + "y": -332 + } + }, + "type": "39" + }, { + "blocks": [], + "data": { + "inputs": { + "inputParameters": [{ + "input": { + "type": "string", + "value": { + "content": { + "blockID": "100001", + "name": "CONVERSATION_NAME", + "source": "block-output" + }, + "rawMeta": { + "type": 1 + }, + "type": "ref" + } + }, + "name": "conversationName" + }, { + "input": { + "type": "string", + "value": { + "content": "user", + "type": "literal" + } + }, + "name": "role" + }, { + "input": { + "type": "string", + "value": { + "content": { + "blockID": "100001", + "name": "USER_INPUT", + "source": "block-output" + }, + "rawMeta": { + "type": 1 + }, + "type": "ref" + } + }, + "name": "content" + }] + }, + "nodeMeta": { + "description": "用于创建消息", + "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/dvsmryvd_avi_dvsm/ljhwZthlaukjlkulzlp/icon/icon-创建消息.jpg", + "mainColor": "#F2B600", + "subTitle": "创建消息", + "title": "创建消息" + }, + "outputs": [{ + "name": "isSuccess", + "type": "boolean" + }, { + "name": "message", + "schema": [{ + "name": "messageId", + "type": "string" + }, { + "name": "role", + "type": "string" + }, { + "name": "contentType", + "type": "string" + }, { + "name": "content", + "type": "string" + }], + "type": "object" + }] + }, + "edges": null, + "id": "157061", + "meta": { + "position": { + "x": 479, + "y": -127 + } + }, + "type": "55" + }], + "edges": [{ + "sourceNodeID": "100001", + "targetNodeID": "166724", + "sourcePortID": "" + }, { + "sourceNodeID": "132703", + "targetNodeID": "900001", + "sourcePortID": "" + }, { + "sourceNodeID": "157061", + "targetNodeID": "132703", + "sourcePortID": "" + }, { + "sourceNodeID": "166724", + "targetNodeID": "157061", + "sourcePortID": "" + }], + "versions": { + "loop": "v2" + } +} \ No newline at end of file diff --git a/backend/domain/workflow/internal/canvas/validate/canvas_validate.go b/backend/domain/workflow/internal/canvas/validate/canvas_validate.go index c3969158..0ba39363 100644 --- a/backend/domain/workflow/internal/canvas/validate/canvas_validate.go +++ b/backend/domain/workflow/internal/canvas/validate/canvas_validate.go @@ -19,6 +19,7 @@ package validate import ( "context" "fmt" + "regexp" "strconv" @@ -29,6 +30,7 @@ import ( "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" "github.com/coze-dev/coze-studio/backend/domain/workflow/variable" + "github.com/coze-dev/coze-studio/backend/pkg/errorx" "github.com/coze-dev/coze-studio/backend/pkg/sonic" "github.com/coze-dev/coze-studio/backend/types/errno" ) @@ -390,10 +392,13 @@ func (cv *CanvasValidator) CheckSubWorkFlowTerminatePlanType(ctx context.Context if len(subID2SubVersion) > 0 { for id, version := range subID2SubVersion { - v, err := workflow.GetRepository().GetVersion(ctx, id, version) + v, existed, err := workflow.GetRepository().GetVersion(ctx, id, version) if err != nil { return nil, err } + if !existed { + return nil, vo.WrapError(errno.ErrWorkflowNotFound, fmt.Errorf("workflow version %s not found for ID %d: %w", version, id, err), errorx.KV("id", strconv.FormatInt(id, 10))) + } var canvas vo.Canvas if err = sonic.UnmarshalString(v.Canvas, &canvas); err != nil { diff --git a/backend/domain/workflow/internal/compose/state.go b/backend/domain/workflow/internal/compose/state.go index a807a465..1acf90fc 100644 --- a/backend/domain/workflow/internal/compose/state.go +++ b/backend/domain/workflow/internal/compose/state.go @@ -28,6 +28,7 @@ import ( workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" workflow2 "github.com/coze-dev/coze-studio/backend/api/model/workflow" + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" "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/domain/workflow/internal/execute" @@ -87,6 +88,11 @@ func init() { _ = compose.RegisterSerializableType[workflowModel.Locator]("wf_locator") _ = compose.RegisterSerializableType[workflowModel.BizType]("biz_type") _ = compose.RegisterSerializableType[*execute.AppVariables]("app_variables") + _ = compose.RegisterSerializableType[workflow2.WorkflowMode]("workflow_mode") + _ = compose.RegisterSerializableType[*schema.Message]("schema_message") + _ = compose.RegisterSerializableType[*crossmessage.WfMessage]("history_messages") + _ = compose.RegisterSerializableType[*crossmessage.Content]("content") + } func (s *State) AddQuestion(nodeKey vo.NodeKey, question *qa.Question) { diff --git a/backend/domain/workflow/internal/compose/test/llm_test.go b/backend/domain/workflow/internal/compose/test/llm_test.go index ac31cce8..1ced79da 100644 --- a/backend/domain/workflow/internal/compose/test/llm_test.go +++ b/backend/domain/workflow/internal/compose/test/llm_test.go @@ -31,6 +31,8 @@ import ( model2 "github.com/cloudwego/eino/components/model" "github.com/cloudwego/eino/compose" "github.com/cloudwego/eino/schema" + workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" @@ -98,6 +100,14 @@ func TestLLM(t *testing.T) { ctx := ctxcache.Init(context.Background()) + defer mockey.Mock(execute.GetExeCtx).Return(&execute.Context{ + RootCtx: execute.RootCtx{ + ExeCfg: workflowModel.ExecuteConfig{ + WorkflowMode: 0, + }, + }, + NodeCtx: &execute.NodeCtx{}, + }).Build().UnPatch() t.Run("plain text output, non-streaming mode", func(t *testing.T) { if openaiModel == nil { defer func() { diff --git a/backend/domain/workflow/internal/compose/test/question_answer_test.go b/backend/domain/workflow/internal/compose/test/question_answer_test.go index 9bf8920a..e697b7f7 100644 --- a/backend/domain/workflow/internal/compose/test/question_answer_test.go +++ b/backend/domain/workflow/internal/compose/test/question_answer_test.go @@ -98,13 +98,24 @@ func TestQuestionAnswer(t *testing.T) { defer s.Close() redisClient := redis.NewWithAddrAndPassword(s.Addr(), "") - + var oneChatModel = chatModel + if oneChatModel == nil { + oneChatModel = &testutil.UTChatModel{ + InvokeResultProvider: func(_ int, in []*schema.Message) (*schema.Message, error) { + return &schema.Message{ + Role: schema.Assistant, + Content: "-1", + }, nil + }, + } + } mockIDGen := mock.NewMockIDGenerator(ctrl) mockIDGen.EXPECT().GenID(gomock.Any()).Return(time.Now().UnixNano(), nil).AnyTimes() mockTos := storageMock.NewMockStorage(ctrl) mockTos.EXPECT().GetObjectUrl(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil).AnyTimes() - repo := repo2.NewRepository(mockIDGen, db, redisClient, mockTos, - checkpoint.NewRedisStore(redisClient), nil, nil) + repo, _ := repo2.NewRepository(mockIDGen, db, redisClient, mockTos, + checkpoint.NewRedisStore(redisClient), oneChatModel, nil) + mockey.Mock(workflow.GetRepository).Return(repo).Build() t.Run("answer directly, no structured output", func(t *testing.T) { diff --git a/backend/domain/workflow/internal/execute/event_handle.go b/backend/domain/workflow/internal/execute/event_handle.go index b6cfcc8e..ec07112a 100644 --- a/backend/domain/workflow/internal/execute/event_handle.go +++ b/backend/domain/workflow/internal/execute/event_handle.go @@ -380,7 +380,7 @@ func handleEvent(ctx context.Context, event *Event, repo workflow.Repository, } if updatedRows, currentStatus, err = repo.UpdateWorkflowExecution(ctx, wfExec, []entity.WorkflowExecuteStatus{entity.WorkflowRunning, - entity.WorkflowInterrupted}); err != nil { + entity.WorkflowInterrupted, entity.WorkflowCancel}); err != nil { return noTerminate, fmt.Errorf("failed to save workflow execution when canceled: %v", err) } else if updatedRows == 0 { return noTerminate, fmt.Errorf("failed to update workflow execution to canceled for execution id %d, current status is %v", exeID, currentStatus) diff --git a/backend/domain/workflow/internal/nodes/conversation/clearconversationhistory.go b/backend/domain/workflow/internal/nodes/conversation/clearconversationhistory.go new file mode 100644 index 00000000..568deefe --- /dev/null +++ b/backend/domain/workflow/internal/nodes/conversation/clearconversationhistory.go @@ -0,0 +1,141 @@ +/* + * 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 conversation + +import ( + "context" + "errors" + "fmt" + "sync/atomic" + + workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + crossconversation "github.com/coze-dev/coze-studio/backend/crossdomain/contract/conversation" + wf "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/domain/workflow/internal/canvas/convert" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" + "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/ternary" + "github.com/coze-dev/coze-studio/backend/types/errno" +) + +type ClearConversationHistoryConfig struct{} + +type ClearConversationHistory struct{} + +func (c *ClearConversationHistoryConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) { + ns := &schema.NodeSchema{ + Key: vo.NodeKey(n.ID), + Type: entity.NodeTypeClearConversationHistory, + Name: n.Data.Meta.Title, + Configs: c, + } + + if err := convert.SetInputsForNodeSchema(n, ns); err != nil { + return nil, err + } + + if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil { + return nil, err + } + + return ns, nil +} + +func (c *ClearConversationHistoryConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) { + return &ClearConversationHistory{}, nil +} + +func (c *ClearConversationHistory) Invoke(ctx context.Context, in map[string]any) (map[string]any, error) { + + var ( + execCtx = execute.GetExeCtx(ctx) + env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft) + appID = execCtx.ExeCfg.AppID + agentID = execCtx.ExeCfg.AgentID + connectorID = execCtx.ExeCfg.ConnectorID + userID = execCtx.ExeCfg.Operator + version = execCtx.ExeCfg.Version + ) + + if agentID != nil { + return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("in the agent scenario, query conversation list is not available")) + } + if appID == nil { + return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("query conversation list node, app id is required")) + } + + conversationName, ok := in["conversationName"].(string) + if !ok { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversation name is required")) + } + + t, existed, err := wf.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{ + AppID: appID, + Name: ptr.Of(conversationName), + Version: ptr.Of(version), + }) + + if err != nil { + return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + var conversationID int64 + if existed { + ret, existed, err := wf.GetRepository().GetStaticConversationByTemplateID(ctx, env, userID, connectorID, t.TemplateID) + if err != nil { + return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + if existed { + conversationID = ret.ConversationID + } + } else { + ret, existed, err := wf.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName) + if err != nil { + return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + if existed { + conversationID = ret.ConversationID + } + } + + if !existed { + return map[string]any{ + "isSuccess": false, + }, nil + } + + resp, err := crossconversation.DefaultSVC().ClearConversationHistory(ctx, &crossconversation.ClearConversationHistoryReq{ + ConversationID: conversationID, + }) + if err != nil { + return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + if resp == nil { + return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, fmt.Errorf("clear conversation history failed, response is nil")) + } + if execCtx.ExeCfg.SectionID != nil { + atomic.StoreInt64(execCtx.ExeCfg.SectionID, resp.SectionID) + } + return map[string]any{ + "isSuccess": true, + }, nil + +} diff --git a/backend/domain/workflow/internal/nodes/conversation/conversationhistory.go b/backend/domain/workflow/internal/nodes/conversation/conversationhistory.go new file mode 100644 index 00000000..35ef1b2a --- /dev/null +++ b/backend/domain/workflow/internal/nodes/conversation/conversationhistory.go @@ -0,0 +1,190 @@ +/* + * 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 conversation + +import ( + "context" + "errors" + "fmt" + + workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + crossconversation "github.com/coze-dev/coze-studio/backend/crossdomain/contract/conversation" + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" + + wf "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/domain/workflow/internal/canvas/convert" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" + "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/ternary" + "github.com/coze-dev/coze-studio/backend/types/errno" +) + +type ConversationHistoryConfig struct{} + +type ConversationHistory struct{} + +func (ch *ConversationHistoryConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) { + ns := &schema.NodeSchema{ + Key: vo.NodeKey(n.ID), + Type: entity.NodeTypeConversationHistory, + Name: n.Data.Meta.Title, + Configs: ch, + } + + if err := convert.SetInputsForNodeSchema(n, ns); err != nil { + return nil, err + } + + if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil { + return nil, err + } + + return ns, nil +} + +func (ch *ConversationHistoryConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) { + return &ConversationHistory{}, nil +} + +func (ch *ConversationHistory) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) { + var ( + execCtx = execute.GetExeCtx(ctx) + env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft) + appID = execCtx.ExeCfg.AppID + agentID = execCtx.ExeCfg.AgentID + connectorID = execCtx.ExeCfg.ConnectorID + userID = execCtx.ExeCfg.Operator + version = execCtx.ExeCfg.Version + initRunID = execCtx.ExeCfg.InitRoundID + ) + if agentID != nil { + return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("in the agent scenario, query conversation list is not available")) + } + if appID == nil { + return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("query conversation list node, app id is required")) + } + + conversationName, ok := input["conversationName"].(string) + if !ok { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversation name is required")) + } + + rounds, ok := input["rounds"].(int64) + if !ok { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("rounds is required")) + } + + template, existed, err := wf.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{ + AppID: appID, + Name: ptr.Of(conversationName), + Version: ptr.Of(version), + }) + + if err != nil { + return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + var conversationID int64 + if existed { + var sc *entity.StaticConversation + sc, existed, err = wf.GetRepository().GetStaticConversationByTemplateID(ctx, env, userID, connectorID, template.TemplateID) + if err != nil { + return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + if existed { + conversationID = sc.ConversationID + } + + } else { + var dc *entity.DynamicConversation + dc, existed, err = wf.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName) + if err != nil { + return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + if existed { + conversationID = dc.ConversationID + } + } + + if !existed { + return nil, vo.WrapError(errno.ErrConversationOfAppNotFound, fmt.Errorf("the conversation name does not exist: '%v'", conversationName)) + } + + currentConversationID := execCtx.ExeCfg.ConversationID + isCurrentConversation := currentConversationID != nil && *currentConversationID == conversationID + var sectionID int64 + if isCurrentConversation { + if execCtx.ExeCfg.SectionID == nil { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("section id is required")) + } + sectionID = *execCtx.ExeCfg.SectionID + } else { + cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, conversationID) + if err != nil { + return nil, err + } + sectionID = cInfo.SectionID + } + + runIDs, err := crossmessage.DefaultSVC().GetLatestRunIDs(ctx, &crossmessage.GetLatestRunIDsRequest{ + ConversationID: conversationID, + UserID: userID, + AppID: *appID, + Rounds: rounds, + InitRunID: initRunID, + SectionID: sectionID, + }) + + if err != nil { + return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + if len(runIDs) == 0 { + return map[string]any{ + "messageList": []any{}, + }, nil + } + + response, err := crossmessage.DefaultSVC().GetMessagesByRunIDs(ctx, &crossmessage.GetMessagesByRunIDsRequest{ + ConversationID: conversationID, + RunIDs: runIDs, + }) + if err != nil { + return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + var messageList []any + for _, msg := range response.Messages { + content, err := nodes.ConvertMessageToString(ctx, msg) + if err != nil { + return nil, vo.WrapError(errno.ErrConversationNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + messageList = append(messageList, map[string]any{ + "role": string(msg.Role), + "content": content, + }) + } + + return map[string]any{ + "messageList": messageList, + }, nil +} diff --git a/backend/domain/workflow/internal/nodes/conversation/conversationlist.go b/backend/domain/workflow/internal/nodes/conversation/conversationlist.go new file mode 100644 index 00000000..2ca5b083 --- /dev/null +++ b/backend/domain/workflow/internal/nodes/conversation/conversationlist.go @@ -0,0 +1,153 @@ +/* + * 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 conversation + +import ( + "context" + "fmt" + "strconv" + + workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + + "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/domain/workflow/internal/canvas/convert" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" + "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/pkg/lang/ternary" + "github.com/coze-dev/coze-studio/backend/types/errno" +) + +type ConversationList struct{} + +type ConversationListConfig struct{} + +func (c *ConversationListConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) { + ns := &schema.NodeSchema{ + Key: vo.NodeKey(n.ID), + Type: entity.NodeTypeConversationList, + Name: n.Data.Meta.Title, + Configs: c, + } + + if err := convert.SetInputsForNodeSchema(n, ns); err != nil { + return nil, err + } + + if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil { + return nil, err + } + + return ns, nil +} + +func (c *ConversationListConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) { + return &ConversationList{}, nil +} + +type conversationInfo struct { + conversationName string + conversationId string +} + +func (c *ConversationList) Invoke(ctx context.Context, _ map[string]any) (map[string]any, error) { + var ( + execCtx = execute.GetExeCtx(ctx) + env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft) + appID = execCtx.ExeCfg.AppID + agentID = execCtx.ExeCfg.AgentID + connectorID = execCtx.ExeCfg.ConnectorID + userID = execCtx.ExeCfg.Operator + version = execCtx.ExeCfg.Version + ) + if agentID != nil { + return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("in the agent scenario, query conversation list is not available")) + } + if appID == nil { + return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("query conversation list node, app id is required")) + } + + templates, err := workflow.GetRepository().ListConversationTemplate(ctx, env, &vo.ListConversationTemplatePolicy{ + AppID: *appID, + Version: ptr.Of(version), + }) + if err != nil { + return nil, err + } + + templateIds := make([]int64, 0, len(templates)) + for _, template := range templates { + templateIds = append(templateIds, template.TemplateID) + } + + staticConversations, err := workflow.GetRepository().MGetStaticConversation(ctx, env, userID, connectorID, templateIds) + if err != nil { + return nil, err + } + + templateIDToConvID := slices.ToMap(staticConversations, func(conv *entity.StaticConversation) (int64, int64) { + return conv.TemplateID, conv.ConversationID + }) + + var conversationList []conversationInfo + + for _, template := range templates { + convID, ok := templateIDToConvID[template.TemplateID] + if !ok { + convID = 0 + } + conversationList = append(conversationList, conversationInfo{ + conversationName: template.Name, + conversationId: strconv.FormatInt(convID, 10), + }) + } + + dynamicConversations, err := workflow.GetRepository().ListDynamicConversation(ctx, env, &vo.ListConversationPolicy{ + ListConversationMeta: vo.ListConversationMeta{ + APPID: *appID, + UserID: userID, + ConnectorID: connectorID, + }, + }) + if err != nil { + return nil, err + } + + for _, conv := range dynamicConversations { + conversationList = append(conversationList, conversationInfo{ + conversationName: conv.Name, + conversationId: strconv.FormatInt(conv.ConversationID, 10), + }) + } + + resultList := make([]any, len(conversationList)) + for i, v := range conversationList { + resultList[i] = map[string]any{ + "conversationName": v.conversationName, + "conversationId": v.conversationId, + } + } + + return map[string]any{ + "conversationList": resultList, + }, nil + +} diff --git a/backend/domain/workflow/internal/nodes/conversation/createconversation.go b/backend/domain/workflow/internal/nodes/conversation/createconversation.go new file mode 100644 index 00000000..688d5b1c --- /dev/null +++ b/backend/domain/workflow/internal/nodes/conversation/createconversation.go @@ -0,0 +1,142 @@ +/* + * 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 conversation + +import ( + "context" + "errors" + "fmt" + "github.com/coze-dev/coze-studio/backend/api/model/conversation/common" + + workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + crossconversation "github.com/coze-dev/coze-studio/backend/crossdomain/contract/conversation" + conventity "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity" + + "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/domain/workflow/internal/canvas/convert" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" + "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/types/errno" +) + +type CreateConversationConfig struct{} + +type CreateConversation struct{} + +func (c *CreateConversationConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) { + ns := &schema.NodeSchema{ + Key: vo.NodeKey(n.ID), + Type: entity.NodeTypeCreateConversation, + Name: n.Data.Meta.Title, + Configs: c, + } + + if err := convert.SetInputsForNodeSchema(n, ns); err != nil { + return nil, err + } + + if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil { + return nil, err + } + + return ns, nil +} + +func (c *CreateConversationConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) { + return &CreateConversation{}, nil +} + +func (c *CreateConversation) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) { + + var ( + execCtx = execute.GetExeCtx(ctx) + env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft) + appID = execCtx.ExeCfg.AppID + agentID = execCtx.ExeCfg.AgentID + version = execCtx.ExeCfg.Version + connectorID = execCtx.ExeCfg.ConnectorID + userID = execCtx.ExeCfg.Operator + conversationIDGenerator = workflow.ConversationIDGenerator(func(ctx context.Context, appID int64, userID, connectorID int64) (*conventity.Conversation, error) { + return crossconversation.DefaultSVC().CreateConversation(ctx, &conventity.CreateMeta{ + AgentID: appID, + UserID: userID, + ConnectorID: connectorID, + Scene: common.Scene_SceneWorkflow, + }) + }) + ) + if agentID != nil { + return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("in the agent scenario, create conversation is not available")) + } + + if appID == nil { + return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, errors.New("create conversation node, app id is required")) + } + + conversationName, ok := input["conversationName"].(string) + if !ok { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversation name is required")) + } + + template, existed, err := workflow.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{ + AppID: appID, + Name: ptr.Of(conversationName), + Version: ptr.Of(version), + }) + if err != nil { + return nil, err + } + + if existed { + cID, _, existed, err := workflow.GetRepository().GetOrCreateStaticConversation(ctx, env, conversationIDGenerator, &vo.CreateStaticConversation{ + AppID: ptr.From(appID), + TemplateID: template.TemplateID, + UserID: userID, + ConnectorID: connectorID, + }) + if err != nil { + return nil, err + } + return map[string]any{ + "isSuccess": true, + "conversationId": cID, + "isExisted": existed, + }, nil + } + + cID, _, existed, err := workflow.GetRepository().GetOrCreateDynamicConversation(ctx, env, conversationIDGenerator, &vo.CreateDynamicConversation{ + AppID: ptr.From(appID), + UserID: userID, + ConnectorID: connectorID, + Name: conversationName, + }) + if err != nil { + return nil, err + } + + return map[string]any{ + "isSuccess": true, + "conversationId": cID, + "isExisted": existed, + }, nil + +} diff --git a/backend/domain/workflow/internal/nodes/conversation/createmessage.go b/backend/domain/workflow/internal/nodes/conversation/createmessage.go new file mode 100644 index 00000000..baded0c9 --- /dev/null +++ b/backend/domain/workflow/internal/nodes/conversation/createmessage.go @@ -0,0 +1,299 @@ +/* + * 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 conversation + +import ( + "context" + "errors" + "fmt" + "github.com/coze-dev/coze-studio/backend/api/model/conversation/common" + conventity "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity" + + "strconv" + "sync/atomic" + + einoSchema "github.com/cloudwego/eino/schema" + model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" + workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + crossagentrun "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agentrun" + crossconversation "github.com/coze-dev/coze-studio/backend/crossdomain/contract/conversation" + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" + "github.com/coze-dev/coze-studio/backend/pkg/errorx" + + agententity "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" + "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/domain/workflow/internal/canvas/convert" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" + "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/types/errno" +) + +type CreateMessageConfig struct{} + +type CreateMessage struct{} + +func (c *CreateMessageConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) { + ns := &schema.NodeSchema{ + Key: vo.NodeKey(n.ID), + Type: entity.NodeTypeCreateMessage, + Name: n.Data.Meta.Title, + Configs: c, + } + + if err := convert.SetInputsForNodeSchema(n, ns); err != nil { + return nil, err + } + + if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil { + return nil, err + } + + return ns, nil +} + +func (c *CreateMessageConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) { + return &CreateMessage{}, nil +} + +func (c *CreateMessage) getConversationIDByName(ctx context.Context, env vo.Env, appID *int64, version, conversationName string, userID, connectorID int64) (int64, error) { + template, isExist, err := workflow.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{ + AppID: appID, + Name: ptr.Of(conversationName), + Version: ptr.Of(version), + }) + if err != nil { + return 0, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + conversationIDGenerator := workflow.ConversationIDGenerator(func(ctx context.Context, appID int64, userID, connectorID int64) (*conventity.Conversation, error) { + return crossconversation.DefaultSVC().CreateConversation(ctx, &conventity.CreateMeta{ + AgentID: appID, + UserID: userID, + ConnectorID: connectorID, + Scene: common.Scene_SceneWorkflow, + }) + }) + + var conversationID int64 + if isExist { + cID, _, _, err := workflow.GetRepository().GetOrCreateStaticConversation(ctx, env, conversationIDGenerator, &vo.CreateStaticConversation{ + AppID: ptr.From(appID), + TemplateID: template.TemplateID, + UserID: userID, + ConnectorID: connectorID, + }) + if err != nil { + return 0, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + conversationID = cID + } else { + dc, _, err := workflow.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName) + if err != nil { + return 0, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + if dc != nil { + conversationID = dc.ConversationID + } + } + return conversationID, nil +} + +func (c *CreateMessage) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) { + var ( + execCtx = execute.GetExeCtx(ctx) + env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft) + appID = execCtx.ExeCfg.AppID + agentID = execCtx.ExeCfg.AgentID + version = execCtx.ExeCfg.Version + connectorID = execCtx.ExeCfg.ConnectorID + userID = execCtx.ExeCfg.Operator + ) + + conversationName, ok := input["conversationName"].(string) + if !ok { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversationName is required")) + } + + role, ok := input["role"].(string) + if !ok { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("role is required")) + } + if role != "user" && role != "assistant" { + return nil, vo.WrapError(errno.ErrInvalidParameter, fmt.Errorf("role must be user or assistant")) + } + + content, ok := input["content"].(string) + if !ok { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("content is required")) + } + + var conversationID int64 + var err error + var resolvedAppID int64 + if appID == nil { + if conversationName != "Default" { + return nil, vo.WrapError(errno.ErrOnlyDefaultConversationAllowInAgentScenario, errors.New("conversation node only allow in application")) + } + if agentID == nil || execCtx.ExeCfg.ConversationID == nil { + return map[string]any{ + "isSuccess": false, + "message": map[string]any{ + "messageId": "0", + "role": role, + "contentType": "text", + "content": content, + }, + }, nil + } + conversationID = *execCtx.ExeCfg.ConversationID + resolvedAppID = *agentID + } else { + conversationID, err = c.getConversationIDByName(ctx, env, appID, version, conversationName, userID, connectorID) + if err != nil { + return nil, err + } + resolvedAppID = *appID + } + + if conversationID == 0 { + return map[string]any{ + "isSuccess": false, + "message": map[string]any{ + "messageId": "0", + "role": role, + "contentType": "text", + "content": content, + }, + }, nil + } + + currentConversationID := execCtx.ExeCfg.ConversationID + isCurrentConversation := currentConversationID != nil && *currentConversationID == conversationID + var runID int64 + var sectionID int64 + if isCurrentConversation { + if execCtx.ExeCfg.SectionID != nil { + sectionID = *execCtx.ExeCfg.SectionID + } else { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("section id is required")) + } + } else { + cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, conversationID) + if err != nil { + return nil, err + } + sectionID = cInfo.SectionID + } + + if role == "user" { + // For user messages, always create a new run and store the ID in the context. + runRecord, err := crossagentrun.DefaultSVC().Create(ctx, &agententity.AgentRunMeta{ + AgentID: resolvedAppID, + ConversationID: conversationID, + UserID: strconv.FormatInt(userID, 10), + ConnectorID: connectorID, + SectionID: sectionID, + }) + if err != nil { + return nil, err + } + newRunID := runRecord.ID + if execCtx.ExeCfg.RoundID != nil { + atomic.StoreInt64(execCtx.ExeCfg.RoundID, newRunID) + } + runID = newRunID + } else if isCurrentConversation { + // For assistant messages in the same conversation, reuse the runID from the context. + if execCtx.ExeCfg.RoundID == nil { + // This indicates an inconsistent state, as a user message should have set this. + return map[string]any{ + "isSuccess": false, + "message": map[string]any{ + "messageId": "0", + "role": role, + "contentType": "text", + "content": content, + }, + }, nil + } + runID = *execCtx.ExeCfg.RoundID + } else { + // For assistant messages in a different conversation or a new workflow run, + // find the latest runID or create a new one as a fallback. + runIDs, err := crossmessage.DefaultSVC().GetLatestRunIDs(ctx, &crossmessage.GetLatestRunIDsRequest{ + ConversationID: conversationID, + UserID: userID, + AppID: resolvedAppID, + Rounds: 1, + }) + if err != nil { + return nil, err + } + if len(runIDs) > 0 && runIDs[0] != 0 { + runID = runIDs[0] + } else { + runRecord, err := crossagentrun.DefaultSVC().Create(ctx, &agententity.AgentRunMeta{ + AgentID: resolvedAppID, + ConversationID: conversationID, + UserID: strconv.FormatInt(userID, 10), + ConnectorID: connectorID, + SectionID: sectionID, + }) + if err != nil { + return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + runID = runRecord.ID + } + } + + message := &model.Message{ + ConversationID: conversationID, + Role: einoSchema.RoleType(role), + Content: content, + ContentType: model.ContentType("text"), + UserID: strconv.FormatInt(userID, 10), + AgentID: resolvedAppID, + RunID: runID, + SectionID: sectionID, + } + if message.Role == einoSchema.User { + message.MessageType = model.MessageTypeQuestion + } else { + message.MessageType = model.MessageTypeAnswer + } + msg, err := crossmessage.DefaultSVC().Create(ctx, message) + if err != nil { + return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + messageOutput := map[string]any{ + "messageId": msg.ID, + "role": role, + "contentType": "text", + "content": content, + } + + return map[string]any{ + "isSuccess": true, + "message": messageOutput, + }, nil +} diff --git a/backend/domain/workflow/internal/nodes/conversation/deleteconversation.go b/backend/domain/workflow/internal/nodes/conversation/deleteconversation.go new file mode 100644 index 00000000..e1785029 --- /dev/null +++ b/backend/domain/workflow/internal/nodes/conversation/deleteconversation.go @@ -0,0 +1,122 @@ +/* + * 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 conversation + +import ( + "context" + "errors" + "fmt" + + workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + + "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/domain/workflow/internal/canvas/convert" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" + "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/types/errno" +) + +type DeleteConversationConfig struct{} + +type DeleteConversation struct{} + +func (d *DeleteConversationConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) { + ns := &schema.NodeSchema{ + Key: vo.NodeKey(n.ID), + Type: entity.NodeTypeConversationDelete, + Name: n.Data.Meta.Title, + Configs: d, + } + + if err := convert.SetInputsForNodeSchema(n, ns); err != nil { + return nil, err + } + + if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil { + return nil, err + } + + return ns, nil +} + +func (d *DeleteConversationConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) { + return &DeleteConversation{}, nil +} + +func (d *DeleteConversation) Invoke(ctx context.Context, in map[string]any) (map[string]any, error) { + + var ( + execCtx = execute.GetExeCtx(ctx) + env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft) + appID = execCtx.ExeCfg.AppID + agentID = execCtx.ExeCfg.AgentID + version = execCtx.ExeCfg.Version + connectorID = execCtx.ExeCfg.ConnectorID + userID = execCtx.ExeCfg.Operator + ) + + if agentID != nil { + return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("in the agent scenario, delete conversation is not available")) + } + + if appID == nil { + return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, errors.New("delete conversation node, app id is required")) + } + + cName, ok := in["conversationName"] + if !ok { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversation name is required")) + } + + conversationName := cName.(string) + + _, existed, err := workflow.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{ + AppID: appID, + Name: ptr.Of(conversationName), + Version: ptr.Of(version), + }) + if err != nil { + return nil, err + } + + if existed { + return nil, vo.WrapError(errno.ErrConversationNodeInvalidOperation, fmt.Errorf("only conversation created through nodes are allowed to be modified or deleted")) + } + + dyConversation, existed, err := workflow.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName) + if err != nil { + return nil, err + } + + if !existed { + return nil, vo.WrapError(errno.ErrConversationOfAppNotFound, fmt.Errorf("the conversation name does not exist: '%v'", conversationName)) + } + + _, err = workflow.GetRepository().DeleteDynamicConversation(ctx, env, dyConversation.ID) + if err != nil { + return nil, err + } + + return map[string]any{ + "isSuccess": true, + }, nil +} diff --git a/backend/domain/workflow/internal/nodes/conversation/deletemessage.go b/backend/domain/workflow/internal/nodes/conversation/deletemessage.go new file mode 100644 index 00000000..2b4a66ad --- /dev/null +++ b/backend/domain/workflow/internal/nodes/conversation/deletemessage.go @@ -0,0 +1,162 @@ +/* + * 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 conversation + +import ( + "context" + "errors" + "fmt" + "strconv" + + workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" + msgentity "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity" + "github.com/coze-dev/coze-studio/backend/pkg/errorx" + + wf "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/domain/workflow/internal/canvas/convert" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" + "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/types/errno" +) + +type DeleteMessageConfig struct{} + +type DeleteMessage struct{} + +func (d *DeleteMessageConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) { + ns := &schema.NodeSchema{ + Key: vo.NodeKey(n.ID), + Type: entity.NodeTypeDeleteMessage, + Name: n.Data.Meta.Title, + Configs: d, + } + + if err := convert.SetInputsForNodeSchema(n, ns); err != nil { + return nil, err + } + + if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil { + return nil, err + } + + return ns, nil +} + +func (d *DeleteMessageConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) { + return &DeleteMessage{}, nil +} + +func (d *DeleteMessage) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) { + var ( + execCtx = execute.GetExeCtx(ctx) + env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft) + appID = execCtx.ExeCfg.AppID + agentID = execCtx.ExeCfg.AgentID + version = execCtx.ExeCfg.Version + connectorID = execCtx.ExeCfg.ConnectorID + userID = execCtx.ExeCfg.Operator + + successMap = map[string]any{ + "isSuccess": true, + } + failedMap = map[string]any{ + "isSuccess": false, + } + ) + + conversationName, ok := input["conversationName"].(string) + if !ok { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversationName is required")) + } + messageStr, ok := input["messageId"].(string) + if !ok { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("messageId is required")) + } + messageID, err := strconv.ParseInt(messageStr, 10, 64) + if err != nil { + return nil, vo.WrapError(errno.ErrInvalidParameter, err) + } + + if appID == nil { + if conversationName != "Default" { + return nil, vo.WrapError(errno.ErrOnlyDefaultConversationAllowInAgentScenario, fmt.Errorf("only default conversation allow in agent scenario")) + } + + if agentID == nil || execCtx.ExeCfg.ConversationID == nil { + return failedMap, nil + } + + err = crossmessage.DefaultSVC().Delete(ctx, &msgentity.DeleteMeta{MessageIDs: []int64{messageID}, ConversationID: execCtx.ExeCfg.ConversationID}) + if err != nil { + return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + return successMap, nil + } + + t, existed, err := wf.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{ + AppID: appID, + Name: ptr.Of(conversationName), + Version: ptr.Of(version), + }) + if err != nil { + return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + if existed { + sc, existed, err := wf.GetRepository().GetStaticConversationByTemplateID(ctx, env, userID, connectorID, t.TemplateID) + if err != nil { + return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + if !existed { + return failedMap, nil + } + + err = crossmessage.DefaultSVC().Delete(ctx, &msgentity.DeleteMeta{MessageIDs: []int64{messageID}, ConversationID: ptr.Of(sc.ConversationID)}) + if err != nil { + return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + return successMap, nil + + } else { + dc, existed, err := wf.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName) + if err != nil { + return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + if !existed { + return failedMap, nil + } + + err = crossmessage.DefaultSVC().Delete(ctx, &msgentity.DeleteMeta{MessageIDs: []int64{messageID}, ConversationID: ptr.Of(dc.ConversationID)}) + if err != nil { + return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + return successMap, nil + + } + +} diff --git a/backend/domain/workflow/internal/nodes/conversation/editmessage.go b/backend/domain/workflow/internal/nodes/conversation/editmessage.go new file mode 100644 index 00000000..84b7102d --- /dev/null +++ b/backend/domain/workflow/internal/nodes/conversation/editmessage.go @@ -0,0 +1,181 @@ +/* + * 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 conversation + +import ( + "context" + "errors" + "fmt" + model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" + "strconv" + + workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" + "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/domain/workflow/internal/canvas/convert" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" + "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/ternary" + "github.com/coze-dev/coze-studio/backend/types/errno" +) + +type EditMessageConfig struct{} + +type EditMessage struct{} + +func (e *EditMessageConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) { + ns := &schema.NodeSchema{ + Key: vo.NodeKey(n.ID), + Type: entity.NodeTypeEditMessage, + Name: n.Data.Meta.Title, + Configs: e, + } + + if err := convert.SetInputsForNodeSchema(n, ns); err != nil { + return nil, err + } + + if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil { + return nil, err + } + + return ns, nil +} + +func (e *EditMessageConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) { + return &EditMessage{}, nil +} + +func (e *EditMessage) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) { + var ( + execCtx = execute.GetExeCtx(ctx) + env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft) + appID = execCtx.ExeCfg.AppID + agentID = execCtx.ExeCfg.AgentID + version = execCtx.ExeCfg.Version + connectorID = execCtx.ExeCfg.ConnectorID + userID = execCtx.ExeCfg.Operator + + successMap = map[string]any{ + "isSuccess": true, + } + failedMap = map[string]any{ + "isSuccess": false, + } + ) + + conversationName, ok := input["conversationName"].(string) + if !ok { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversationName is required")) + } + + messageStr, ok := input["messageId"].(string) + if !ok { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("messageId is required")) + } + + messageID, err := strconv.ParseInt(messageStr, 10, 64) + if err != nil { + return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + newContent, ok := input["newContent"].(string) + if !ok { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("newContent is required")) + } + + if appID == nil { + if conversationName != "Default" { + return nil, vo.WrapError(errno.ErrOnlyDefaultConversationAllowInAgentScenario, fmt.Errorf("only default conversation allow in agent scenario")) + } + + if agentID == nil || execCtx.ExeCfg.ConversationID == nil { + return failedMap, nil + } + + _, err = crossmessage.DefaultSVC().Edit(ctx, &model.Message{ConversationID: *execCtx.ExeCfg.ConversationID, ID: messageID, Content: newContent}) + if err != nil { + return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + return successMap, err + } + + msg, err := message.DefaultSVC().GetMessageByID(ctx, messageID) + if err != nil { + return nil, err + } + + if msg == nil { + return nil, vo.NewError(errno.ErrMessageNodeOperationFail, errorx.KV("cause", "message not found")) + } + + if msg.Content == newContent { + return successMap, nil + } + + t, existed, err := workflow.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{ + AppID: appID, + Name: ptr.Of(conversationName), + Version: ptr.Of(version), + }) + if err != nil { + return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + if existed { + sts, existed, err := workflow.GetRepository().GetStaticConversationByTemplateID(ctx, env, userID, connectorID, t.TemplateID) + if err != nil { + return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + if !existed { + return failedMap, nil + } + + _, err = crossmessage.DefaultSVC().Edit(ctx, &model.Message{ConversationID: sts.ConversationID, ID: messageID, Content: newContent}) + if err != nil { + return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + return successMap, nil + + } else { + dyConversation, existed, err := workflow.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName) + if err != nil { + return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + if !existed { + return failedMap, nil + } + + _, err = crossmessage.DefaultSVC().Edit(ctx, &model.Message{ConversationID: dyConversation.ConversationID, ID: messageID, Content: newContent}) + if err != nil { + return nil, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + return successMap, nil + + } + +} diff --git a/backend/domain/workflow/internal/nodes/conversation/messagelist.go b/backend/domain/workflow/internal/nodes/conversation/messagelist.go new file mode 100644 index 00000000..be50af48 --- /dev/null +++ b/backend/domain/workflow/internal/nodes/conversation/messagelist.go @@ -0,0 +1,207 @@ +/* + * 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 conversation + +import ( + "context" + "errors" + "fmt" + "strconv" + + workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" + "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/domain/workflow/internal/canvas/convert" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" + "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/ternary" + "github.com/coze-dev/coze-studio/backend/types/errno" +) + +type MessageListConfig struct{} + +type MessageList struct{} + +func (m *MessageListConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) { + ns := &schema.NodeSchema{ + Key: vo.NodeKey(n.ID), + Type: entity.NodeTypeMessageList, + Name: n.Data.Meta.Title, + Configs: m, + } + + if err := convert.SetInputsForNodeSchema(n, ns); err != nil { + return nil, err + } + + if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil { + return nil, err + } + + return ns, nil +} + +func (m *MessageListConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) { + return &MessageList{}, nil +} + +func (m *MessageList) getConversationIDByName(ctx context.Context, env vo.Env, appID *int64, version, conversationName string, userID, connectorID int64) (int64, error) { + template, isExist, err := workflow.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{ + AppID: appID, + Name: ptr.Of(conversationName), + Version: ptr.Of(version), + }) + + if err != nil { + return 0, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + + var conversationID int64 + if isExist { + sc, _, err := workflow.GetRepository().GetStaticConversationByTemplateID(ctx, env, userID, connectorID, template.TemplateID) + if err != nil { + return 0, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + if sc != nil { + conversationID = sc.ConversationID + } + } else { + dc, _, err := workflow.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName) + if err != nil { + return 0, vo.WrapError(errno.ErrMessageNodeOperationFail, err, errorx.KV("cause", vo.UnwrapRootErr(err).Error())) + } + if dc != nil { + conversationID = dc.ConversationID + } + } + + return conversationID, nil +} + +func (m *MessageList) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) { + var ( + execCtx = execute.GetExeCtx(ctx) + env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft) + appID = execCtx.ExeCfg.AppID + agentID = execCtx.ExeCfg.AgentID + version = execCtx.ExeCfg.Version + connectorID = execCtx.ExeCfg.ConnectorID + userID = execCtx.ExeCfg.Operator + ) + + conversationName, ok := input["conversationName"].(string) + if !ok { + return nil, vo.WrapError(errno.ErrConversationNodeInvalidOperation, errors.New("ConversationName is required")) + } + + var conversationID int64 + var err error + var resolvedAppID int64 + if appID == nil { + if conversationName != "Default" { + return nil, vo.WrapError(errno.ErrOnlyDefaultConversationAllowInAgentScenario, errors.New("conversation node only allow in application")) + } + if agentID == nil || execCtx.ExeCfg.ConversationID == nil { + return map[string]any{ + "messageList": []any{}, + "firstId": "0", + "lastId": "0", + "hasMore": false, + }, nil + } + conversationID = *execCtx.ExeCfg.ConversationID + resolvedAppID = *agentID + } else { + conversationID, err = m.getConversationIDByName(ctx, env, appID, version, conversationName, userID, connectorID) + if err != nil { + return nil, err + } + resolvedAppID = *appID + } + + req := &crossmessage.MessageListRequest{ + UserID: userID, + AppID: resolvedAppID, + ConversationID: conversationID, + } + + if req.ConversationID == 0 { + return map[string]any{ + "messageList": []any{}, + "firstId": "0", + "lastId": "0", + "hasMore": false, + }, nil + } + + limit, ok := input["limit"].(int64) + if ok { + if limit > 0 && limit <= 50 { + req.Limit = limit + } else { + req.Limit = 50 + } + } else { + req.Limit = 50 + } + beforeID, ok := input["beforeId"].(string) + if ok { + + req.BeforeID = &beforeID + } + afterID, ok := input["afterId"].(string) + if ok { + + req.AfterID = &afterID + } + + if beforeID != "" && afterID != "" { + return nil, vo.WrapError(errno.ErrInvalidParameter, fmt.Errorf("BeforeID and AfterID cannot be set at the same time")) + } + + ml, err := crossmessage.DefaultSVC().MessageList(ctx, req) + if err != nil { + return nil, err + } + + var messageList []any + for _, msg := range ml.Messages { + content, err := nodes.ConvertMessageToString(ctx, msg) + if err != nil { + return nil, err + } + messageList = append(messageList, map[string]any{ + "messageId": strconv.FormatInt(msg.ID, 10), + "role": string(msg.Role), + "contentType": msg.ContentType, + "content": content, + }) + } + + return map[string]any{ + "messageList": messageList, + "firstId": ml.FirstID, + "lastId": ml.LastID, + "hasMore": ml.HasMore, + }, nil + +} diff --git a/backend/domain/workflow/internal/nodes/conversation/updateconversation.go b/backend/domain/workflow/internal/nodes/conversation/updateconversation.go new file mode 100644 index 00000000..00ff9031 --- /dev/null +++ b/backend/domain/workflow/internal/nodes/conversation/updateconversation.go @@ -0,0 +1,149 @@ +/* + * 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 conversation + +import ( + "context" + "errors" + "fmt" + "strconv" + + workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + + wf "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/domain/workflow/internal/canvas/convert" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" + "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/types/errno" +) + +type UpdateConversationConfig struct{} + +type UpdateConversation struct{} + +func (c *UpdateConversationConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) { + ns := &schema.NodeSchema{ + Key: vo.NodeKey(n.ID), + Type: entity.NodeTypeConversationUpdate, + Name: n.Data.Meta.Title, + Configs: c, + } + + if err := convert.SetInputsForNodeSchema(n, ns); err != nil { + return nil, err + } + + if err := convert.SetOutputTypesForNodeSchema(n, ns); err != nil { + return nil, err + } + + return ns, nil +} + +func (c *UpdateConversationConfig) Build(_ context.Context, ns *schema.NodeSchema, _ ...schema.BuildOption) (any, error) { + return &UpdateConversation{}, nil +} + +func (c *UpdateConversation) Invoke(ctx context.Context, in map[string]any) (map[string]any, error) { + + var ( + execCtx = execute.GetExeCtx(ctx) + env = ternary.IFElse(execCtx.ExeCfg.Mode == workflowModel.ExecuteModeRelease, vo.Online, vo.Draft) + appID = execCtx.ExeCfg.AppID + agentID = execCtx.ExeCfg.AgentID + version = execCtx.ExeCfg.Version + connectorID = execCtx.ExeCfg.ConnectorID + userID = execCtx.ExeCfg.Operator + ) + cName, ok := in["conversationName"] + if !ok { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("conversation name is required")) + } + + conversationName := cName.(string) + + ncName, ok := in["newConversationName"] + if !ok { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("new conversationName name is required")) + } + + newConversationName := ncName.(string) + + if agentID != nil { + return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, fmt.Errorf("in the agent scenario, update conversation is not available")) + } + + if appID == nil { + return nil, vo.WrapError(errno.ErrConversationNodesNotAvailable, errors.New("conversation update node, app id is required")) + } + + _, existed, err := wf.GetRepository().GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{ + AppID: appID, + Name: ptr.Of(conversationName), + Version: ptr.Of(version), + }) + if err != nil { + return nil, err + } + + if existed { + return nil, vo.WrapError(errno.ErrConversationNodeInvalidOperation, fmt.Errorf("only conversation created through nodes are allowed to be modified or deleted")) + } + + conversation, existed, err := wf.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, conversationName) + if err != nil { + return nil, err + } + + if !existed { + return map[string]any{ + "conversationId": "0", + "isSuccess": false, + "isExisted": false, + }, nil + } + + ncConversation, existed, err := wf.GetRepository().GetDynamicConversationByName(ctx, env, *appID, connectorID, userID, newConversationName) + if err != nil { + return nil, err + } + + if existed { + return map[string]any{ + "conversationId": strconv.FormatInt(ncConversation.ConversationID, 10), + "isSuccess": false, + "isExisted": true, + }, nil + } + + err = wf.GetRepository().UpdateDynamicConversationNameByID(ctx, env, conversation.ID, newConversationName) + if err != nil { + return nil, err + } + + return map[string]any{ + "conversationId": strconv.FormatInt(conversation.ConversationID, 10), + "isSuccess": true, + "isExisted": false, + }, nil + +} diff --git a/backend/domain/workflow/internal/nodes/intentdetector/intent_detector.go b/backend/domain/workflow/internal/nodes/intentdetector/intent_detector.go index db256bb1..114244d1 100644 --- a/backend/domain/workflow/internal/nodes/intentdetector/intent_detector.go +++ b/backend/domain/workflow/internal/nodes/intentdetector/intent_detector.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "maps" "strconv" "strings" @@ -29,21 +30,26 @@ import ( "github.com/spf13/cast" model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/modelmgr" + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" crossmodelmgr "github.com/coze-dev/coze-studio/backend/crossdomain/contract/modelmgr" "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/domain/workflow/internal/canvas/convert" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes" schema2 "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" + "github.com/coze-dev/coze-studio/backend/pkg/ctxcache" "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/sonic" ) type Config struct { - Intents []string - SystemPrompt string - IsFastMode bool - LLMParams *model.LLMParams + Intents []string + SystemPrompt string + IsFastMode bool + LLMParams *model.LLMParams + ChatHistorySetting *vo.ChatHistorySetting } func (c *Config) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema2.NodeSchema, error) { @@ -59,6 +65,10 @@ func (c *Config) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (* return nil, fmt.Errorf("intent detector node's llmParam is nil") } + if n.Data.Inputs.ChatHistorySetting != nil { + c.ChatHistorySetting = n.Data.Inputs.ChatHistorySetting + } + llmParam, ok := param.(vo.IntentDetectorLLMParam) if !ok { return nil, fmt.Errorf("llm node's llmParam must be LLMParam, got %v", llmParam) @@ -141,14 +151,16 @@ func (c *Config) Build(ctx context.Context, _ *schema2.NodeSchema, _ ...schema2. &schema.Message{Content: sptTemplate, Role: schema.System}, &schema.Message{Content: "{{query}}", Role: schema.User}) - r, err := chain.AppendChatTemplate(prompts).AppendChatModel(m).Compile(ctx) + r, err := chain.AppendChatTemplate(newHistoryChatTemplate(prompts, c.ChatHistorySetting)).AppendChatModel(m).Compile(ctx) if err != nil { return nil, err } + return &IntentDetector{ - isFastMode: c.IsFastMode, - systemPrompt: c.SystemPrompt, - runner: r, + isFastMode: c.IsFastMode, + systemPrompt: c.SystemPrompt, + runner: r, + ChatHistorySetting: c.ChatHistorySetting, }, nil } @@ -182,6 +194,10 @@ func (c *Config) ExpectPorts(ctx context.Context, n *vo.Node) []string { return expects } +type contextKey string + +const chatHistoryKey contextKey = "chatHistory" + const SystemIntentPrompt = ` # Role You are an intention classification expert, good at being able to judge which classification the user's input belongs to. @@ -240,9 +256,10 @@ Note: const classificationID = "classificationId" type IntentDetector struct { - isFastMode bool - systemPrompt string - runner compose.Runnable[map[string]any, *schema.Message] + isFastMode bool + systemPrompt string + runner compose.Runnable[map[string]any, *schema.Message] + ChatHistorySetting *vo.ChatHistorySetting } func (id *IntentDetector) parseToNodeOut(content string) (map[string]any, error) { @@ -320,3 +337,66 @@ func toIntentString(its []string) (string, error) { return sonic.MarshalString(vs) } + +func (id *IntentDetector) ToCallbackInput(ctx context.Context, in map[string]any) (map[string]any, error) { + if id.ChatHistorySetting == nil || !id.ChatHistorySetting.EnableChatHistory { + return in, nil + } + + var messages []*crossmessage.WfMessage + var scMessages []*schema.Message + var sectionID *int64 + execCtx := execute.GetExeCtx(ctx) + if execCtx != nil { + messages = execCtx.ExeCfg.ConversationHistory + scMessages = execCtx.ExeCfg.ConversationHistorySchemaMessages + sectionID = execCtx.ExeCfg.SectionID + } + + ret := map[string]any{ + "chatHistory": []any{}, + } + maps.Copy(ret, in) + + if len(messages) == 0 { + return ret, nil + } + if sectionID != nil && messages[0].SectionID != *sectionID { + return ret, nil + } + + maxRounds := int(id.ChatHistorySetting.ChatHistoryRound) + if execCtx != nil && execCtx.ExeCfg.MaxHistoryRounds != nil { + maxRounds = min(int(*execCtx.ExeCfg.MaxHistoryRounds), maxRounds) + } + + count := 0 + startIdx := 0 + for i := len(messages) - 1; i >= 0; i-- { + if messages[i].Role == schema.User { + count++ + } + if count >= maxRounds { + startIdx = i + break + } + } + + var historyMessages []any + for _, msg := range messages[startIdx:] { + content, err := nodes.ConvertMessageToString(ctx, msg) + if err != nil { + logs.CtxWarnf(ctx, "failed to convert message to string: %v", err) + continue + } + historyMessages = append(historyMessages, map[string]any{ + "role": string(msg.Role), + "content": content, + }) + } + + ctxcache.Store(ctx, chatHistoryKey, scMessages[startIdx:]) + + ret["chatHistory"] = historyMessages + return ret, nil +} diff --git a/backend/domain/workflow/internal/nodes/intentdetector/prompt.go b/backend/domain/workflow/internal/nodes/intentdetector/prompt.go new file mode 100644 index 00000000..4b08686d --- /dev/null +++ b/backend/domain/workflow/internal/nodes/intentdetector/prompt.go @@ -0,0 +1,85 @@ +/* + * 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 intentdetector + +import ( + "context" + "fmt" + "github.com/cloudwego/eino/components/prompt" + "github.com/cloudwego/eino/schema" + + "github.com/coze-dev/coze-studio/backend/api/model/workflow" + "github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute" + "github.com/coze-dev/coze-studio/backend/pkg/ctxcache" + "github.com/coze-dev/coze-studio/backend/pkg/logs" +) + +type historyChatTemplate struct { + basePrompt prompt.ChatTemplate + chatHistorySetting *vo.ChatHistorySetting +} + +func newHistoryChatTemplate(basePrompt prompt.ChatTemplate, chatHistorySetting *vo.ChatHistorySetting) prompt.ChatTemplate { + return &historyChatTemplate{ + basePrompt: basePrompt, + chatHistorySetting: chatHistorySetting, + } +} + +func (t *historyChatTemplate) Format(ctx context.Context, vs map[string]any, opts ...prompt.Option) ([]*schema.Message, error) { + baseMessages, err := t.basePrompt.Format(ctx, vs, opts...) + if err != nil { + return nil, fmt.Errorf("failed to format base prompt: %w", err) + } + if len(baseMessages) == 0 { + return nil, fmt.Errorf("base prompt returned no messages") + } + + if t.chatHistorySetting == nil || !t.chatHistorySetting.EnableChatHistory { + return baseMessages, nil + } + + exeCtx := execute.GetExeCtx(ctx) + if exeCtx == nil { + logs.CtxWarnf(ctx, "execute context is nil, skipping chat history") + return baseMessages, nil + } + + if exeCtx.ExeCfg.WorkflowMode != workflow.WorkflowMode_ChatFlow { + return baseMessages, nil + } + + historyMessages, ok := ctxcache.Get[[]*schema.Message](ctx, chatHistoryKey) + if !ok || len(historyMessages) == 0 { + logs.CtxWarnf(ctx, "conversation history is empty") + return baseMessages, nil + } + + if len(historyMessages) == 0 { + return baseMessages, nil + } + + finalMessages := make([]*schema.Message, 0, len(baseMessages)+len(historyMessages)) + finalMessages = append(finalMessages, baseMessages[0]) // System prompt + finalMessages = append(finalMessages, historyMessages...) + if len(baseMessages) > 1 { + finalMessages = append(finalMessages, baseMessages[1:]...) // User prompt and any others + } + + return finalMessages, nil +} diff --git a/backend/domain/workflow/internal/nodes/knowledge/knowledge_retrieve.go b/backend/domain/workflow/internal/nodes/knowledge/knowledge_retrieve.go index f81c5688..ea1258f1 100644 --- a/backend/domain/workflow/internal/nodes/knowledge/knowledge_retrieve.go +++ b/backend/domain/workflow/internal/nodes/knowledge/knowledge_retrieve.go @@ -19,24 +19,37 @@ package knowledge import ( "context" "errors" + "maps" "github.com/spf13/cast" + einoSchema "github.com/cloudwego/eino/schema" + "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/knowledge" + "github.com/coze-dev/coze-studio/backend/api/model/workflow" crossknowledge "github.com/coze-dev/coze-studio/backend/crossdomain/contract/knowledge" + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" "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/domain/workflow/internal/canvas/convert" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" + "github.com/coze-dev/coze-studio/backend/pkg/ctxcache" "github.com/coze-dev/coze-studio/backend/pkg/lang/slices" + "github.com/coze-dev/coze-studio/backend/pkg/logs" ) const outputList = "outputList" +type contextKey string + +const chatHistoryKey contextKey = "chatHistory" + type RetrieveConfig struct { - KnowledgeIDs []int64 - RetrievalStrategy *knowledge.RetrievalStrategy + KnowledgeIDs []int64 + RetrievalStrategy *knowledge.RetrievalStrategy + ChatHistorySetting *vo.ChatHistorySetting } func (r *RetrieveConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema.NodeSchema, error) { @@ -60,6 +73,10 @@ func (r *RetrieveConfig) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOp } r.KnowledgeIDs = knowledgeIDs + if inputs.ChatHistorySetting != nil { + r.ChatHistorySetting = inputs.ChatHistorySetting + } + retrievalStrategy := &knowledge.RetrievalStrategy{} var getDesignatedParamContent = func(name string) (any, bool) { @@ -154,14 +171,16 @@ func (r *RetrieveConfig) Build(_ context.Context, _ *schema.NodeSchema, _ ...sch } return &Retrieve{ - knowledgeIDs: r.KnowledgeIDs, - retrievalStrategy: r.RetrievalStrategy, + knowledgeIDs: r.KnowledgeIDs, + retrievalStrategy: r.RetrievalStrategy, + ChatHistorySetting: r.ChatHistorySetting, }, nil } type Retrieve struct { - knowledgeIDs []int64 - retrievalStrategy *knowledge.RetrievalStrategy + knowledgeIDs []int64 + retrievalStrategy *knowledge.RetrievalStrategy + ChatHistorySetting *vo.ChatHistorySetting } func (kr *Retrieve) Invoke(ctx context.Context, input map[string]any) (map[string]any, error) { @@ -173,6 +192,7 @@ func (kr *Retrieve) Invoke(ctx context.Context, input map[string]any) (map[strin req := &knowledge.RetrieveRequest{ Query: query, KnowledgeIDs: kr.knowledgeIDs, + ChatHistory: kr.GetChatHistoryOrNil(ctx, kr.ChatHistorySetting), Strategy: kr.retrievalStrategy, } @@ -190,3 +210,89 @@ func (kr *Retrieve) Invoke(ctx context.Context, input map[string]any) (map[strin return result, nil } + +func (kr *Retrieve) GetChatHistoryOrNil(ctx context.Context, ChatHistorySetting *vo.ChatHistorySetting) []*einoSchema.Message { + if ChatHistorySetting == nil || !ChatHistorySetting.EnableChatHistory { + return nil + } + + exeCtx := execute.GetExeCtx(ctx) + if exeCtx == nil { + logs.CtxWarnf(ctx, "execute context is nil, skipping chat history") + return nil + } + if exeCtx.ExeCfg.WorkflowMode != workflow.WorkflowMode_ChatFlow { + return nil + } + + historyMessages, ok := ctxcache.Get[[]*einoSchema.Message](ctx, chatHistoryKey) + + if !ok || len(historyMessages) == 0 { + logs.CtxWarnf(ctx, "conversation history is empty") + return nil + } + return historyMessages +} + +func (kr *Retrieve) ToCallbackInput(ctx context.Context, in map[string]any) (map[string]any, error) { + if kr.ChatHistorySetting == nil || !kr.ChatHistorySetting.EnableChatHistory { + return in, nil + } + + var messages []*crossmessage.WfMessage + var scMessages []*einoSchema.Message + var sectionID *int64 + execCtx := execute.GetExeCtx(ctx) + if execCtx != nil { + messages = execCtx.ExeCfg.ConversationHistory + scMessages = execCtx.ExeCfg.ConversationHistorySchemaMessages + sectionID = execCtx.ExeCfg.SectionID + } + + ret := map[string]any{ + "chatHistory": []any{}, + } + maps.Copy(ret, in) + + if len(messages) == 0 { + return ret, nil + } + + if sectionID != nil && messages[0].SectionID != *sectionID { + return ret, nil + } + + maxRounds := int(kr.ChatHistorySetting.ChatHistoryRound) + if execCtx != nil && execCtx.ExeCfg.MaxHistoryRounds != nil { + maxRounds = min(int(*execCtx.ExeCfg.MaxHistoryRounds), maxRounds) + } + + count := 0 + startIdx := 0 + for i := len(messages) - 1; i >= 0; i-- { + if messages[i].Role == einoSchema.User { + count++ + } + if count >= maxRounds { + startIdx = i + break + } + } + + var historyMessages []any + for _, msg := range messages[startIdx:] { + content, err := nodes.ConvertMessageToString(ctx, msg) + if err != nil { + logs.CtxWarnf(ctx, "failed to convert message to string: %v", err) + continue + } + historyMessages = append(historyMessages, map[string]any{ + "role": string(msg.Role), + "content": content, + }) + } + ctxcache.Store(ctx, chatHistoryKey, scMessages[startIdx:]) + + ret["chatHistory"] = historyMessages + return ret, nil +} diff --git a/backend/domain/workflow/internal/nodes/llm/llm.go b/backend/domain/workflow/internal/nodes/llm/llm.go index 58a6e762..24159540 100644 --- a/backend/domain/workflow/internal/nodes/llm/llm.go +++ b/backend/domain/workflow/internal/nodes/llm/llm.go @@ -40,6 +40,7 @@ import ( workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" workflow3 "github.com/coze-dev/coze-studio/backend/api/model/workflow" crossknowledge "github.com/coze-dev/coze-studio/backend/crossdomain/contract/knowledge" + 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" "github.com/coze-dev/coze-studio/backend/domain/workflow" @@ -59,6 +60,10 @@ import ( "github.com/coze-dev/coze-studio/backend/types/errno" ) +type contextKey string + +const chatHistoryKey contextKey = "chatHistory" + type Format int const ( @@ -167,12 +172,14 @@ type KnowledgeRecallConfig struct { } type Config struct { - SystemPrompt string - UserPrompt string - OutputFormat Format - LLMParams *crossmodel.LLMParams - FCParam *vo.FCParam - BackupLLMParams *crossmodel.LLMParams + SystemPrompt string + UserPrompt string + OutputFormat Format + LLMParams *crossmodel.LLMParams + FCParam *vo.FCParam + BackupLLMParams *crossmodel.LLMParams + ChatHistorySetting *vo.ChatHistorySetting + AssociateStartNodeUserInputFields map[string]struct{} } func (c *Config) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (*schema2.NodeSchema, error) { @@ -202,6 +209,13 @@ func (c *Config) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (* c.SystemPrompt = convertedLLMParam.SystemPrompt c.UserPrompt = convertedLLMParam.Prompt + if convertedLLMParam.EnableChatHistory { + c.ChatHistorySetting = &vo.ChatHistorySetting{ + EnableChatHistory: true, + ChatHistoryRound: convertedLLMParam.ChatHistoryRound, + } + } + var resFormat Format switch convertedLLMParam.ResponseFormat { case crossmodel.ResponseFormatText: @@ -273,6 +287,15 @@ func (c *Config) Adapt(_ context.Context, n *vo.Node, _ ...nodes.AdaptOption) (* } } + c.AssociateStartNodeUserInputFields = make(map[string]struct{}) + for _, info := range ns.InputSources { + if len(info.Path) == 1 && info.Source.Ref != nil && info.Source.Ref.FromNodeKey == entity.EntryNodeKey { + if compose.FromFieldPath(info.Source.Ref.FromPath).Equals(compose.FromField("USER_INPUT")) { + c.AssociateStartNodeUserInputFields[info.Path[0]] = struct{}{} + } + } + } + return ns, nil } @@ -320,7 +343,14 @@ func llmParamsToLLMParam(params vo.LLMParam) (*crossmodel.LLMParams, error) { case "systemPrompt": strVal := param.Input.Value.Content.(string) p.SystemPrompt = strVal - case "chatHistoryRound", "generationDiversity", "frequencyPenalty", "presencePenalty": + case "chatHistoryRound": + strVal := param.Input.Value.Content.(string) + int64Val, err := strconv.ParseInt(strVal, 10, 64) + if err != nil { + return nil, err + } + p.ChatHistoryRound = int64Val + case "generationDiversity", "frequencyPenalty", "presencePenalty": // do nothing case "topP": strVal := param.Input.Value.Content.(string) @@ -590,11 +620,12 @@ func (c *Config) Build(ctx context.Context, ns *schema2.NodeSchema, _ ...schema2 inputs[knowledgeUserPromptTemplateKey] = &vo.TypeInfo{ Type: vo.DataTypeString, } - sp := newPromptTpl(schema.System, c.SystemPrompt, inputs, nil) - up := newPromptTpl(schema.User, userPrompt, inputs, []string{knowledgeUserPromptTemplateKey}) + sp := newPromptTpl(schema.System, c.SystemPrompt, inputs) + up := newPromptTpl(schema.User, userPrompt, inputs, withReservedKeys([]string{knowledgeUserPromptTemplateKey}), withAssociateUserInputFields(c.AssociateStartNodeUserInputFields)) template := newPrompts(sp, up, modelWithInfo) + templateWithChatHistory := newPromptsWithChatHistory(template, c.ChatHistorySetting) - _ = g.AddChatTemplateNode(templateNodeKey, template, + _ = g.AddChatTemplateNode(templateNodeKey, templateWithChatHistory, compose.WithStatePreHandler(func(ctx context.Context, in map[string]any, state llmState) (map[string]any, error) { for k, v := range state { in[k] = v @@ -604,10 +635,12 @@ func (c *Config) Build(ctx context.Context, ns *schema2.NodeSchema, _ ...schema2 _ = g.AddEdge(knowledgeLambdaKey, templateNodeKey) } else { - sp := newPromptTpl(schema.System, c.SystemPrompt, ns.InputTypes, nil) - up := newPromptTpl(schema.User, userPrompt, ns.InputTypes, nil) + sp := newPromptTpl(schema.System, c.SystemPrompt, ns.InputTypes) + up := newPromptTpl(schema.User, userPrompt, ns.InputTypes, withAssociateUserInputFields(c.AssociateStartNodeUserInputFields)) template := newPrompts(sp, up, modelWithInfo) - _ = g.AddChatTemplateNode(templateNodeKey, template) + templateWithChatHistory := newPromptsWithChatHistory(template, c.ChatHistorySetting) + + _ = g.AddChatTemplateNode(templateNodeKey, templateWithChatHistory) _ = g.AddEdge(compose.START, templateNodeKey) } @@ -747,10 +780,11 @@ func (c *Config) Build(ctx context.Context, ns *schema2.NodeSchema, _ ...schema2 } llm := &LLM{ - r: r, - outputFormat: format, - requireCheckpoint: requireCheckpoint, - fullSources: ns.FullSources, + r: r, + outputFormat: format, + requireCheckpoint: requireCheckpoint, + fullSources: ns.FullSources, + chatHistorySetting: c.ChatHistorySetting, } return llm, nil @@ -825,10 +859,11 @@ func toRetrievalSearchType(s int64) (knowledge.SearchType, error) { } type LLM struct { - r compose.Runnable[map[string]any, map[string]any] - outputFormat Format - requireCheckpoint bool - fullSources map[string]*schema2.SourceInfo + r compose.Runnable[map[string]any, map[string]any] + outputFormat Format + requireCheckpoint bool + fullSources map[string]*schema2.SourceInfo + chatHistorySetting *vo.ChatHistorySetting } const ( @@ -1193,6 +1228,68 @@ type ToolInterruptEventStore interface { ResumeToolInterruptEvent(llmNodeKey vo.NodeKey, toolCallID string) (string, error) } +func (l *LLM) ToCallbackInput(ctx context.Context, input map[string]any) (map[string]any, error) { + if l.chatHistorySetting == nil || !l.chatHistorySetting.EnableChatHistory { + return input, nil + } + + var messages []*crossmessage.WfMessage + var scMessages []*schema.Message + var sectionID *int64 + execCtx := execute.GetExeCtx(ctx) + if execCtx != nil { + messages = execCtx.ExeCfg.ConversationHistory + scMessages = execCtx.ExeCfg.ConversationHistorySchemaMessages + sectionID = execCtx.ExeCfg.SectionID + } + + ret := map[string]any{ + "chatHistory": []any{}, + } + maps.Copy(ret, input) + + if len(messages) == 0 { + return ret, nil + } + + if sectionID != nil && messages[0].SectionID != *sectionID { + return ret, nil + } + + maxRounds := int(l.chatHistorySetting.ChatHistoryRound) + if execCtx != nil && execCtx.ExeCfg.MaxHistoryRounds != nil { + maxRounds = min(int(*execCtx.ExeCfg.MaxHistoryRounds), maxRounds) + } + count := 0 + startIdx := 0 + for i := len(messages) - 1; i >= 0; i-- { + if messages[i].Role == schema.User { + count++ + } + if count >= maxRounds { + startIdx = i + break + } + } + + var historyMessages []any + for _, msg := range messages[startIdx:] { + content, err := nodes.ConvertMessageToString(ctx, msg) + if err != nil { + logs.CtxWarnf(ctx, "failed to convert message to string: %v", err) + continue + } + historyMessages = append(historyMessages, map[string]any{ + "role": string(msg.Role), + "content": content, + }) + } + ctxcache.Store(ctx, chatHistoryKey, scMessages[startIdx:]) + + ret["chatHistory"] = historyMessages + return ret, nil +} + func (l *LLM) ToCallbackOutput(ctx context.Context, output map[string]any) (*nodes.StructuredCallbackOutput, error) { c := execute.GetExeCtx(ctx) if c == nil { diff --git a/backend/domain/workflow/internal/nodes/llm/prompt.go b/backend/domain/workflow/internal/nodes/llm/prompt.go index 2cc5d1be..b4f237a7 100644 --- a/backend/domain/workflow/internal/nodes/llm/prompt.go +++ b/backend/domain/workflow/internal/nodes/llm/prompt.go @@ -23,12 +23,14 @@ import ( "github.com/cloudwego/eino/components/prompt" "github.com/cloudwego/eino/schema" + "github.com/coze-dev/coze-studio/backend/api/model/workflow" "github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/execute" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes" schema2 "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" "github.com/coze-dev/coze-studio/backend/infra/contract/modelmgr" "github.com/coze-dev/coze-studio/backend/pkg/ctxcache" + "github.com/coze-dev/coze-studio/backend/pkg/logs" "github.com/coze-dev/coze-studio/backend/pkg/sonic" ) @@ -38,12 +40,30 @@ type prompts struct { mwi ModelWithInfo } +type promptsWithChatHistory struct { + prompts *prompts + cfg *vo.ChatHistorySetting +} + +func withReservedKeys(keys []string) func(tpl *promptTpl) { + return func(tpl *promptTpl) { + tpl.reservedKeys = keys + } +} + +func withAssociateUserInputFields(fs map[string]struct{}) func(tpl *promptTpl) { + return func(tpl *promptTpl) { + tpl.associateUserInputFields = fs + } +} + type promptTpl struct { - role schema.RoleType - tpl string - parts []promptPart - hasMultiModal bool - reservedKeys []string + role schema.RoleType + tpl string + parts []promptPart + hasMultiModal bool + reservedKeys []string + associateUserInputFields map[string]struct{} } type promptPart struct { @@ -54,12 +74,20 @@ type promptPart struct { func newPromptTpl(role schema.RoleType, tpl string, inputTypes map[string]*vo.TypeInfo, - reservedKeys []string, + opts ...func(*promptTpl), ) *promptTpl { if len(tpl) == 0 { return nil } + pTpl := &promptTpl{ + role: role, + tpl: tpl, + } + for _, opt := range opts { + opt(pTpl) + } + parts := nodes.ParseTemplate(tpl) promptParts := make([]promptPart, 0, len(parts)) hasMultiModal := false @@ -87,14 +115,10 @@ func newPromptTpl(role schema.RoleType, hasMultiModal = true } + pTpl.parts = promptParts + pTpl.hasMultiModal = hasMultiModal - return &promptTpl{ - role: role, - tpl: tpl, - parts: promptParts, - hasMultiModal: hasMultiModal, - reservedKeys: reservedKeys, - } + return pTpl } const sourceKey = "sources_%s" @@ -107,23 +131,53 @@ func newPrompts(sp, up *promptTpl, model ModelWithInfo) *prompts { } } +func newPromptsWithChatHistory(prompts *prompts, cfg *vo.ChatHistorySetting) *promptsWithChatHistory { + return &promptsWithChatHistory{ + prompts: prompts, + cfg: cfg, + } +} + func (pl *promptTpl) render(ctx context.Context, vs map[string]any, sources map[string]*schema2.SourceInfo, supportedModals map[modelmgr.Modal]bool, ) (*schema.Message, error) { - if !pl.hasMultiModal || len(supportedModals) == 0 { - var opts []nodes.RenderOption - if len(pl.reservedKeys) > 0 { - opts = append(opts, nodes.WithReservedKey(pl.reservedKeys...)) + isChatFlow := execute.GetExeCtx(ctx).ExeCfg.WorkflowMode == workflow.WorkflowMode_ChatFlow + userMessage := execute.GetExeCtx(ctx).ExeCfg.UserMessage + + if !isChatFlow { + if !pl.hasMultiModal || len(supportedModals) == 0 { + var opts []nodes.RenderOption + if len(pl.reservedKeys) > 0 { + opts = append(opts, nodes.WithReservedKey(pl.reservedKeys...)) + } + r, err := nodes.Render(ctx, pl.tpl, vs, sources, opts...) + if err != nil { + return nil, err + } + return &schema.Message{ + Role: pl.role, + Content: r, + }, nil } - r, err := nodes.Render(ctx, pl.tpl, vs, sources, opts...) - if err != nil { - return nil, err + } else { + if (!pl.hasMultiModal || len(supportedModals) == 0) && + (len(pl.associateUserInputFields) == 0 || + (len(pl.associateUserInputFields) > 0 && userMessage != nil && userMessage.MultiContent == nil)) { + var opts []nodes.RenderOption + if len(pl.reservedKeys) > 0 { + opts = append(opts, nodes.WithReservedKey(pl.reservedKeys...)) + } + r, err := nodes.Render(ctx, pl.tpl, vs, sources, opts...) + if err != nil { + return nil, err + } + return &schema.Message{ + Role: pl.role, + Content: r, + }, nil } - return &schema.Message{ - Role: pl.role, - Content: r, - }, nil + } multiParts := make([]schema.ChatMessagePart, 0, len(pl.parts)) @@ -141,6 +195,13 @@ func (pl *promptTpl) render(ctx context.Context, vs map[string]any, continue } + if _, ok := pl.associateUserInputFields[part.part.Value]; ok && userMessage != nil && isChatFlow { + for _, p := range userMessage.MultiContent { + multiParts = append(multiParts, transformMessagePart(p, supportedModals)) + } + continue + } + skipped, invalid := part.part.Skipped(sources) if invalid { var reserved bool @@ -164,6 +225,7 @@ func (pl *promptTpl) render(ctx context.Context, vs map[string]any, if err != nil { return nil, err } + if part.fileType == nil { multiParts = append(multiParts, schema.ChatMessagePart{ Type: schema.ChatMessagePartTypeText, @@ -172,64 +234,38 @@ func (pl *promptTpl) render(ctx context.Context, vs map[string]any, continue } + var originalPart schema.ChatMessagePart switch *part.fileType { case vo.FileTypeImage, vo.FileTypeSVG: - if _, ok := supportedModals[modelmgr.ModalImage]; !ok { - multiParts = append(multiParts, schema.ChatMessagePart{ - Type: schema.ChatMessagePartTypeText, - Text: r, - }) - } else { - multiParts = append(multiParts, schema.ChatMessagePart{ - Type: schema.ChatMessagePartTypeImageURL, - ImageURL: &schema.ChatMessageImageURL{ - URL: r, - }, - }) + originalPart = schema.ChatMessagePart{ + Type: schema.ChatMessagePartTypeImageURL, + ImageURL: &schema.ChatMessageImageURL{ + URL: r, + }, } case vo.FileTypeAudio, vo.FileTypeVoice: - if _, ok := supportedModals[modelmgr.ModalAudio]; !ok { - multiParts = append(multiParts, schema.ChatMessagePart{ - Type: schema.ChatMessagePartTypeText, - Text: r, - }) - } else { - multiParts = append(multiParts, schema.ChatMessagePart{ - Type: schema.ChatMessagePartTypeAudioURL, - AudioURL: &schema.ChatMessageAudioURL{ - URL: r, - }, - }) + originalPart = schema.ChatMessagePart{ + Type: schema.ChatMessagePartTypeAudioURL, + AudioURL: &schema.ChatMessageAudioURL{ + URL: r, + }, } case vo.FileTypeVideo: - if _, ok := supportedModals[modelmgr.ModalVideo]; !ok { - multiParts = append(multiParts, schema.ChatMessagePart{ - Type: schema.ChatMessagePartTypeText, - Text: r, - }) - } else { - multiParts = append(multiParts, schema.ChatMessagePart{ - Type: schema.ChatMessagePartTypeVideoURL, - VideoURL: &schema.ChatMessageVideoURL{ - URL: r, - }, - }) + originalPart = schema.ChatMessagePart{ + Type: schema.ChatMessagePartTypeVideoURL, + VideoURL: &schema.ChatMessageVideoURL{ + URL: r, + }, } default: - if _, ok := supportedModals[modelmgr.ModalFile]; !ok { - multiParts = append(multiParts, schema.ChatMessagePart{ - Type: schema.ChatMessagePartTypeText, - Text: r, - }) - } else { - multiParts = append(multiParts, schema.ChatMessagePart{ - Type: schema.ChatMessagePartTypeFileURL, - FileURL: &schema.ChatMessageFileURL{ - URL: r, - }, - }) + originalPart = schema.ChatMessagePart{ + Type: schema.ChatMessagePartTypeFileURL, + FileURL: &schema.ChatMessageFileURL{ + URL: r, + }, } } + multiParts = append(multiParts, transformMessagePart(originalPart, supportedModals)) } return &schema.Message{ @@ -238,6 +274,40 @@ func (pl *promptTpl) render(ctx context.Context, vs map[string]any, }, nil } +func transformMessagePart(part schema.ChatMessagePart, supportedModals map[modelmgr.Modal]bool) schema.ChatMessagePart { + switch part.Type { + case schema.ChatMessagePartTypeImageURL: + if _, ok := supportedModals[modelmgr.ModalImage]; !ok { + return schema.ChatMessagePart{ + Type: schema.ChatMessagePartTypeText, + Text: part.ImageURL.URL, + } + } + case schema.ChatMessagePartTypeAudioURL: + if _, ok := supportedModals[modelmgr.ModalAudio]; !ok { + return schema.ChatMessagePart{ + Type: schema.ChatMessagePartTypeText, + Text: part.AudioURL.URL, + } + } + case schema.ChatMessagePartTypeVideoURL: + if _, ok := supportedModals[modelmgr.ModalVideo]; !ok { + return schema.ChatMessagePart{ + Type: schema.ChatMessagePartTypeText, + Text: part.VideoURL.URL, + } + } + case schema.ChatMessagePartTypeFileURL: + if _, ok := supportedModals[modelmgr.ModalFile]; !ok { + return schema.ChatMessagePart{ + Type: schema.ChatMessagePartTypeText, + Text: part.FileURL.URL, + } + } + } + return part +} + func (p *prompts) Format(ctx context.Context, vs map[string]any, _ ...prompt.Option) ( _ []*schema.Message, err error, ) { @@ -288,3 +358,45 @@ func (p *prompts) Format(ctx context.Context, vs map[string]any, _ ...prompt.Opt return []*schema.Message{systemMsg, userMsg}, nil } + +func (p *promptsWithChatHistory) Format(ctx context.Context, vs map[string]any, _ ...prompt.Option) ( + []*schema.Message, error) { + baseMessages, err := p.prompts.Format(ctx, vs) + if err != nil { + return nil, err + } + if p.cfg == nil || !p.cfg.EnableChatHistory { + return baseMessages, nil + } + + exeCtx := execute.GetExeCtx(ctx) + if exeCtx == nil { + logs.CtxWarnf(ctx, "execute context is nil, skipping chat history") + return baseMessages, nil + } + + if exeCtx.ExeCfg.WorkflowMode != workflow.WorkflowMode_ChatFlow { + return baseMessages, nil + } + + historyMessages, ok := ctxcache.Get[[]*schema.Message](ctx, chatHistoryKey) + + if !ok || len(historyMessages) == 0 { + logs.CtxWarnf(ctx, "conversation history is empty") + return baseMessages, nil + } + + if len(historyMessages) == 0 { + return baseMessages, nil + } + + finalMessages := make([]*schema.Message, 0, len(baseMessages)+len(historyMessages)) + if len(baseMessages) > 0 && baseMessages[0].Role == schema.System { + finalMessages = append(finalMessages, baseMessages[0]) + baseMessages = baseMessages[1:] + } + finalMessages = append(finalMessages, historyMessages...) + finalMessages = append(finalMessages, baseMessages...) + + return finalMessages, nil +} diff --git a/backend/domain/workflow/internal/nodes/utils.go b/backend/domain/workflow/internal/nodes/utils.go index 09fc860b..32b3dab8 100644 --- a/backend/domain/workflow/internal/nodes/utils.go +++ b/backend/domain/workflow/internal/nodes/utils.go @@ -17,14 +17,17 @@ package nodes import ( + "context" + "errors" "fmt" "maps" "reflect" "strings" "github.com/cloudwego/eino/compose" - + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" "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/logs" "github.com/coze-dev/coze-studio/backend/pkg/sonic" "github.com/coze-dev/coze-studio/backend/types/errno" @@ -279,3 +282,30 @@ func GetConcatFunc(typ reflect.Type) func(reflect.Value) (reflect.Value, error) return nil } + +func ConvertMessageToString(_ context.Context, msg *crossmessage.WfMessage) (string, error) { + if msg.MultiContent != nil { + var textContents []string + var otherContents []string + for _, m := range msg.MultiContent { + if m.Text != nil { + textContents = append(textContents, ptr.From(m.Text)) + } else if m.Uri != nil { + otherContents = append(otherContents, ptr.From(m.Url)) + } + } + + var allParts []string + if len(textContents) > 0 { + allParts = append(allParts, textContents...) + } + if len(otherContents) > 0 { + allParts = append(allParts, otherContents...) + } + return strings.Join(allParts, ","), nil + } else if msg.Text != nil { + return ptr.From(msg.Text), nil + } else { + return "", vo.WrapError(errno.ErrInvalidParameter, errors.New("message is invalid")) + } +} diff --git a/backend/domain/workflow/internal/repo/conversation_repository.go b/backend/domain/workflow/internal/repo/conversation_repository.go new file mode 100644 index 00000000..a3ae07be --- /dev/null +++ b/backend/domain/workflow/internal/repo/conversation_repository.go @@ -0,0 +1,940 @@ +/* + * 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 repo + +import ( + "context" + "errors" + "fmt" + + "gorm.io/gen" + "gorm.io/gorm" + + crossconversation "github.com/coze-dev/coze-studio/backend/crossdomain/contract/conversation" + "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/domain/workflow/internal/repo/dal/model" + "github.com/coze-dev/coze-studio/backend/pkg/lang/slices" + "github.com/coze-dev/coze-studio/backend/types/errno" +) + +const batchSize = 10 + +func (r *RepositoryImpl) CreateDraftConversationTemplate(ctx context.Context, template *vo.CreateConversationTemplateMeta) (int64, error) { + id, err := r.GenID(ctx) + if err != nil { + return 0, vo.WrapError(errno.ErrIDGenError, err) + } + m := &model.AppConversationTemplateDraft{ + ID: id, + AppID: template.AppID, + SpaceID: template.SpaceID, + Name: template.Name, + CreatorID: template.UserID, + TemplateID: id, + } + err = r.query.AppConversationTemplateDraft.WithContext(ctx).Create(m) + if err != nil { + return 0, vo.WrapError(errno.ErrDatabaseError, err) + } + + return id, nil +} + +func (r *RepositoryImpl) GetConversationTemplate(ctx context.Context, env vo.Env, policy vo.GetConversationTemplatePolicy) (*entity.ConversationTemplate, bool, error) { + var ( + appID = policy.AppID + name = policy.Name + version = policy.Version + templateID = policy.TemplateID + ) + + conditions := make([]gen.Condition, 0) + if env == vo.Draft { + if appID != nil { + conditions = append(conditions, r.query.AppConversationTemplateDraft.AppID.Eq(*appID)) + } + if name != nil { + conditions = append(conditions, r.query.AppConversationTemplateDraft.Name.Eq(*name)) + } + if templateID != nil { + conditions = append(conditions, r.query.AppConversationTemplateDraft.TemplateID.Eq(*templateID)) + } + + template, err := r.query.AppConversationTemplateDraft.WithContext(ctx).Where(conditions...).First() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, false, nil + } + return nil, false, vo.WrapError(errno.ErrDatabaseError, err) + } + return &entity.ConversationTemplate{ + AppID: template.AppID, + Name: template.Name, + TemplateID: template.TemplateID, + }, true, nil + + } else if env == vo.Online { + if policy.Version != nil { + conditions = append(conditions, r.query.AppConversationTemplateOnline.Version.Eq(*version)) + } + if appID != nil { + conditions = append(conditions, r.query.AppConversationTemplateOnline.AppID.Eq(*appID)) + } + if name != nil { + conditions = append(conditions, r.query.AppConversationTemplateOnline.Name.Eq(*name)) + } + + if templateID != nil { + conditions = append(conditions, r.query.AppConversationTemplateOnline.TemplateID.Eq(*templateID)) + } + + template, err := r.query.AppConversationTemplateOnline.WithContext(ctx).Where(conditions...).Order(r.query.AppConversationTemplateOnline.CreatedAt.Desc()).First() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, false, nil + } + return nil, false, err + } + return &entity.ConversationTemplate{ + AppID: template.AppID, + Name: template.Name, + TemplateID: template.TemplateID, + }, true, nil + } + + return nil, false, fmt.Errorf("unknown env %v", env) + +} + +func (r *RepositoryImpl) UpdateDraftConversationTemplateName(ctx context.Context, templateID int64, name string) error { + _, err := r.query.AppConversationTemplateDraft.WithContext(ctx).Where( + r.query.AppConversationTemplateDraft.TemplateID.Eq(templateID), + ).UpdateColumnSimple(r.query.AppConversationTemplateDraft.Name.Value(name)) + + if err != nil { + return vo.WrapError(errno.ErrDatabaseError, err) + } + return nil + +} + +func (r *RepositoryImpl) DeleteDraftConversationTemplate(ctx context.Context, templateID int64) (int64, error) { + resultInfo, err := r.query.AppConversationTemplateDraft.WithContext(ctx).Where( + r.query.AppConversationTemplateDraft.TemplateID.Eq(templateID), + ).Delete() + + if err != nil { + return 0, vo.WrapError(errno.ErrDatabaseError, err) + } + return resultInfo.RowsAffected, nil + +} + +func (r *RepositoryImpl) DeleteDynamicConversation(ctx context.Context, env vo.Env, id int64) (int64, error) { + if env == vo.Draft { + info, err := r.query.AppDynamicConversationDraft.WithContext(ctx).Where(r.query.AppDynamicConversationDraft.ID.Eq(id)).Delete() + if err != nil { + return 0, vo.WrapError(errno.ErrDatabaseError, err) + } + return info.RowsAffected, nil + } else if env == vo.Online { + info, err := r.query.AppDynamicConversationOnline.WithContext(ctx).Where(r.query.AppDynamicConversationOnline.ID.Eq(id)).Delete() + if err != nil { + return 0, vo.WrapError(errno.ErrDatabaseError, err) + } + return info.RowsAffected, nil + } else { + return 0, fmt.Errorf("unknown env %v", env) + } +} + +func (r *RepositoryImpl) ListConversationTemplate(ctx context.Context, env vo.Env, policy *vo.ListConversationTemplatePolicy) ([]*entity.ConversationTemplate, error) { + if env == vo.Draft { + return r.listDraftConversationTemplate(ctx, policy) + } else if env == vo.Online { + return r.listOnlineConversationTemplate(ctx, policy) + } else { + return nil, fmt.Errorf("unknown env %v", env) + } +} + +func (r *RepositoryImpl) listDraftConversationTemplate(ctx context.Context, policy *vo.ListConversationTemplatePolicy) ([]*entity.ConversationTemplate, error) { + conditions := make([]gen.Condition, 0) + conditions = append(conditions, r.query.AppConversationTemplateDraft.AppID.Eq(policy.AppID)) + + if policy.NameLike != nil { + conditions = append(conditions, r.query.AppConversationTemplateDraft.Name.Like("%%"+*policy.NameLike+"%%")) + } + appConversationTemplateDraftDao := r.query.AppConversationTemplateDraft.WithContext(ctx) + var ( + templates []*model.AppConversationTemplateDraft + err error + ) + + if policy.Page != nil { + templates, err = appConversationTemplateDraftDao.Where(conditions...).Offset(policy.Page.Offset()).Limit(policy.Page.Limit()).Find() + } else { + templates, err = appConversationTemplateDraftDao.Where(conditions...).Find() + + } + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return []*entity.ConversationTemplate{}, nil + } + return nil, vo.WrapError(errno.ErrDatabaseError, err) + } + + return slices.Transform(templates, func(a *model.AppConversationTemplateDraft) *entity.ConversationTemplate { + return &entity.ConversationTemplate{ + SpaceID: a.SpaceID, + AppID: a.AppID, + Name: a.Name, + TemplateID: a.TemplateID, + } + }), nil + +} + +func (r *RepositoryImpl) listOnlineConversationTemplate(ctx context.Context, policy *vo.ListConversationTemplatePolicy) ([]*entity.ConversationTemplate, error) { + conditions := make([]gen.Condition, 0) + conditions = append(conditions, r.query.AppConversationTemplateOnline.AppID.Eq(policy.AppID)) + if policy.Version == nil { + return nil, fmt.Errorf("list online template fail, version is required") + } + conditions = append(conditions, r.query.AppConversationTemplateOnline.Version.Eq(*policy.Version)) + + if policy.NameLike != nil { + conditions = append(conditions, r.query.AppConversationTemplateOnline.Name.Like("%%"+*policy.NameLike+"%%")) + } + appConversationTemplateOnlineDao := r.query.AppConversationTemplateOnline.WithContext(ctx) + var ( + templates []*model.AppConversationTemplateOnline + err error + ) + if policy.Page != nil { + templates, err = appConversationTemplateOnlineDao.Where(conditions...).Offset(policy.Page.Offset()).Limit(policy.Page.Limit()).Find() + + } else { + templates, err = appConversationTemplateOnlineDao.Where(conditions...).Find() + + } + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return []*entity.ConversationTemplate{}, nil + } + return nil, vo.WrapError(errno.ErrDatabaseError, err) + } + + return slices.Transform(templates, func(a *model.AppConversationTemplateOnline) *entity.ConversationTemplate { + return &entity.ConversationTemplate{ + SpaceID: a.SpaceID, + AppID: a.AppID, + Name: a.Name, + TemplateID: a.TemplateID, + } + }), nil + +} + +func (r *RepositoryImpl) MGetStaticConversation(ctx context.Context, env vo.Env, userID, connectorID int64, templateIDs []int64) ([]*entity.StaticConversation, error) { + if env == vo.Draft { + return r.mGetDraftStaticConversation(ctx, userID, connectorID, templateIDs) + } else if env == vo.Online { + return r.mGetOnlineStaticConversation(ctx, userID, connectorID, templateIDs) + } else { + return nil, fmt.Errorf("unknown env %v", env) + } +} + +func (r *RepositoryImpl) mGetDraftStaticConversation(ctx context.Context, userID, connectorID int64, templateIDs []int64) ([]*entity.StaticConversation, error) { + conditions := make([]gen.Condition, 0, 3) + conditions = append(conditions, r.query.AppStaticConversationDraft.UserID.Eq(userID)) + conditions = append(conditions, r.query.AppStaticConversationDraft.ConnectorID.Eq(connectorID)) + if len(templateIDs) == 1 { + conditions = append(conditions, r.query.AppStaticConversationDraft.TemplateID.Eq(templateIDs[0])) + } else { + conditions = append(conditions, r.query.AppStaticConversationDraft.TemplateID.In(templateIDs...)) + } + + cs, err := r.query.AppStaticConversationDraft.WithContext(ctx).Where(conditions...).Find() + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return []*entity.StaticConversation{}, nil + } + return nil, vo.WrapError(errno.ErrDatabaseError, err) + } + + return slices.Transform(cs, func(a *model.AppStaticConversationDraft) *entity.StaticConversation { + return &entity.StaticConversation{ + TemplateID: a.TemplateID, + ConversationID: a.ConversationID, + UserID: a.UserID, + ConnectorID: a.ConnectorID, + } + }), nil +} + +func (r *RepositoryImpl) mGetOnlineStaticConversation(ctx context.Context, userID, connectorID int64, templateIDs []int64) ([]*entity.StaticConversation, error) { + conditions := make([]gen.Condition, 0, 3) + conditions = append(conditions, r.query.AppStaticConversationOnline.UserID.Eq(userID)) + conditions = append(conditions, r.query.AppStaticConversationOnline.ConnectorID.Eq(connectorID)) + if len(templateIDs) == 1 { + conditions = append(conditions, r.query.AppStaticConversationOnline.TemplateID.Eq(templateIDs[0])) + } else { + conditions = append(conditions, r.query.AppStaticConversationOnline.TemplateID.In(templateIDs...)) + } + + cs, err := r.query.AppStaticConversationOnline.WithContext(ctx).Where(conditions...).Find() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return []*entity.StaticConversation{}, nil + } + return nil, vo.WrapError(errno.ErrDatabaseError, err) + } + + return slices.Transform(cs, func(a *model.AppStaticConversationOnline) *entity.StaticConversation { + return &entity.StaticConversation{ + TemplateID: a.TemplateID, + ConversationID: a.ConversationID, + } + }), nil +} + +func (r *RepositoryImpl) ListDynamicConversation(ctx context.Context, env vo.Env, policy *vo.ListConversationPolicy) ([]*entity.DynamicConversation, error) { + if env == vo.Draft { + return r.listDraftDynamicConversation(ctx, policy) + } else if env == vo.Online { + return r.listOnlineDynamicConversation(ctx, policy) + } else { + return nil, fmt.Errorf("unknown env %v", env) + } + +} + +func (r *RepositoryImpl) listDraftDynamicConversation(ctx context.Context, policy *vo.ListConversationPolicy) ([]*entity.DynamicConversation, error) { + var ( + appID = policy.APPID + userID = policy.UserID + connectorID = policy.ConnectorID + ) + + conditions := make([]gen.Condition, 0) + conditions = append(conditions, r.query.AppDynamicConversationDraft.AppID.Eq(appID)) + conditions = append(conditions, r.query.AppDynamicConversationDraft.UserID.Eq(userID)) + conditions = append(conditions, r.query.AppDynamicConversationDraft.ConnectorID.Eq(connectorID)) + if policy.NameLike != nil { + conditions = append(conditions, r.query.AppDynamicConversationDraft.Name.Like("%%"+*policy.NameLike+"%%")) + } + + appDynamicConversationDraftDao := r.query.AppDynamicConversationDraft.WithContext(ctx).Where(conditions...) + var ( + dynamicConversations = make([]*model.AppDynamicConversationDraft, 0) + err error + ) + + if policy.Page != nil { + dynamicConversations, err = appDynamicConversationDraftDao.Offset(policy.Page.Offset()).Limit(policy.Page.Limit()).Find() + + } else { + dynamicConversations, err = appDynamicConversationDraftDao.Where(conditions...).Find() + } + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return []*entity.DynamicConversation{}, nil + } + return nil, vo.WrapError(errno.ErrDatabaseError, err) + } + return slices.Transform(dynamicConversations, func(a *model.AppDynamicConversationDraft) *entity.DynamicConversation { + return &entity.DynamicConversation{ + ID: a.ID, + Name: a.Name, + UserID: a.UserID, + ConnectorID: a.ConnectorID, + ConversationID: a.ConversationID, + } + }), nil +} + +func (r *RepositoryImpl) listOnlineDynamicConversation(ctx context.Context, policy *vo.ListConversationPolicy) ([]*entity.DynamicConversation, error) { + var ( + appID = policy.APPID + userID = policy.UserID + connectorID = policy.ConnectorID + ) + + conditions := make([]gen.Condition, 0) + conditions = append(conditions, r.query.AppDynamicConversationOnline.AppID.Eq(appID)) + conditions = append(conditions, r.query.AppDynamicConversationOnline.UserID.Eq(userID)) + conditions = append(conditions, r.query.AppDynamicConversationOnline.AppID.Eq(appID)) + conditions = append(conditions, r.query.AppDynamicConversationOnline.ConnectorID.Eq(connectorID)) + if policy.NameLike != nil { + conditions = append(conditions, r.query.AppDynamicConversationOnline.Name.Like("%%"+*policy.NameLike+"%%")) + } + + appDynamicConversationOnlineDao := r.query.AppDynamicConversationOnline.WithContext(ctx).Where(conditions...) + var ( + dynamicConversations = make([]*model.AppDynamicConversationOnline, 0) + err error + ) + if policy.Page != nil { + dynamicConversations, err = appDynamicConversationOnlineDao.Offset(policy.Page.Offset()).Limit(policy.Page.Limit()).Find() + } else { + dynamicConversations, err = appDynamicConversationOnlineDao.Where(conditions...).Find() + } + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return []*entity.DynamicConversation{}, nil + } + return nil, vo.WrapError(errno.ErrDatabaseError, err) + } + + return slices.Transform(dynamicConversations, func(a *model.AppDynamicConversationOnline) *entity.DynamicConversation { + return &entity.DynamicConversation{ + ID: a.ID, + Name: a.Name, + UserID: a.UserID, + ConnectorID: a.ConnectorID, + ConversationID: a.ConversationID, + } + }), nil +} + +func (r *RepositoryImpl) GetOrCreateStaticConversation(ctx context.Context, env vo.Env, idGen workflow.ConversationIDGenerator, meta *vo.CreateStaticConversation) (int64, int64, bool, error) { + if env == vo.Draft { + return r.getOrCreateDraftStaticConversation(ctx, idGen, meta) + } else if env == vo.Online { + return r.getOrCreateOnlineStaticConversation(ctx, idGen, meta) + } else { + return 0, 0, false, fmt.Errorf("unknown env %v", env) + } + +} +func (r *RepositoryImpl) GetOrCreateDynamicConversation(ctx context.Context, env vo.Env, idGen workflow.ConversationIDGenerator, meta *vo.CreateDynamicConversation) (int64, int64, bool, error) { + if env == vo.Draft { + + appDynamicConversationDraft := r.query.AppDynamicConversationDraft + ret, err := appDynamicConversationDraft.WithContext(ctx).Where( + appDynamicConversationDraft.AppID.Eq(meta.AppID), + appDynamicConversationDraft.ConnectorID.Eq(meta.ConnectorID), + appDynamicConversationDraft.UserID.Eq(meta.UserID), + appDynamicConversationDraft.Name.Eq(meta.Name), + ).First() + if err == nil { + cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, ret.ConversationID) + if err != nil { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err) + } + if cInfo == nil { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, fmt.Errorf("conversation not found")) + } + return ret.ConversationID, cInfo.SectionID, true, nil + } + + if !errors.Is(err, gorm.ErrRecordNotFound) { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err) + } + + conv, err := idGen(ctx, meta.AppID, meta.UserID, meta.ConnectorID) + if err != nil { + return 0, 0, false, err + } + + id, err := r.GenID(ctx) + if err != nil { + return 0, 0, false, vo.WrapError(errno.ErrIDGenError, err) + } + + err = r.query.AppDynamicConversationDraft.WithContext(ctx).Create(&model.AppDynamicConversationDraft{ + ID: id, + AppID: meta.AppID, + Name: meta.Name, + UserID: meta.UserID, + ConnectorID: meta.ConnectorID, + ConversationID: conv.ID, + }) + if err != nil { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err) + } + + return conv.ID, conv.SectionID, false, nil + + } else if env == vo.Online { + appDynamicConversationOnline := r.query.AppDynamicConversationOnline + ret, err := appDynamicConversationOnline.WithContext(ctx).Where( + appDynamicConversationOnline.AppID.Eq(meta.AppID), + appDynamicConversationOnline.ConnectorID.Eq(meta.ConnectorID), + appDynamicConversationOnline.UserID.Eq(meta.UserID), + appDynamicConversationOnline.Name.Eq(meta.Name), + ).First() + if err == nil { + cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, ret.ConversationID) + if err != nil { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err) + } + if cInfo == nil { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, fmt.Errorf("conversation not found")) + } + return ret.ConversationID, cInfo.SectionID, true, nil + } + if !errors.Is(err, gorm.ErrRecordNotFound) { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err) + } + + conv, err := idGen(ctx, meta.AppID, meta.UserID, meta.ConnectorID) + if err != nil { + return 0, 0, false, err + } + id, err := r.GenID(ctx) + if err != nil { + return 0, 0, false, vo.WrapError(errno.ErrIDGenError, err) + } + + err = r.query.AppDynamicConversationOnline.WithContext(ctx).Create(&model.AppDynamicConversationOnline{ + ID: id, + AppID: meta.AppID, + Name: meta.Name, + UserID: meta.UserID, + ConnectorID: meta.ConnectorID, + ConversationID: conv.ID, + }) + if err != nil { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err) + } + + return conv.ID, conv.SectionID, false, nil + + } else { + return 0, 0, false, fmt.Errorf("unknown env %v", env) + } + +} + +func (r *RepositoryImpl) GetStaticConversationByTemplateID(ctx context.Context, env vo.Env, userID, connectorID, templateID int64) (*entity.StaticConversation, bool, error) { + if env == vo.Draft { + conditions := make([]gen.Condition, 0, 3) + conditions = append(conditions, r.query.AppStaticConversationDraft.UserID.Eq(userID)) + conditions = append(conditions, r.query.AppStaticConversationDraft.ConnectorID.Eq(connectorID)) + conditions = append(conditions, r.query.AppStaticConversationDraft.TemplateID.Eq(templateID)) + cs, err := r.query.AppStaticConversationDraft.WithContext(ctx).Where(conditions...).First() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, false, nil + } + return nil, false, vo.WrapError(errno.ErrDatabaseError, err) + } + return &entity.StaticConversation{ + UserID: cs.UserID, + ConnectorID: cs.ConnectorID, + TemplateID: cs.TemplateID, + ConversationID: cs.ConversationID, + }, true, nil + } else if env == vo.Online { + conditions := make([]gen.Condition, 0, 3) + conditions = append(conditions, r.query.AppStaticConversationOnline.UserID.Eq(userID)) + conditions = append(conditions, r.query.AppStaticConversationOnline.ConnectorID.Eq(connectorID)) + conditions = append(conditions, r.query.AppStaticConversationOnline.TemplateID.Eq(templateID)) + cs, err := r.query.AppStaticConversationOnline.WithContext(ctx).Where(conditions...).First() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, false, nil + } + return nil, false, vo.WrapError(errno.ErrDatabaseError, err) + } + return &entity.StaticConversation{ + UserID: cs.UserID, + ConnectorID: cs.ConnectorID, + TemplateID: cs.TemplateID, + ConversationID: cs.ConversationID, + }, true, nil + } else { + return nil, false, fmt.Errorf("unknown env %v", env) + } +} + +func (r *RepositoryImpl) getOrCreateDraftStaticConversation(ctx context.Context, idGen workflow.ConversationIDGenerator, meta *vo.CreateStaticConversation) (int64, int64, bool, error) { + cs, err := r.mGetDraftStaticConversation(ctx, meta.UserID, meta.ConnectorID, []int64{meta.TemplateID}) + if err != nil { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err) + } + + if len(cs) > 0 { + cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, cs[0].ConversationID) + if err != nil { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err) + } + if cInfo == nil { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, fmt.Errorf("conversation not found")) + } + return cs[0].ConversationID, cInfo.SectionID, true, nil + } + + conv, err := idGen(ctx, meta.AppID, meta.UserID, meta.ConnectorID) + if err != nil { + return 0, 0, false, err + } + + id, err := r.GenID(ctx) + if err != nil { + return 0, 0, false, vo.WrapError(errno.ErrIDGenError, err) + } + object := &model.AppStaticConversationDraft{ + ID: id, + UserID: meta.UserID, + ConnectorID: meta.ConnectorID, + TemplateID: meta.TemplateID, + ConversationID: conv.ID, + } + err = r.query.AppStaticConversationDraft.WithContext(ctx).Create(object) + if err != nil { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err) + } + + return conv.ID, conv.SectionID, false, nil +} + +func (r *RepositoryImpl) getOrCreateOnlineStaticConversation(ctx context.Context, idGen workflow.ConversationIDGenerator, meta *vo.CreateStaticConversation) (int64, int64, bool, error) { + cs, err := r.mGetOnlineStaticConversation(ctx, meta.UserID, meta.ConnectorID, []int64{meta.TemplateID}) + if err != nil { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err) + } + + if len(cs) > 0 { + cInfo, err := crossconversation.DefaultSVC().GetByID(ctx, cs[0].ConversationID) + if err != nil { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err) + } + if cInfo == nil { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, fmt.Errorf("conversation not found")) + } + return cs[0].ConversationID, cInfo.SectionID, true, nil + } + + conv, err := idGen(ctx, meta.AppID, meta.UserID, meta.ConnectorID) + if err != nil { + return 0, 0, false, err + } + + id, err := r.GenID(ctx) + if err != nil { + return 0, 0, false, vo.WrapError(errno.ErrIDGenError, err) + } + object := &model.AppStaticConversationOnline{ + ID: id, + UserID: meta.UserID, + ConnectorID: meta.ConnectorID, + TemplateID: meta.TemplateID, + ConversationID: conv.ID, + } + err = r.query.AppStaticConversationOnline.WithContext(ctx).Create(object) + if err != nil { + return 0, 0, false, vo.WrapError(errno.ErrDatabaseError, err) + } + + return conv.ID, conv.SectionID, false, nil +} + +func (r *RepositoryImpl) BatchCreateOnlineConversationTemplate(ctx context.Context, templates []*entity.ConversationTemplate, version string) error { + ids, err := r.GenMultiIDs(ctx, len(templates)) + if err != nil { + return vo.WrapError(errno.ErrIDGenError, err) + } + + objects := make([]*model.AppConversationTemplateOnline, 0, len(templates)) + for idx := range templates { + template := templates[idx] + objects = append(objects, &model.AppConversationTemplateOnline{ + ID: ids[idx], + SpaceID: template.SpaceID, + AppID: template.AppID, + TemplateID: template.TemplateID, + Name: template.Name, + Version: version, + }) + } + + err = r.query.AppConversationTemplateOnline.WithContext(ctx).CreateInBatches(objects, batchSize) + if err != nil { + return vo.WrapError(errno.ErrDatabaseError, err) + } + return nil + +} + +func (r *RepositoryImpl) GetDynamicConversationByName(ctx context.Context, env vo.Env, appID, connectorID, userID int64, name string) (*entity.DynamicConversation, bool, error) { + if env == vo.Draft { + appDynamicConversationDraft := r.query.AppDynamicConversationDraft + ret, err := appDynamicConversationDraft.WithContext(ctx).Where( + appDynamicConversationDraft.AppID.Eq(appID), + appDynamicConversationDraft.ConnectorID.Eq(connectorID), + appDynamicConversationDraft.UserID.Eq(userID), + appDynamicConversationDraft.Name.Eq(name)).First() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, false, nil + } + return nil, false, err + } + + return &entity.DynamicConversation{ + ID: ret.ID, + UserID: ret.UserID, + ConnectorID: ret.ConnectorID, + ConversationID: ret.ConversationID, + Name: ret.Name, + }, true, nil + + } else if env == vo.Online { + appDynamicConversationOnline := r.query.AppDynamicConversationOnline + ret, err := appDynamicConversationOnline.WithContext(ctx).Where( + appDynamicConversationOnline.AppID.Eq(appID), + appDynamicConversationOnline.ConnectorID.Eq(connectorID), + appDynamicConversationOnline.UserID.Eq(userID), + appDynamicConversationOnline.Name.Eq(name)).First() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, false, nil + } + return nil, false, err + } + return &entity.DynamicConversation{ + ID: ret.ID, + UserID: ret.UserID, + ConnectorID: ret.ConnectorID, + ConversationID: ret.ConversationID, + Name: ret.Name, + }, true, nil + + } else { + return nil, false, fmt.Errorf("unknown env %v", env) + } + +} + +func (r *RepositoryImpl) UpdateDynamicConversationNameByID(ctx context.Context, env vo.Env, templateID int64, name string) error { + if env == vo.Draft { + appDynamicConversationDraft := r.query.AppDynamicConversationDraft + _, err := appDynamicConversationDraft.WithContext(ctx).Where( + appDynamicConversationDraft.ID.Eq(templateID), + ).UpdateColumnSimple(appDynamicConversationDraft.Name.Value(name)) + if err != nil { + return vo.WrapError(errno.ErrDatabaseError, err) + } + return nil + } else if env == vo.Online { + appDynamicConversationOnline := r.query.AppDynamicConversationOnline + _, err := appDynamicConversationOnline.WithContext(ctx).Where( + appDynamicConversationOnline.ID.Eq(templateID), + ).UpdateColumnSimple(appDynamicConversationOnline.Name.Value(name)) + if err != nil { + return vo.WrapError(errno.ErrDatabaseError, err) + } + return nil + + } else { + return fmt.Errorf("unknown env %v", env) + } + +} + +func (r *RepositoryImpl) UpdateStaticConversation(ctx context.Context, env vo.Env, templateID int64, connectorID int64, userID int64, newConversationID int64) error { + + if env == vo.Draft { + appStaticConversationDraft := r.query.AppStaticConversationDraft + _, err := appStaticConversationDraft.WithContext(ctx).Where( + appStaticConversationDraft.TemplateID.Eq(templateID), + appStaticConversationDraft.ConnectorID.Eq(connectorID), + appStaticConversationDraft.UserID.Eq(userID), + ).UpdateColumn(appStaticConversationDraft.ConversationID, newConversationID) + + if err != nil { + return err + } + return err + + } else if env == vo.Online { + appStaticConversationOnline := r.query.AppStaticConversationOnline + _, err := appStaticConversationOnline.WithContext(ctx).Where( + appStaticConversationOnline.TemplateID.Eq(templateID), + appStaticConversationOnline.ConnectorID.Eq(connectorID), + appStaticConversationOnline.UserID.Eq(userID), + ).UpdateColumn(appStaticConversationOnline.ConversationID, newConversationID) + if err != nil { + return err + } + return nil + } else { + return fmt.Errorf("unknown env %v", env) + } + +} + +func (r *RepositoryImpl) UpdateDynamicConversation(ctx context.Context, env vo.Env, conversationID, newConversationID int64) error { + if env == vo.Draft { + appDynamicConversationDraft := r.query.AppDynamicConversationDraft + _, err := appDynamicConversationDraft.WithContext(ctx).Where(appDynamicConversationDraft.ConversationID.Eq(conversationID)). + UpdateColumn(appDynamicConversationDraft.ConversationID, newConversationID) + if err != nil { + return err + } + + return nil + } else if env == vo.Online { + appDynamicConversationOnline := r.query.AppDynamicConversationOnline + _, err := appDynamicConversationOnline.WithContext(ctx).Where(appDynamicConversationOnline.ConversationID.Eq(conversationID)). + UpdateColumn(appDynamicConversationOnline.ConversationID, newConversationID) + if err != nil { + return err + } + + return nil + } else { + return fmt.Errorf("unknown env %v", env) + } + +} + +func (r *RepositoryImpl) CopyTemplateConversationByAppID(ctx context.Context, appID int64, toAppID int64) error { + appConversationTemplateDraft := r.query.AppConversationTemplateDraft + templates, err := appConversationTemplateDraft.WithContext(ctx).Where(appConversationTemplateDraft.AppID.Eq(appID), appConversationTemplateDraft.Name.Neq("Default")).Find() + if err != nil { + return vo.WrapError(errno.ErrDatabaseError, err) + } + + if len(templates) == 0 { + return nil + } + templateTemplates := make([]*model.AppConversationTemplateDraft, 0, len(templates)) + ids, err := r.GenMultiIDs(ctx, len(templates)) + if err != nil { + return vo.WrapError(errno.ErrIDGenError, err) + } + for i := range templates { + copiedTemplate := templates[i] + copiedTemplate.ID = ids[i] + copiedTemplate.TemplateID = ids[i] + copiedTemplate.AppID = toAppID + templateTemplates = append(templateTemplates, copiedTemplate) + } + err = appConversationTemplateDraft.WithContext(ctx).CreateInBatches(templateTemplates, batchSize) + if err != nil { + return vo.WrapError(errno.ErrDatabaseError, err) + } + return nil + +} + +func (r *RepositoryImpl) GetStaticConversationByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (string, bool, error) { + if env == vo.Draft { + appStaticConversationDraft := r.query.AppStaticConversationDraft + ret, err := appStaticConversationDraft.WithContext(ctx).Where( + appStaticConversationDraft.ConnectorID.Eq(connectorID), + appStaticConversationDraft.ConversationID.Eq(conversationID), + ).First() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return "", false, nil + } + return "", false, err + } + appConversationTemplateDraft := r.query.AppConversationTemplateDraft + template, err := appConversationTemplateDraft.WithContext(ctx).Where( + appConversationTemplateDraft.TemplateID.Eq(ret.TemplateID), + appConversationTemplateDraft.AppID.Eq(appID), + ).First() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return "", false, nil + } + return "", false, err + } + return template.Name, true, nil + } else if env == vo.Online { + appStaticConversationOnline := r.query.AppStaticConversationOnline + ret, err := appStaticConversationOnline.WithContext(ctx).Where( + appStaticConversationOnline.ConnectorID.Eq(connectorID), + appStaticConversationOnline.ConversationID.Eq(conversationID), + ).First() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return "", false, nil + } + return "", false, err + } + appConversationTemplateOnline := r.query.AppConversationTemplateOnline + template, err := appConversationTemplateOnline.WithContext(ctx).Where( + appConversationTemplateOnline.TemplateID.Eq(ret.TemplateID), + appConversationTemplateOnline.AppID.Eq(appID), + ).First() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return "", false, nil + } + return "", false, err + } + return template.Name, true, nil + } + return "", false, fmt.Errorf("unknown env %v", env) +} + +func (r *RepositoryImpl) GetDynamicConversationByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (*entity.DynamicConversation, bool, error) { + if env == vo.Draft { + appDynamicConversationDraft := r.query.AppDynamicConversationDraft + ret, err := appDynamicConversationDraft.WithContext(ctx).Where( + appDynamicConversationDraft.AppID.Eq(appID), + appDynamicConversationDraft.ConnectorID.Eq(connectorID), + appDynamicConversationDraft.ConversationID.Eq(conversationID), + ).First() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, false, nil + } + return nil, false, err + } + return &entity.DynamicConversation{ + ID: ret.ID, + UserID: ret.UserID, + ConnectorID: ret.ConnectorID, + ConversationID: ret.ConversationID, + Name: ret.Name, + }, true, nil + } else if env == vo.Online { + appDynamicConversationOnline := r.query.AppDynamicConversationOnline + ret, err := appDynamicConversationOnline.WithContext(ctx).Where( + appDynamicConversationOnline.AppID.Eq(appID), + appDynamicConversationOnline.ConnectorID.Eq(connectorID), + appDynamicConversationOnline.ConversationID.Eq(conversationID), + ).First() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, false, nil + } + return nil, false, err + } + return &entity.DynamicConversation{ + ID: ret.ID, + UserID: ret.UserID, + ConnectorID: ret.ConnectorID, + ConversationID: ret.ConversationID, + Name: ret.Name, + }, true, nil + } + return nil, false, fmt.Errorf("unknown env %v", env) +} diff --git a/backend/domain/workflow/internal/repo/dal/model/app_conversation_template_draft.gen.go b/backend/domain/workflow/internal/repo/dal/model/app_conversation_template_draft.gen.go new file mode 100644 index 00000000..7f4c6af8 --- /dev/null +++ b/backend/domain/workflow/internal/repo/dal/model/app_conversation_template_draft.gen.go @@ -0,0 +1,29 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package model + +import ( + "gorm.io/gorm" +) + +const TableNameAppConversationTemplateDraft = "app_conversation_template_draft" + +// AppConversationTemplateDraft mapped from table +type AppConversationTemplateDraft struct { + ID int64 `gorm:"column:id;primaryKey;comment:id" json:"id"` // id + AppID int64 `gorm:"column:app_id;not null;comment:app id" json:"app_id"` // app id + SpaceID int64 `gorm:"column:space_id;not null;comment:space id" json:"space_id"` // space id + Name string `gorm:"column:name;not null;comment:conversion name" json:"name"` // conversion name + TemplateID int64 `gorm:"column:template_id;not null;comment:template id" json:"template_id"` // template id + CreatorID int64 `gorm:"column:creator_id;not null;comment:creator id" json:"creator_id"` // creator id + CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:create time in millisecond" json:"created_at"` // create time in millisecond + UpdatedAt int64 `gorm:"column:updated_at;autoUpdateTime:milli;comment:update time in millisecond" json:"updated_at"` // update time in millisecond + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:delete time in millisecond" json:"deleted_at"` // delete time in millisecond +} + +// TableName AppConversationTemplateDraft's table name +func (*AppConversationTemplateDraft) TableName() string { + return TableNameAppConversationTemplateDraft +} diff --git a/backend/domain/workflow/internal/repo/dal/model/app_conversation_template_online.gen.go b/backend/domain/workflow/internal/repo/dal/model/app_conversation_template_online.gen.go new file mode 100644 index 00000000..25489151 --- /dev/null +++ b/backend/domain/workflow/internal/repo/dal/model/app_conversation_template_online.gen.go @@ -0,0 +1,24 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package model + +const TableNameAppConversationTemplateOnline = "app_conversation_template_online" + +// AppConversationTemplateOnline mapped from table +type AppConversationTemplateOnline struct { + ID int64 `gorm:"column:id;primaryKey;comment:id" json:"id"` // id + AppID int64 `gorm:"column:app_id;not null;comment:app id" json:"app_id"` // app id + SpaceID int64 `gorm:"column:space_id;not null;comment:space id" json:"space_id"` // space id + Name string `gorm:"column:name;not null;comment:conversion name" json:"name"` // conversion name + TemplateID int64 `gorm:"column:template_id;not null;comment:template id" json:"template_id"` // template id + Version string `gorm:"column:version;not null;comment:version name" json:"version"` // version name + CreatorID int64 `gorm:"column:creator_id;not null;comment:creator id" json:"creator_id"` // creator id + CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:create time in millisecond" json:"created_at"` // create time in millisecond +} + +// TableName AppConversationTemplateOnline's table name +func (*AppConversationTemplateOnline) TableName() string { + return TableNameAppConversationTemplateOnline +} diff --git a/backend/domain/workflow/internal/repo/dal/model/app_dynamic_conversation_draft.gen.go b/backend/domain/workflow/internal/repo/dal/model/app_dynamic_conversation_draft.gen.go new file mode 100644 index 00000000..a49e4d6b --- /dev/null +++ b/backend/domain/workflow/internal/repo/dal/model/app_dynamic_conversation_draft.gen.go @@ -0,0 +1,28 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package model + +import ( + "gorm.io/gorm" +) + +const TableNameAppDynamicConversationDraft = "app_dynamic_conversation_draft" + +// AppDynamicConversationDraft mapped from table +type AppDynamicConversationDraft struct { + ID int64 `gorm:"column:id;primaryKey;comment:id" json:"id"` // id + AppID int64 `gorm:"column:app_id;not null;comment:app id" json:"app_id"` // app id + Name string `gorm:"column:name;not null;comment:conversion name" json:"name"` // conversion name + UserID int64 `gorm:"column:user_id;not null;comment:user id" json:"user_id"` // user id + ConnectorID int64 `gorm:"column:connector_id;not null;comment:connector id" json:"connector_id"` // connector id + ConversationID int64 `gorm:"column:conversation_id;not null;comment:conversation id" json:"conversation_id"` // conversation id + CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:create time in millisecond" json:"created_at"` // create time in millisecond + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:delete time in millisecond" json:"deleted_at"` // delete time in millisecond +} + +// TableName AppDynamicConversationDraft's table name +func (*AppDynamicConversationDraft) TableName() string { + return TableNameAppDynamicConversationDraft +} diff --git a/backend/domain/workflow/internal/repo/dal/model/app_dynamic_conversation_online.gen.go b/backend/domain/workflow/internal/repo/dal/model/app_dynamic_conversation_online.gen.go new file mode 100644 index 00000000..9d0ece73 --- /dev/null +++ b/backend/domain/workflow/internal/repo/dal/model/app_dynamic_conversation_online.gen.go @@ -0,0 +1,28 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package model + +import ( + "gorm.io/gorm" +) + +const TableNameAppDynamicConversationOnline = "app_dynamic_conversation_online" + +// AppDynamicConversationOnline mapped from table +type AppDynamicConversationOnline struct { + ID int64 `gorm:"column:id;primaryKey;comment:id" json:"id"` // id + AppID int64 `gorm:"column:app_id;not null;comment:app id" json:"app_id"` // app id + Name string `gorm:"column:name;not null;comment:conversion name" json:"name"` // conversion name + UserID int64 `gorm:"column:user_id;not null;comment:user id" json:"user_id"` // user id + ConnectorID int64 `gorm:"column:connector_id;not null;comment:connector id" json:"connector_id"` // connector id + ConversationID int64 `gorm:"column:conversation_id;not null;comment:conversation id" json:"conversation_id"` // conversation id + CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:create time in millisecond" json:"created_at"` // create time in millisecond + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:delete time in millisecond" json:"deleted_at"` // delete time in millisecond +} + +// TableName AppDynamicConversationOnline's table name +func (*AppDynamicConversationOnline) TableName() string { + return TableNameAppDynamicConversationOnline +} diff --git a/backend/domain/workflow/internal/repo/dal/model/app_static_conversation_draft.gen.go b/backend/domain/workflow/internal/repo/dal/model/app_static_conversation_draft.gen.go new file mode 100644 index 00000000..bd54ca70 --- /dev/null +++ b/backend/domain/workflow/internal/repo/dal/model/app_static_conversation_draft.gen.go @@ -0,0 +1,27 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package model + +import ( + "gorm.io/gorm" +) + +const TableNameAppStaticConversationDraft = "app_static_conversation_draft" + +// AppStaticConversationDraft mapped from table +type AppStaticConversationDraft struct { + ID int64 `gorm:"column:id;primaryKey;comment:id" json:"id"` // id + TemplateID int64 `gorm:"column:template_id;not null;comment:template id" json:"template_id"` // template id + UserID int64 `gorm:"column:user_id;not null;comment:user id" json:"user_id"` // user id + ConnectorID int64 `gorm:"column:connector_id;not null;comment:connector id" json:"connector_id"` // connector id + ConversationID int64 `gorm:"column:conversation_id;not null;comment:conversation id" json:"conversation_id"` // conversation id + CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:create time in millisecond" json:"created_at"` // create time in millisecond + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:delete time in millisecond" json:"deleted_at"` // delete time in millisecond +} + +// TableName AppStaticConversationDraft's table name +func (*AppStaticConversationDraft) TableName() string { + return TableNameAppStaticConversationDraft +} diff --git a/backend/domain/workflow/internal/repo/dal/model/app_static_conversation_online.gen.go b/backend/domain/workflow/internal/repo/dal/model/app_static_conversation_online.gen.go new file mode 100644 index 00000000..a178d963 --- /dev/null +++ b/backend/domain/workflow/internal/repo/dal/model/app_static_conversation_online.gen.go @@ -0,0 +1,22 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package model + +const TableNameAppStaticConversationOnline = "app_static_conversation_online" + +// AppStaticConversationOnline mapped from table +type AppStaticConversationOnline struct { + ID int64 `gorm:"column:id;primaryKey;comment:id" json:"id"` // id + TemplateID int64 `gorm:"column:template_id;not null;comment:template id" json:"template_id"` // template id + UserID int64 `gorm:"column:user_id;not null;comment:user id" json:"user_id"` // user id + ConnectorID int64 `gorm:"column:connector_id;not null;comment:connector id" json:"connector_id"` // connector id + ConversationID int64 `gorm:"column:conversation_id;not null;comment:conversation id" json:"conversation_id"` // conversation id + CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:create time in millisecond" json:"created_at"` // create time in millisecond +} + +// TableName AppStaticConversationOnline's table name +func (*AppStaticConversationOnline) TableName() string { + return TableNameAppStaticConversationOnline +} diff --git a/backend/domain/workflow/internal/repo/dal/model/chat_flow_role_config.gen.go b/backend/domain/workflow/internal/repo/dal/model/chat_flow_role_config.gen.go new file mode 100644 index 00000000..69c6f60e --- /dev/null +++ b/backend/domain/workflow/internal/repo/dal/model/chat_flow_role_config.gen.go @@ -0,0 +1,36 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package model + +import ( + "gorm.io/gorm" +) + +const TableNameChatFlowRoleConfig = "chat_flow_role_config" + +// ChatFlowRoleConfig mapped from table +type ChatFlowRoleConfig struct { + ID int64 `gorm:"column:id;primaryKey;comment:id" json:"id"` // id + WorkflowID int64 `gorm:"column:workflow_id;not null;comment:workflow id" json:"workflow_id"` // workflow id + Name string `gorm:"column:name;not null;comment:role name" json:"name"` // role name + Description string `gorm:"column:description;not null;comment:role description" json:"description"` // role description + Version string `gorm:"column:version;not null;comment:version" json:"version"` // version + Avatar string `gorm:"column:avatar;not null;comment:avatar uri" json:"avatar"` // avatar uri + BackgroundImageInfo string `gorm:"column:background_image_info;not null;comment:background image information, object structure" json:"background_image_info"` // background image information, object structure + OnboardingInfo string `gorm:"column:onboarding_info;not null;comment:intro information, object structure" json:"onboarding_info"` // intro information, object structure + SuggestReplyInfo string `gorm:"column:suggest_reply_info;not null;comment:user suggestions, object structure" json:"suggest_reply_info"` // user suggestions, object structure + AudioConfig string `gorm:"column:audio_config;not null;comment:agent audio config, object structure" json:"audio_config"` // agent audio config, object structure + UserInputConfig string `gorm:"column:user_input_config;not null;comment:user input config, object structure" json:"user_input_config"` // user input config, object structure + CreatorID int64 `gorm:"column:creator_id;not null;comment:creator id" json:"creator_id"` // creator id + CreatedAt int64 `gorm:"column:created_at;not null;autoCreateTime:milli;comment:create time in millisecond" json:"created_at"` // create time in millisecond + UpdatedAt int64 `gorm:"column:updated_at;autoUpdateTime:milli;comment:update time in millisecond" json:"updated_at"` // update time in millisecond + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;comment:delete time in millisecond" json:"deleted_at"` // delete time in millisecond + ConnectorID int64 `gorm:"column:connector_id;comment:connector id" json:"connector_id"` // connector id +} + +// TableName ChatFlowRoleConfig's table name +func (*ChatFlowRoleConfig) TableName() string { + return TableNameChatFlowRoleConfig +} diff --git a/backend/domain/workflow/internal/repo/dal/query/app_conversation_template_draft.gen.go b/backend/domain/workflow/internal/repo/dal/query/app_conversation_template_draft.gen.go new file mode 100644 index 00000000..14936e08 --- /dev/null +++ b/backend/domain/workflow/internal/repo/dal/query/app_conversation_template_draft.gen.go @@ -0,0 +1,412 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/repo/dal/model" +) + +func newAppConversationTemplateDraft(db *gorm.DB, opts ...gen.DOOption) appConversationTemplateDraft { + _appConversationTemplateDraft := appConversationTemplateDraft{} + + _appConversationTemplateDraft.appConversationTemplateDraftDo.UseDB(db, opts...) + _appConversationTemplateDraft.appConversationTemplateDraftDo.UseModel(&model.AppConversationTemplateDraft{}) + + tableName := _appConversationTemplateDraft.appConversationTemplateDraftDo.TableName() + _appConversationTemplateDraft.ALL = field.NewAsterisk(tableName) + _appConversationTemplateDraft.ID = field.NewInt64(tableName, "id") + _appConversationTemplateDraft.AppID = field.NewInt64(tableName, "app_id") + _appConversationTemplateDraft.SpaceID = field.NewInt64(tableName, "space_id") + _appConversationTemplateDraft.Name = field.NewString(tableName, "name") + _appConversationTemplateDraft.TemplateID = field.NewInt64(tableName, "template_id") + _appConversationTemplateDraft.CreatorID = field.NewInt64(tableName, "creator_id") + _appConversationTemplateDraft.CreatedAt = field.NewInt64(tableName, "created_at") + _appConversationTemplateDraft.UpdatedAt = field.NewInt64(tableName, "updated_at") + _appConversationTemplateDraft.DeletedAt = field.NewField(tableName, "deleted_at") + + _appConversationTemplateDraft.fillFieldMap() + + return _appConversationTemplateDraft +} + +type appConversationTemplateDraft struct { + appConversationTemplateDraftDo + + ALL field.Asterisk + ID field.Int64 // id + AppID field.Int64 // app id + SpaceID field.Int64 // space id + Name field.String // conversion name + TemplateID field.Int64 // template id + CreatorID field.Int64 // creator id + CreatedAt field.Int64 // create time in millisecond + UpdatedAt field.Int64 // update time in millisecond + DeletedAt field.Field // delete time in millisecond + + fieldMap map[string]field.Expr +} + +func (a appConversationTemplateDraft) Table(newTableName string) *appConversationTemplateDraft { + a.appConversationTemplateDraftDo.UseTable(newTableName) + return a.updateTableName(newTableName) +} + +func (a appConversationTemplateDraft) As(alias string) *appConversationTemplateDraft { + a.appConversationTemplateDraftDo.DO = *(a.appConversationTemplateDraftDo.As(alias).(*gen.DO)) + return a.updateTableName(alias) +} + +func (a *appConversationTemplateDraft) updateTableName(table string) *appConversationTemplateDraft { + a.ALL = field.NewAsterisk(table) + a.ID = field.NewInt64(table, "id") + a.AppID = field.NewInt64(table, "app_id") + a.SpaceID = field.NewInt64(table, "space_id") + a.Name = field.NewString(table, "name") + a.TemplateID = field.NewInt64(table, "template_id") + a.CreatorID = field.NewInt64(table, "creator_id") + a.CreatedAt = field.NewInt64(table, "created_at") + a.UpdatedAt = field.NewInt64(table, "updated_at") + a.DeletedAt = field.NewField(table, "deleted_at") + + a.fillFieldMap() + + return a +} + +func (a *appConversationTemplateDraft) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := a.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (a *appConversationTemplateDraft) fillFieldMap() { + a.fieldMap = make(map[string]field.Expr, 9) + a.fieldMap["id"] = a.ID + a.fieldMap["app_id"] = a.AppID + a.fieldMap["space_id"] = a.SpaceID + a.fieldMap["name"] = a.Name + a.fieldMap["template_id"] = a.TemplateID + a.fieldMap["creator_id"] = a.CreatorID + a.fieldMap["created_at"] = a.CreatedAt + a.fieldMap["updated_at"] = a.UpdatedAt + a.fieldMap["deleted_at"] = a.DeletedAt +} + +func (a appConversationTemplateDraft) clone(db *gorm.DB) appConversationTemplateDraft { + a.appConversationTemplateDraftDo.ReplaceConnPool(db.Statement.ConnPool) + return a +} + +func (a appConversationTemplateDraft) replaceDB(db *gorm.DB) appConversationTemplateDraft { + a.appConversationTemplateDraftDo.ReplaceDB(db) + return a +} + +type appConversationTemplateDraftDo struct{ gen.DO } + +type IAppConversationTemplateDraftDo interface { + gen.SubQuery + Debug() IAppConversationTemplateDraftDo + WithContext(ctx context.Context) IAppConversationTemplateDraftDo + WithResult(fc func(tx gen.Dao)) gen.ResultInfo + ReplaceDB(db *gorm.DB) + ReadDB() IAppConversationTemplateDraftDo + WriteDB() IAppConversationTemplateDraftDo + As(alias string) gen.Dao + Session(config *gorm.Session) IAppConversationTemplateDraftDo + Columns(cols ...field.Expr) gen.Columns + Clauses(conds ...clause.Expression) IAppConversationTemplateDraftDo + Not(conds ...gen.Condition) IAppConversationTemplateDraftDo + Or(conds ...gen.Condition) IAppConversationTemplateDraftDo + Select(conds ...field.Expr) IAppConversationTemplateDraftDo + Where(conds ...gen.Condition) IAppConversationTemplateDraftDo + Order(conds ...field.Expr) IAppConversationTemplateDraftDo + Distinct(cols ...field.Expr) IAppConversationTemplateDraftDo + Omit(cols ...field.Expr) IAppConversationTemplateDraftDo + Join(table schema.Tabler, on ...field.Expr) IAppConversationTemplateDraftDo + LeftJoin(table schema.Tabler, on ...field.Expr) IAppConversationTemplateDraftDo + RightJoin(table schema.Tabler, on ...field.Expr) IAppConversationTemplateDraftDo + Group(cols ...field.Expr) IAppConversationTemplateDraftDo + Having(conds ...gen.Condition) IAppConversationTemplateDraftDo + Limit(limit int) IAppConversationTemplateDraftDo + Offset(offset int) IAppConversationTemplateDraftDo + Count() (count int64, err error) + Scopes(funcs ...func(gen.Dao) gen.Dao) IAppConversationTemplateDraftDo + Unscoped() IAppConversationTemplateDraftDo + Create(values ...*model.AppConversationTemplateDraft) error + CreateInBatches(values []*model.AppConversationTemplateDraft, batchSize int) error + Save(values ...*model.AppConversationTemplateDraft) error + First() (*model.AppConversationTemplateDraft, error) + Take() (*model.AppConversationTemplateDraft, error) + Last() (*model.AppConversationTemplateDraft, error) + Find() ([]*model.AppConversationTemplateDraft, error) + FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AppConversationTemplateDraft, err error) + FindInBatches(result *[]*model.AppConversationTemplateDraft, batchSize int, fc func(tx gen.Dao, batch int) error) error + Pluck(column field.Expr, dest interface{}) error + Delete(...*model.AppConversationTemplateDraft) (info gen.ResultInfo, err error) + Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + Updates(value interface{}) (info gen.ResultInfo, err error) + UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + UpdateColumns(value interface{}) (info gen.ResultInfo, err error) + UpdateFrom(q gen.SubQuery) gen.Dao + Attrs(attrs ...field.AssignExpr) IAppConversationTemplateDraftDo + Assign(attrs ...field.AssignExpr) IAppConversationTemplateDraftDo + Joins(fields ...field.RelationField) IAppConversationTemplateDraftDo + Preload(fields ...field.RelationField) IAppConversationTemplateDraftDo + FirstOrInit() (*model.AppConversationTemplateDraft, error) + FirstOrCreate() (*model.AppConversationTemplateDraft, error) + FindByPage(offset int, limit int) (result []*model.AppConversationTemplateDraft, count int64, err error) + ScanByPage(result interface{}, offset int, limit int) (count int64, err error) + Scan(result interface{}) (err error) + Returning(value interface{}, columns ...string) IAppConversationTemplateDraftDo + UnderlyingDB() *gorm.DB + schema.Tabler +} + +func (a appConversationTemplateDraftDo) Debug() IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Debug()) +} + +func (a appConversationTemplateDraftDo) WithContext(ctx context.Context) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.WithContext(ctx)) +} + +func (a appConversationTemplateDraftDo) ReadDB() IAppConversationTemplateDraftDo { + return a.Clauses(dbresolver.Read) +} + +func (a appConversationTemplateDraftDo) WriteDB() IAppConversationTemplateDraftDo { + return a.Clauses(dbresolver.Write) +} + +func (a appConversationTemplateDraftDo) Session(config *gorm.Session) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Session(config)) +} + +func (a appConversationTemplateDraftDo) Clauses(conds ...clause.Expression) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Clauses(conds...)) +} + +func (a appConversationTemplateDraftDo) Returning(value interface{}, columns ...string) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Returning(value, columns...)) +} + +func (a appConversationTemplateDraftDo) Not(conds ...gen.Condition) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Not(conds...)) +} + +func (a appConversationTemplateDraftDo) Or(conds ...gen.Condition) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Or(conds...)) +} + +func (a appConversationTemplateDraftDo) Select(conds ...field.Expr) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Select(conds...)) +} + +func (a appConversationTemplateDraftDo) Where(conds ...gen.Condition) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Where(conds...)) +} + +func (a appConversationTemplateDraftDo) Order(conds ...field.Expr) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Order(conds...)) +} + +func (a appConversationTemplateDraftDo) Distinct(cols ...field.Expr) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Distinct(cols...)) +} + +func (a appConversationTemplateDraftDo) Omit(cols ...field.Expr) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Omit(cols...)) +} + +func (a appConversationTemplateDraftDo) Join(table schema.Tabler, on ...field.Expr) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Join(table, on...)) +} + +func (a appConversationTemplateDraftDo) LeftJoin(table schema.Tabler, on ...field.Expr) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.LeftJoin(table, on...)) +} + +func (a appConversationTemplateDraftDo) RightJoin(table schema.Tabler, on ...field.Expr) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.RightJoin(table, on...)) +} + +func (a appConversationTemplateDraftDo) Group(cols ...field.Expr) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Group(cols...)) +} + +func (a appConversationTemplateDraftDo) Having(conds ...gen.Condition) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Having(conds...)) +} + +func (a appConversationTemplateDraftDo) Limit(limit int) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Limit(limit)) +} + +func (a appConversationTemplateDraftDo) Offset(offset int) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Offset(offset)) +} + +func (a appConversationTemplateDraftDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Scopes(funcs...)) +} + +func (a appConversationTemplateDraftDo) Unscoped() IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Unscoped()) +} + +func (a appConversationTemplateDraftDo) Create(values ...*model.AppConversationTemplateDraft) error { + if len(values) == 0 { + return nil + } + return a.DO.Create(values) +} + +func (a appConversationTemplateDraftDo) CreateInBatches(values []*model.AppConversationTemplateDraft, batchSize int) error { + return a.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (a appConversationTemplateDraftDo) Save(values ...*model.AppConversationTemplateDraft) error { + if len(values) == 0 { + return nil + } + return a.DO.Save(values) +} + +func (a appConversationTemplateDraftDo) First() (*model.AppConversationTemplateDraft, error) { + if result, err := a.DO.First(); err != nil { + return nil, err + } else { + return result.(*model.AppConversationTemplateDraft), nil + } +} + +func (a appConversationTemplateDraftDo) Take() (*model.AppConversationTemplateDraft, error) { + if result, err := a.DO.Take(); err != nil { + return nil, err + } else { + return result.(*model.AppConversationTemplateDraft), nil + } +} + +func (a appConversationTemplateDraftDo) Last() (*model.AppConversationTemplateDraft, error) { + if result, err := a.DO.Last(); err != nil { + return nil, err + } else { + return result.(*model.AppConversationTemplateDraft), nil + } +} + +func (a appConversationTemplateDraftDo) Find() ([]*model.AppConversationTemplateDraft, error) { + result, err := a.DO.Find() + return result.([]*model.AppConversationTemplateDraft), err +} + +func (a appConversationTemplateDraftDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AppConversationTemplateDraft, err error) { + buf := make([]*model.AppConversationTemplateDraft, 0, batchSize) + err = a.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (a appConversationTemplateDraftDo) FindInBatches(result *[]*model.AppConversationTemplateDraft, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return a.DO.FindInBatches(result, batchSize, fc) +} + +func (a appConversationTemplateDraftDo) Attrs(attrs ...field.AssignExpr) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Attrs(attrs...)) +} + +func (a appConversationTemplateDraftDo) Assign(attrs ...field.AssignExpr) IAppConversationTemplateDraftDo { + return a.withDO(a.DO.Assign(attrs...)) +} + +func (a appConversationTemplateDraftDo) Joins(fields ...field.RelationField) IAppConversationTemplateDraftDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Joins(_f)) + } + return &a +} + +func (a appConversationTemplateDraftDo) Preload(fields ...field.RelationField) IAppConversationTemplateDraftDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Preload(_f)) + } + return &a +} + +func (a appConversationTemplateDraftDo) FirstOrInit() (*model.AppConversationTemplateDraft, error) { + if result, err := a.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*model.AppConversationTemplateDraft), nil + } +} + +func (a appConversationTemplateDraftDo) FirstOrCreate() (*model.AppConversationTemplateDraft, error) { + if result, err := a.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*model.AppConversationTemplateDraft), nil + } +} + +func (a appConversationTemplateDraftDo) FindByPage(offset int, limit int) (result []*model.AppConversationTemplateDraft, count int64, err error) { + result, err = a.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = a.Offset(-1).Limit(-1).Count() + return +} + +func (a appConversationTemplateDraftDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = a.Count() + if err != nil { + return + } + + err = a.Offset(offset).Limit(limit).Scan(result) + return +} + +func (a appConversationTemplateDraftDo) Scan(result interface{}) (err error) { + return a.DO.Scan(result) +} + +func (a appConversationTemplateDraftDo) Delete(models ...*model.AppConversationTemplateDraft) (result gen.ResultInfo, err error) { + return a.DO.Delete(models) +} + +func (a *appConversationTemplateDraftDo) withDO(do gen.Dao) *appConversationTemplateDraftDo { + a.DO = *do.(*gen.DO) + return a +} diff --git a/backend/domain/workflow/internal/repo/dal/query/app_conversation_template_online.gen.go b/backend/domain/workflow/internal/repo/dal/query/app_conversation_template_online.gen.go new file mode 100644 index 00000000..6eed7ee6 --- /dev/null +++ b/backend/domain/workflow/internal/repo/dal/query/app_conversation_template_online.gen.go @@ -0,0 +1,408 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/repo/dal/model" +) + +func newAppConversationTemplateOnline(db *gorm.DB, opts ...gen.DOOption) appConversationTemplateOnline { + _appConversationTemplateOnline := appConversationTemplateOnline{} + + _appConversationTemplateOnline.appConversationTemplateOnlineDo.UseDB(db, opts...) + _appConversationTemplateOnline.appConversationTemplateOnlineDo.UseModel(&model.AppConversationTemplateOnline{}) + + tableName := _appConversationTemplateOnline.appConversationTemplateOnlineDo.TableName() + _appConversationTemplateOnline.ALL = field.NewAsterisk(tableName) + _appConversationTemplateOnline.ID = field.NewInt64(tableName, "id") + _appConversationTemplateOnline.AppID = field.NewInt64(tableName, "app_id") + _appConversationTemplateOnline.SpaceID = field.NewInt64(tableName, "space_id") + _appConversationTemplateOnline.Name = field.NewString(tableName, "name") + _appConversationTemplateOnline.TemplateID = field.NewInt64(tableName, "template_id") + _appConversationTemplateOnline.Version = field.NewString(tableName, "version") + _appConversationTemplateOnline.CreatorID = field.NewInt64(tableName, "creator_id") + _appConversationTemplateOnline.CreatedAt = field.NewInt64(tableName, "created_at") + + _appConversationTemplateOnline.fillFieldMap() + + return _appConversationTemplateOnline +} + +type appConversationTemplateOnline struct { + appConversationTemplateOnlineDo + + ALL field.Asterisk + ID field.Int64 // id + AppID field.Int64 // app id + SpaceID field.Int64 // space id + Name field.String // conversion name + TemplateID field.Int64 // template id + Version field.String // version name + CreatorID field.Int64 // creator id + CreatedAt field.Int64 // create time in millisecond + + fieldMap map[string]field.Expr +} + +func (a appConversationTemplateOnline) Table(newTableName string) *appConversationTemplateOnline { + a.appConversationTemplateOnlineDo.UseTable(newTableName) + return a.updateTableName(newTableName) +} + +func (a appConversationTemplateOnline) As(alias string) *appConversationTemplateOnline { + a.appConversationTemplateOnlineDo.DO = *(a.appConversationTemplateOnlineDo.As(alias).(*gen.DO)) + return a.updateTableName(alias) +} + +func (a *appConversationTemplateOnline) updateTableName(table string) *appConversationTemplateOnline { + a.ALL = field.NewAsterisk(table) + a.ID = field.NewInt64(table, "id") + a.AppID = field.NewInt64(table, "app_id") + a.SpaceID = field.NewInt64(table, "space_id") + a.Name = field.NewString(table, "name") + a.TemplateID = field.NewInt64(table, "template_id") + a.Version = field.NewString(table, "version") + a.CreatorID = field.NewInt64(table, "creator_id") + a.CreatedAt = field.NewInt64(table, "created_at") + + a.fillFieldMap() + + return a +} + +func (a *appConversationTemplateOnline) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := a.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (a *appConversationTemplateOnline) fillFieldMap() { + a.fieldMap = make(map[string]field.Expr, 8) + a.fieldMap["id"] = a.ID + a.fieldMap["app_id"] = a.AppID + a.fieldMap["space_id"] = a.SpaceID + a.fieldMap["name"] = a.Name + a.fieldMap["template_id"] = a.TemplateID + a.fieldMap["version"] = a.Version + a.fieldMap["creator_id"] = a.CreatorID + a.fieldMap["created_at"] = a.CreatedAt +} + +func (a appConversationTemplateOnline) clone(db *gorm.DB) appConversationTemplateOnline { + a.appConversationTemplateOnlineDo.ReplaceConnPool(db.Statement.ConnPool) + return a +} + +func (a appConversationTemplateOnline) replaceDB(db *gorm.DB) appConversationTemplateOnline { + a.appConversationTemplateOnlineDo.ReplaceDB(db) + return a +} + +type appConversationTemplateOnlineDo struct{ gen.DO } + +type IAppConversationTemplateOnlineDo interface { + gen.SubQuery + Debug() IAppConversationTemplateOnlineDo + WithContext(ctx context.Context) IAppConversationTemplateOnlineDo + WithResult(fc func(tx gen.Dao)) gen.ResultInfo + ReplaceDB(db *gorm.DB) + ReadDB() IAppConversationTemplateOnlineDo + WriteDB() IAppConversationTemplateOnlineDo + As(alias string) gen.Dao + Session(config *gorm.Session) IAppConversationTemplateOnlineDo + Columns(cols ...field.Expr) gen.Columns + Clauses(conds ...clause.Expression) IAppConversationTemplateOnlineDo + Not(conds ...gen.Condition) IAppConversationTemplateOnlineDo + Or(conds ...gen.Condition) IAppConversationTemplateOnlineDo + Select(conds ...field.Expr) IAppConversationTemplateOnlineDo + Where(conds ...gen.Condition) IAppConversationTemplateOnlineDo + Order(conds ...field.Expr) IAppConversationTemplateOnlineDo + Distinct(cols ...field.Expr) IAppConversationTemplateOnlineDo + Omit(cols ...field.Expr) IAppConversationTemplateOnlineDo + Join(table schema.Tabler, on ...field.Expr) IAppConversationTemplateOnlineDo + LeftJoin(table schema.Tabler, on ...field.Expr) IAppConversationTemplateOnlineDo + RightJoin(table schema.Tabler, on ...field.Expr) IAppConversationTemplateOnlineDo + Group(cols ...field.Expr) IAppConversationTemplateOnlineDo + Having(conds ...gen.Condition) IAppConversationTemplateOnlineDo + Limit(limit int) IAppConversationTemplateOnlineDo + Offset(offset int) IAppConversationTemplateOnlineDo + Count() (count int64, err error) + Scopes(funcs ...func(gen.Dao) gen.Dao) IAppConversationTemplateOnlineDo + Unscoped() IAppConversationTemplateOnlineDo + Create(values ...*model.AppConversationTemplateOnline) error + CreateInBatches(values []*model.AppConversationTemplateOnline, batchSize int) error + Save(values ...*model.AppConversationTemplateOnline) error + First() (*model.AppConversationTemplateOnline, error) + Take() (*model.AppConversationTemplateOnline, error) + Last() (*model.AppConversationTemplateOnline, error) + Find() ([]*model.AppConversationTemplateOnline, error) + FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AppConversationTemplateOnline, err error) + FindInBatches(result *[]*model.AppConversationTemplateOnline, batchSize int, fc func(tx gen.Dao, batch int) error) error + Pluck(column field.Expr, dest interface{}) error + Delete(...*model.AppConversationTemplateOnline) (info gen.ResultInfo, err error) + Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + Updates(value interface{}) (info gen.ResultInfo, err error) + UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + UpdateColumns(value interface{}) (info gen.ResultInfo, err error) + UpdateFrom(q gen.SubQuery) gen.Dao + Attrs(attrs ...field.AssignExpr) IAppConversationTemplateOnlineDo + Assign(attrs ...field.AssignExpr) IAppConversationTemplateOnlineDo + Joins(fields ...field.RelationField) IAppConversationTemplateOnlineDo + Preload(fields ...field.RelationField) IAppConversationTemplateOnlineDo + FirstOrInit() (*model.AppConversationTemplateOnline, error) + FirstOrCreate() (*model.AppConversationTemplateOnline, error) + FindByPage(offset int, limit int) (result []*model.AppConversationTemplateOnline, count int64, err error) + ScanByPage(result interface{}, offset int, limit int) (count int64, err error) + Scan(result interface{}) (err error) + Returning(value interface{}, columns ...string) IAppConversationTemplateOnlineDo + UnderlyingDB() *gorm.DB + schema.Tabler +} + +func (a appConversationTemplateOnlineDo) Debug() IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Debug()) +} + +func (a appConversationTemplateOnlineDo) WithContext(ctx context.Context) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.WithContext(ctx)) +} + +func (a appConversationTemplateOnlineDo) ReadDB() IAppConversationTemplateOnlineDo { + return a.Clauses(dbresolver.Read) +} + +func (a appConversationTemplateOnlineDo) WriteDB() IAppConversationTemplateOnlineDo { + return a.Clauses(dbresolver.Write) +} + +func (a appConversationTemplateOnlineDo) Session(config *gorm.Session) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Session(config)) +} + +func (a appConversationTemplateOnlineDo) Clauses(conds ...clause.Expression) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Clauses(conds...)) +} + +func (a appConversationTemplateOnlineDo) Returning(value interface{}, columns ...string) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Returning(value, columns...)) +} + +func (a appConversationTemplateOnlineDo) Not(conds ...gen.Condition) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Not(conds...)) +} + +func (a appConversationTemplateOnlineDo) Or(conds ...gen.Condition) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Or(conds...)) +} + +func (a appConversationTemplateOnlineDo) Select(conds ...field.Expr) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Select(conds...)) +} + +func (a appConversationTemplateOnlineDo) Where(conds ...gen.Condition) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Where(conds...)) +} + +func (a appConversationTemplateOnlineDo) Order(conds ...field.Expr) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Order(conds...)) +} + +func (a appConversationTemplateOnlineDo) Distinct(cols ...field.Expr) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Distinct(cols...)) +} + +func (a appConversationTemplateOnlineDo) Omit(cols ...field.Expr) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Omit(cols...)) +} + +func (a appConversationTemplateOnlineDo) Join(table schema.Tabler, on ...field.Expr) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Join(table, on...)) +} + +func (a appConversationTemplateOnlineDo) LeftJoin(table schema.Tabler, on ...field.Expr) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.LeftJoin(table, on...)) +} + +func (a appConversationTemplateOnlineDo) RightJoin(table schema.Tabler, on ...field.Expr) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.RightJoin(table, on...)) +} + +func (a appConversationTemplateOnlineDo) Group(cols ...field.Expr) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Group(cols...)) +} + +func (a appConversationTemplateOnlineDo) Having(conds ...gen.Condition) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Having(conds...)) +} + +func (a appConversationTemplateOnlineDo) Limit(limit int) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Limit(limit)) +} + +func (a appConversationTemplateOnlineDo) Offset(offset int) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Offset(offset)) +} + +func (a appConversationTemplateOnlineDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Scopes(funcs...)) +} + +func (a appConversationTemplateOnlineDo) Unscoped() IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Unscoped()) +} + +func (a appConversationTemplateOnlineDo) Create(values ...*model.AppConversationTemplateOnline) error { + if len(values) == 0 { + return nil + } + return a.DO.Create(values) +} + +func (a appConversationTemplateOnlineDo) CreateInBatches(values []*model.AppConversationTemplateOnline, batchSize int) error { + return a.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (a appConversationTemplateOnlineDo) Save(values ...*model.AppConversationTemplateOnline) error { + if len(values) == 0 { + return nil + } + return a.DO.Save(values) +} + +func (a appConversationTemplateOnlineDo) First() (*model.AppConversationTemplateOnline, error) { + if result, err := a.DO.First(); err != nil { + return nil, err + } else { + return result.(*model.AppConversationTemplateOnline), nil + } +} + +func (a appConversationTemplateOnlineDo) Take() (*model.AppConversationTemplateOnline, error) { + if result, err := a.DO.Take(); err != nil { + return nil, err + } else { + return result.(*model.AppConversationTemplateOnline), nil + } +} + +func (a appConversationTemplateOnlineDo) Last() (*model.AppConversationTemplateOnline, error) { + if result, err := a.DO.Last(); err != nil { + return nil, err + } else { + return result.(*model.AppConversationTemplateOnline), nil + } +} + +func (a appConversationTemplateOnlineDo) Find() ([]*model.AppConversationTemplateOnline, error) { + result, err := a.DO.Find() + return result.([]*model.AppConversationTemplateOnline), err +} + +func (a appConversationTemplateOnlineDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AppConversationTemplateOnline, err error) { + buf := make([]*model.AppConversationTemplateOnline, 0, batchSize) + err = a.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (a appConversationTemplateOnlineDo) FindInBatches(result *[]*model.AppConversationTemplateOnline, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return a.DO.FindInBatches(result, batchSize, fc) +} + +func (a appConversationTemplateOnlineDo) Attrs(attrs ...field.AssignExpr) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Attrs(attrs...)) +} + +func (a appConversationTemplateOnlineDo) Assign(attrs ...field.AssignExpr) IAppConversationTemplateOnlineDo { + return a.withDO(a.DO.Assign(attrs...)) +} + +func (a appConversationTemplateOnlineDo) Joins(fields ...field.RelationField) IAppConversationTemplateOnlineDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Joins(_f)) + } + return &a +} + +func (a appConversationTemplateOnlineDo) Preload(fields ...field.RelationField) IAppConversationTemplateOnlineDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Preload(_f)) + } + return &a +} + +func (a appConversationTemplateOnlineDo) FirstOrInit() (*model.AppConversationTemplateOnline, error) { + if result, err := a.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*model.AppConversationTemplateOnline), nil + } +} + +func (a appConversationTemplateOnlineDo) FirstOrCreate() (*model.AppConversationTemplateOnline, error) { + if result, err := a.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*model.AppConversationTemplateOnline), nil + } +} + +func (a appConversationTemplateOnlineDo) FindByPage(offset int, limit int) (result []*model.AppConversationTemplateOnline, count int64, err error) { + result, err = a.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = a.Offset(-1).Limit(-1).Count() + return +} + +func (a appConversationTemplateOnlineDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = a.Count() + if err != nil { + return + } + + err = a.Offset(offset).Limit(limit).Scan(result) + return +} + +func (a appConversationTemplateOnlineDo) Scan(result interface{}) (err error) { + return a.DO.Scan(result) +} + +func (a appConversationTemplateOnlineDo) Delete(models ...*model.AppConversationTemplateOnline) (result gen.ResultInfo, err error) { + return a.DO.Delete(models) +} + +func (a *appConversationTemplateOnlineDo) withDO(do gen.Dao) *appConversationTemplateOnlineDo { + a.DO = *do.(*gen.DO) + return a +} diff --git a/backend/domain/workflow/internal/repo/dal/query/app_dynamic_conversation_draft.gen.go b/backend/domain/workflow/internal/repo/dal/query/app_dynamic_conversation_draft.gen.go new file mode 100644 index 00000000..48627256 --- /dev/null +++ b/backend/domain/workflow/internal/repo/dal/query/app_dynamic_conversation_draft.gen.go @@ -0,0 +1,408 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/repo/dal/model" +) + +func newAppDynamicConversationDraft(db *gorm.DB, opts ...gen.DOOption) appDynamicConversationDraft { + _appDynamicConversationDraft := appDynamicConversationDraft{} + + _appDynamicConversationDraft.appDynamicConversationDraftDo.UseDB(db, opts...) + _appDynamicConversationDraft.appDynamicConversationDraftDo.UseModel(&model.AppDynamicConversationDraft{}) + + tableName := _appDynamicConversationDraft.appDynamicConversationDraftDo.TableName() + _appDynamicConversationDraft.ALL = field.NewAsterisk(tableName) + _appDynamicConversationDraft.ID = field.NewInt64(tableName, "id") + _appDynamicConversationDraft.AppID = field.NewInt64(tableName, "app_id") + _appDynamicConversationDraft.Name = field.NewString(tableName, "name") + _appDynamicConversationDraft.UserID = field.NewInt64(tableName, "user_id") + _appDynamicConversationDraft.ConnectorID = field.NewInt64(tableName, "connector_id") + _appDynamicConversationDraft.ConversationID = field.NewInt64(tableName, "conversation_id") + _appDynamicConversationDraft.CreatedAt = field.NewInt64(tableName, "created_at") + _appDynamicConversationDraft.DeletedAt = field.NewField(tableName, "deleted_at") + + _appDynamicConversationDraft.fillFieldMap() + + return _appDynamicConversationDraft +} + +type appDynamicConversationDraft struct { + appDynamicConversationDraftDo + + ALL field.Asterisk + ID field.Int64 // id + AppID field.Int64 // app id + Name field.String // conversion name + UserID field.Int64 // user id + ConnectorID field.Int64 // connector id + ConversationID field.Int64 // conversation id + CreatedAt field.Int64 // create time in millisecond + DeletedAt field.Field // delete time in millisecond + + fieldMap map[string]field.Expr +} + +func (a appDynamicConversationDraft) Table(newTableName string) *appDynamicConversationDraft { + a.appDynamicConversationDraftDo.UseTable(newTableName) + return a.updateTableName(newTableName) +} + +func (a appDynamicConversationDraft) As(alias string) *appDynamicConversationDraft { + a.appDynamicConversationDraftDo.DO = *(a.appDynamicConversationDraftDo.As(alias).(*gen.DO)) + return a.updateTableName(alias) +} + +func (a *appDynamicConversationDraft) updateTableName(table string) *appDynamicConversationDraft { + a.ALL = field.NewAsterisk(table) + a.ID = field.NewInt64(table, "id") + a.AppID = field.NewInt64(table, "app_id") + a.Name = field.NewString(table, "name") + a.UserID = field.NewInt64(table, "user_id") + a.ConnectorID = field.NewInt64(table, "connector_id") + a.ConversationID = field.NewInt64(table, "conversation_id") + a.CreatedAt = field.NewInt64(table, "created_at") + a.DeletedAt = field.NewField(table, "deleted_at") + + a.fillFieldMap() + + return a +} + +func (a *appDynamicConversationDraft) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := a.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (a *appDynamicConversationDraft) fillFieldMap() { + a.fieldMap = make(map[string]field.Expr, 8) + a.fieldMap["id"] = a.ID + a.fieldMap["app_id"] = a.AppID + a.fieldMap["name"] = a.Name + a.fieldMap["user_id"] = a.UserID + a.fieldMap["connector_id"] = a.ConnectorID + a.fieldMap["conversation_id"] = a.ConversationID + a.fieldMap["created_at"] = a.CreatedAt + a.fieldMap["deleted_at"] = a.DeletedAt +} + +func (a appDynamicConversationDraft) clone(db *gorm.DB) appDynamicConversationDraft { + a.appDynamicConversationDraftDo.ReplaceConnPool(db.Statement.ConnPool) + return a +} + +func (a appDynamicConversationDraft) replaceDB(db *gorm.DB) appDynamicConversationDraft { + a.appDynamicConversationDraftDo.ReplaceDB(db) + return a +} + +type appDynamicConversationDraftDo struct{ gen.DO } + +type IAppDynamicConversationDraftDo interface { + gen.SubQuery + Debug() IAppDynamicConversationDraftDo + WithContext(ctx context.Context) IAppDynamicConversationDraftDo + WithResult(fc func(tx gen.Dao)) gen.ResultInfo + ReplaceDB(db *gorm.DB) + ReadDB() IAppDynamicConversationDraftDo + WriteDB() IAppDynamicConversationDraftDo + As(alias string) gen.Dao + Session(config *gorm.Session) IAppDynamicConversationDraftDo + Columns(cols ...field.Expr) gen.Columns + Clauses(conds ...clause.Expression) IAppDynamicConversationDraftDo + Not(conds ...gen.Condition) IAppDynamicConversationDraftDo + Or(conds ...gen.Condition) IAppDynamicConversationDraftDo + Select(conds ...field.Expr) IAppDynamicConversationDraftDo + Where(conds ...gen.Condition) IAppDynamicConversationDraftDo + Order(conds ...field.Expr) IAppDynamicConversationDraftDo + Distinct(cols ...field.Expr) IAppDynamicConversationDraftDo + Omit(cols ...field.Expr) IAppDynamicConversationDraftDo + Join(table schema.Tabler, on ...field.Expr) IAppDynamicConversationDraftDo + LeftJoin(table schema.Tabler, on ...field.Expr) IAppDynamicConversationDraftDo + RightJoin(table schema.Tabler, on ...field.Expr) IAppDynamicConversationDraftDo + Group(cols ...field.Expr) IAppDynamicConversationDraftDo + Having(conds ...gen.Condition) IAppDynamicConversationDraftDo + Limit(limit int) IAppDynamicConversationDraftDo + Offset(offset int) IAppDynamicConversationDraftDo + Count() (count int64, err error) + Scopes(funcs ...func(gen.Dao) gen.Dao) IAppDynamicConversationDraftDo + Unscoped() IAppDynamicConversationDraftDo + Create(values ...*model.AppDynamicConversationDraft) error + CreateInBatches(values []*model.AppDynamicConversationDraft, batchSize int) error + Save(values ...*model.AppDynamicConversationDraft) error + First() (*model.AppDynamicConversationDraft, error) + Take() (*model.AppDynamicConversationDraft, error) + Last() (*model.AppDynamicConversationDraft, error) + Find() ([]*model.AppDynamicConversationDraft, error) + FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AppDynamicConversationDraft, err error) + FindInBatches(result *[]*model.AppDynamicConversationDraft, batchSize int, fc func(tx gen.Dao, batch int) error) error + Pluck(column field.Expr, dest interface{}) error + Delete(...*model.AppDynamicConversationDraft) (info gen.ResultInfo, err error) + Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + Updates(value interface{}) (info gen.ResultInfo, err error) + UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + UpdateColumns(value interface{}) (info gen.ResultInfo, err error) + UpdateFrom(q gen.SubQuery) gen.Dao + Attrs(attrs ...field.AssignExpr) IAppDynamicConversationDraftDo + Assign(attrs ...field.AssignExpr) IAppDynamicConversationDraftDo + Joins(fields ...field.RelationField) IAppDynamicConversationDraftDo + Preload(fields ...field.RelationField) IAppDynamicConversationDraftDo + FirstOrInit() (*model.AppDynamicConversationDraft, error) + FirstOrCreate() (*model.AppDynamicConversationDraft, error) + FindByPage(offset int, limit int) (result []*model.AppDynamicConversationDraft, count int64, err error) + ScanByPage(result interface{}, offset int, limit int) (count int64, err error) + Scan(result interface{}) (err error) + Returning(value interface{}, columns ...string) IAppDynamicConversationDraftDo + UnderlyingDB() *gorm.DB + schema.Tabler +} + +func (a appDynamicConversationDraftDo) Debug() IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Debug()) +} + +func (a appDynamicConversationDraftDo) WithContext(ctx context.Context) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.WithContext(ctx)) +} + +func (a appDynamicConversationDraftDo) ReadDB() IAppDynamicConversationDraftDo { + return a.Clauses(dbresolver.Read) +} + +func (a appDynamicConversationDraftDo) WriteDB() IAppDynamicConversationDraftDo { + return a.Clauses(dbresolver.Write) +} + +func (a appDynamicConversationDraftDo) Session(config *gorm.Session) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Session(config)) +} + +func (a appDynamicConversationDraftDo) Clauses(conds ...clause.Expression) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Clauses(conds...)) +} + +func (a appDynamicConversationDraftDo) Returning(value interface{}, columns ...string) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Returning(value, columns...)) +} + +func (a appDynamicConversationDraftDo) Not(conds ...gen.Condition) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Not(conds...)) +} + +func (a appDynamicConversationDraftDo) Or(conds ...gen.Condition) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Or(conds...)) +} + +func (a appDynamicConversationDraftDo) Select(conds ...field.Expr) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Select(conds...)) +} + +func (a appDynamicConversationDraftDo) Where(conds ...gen.Condition) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Where(conds...)) +} + +func (a appDynamicConversationDraftDo) Order(conds ...field.Expr) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Order(conds...)) +} + +func (a appDynamicConversationDraftDo) Distinct(cols ...field.Expr) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Distinct(cols...)) +} + +func (a appDynamicConversationDraftDo) Omit(cols ...field.Expr) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Omit(cols...)) +} + +func (a appDynamicConversationDraftDo) Join(table schema.Tabler, on ...field.Expr) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Join(table, on...)) +} + +func (a appDynamicConversationDraftDo) LeftJoin(table schema.Tabler, on ...field.Expr) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.LeftJoin(table, on...)) +} + +func (a appDynamicConversationDraftDo) RightJoin(table schema.Tabler, on ...field.Expr) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.RightJoin(table, on...)) +} + +func (a appDynamicConversationDraftDo) Group(cols ...field.Expr) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Group(cols...)) +} + +func (a appDynamicConversationDraftDo) Having(conds ...gen.Condition) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Having(conds...)) +} + +func (a appDynamicConversationDraftDo) Limit(limit int) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Limit(limit)) +} + +func (a appDynamicConversationDraftDo) Offset(offset int) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Offset(offset)) +} + +func (a appDynamicConversationDraftDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Scopes(funcs...)) +} + +func (a appDynamicConversationDraftDo) Unscoped() IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Unscoped()) +} + +func (a appDynamicConversationDraftDo) Create(values ...*model.AppDynamicConversationDraft) error { + if len(values) == 0 { + return nil + } + return a.DO.Create(values) +} + +func (a appDynamicConversationDraftDo) CreateInBatches(values []*model.AppDynamicConversationDraft, batchSize int) error { + return a.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (a appDynamicConversationDraftDo) Save(values ...*model.AppDynamicConversationDraft) error { + if len(values) == 0 { + return nil + } + return a.DO.Save(values) +} + +func (a appDynamicConversationDraftDo) First() (*model.AppDynamicConversationDraft, error) { + if result, err := a.DO.First(); err != nil { + return nil, err + } else { + return result.(*model.AppDynamicConversationDraft), nil + } +} + +func (a appDynamicConversationDraftDo) Take() (*model.AppDynamicConversationDraft, error) { + if result, err := a.DO.Take(); err != nil { + return nil, err + } else { + return result.(*model.AppDynamicConversationDraft), nil + } +} + +func (a appDynamicConversationDraftDo) Last() (*model.AppDynamicConversationDraft, error) { + if result, err := a.DO.Last(); err != nil { + return nil, err + } else { + return result.(*model.AppDynamicConversationDraft), nil + } +} + +func (a appDynamicConversationDraftDo) Find() ([]*model.AppDynamicConversationDraft, error) { + result, err := a.DO.Find() + return result.([]*model.AppDynamicConversationDraft), err +} + +func (a appDynamicConversationDraftDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AppDynamicConversationDraft, err error) { + buf := make([]*model.AppDynamicConversationDraft, 0, batchSize) + err = a.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (a appDynamicConversationDraftDo) FindInBatches(result *[]*model.AppDynamicConversationDraft, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return a.DO.FindInBatches(result, batchSize, fc) +} + +func (a appDynamicConversationDraftDo) Attrs(attrs ...field.AssignExpr) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Attrs(attrs...)) +} + +func (a appDynamicConversationDraftDo) Assign(attrs ...field.AssignExpr) IAppDynamicConversationDraftDo { + return a.withDO(a.DO.Assign(attrs...)) +} + +func (a appDynamicConversationDraftDo) Joins(fields ...field.RelationField) IAppDynamicConversationDraftDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Joins(_f)) + } + return &a +} + +func (a appDynamicConversationDraftDo) Preload(fields ...field.RelationField) IAppDynamicConversationDraftDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Preload(_f)) + } + return &a +} + +func (a appDynamicConversationDraftDo) FirstOrInit() (*model.AppDynamicConversationDraft, error) { + if result, err := a.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*model.AppDynamicConversationDraft), nil + } +} + +func (a appDynamicConversationDraftDo) FirstOrCreate() (*model.AppDynamicConversationDraft, error) { + if result, err := a.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*model.AppDynamicConversationDraft), nil + } +} + +func (a appDynamicConversationDraftDo) FindByPage(offset int, limit int) (result []*model.AppDynamicConversationDraft, count int64, err error) { + result, err = a.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = a.Offset(-1).Limit(-1).Count() + return +} + +func (a appDynamicConversationDraftDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = a.Count() + if err != nil { + return + } + + err = a.Offset(offset).Limit(limit).Scan(result) + return +} + +func (a appDynamicConversationDraftDo) Scan(result interface{}) (err error) { + return a.DO.Scan(result) +} + +func (a appDynamicConversationDraftDo) Delete(models ...*model.AppDynamicConversationDraft) (result gen.ResultInfo, err error) { + return a.DO.Delete(models) +} + +func (a *appDynamicConversationDraftDo) withDO(do gen.Dao) *appDynamicConversationDraftDo { + a.DO = *do.(*gen.DO) + return a +} diff --git a/backend/domain/workflow/internal/repo/dal/query/app_dynamic_conversation_online.gen.go b/backend/domain/workflow/internal/repo/dal/query/app_dynamic_conversation_online.gen.go new file mode 100644 index 00000000..72a0153c --- /dev/null +++ b/backend/domain/workflow/internal/repo/dal/query/app_dynamic_conversation_online.gen.go @@ -0,0 +1,408 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/repo/dal/model" +) + +func newAppDynamicConversationOnline(db *gorm.DB, opts ...gen.DOOption) appDynamicConversationOnline { + _appDynamicConversationOnline := appDynamicConversationOnline{} + + _appDynamicConversationOnline.appDynamicConversationOnlineDo.UseDB(db, opts...) + _appDynamicConversationOnline.appDynamicConversationOnlineDo.UseModel(&model.AppDynamicConversationOnline{}) + + tableName := _appDynamicConversationOnline.appDynamicConversationOnlineDo.TableName() + _appDynamicConversationOnline.ALL = field.NewAsterisk(tableName) + _appDynamicConversationOnline.ID = field.NewInt64(tableName, "id") + _appDynamicConversationOnline.AppID = field.NewInt64(tableName, "app_id") + _appDynamicConversationOnline.Name = field.NewString(tableName, "name") + _appDynamicConversationOnline.UserID = field.NewInt64(tableName, "user_id") + _appDynamicConversationOnline.ConnectorID = field.NewInt64(tableName, "connector_id") + _appDynamicConversationOnline.ConversationID = field.NewInt64(tableName, "conversation_id") + _appDynamicConversationOnline.CreatedAt = field.NewInt64(tableName, "created_at") + _appDynamicConversationOnline.DeletedAt = field.NewField(tableName, "deleted_at") + + _appDynamicConversationOnline.fillFieldMap() + + return _appDynamicConversationOnline +} + +type appDynamicConversationOnline struct { + appDynamicConversationOnlineDo + + ALL field.Asterisk + ID field.Int64 // id + AppID field.Int64 // app id + Name field.String // conversion name + UserID field.Int64 // user id + ConnectorID field.Int64 // connector id + ConversationID field.Int64 // conversation id + CreatedAt field.Int64 // create time in millisecond + DeletedAt field.Field // delete time in millisecond + + fieldMap map[string]field.Expr +} + +func (a appDynamicConversationOnline) Table(newTableName string) *appDynamicConversationOnline { + a.appDynamicConversationOnlineDo.UseTable(newTableName) + return a.updateTableName(newTableName) +} + +func (a appDynamicConversationOnline) As(alias string) *appDynamicConversationOnline { + a.appDynamicConversationOnlineDo.DO = *(a.appDynamicConversationOnlineDo.As(alias).(*gen.DO)) + return a.updateTableName(alias) +} + +func (a *appDynamicConversationOnline) updateTableName(table string) *appDynamicConversationOnline { + a.ALL = field.NewAsterisk(table) + a.ID = field.NewInt64(table, "id") + a.AppID = field.NewInt64(table, "app_id") + a.Name = field.NewString(table, "name") + a.UserID = field.NewInt64(table, "user_id") + a.ConnectorID = field.NewInt64(table, "connector_id") + a.ConversationID = field.NewInt64(table, "conversation_id") + a.CreatedAt = field.NewInt64(table, "created_at") + a.DeletedAt = field.NewField(table, "deleted_at") + + a.fillFieldMap() + + return a +} + +func (a *appDynamicConversationOnline) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := a.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (a *appDynamicConversationOnline) fillFieldMap() { + a.fieldMap = make(map[string]field.Expr, 8) + a.fieldMap["id"] = a.ID + a.fieldMap["app_id"] = a.AppID + a.fieldMap["name"] = a.Name + a.fieldMap["user_id"] = a.UserID + a.fieldMap["connector_id"] = a.ConnectorID + a.fieldMap["conversation_id"] = a.ConversationID + a.fieldMap["created_at"] = a.CreatedAt + a.fieldMap["deleted_at"] = a.DeletedAt +} + +func (a appDynamicConversationOnline) clone(db *gorm.DB) appDynamicConversationOnline { + a.appDynamicConversationOnlineDo.ReplaceConnPool(db.Statement.ConnPool) + return a +} + +func (a appDynamicConversationOnline) replaceDB(db *gorm.DB) appDynamicConversationOnline { + a.appDynamicConversationOnlineDo.ReplaceDB(db) + return a +} + +type appDynamicConversationOnlineDo struct{ gen.DO } + +type IAppDynamicConversationOnlineDo interface { + gen.SubQuery + Debug() IAppDynamicConversationOnlineDo + WithContext(ctx context.Context) IAppDynamicConversationOnlineDo + WithResult(fc func(tx gen.Dao)) gen.ResultInfo + ReplaceDB(db *gorm.DB) + ReadDB() IAppDynamicConversationOnlineDo + WriteDB() IAppDynamicConversationOnlineDo + As(alias string) gen.Dao + Session(config *gorm.Session) IAppDynamicConversationOnlineDo + Columns(cols ...field.Expr) gen.Columns + Clauses(conds ...clause.Expression) IAppDynamicConversationOnlineDo + Not(conds ...gen.Condition) IAppDynamicConversationOnlineDo + Or(conds ...gen.Condition) IAppDynamicConversationOnlineDo + Select(conds ...field.Expr) IAppDynamicConversationOnlineDo + Where(conds ...gen.Condition) IAppDynamicConversationOnlineDo + Order(conds ...field.Expr) IAppDynamicConversationOnlineDo + Distinct(cols ...field.Expr) IAppDynamicConversationOnlineDo + Omit(cols ...field.Expr) IAppDynamicConversationOnlineDo + Join(table schema.Tabler, on ...field.Expr) IAppDynamicConversationOnlineDo + LeftJoin(table schema.Tabler, on ...field.Expr) IAppDynamicConversationOnlineDo + RightJoin(table schema.Tabler, on ...field.Expr) IAppDynamicConversationOnlineDo + Group(cols ...field.Expr) IAppDynamicConversationOnlineDo + Having(conds ...gen.Condition) IAppDynamicConversationOnlineDo + Limit(limit int) IAppDynamicConversationOnlineDo + Offset(offset int) IAppDynamicConversationOnlineDo + Count() (count int64, err error) + Scopes(funcs ...func(gen.Dao) gen.Dao) IAppDynamicConversationOnlineDo + Unscoped() IAppDynamicConversationOnlineDo + Create(values ...*model.AppDynamicConversationOnline) error + CreateInBatches(values []*model.AppDynamicConversationOnline, batchSize int) error + Save(values ...*model.AppDynamicConversationOnline) error + First() (*model.AppDynamicConversationOnline, error) + Take() (*model.AppDynamicConversationOnline, error) + Last() (*model.AppDynamicConversationOnline, error) + Find() ([]*model.AppDynamicConversationOnline, error) + FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AppDynamicConversationOnline, err error) + FindInBatches(result *[]*model.AppDynamicConversationOnline, batchSize int, fc func(tx gen.Dao, batch int) error) error + Pluck(column field.Expr, dest interface{}) error + Delete(...*model.AppDynamicConversationOnline) (info gen.ResultInfo, err error) + Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + Updates(value interface{}) (info gen.ResultInfo, err error) + UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + UpdateColumns(value interface{}) (info gen.ResultInfo, err error) + UpdateFrom(q gen.SubQuery) gen.Dao + Attrs(attrs ...field.AssignExpr) IAppDynamicConversationOnlineDo + Assign(attrs ...field.AssignExpr) IAppDynamicConversationOnlineDo + Joins(fields ...field.RelationField) IAppDynamicConversationOnlineDo + Preload(fields ...field.RelationField) IAppDynamicConversationOnlineDo + FirstOrInit() (*model.AppDynamicConversationOnline, error) + FirstOrCreate() (*model.AppDynamicConversationOnline, error) + FindByPage(offset int, limit int) (result []*model.AppDynamicConversationOnline, count int64, err error) + ScanByPage(result interface{}, offset int, limit int) (count int64, err error) + Scan(result interface{}) (err error) + Returning(value interface{}, columns ...string) IAppDynamicConversationOnlineDo + UnderlyingDB() *gorm.DB + schema.Tabler +} + +func (a appDynamicConversationOnlineDo) Debug() IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Debug()) +} + +func (a appDynamicConversationOnlineDo) WithContext(ctx context.Context) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.WithContext(ctx)) +} + +func (a appDynamicConversationOnlineDo) ReadDB() IAppDynamicConversationOnlineDo { + return a.Clauses(dbresolver.Read) +} + +func (a appDynamicConversationOnlineDo) WriteDB() IAppDynamicConversationOnlineDo { + return a.Clauses(dbresolver.Write) +} + +func (a appDynamicConversationOnlineDo) Session(config *gorm.Session) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Session(config)) +} + +func (a appDynamicConversationOnlineDo) Clauses(conds ...clause.Expression) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Clauses(conds...)) +} + +func (a appDynamicConversationOnlineDo) Returning(value interface{}, columns ...string) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Returning(value, columns...)) +} + +func (a appDynamicConversationOnlineDo) Not(conds ...gen.Condition) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Not(conds...)) +} + +func (a appDynamicConversationOnlineDo) Or(conds ...gen.Condition) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Or(conds...)) +} + +func (a appDynamicConversationOnlineDo) Select(conds ...field.Expr) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Select(conds...)) +} + +func (a appDynamicConversationOnlineDo) Where(conds ...gen.Condition) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Where(conds...)) +} + +func (a appDynamicConversationOnlineDo) Order(conds ...field.Expr) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Order(conds...)) +} + +func (a appDynamicConversationOnlineDo) Distinct(cols ...field.Expr) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Distinct(cols...)) +} + +func (a appDynamicConversationOnlineDo) Omit(cols ...field.Expr) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Omit(cols...)) +} + +func (a appDynamicConversationOnlineDo) Join(table schema.Tabler, on ...field.Expr) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Join(table, on...)) +} + +func (a appDynamicConversationOnlineDo) LeftJoin(table schema.Tabler, on ...field.Expr) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.LeftJoin(table, on...)) +} + +func (a appDynamicConversationOnlineDo) RightJoin(table schema.Tabler, on ...field.Expr) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.RightJoin(table, on...)) +} + +func (a appDynamicConversationOnlineDo) Group(cols ...field.Expr) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Group(cols...)) +} + +func (a appDynamicConversationOnlineDo) Having(conds ...gen.Condition) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Having(conds...)) +} + +func (a appDynamicConversationOnlineDo) Limit(limit int) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Limit(limit)) +} + +func (a appDynamicConversationOnlineDo) Offset(offset int) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Offset(offset)) +} + +func (a appDynamicConversationOnlineDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Scopes(funcs...)) +} + +func (a appDynamicConversationOnlineDo) Unscoped() IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Unscoped()) +} + +func (a appDynamicConversationOnlineDo) Create(values ...*model.AppDynamicConversationOnline) error { + if len(values) == 0 { + return nil + } + return a.DO.Create(values) +} + +func (a appDynamicConversationOnlineDo) CreateInBatches(values []*model.AppDynamicConversationOnline, batchSize int) error { + return a.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (a appDynamicConversationOnlineDo) Save(values ...*model.AppDynamicConversationOnline) error { + if len(values) == 0 { + return nil + } + return a.DO.Save(values) +} + +func (a appDynamicConversationOnlineDo) First() (*model.AppDynamicConversationOnline, error) { + if result, err := a.DO.First(); err != nil { + return nil, err + } else { + return result.(*model.AppDynamicConversationOnline), nil + } +} + +func (a appDynamicConversationOnlineDo) Take() (*model.AppDynamicConversationOnline, error) { + if result, err := a.DO.Take(); err != nil { + return nil, err + } else { + return result.(*model.AppDynamicConversationOnline), nil + } +} + +func (a appDynamicConversationOnlineDo) Last() (*model.AppDynamicConversationOnline, error) { + if result, err := a.DO.Last(); err != nil { + return nil, err + } else { + return result.(*model.AppDynamicConversationOnline), nil + } +} + +func (a appDynamicConversationOnlineDo) Find() ([]*model.AppDynamicConversationOnline, error) { + result, err := a.DO.Find() + return result.([]*model.AppDynamicConversationOnline), err +} + +func (a appDynamicConversationOnlineDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AppDynamicConversationOnline, err error) { + buf := make([]*model.AppDynamicConversationOnline, 0, batchSize) + err = a.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (a appDynamicConversationOnlineDo) FindInBatches(result *[]*model.AppDynamicConversationOnline, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return a.DO.FindInBatches(result, batchSize, fc) +} + +func (a appDynamicConversationOnlineDo) Attrs(attrs ...field.AssignExpr) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Attrs(attrs...)) +} + +func (a appDynamicConversationOnlineDo) Assign(attrs ...field.AssignExpr) IAppDynamicConversationOnlineDo { + return a.withDO(a.DO.Assign(attrs...)) +} + +func (a appDynamicConversationOnlineDo) Joins(fields ...field.RelationField) IAppDynamicConversationOnlineDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Joins(_f)) + } + return &a +} + +func (a appDynamicConversationOnlineDo) Preload(fields ...field.RelationField) IAppDynamicConversationOnlineDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Preload(_f)) + } + return &a +} + +func (a appDynamicConversationOnlineDo) FirstOrInit() (*model.AppDynamicConversationOnline, error) { + if result, err := a.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*model.AppDynamicConversationOnline), nil + } +} + +func (a appDynamicConversationOnlineDo) FirstOrCreate() (*model.AppDynamicConversationOnline, error) { + if result, err := a.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*model.AppDynamicConversationOnline), nil + } +} + +func (a appDynamicConversationOnlineDo) FindByPage(offset int, limit int) (result []*model.AppDynamicConversationOnline, count int64, err error) { + result, err = a.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = a.Offset(-1).Limit(-1).Count() + return +} + +func (a appDynamicConversationOnlineDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = a.Count() + if err != nil { + return + } + + err = a.Offset(offset).Limit(limit).Scan(result) + return +} + +func (a appDynamicConversationOnlineDo) Scan(result interface{}) (err error) { + return a.DO.Scan(result) +} + +func (a appDynamicConversationOnlineDo) Delete(models ...*model.AppDynamicConversationOnline) (result gen.ResultInfo, err error) { + return a.DO.Delete(models) +} + +func (a *appDynamicConversationOnlineDo) withDO(do gen.Dao) *appDynamicConversationOnlineDo { + a.DO = *do.(*gen.DO) + return a +} diff --git a/backend/domain/workflow/internal/repo/dal/query/app_static_conversation_draft.gen.go b/backend/domain/workflow/internal/repo/dal/query/app_static_conversation_draft.gen.go new file mode 100644 index 00000000..1f8ad704 --- /dev/null +++ b/backend/domain/workflow/internal/repo/dal/query/app_static_conversation_draft.gen.go @@ -0,0 +1,404 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/repo/dal/model" +) + +func newAppStaticConversationDraft(db *gorm.DB, opts ...gen.DOOption) appStaticConversationDraft { + _appStaticConversationDraft := appStaticConversationDraft{} + + _appStaticConversationDraft.appStaticConversationDraftDo.UseDB(db, opts...) + _appStaticConversationDraft.appStaticConversationDraftDo.UseModel(&model.AppStaticConversationDraft{}) + + tableName := _appStaticConversationDraft.appStaticConversationDraftDo.TableName() + _appStaticConversationDraft.ALL = field.NewAsterisk(tableName) + _appStaticConversationDraft.ID = field.NewInt64(tableName, "id") + _appStaticConversationDraft.TemplateID = field.NewInt64(tableName, "template_id") + _appStaticConversationDraft.UserID = field.NewInt64(tableName, "user_id") + _appStaticConversationDraft.ConnectorID = field.NewInt64(tableName, "connector_id") + _appStaticConversationDraft.ConversationID = field.NewInt64(tableName, "conversation_id") + _appStaticConversationDraft.CreatedAt = field.NewInt64(tableName, "created_at") + _appStaticConversationDraft.DeletedAt = field.NewField(tableName, "deleted_at") + + _appStaticConversationDraft.fillFieldMap() + + return _appStaticConversationDraft +} + +type appStaticConversationDraft struct { + appStaticConversationDraftDo + + ALL field.Asterisk + ID field.Int64 // id + TemplateID field.Int64 // template id + UserID field.Int64 // user id + ConnectorID field.Int64 // connector id + ConversationID field.Int64 // conversation id + CreatedAt field.Int64 // create time in millisecond + DeletedAt field.Field // delete time in millisecond + + fieldMap map[string]field.Expr +} + +func (a appStaticConversationDraft) Table(newTableName string) *appStaticConversationDraft { + a.appStaticConversationDraftDo.UseTable(newTableName) + return a.updateTableName(newTableName) +} + +func (a appStaticConversationDraft) As(alias string) *appStaticConversationDraft { + a.appStaticConversationDraftDo.DO = *(a.appStaticConversationDraftDo.As(alias).(*gen.DO)) + return a.updateTableName(alias) +} + +func (a *appStaticConversationDraft) updateTableName(table string) *appStaticConversationDraft { + a.ALL = field.NewAsterisk(table) + a.ID = field.NewInt64(table, "id") + a.TemplateID = field.NewInt64(table, "template_id") + a.UserID = field.NewInt64(table, "user_id") + a.ConnectorID = field.NewInt64(table, "connector_id") + a.ConversationID = field.NewInt64(table, "conversation_id") + a.CreatedAt = field.NewInt64(table, "created_at") + a.DeletedAt = field.NewField(table, "deleted_at") + + a.fillFieldMap() + + return a +} + +func (a *appStaticConversationDraft) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := a.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (a *appStaticConversationDraft) fillFieldMap() { + a.fieldMap = make(map[string]field.Expr, 7) + a.fieldMap["id"] = a.ID + a.fieldMap["template_id"] = a.TemplateID + a.fieldMap["user_id"] = a.UserID + a.fieldMap["connector_id"] = a.ConnectorID + a.fieldMap["conversation_id"] = a.ConversationID + a.fieldMap["created_at"] = a.CreatedAt + a.fieldMap["deleted_at"] = a.DeletedAt +} + +func (a appStaticConversationDraft) clone(db *gorm.DB) appStaticConversationDraft { + a.appStaticConversationDraftDo.ReplaceConnPool(db.Statement.ConnPool) + return a +} + +func (a appStaticConversationDraft) replaceDB(db *gorm.DB) appStaticConversationDraft { + a.appStaticConversationDraftDo.ReplaceDB(db) + return a +} + +type appStaticConversationDraftDo struct{ gen.DO } + +type IAppStaticConversationDraftDo interface { + gen.SubQuery + Debug() IAppStaticConversationDraftDo + WithContext(ctx context.Context) IAppStaticConversationDraftDo + WithResult(fc func(tx gen.Dao)) gen.ResultInfo + ReplaceDB(db *gorm.DB) + ReadDB() IAppStaticConversationDraftDo + WriteDB() IAppStaticConversationDraftDo + As(alias string) gen.Dao + Session(config *gorm.Session) IAppStaticConversationDraftDo + Columns(cols ...field.Expr) gen.Columns + Clauses(conds ...clause.Expression) IAppStaticConversationDraftDo + Not(conds ...gen.Condition) IAppStaticConversationDraftDo + Or(conds ...gen.Condition) IAppStaticConversationDraftDo + Select(conds ...field.Expr) IAppStaticConversationDraftDo + Where(conds ...gen.Condition) IAppStaticConversationDraftDo + Order(conds ...field.Expr) IAppStaticConversationDraftDo + Distinct(cols ...field.Expr) IAppStaticConversationDraftDo + Omit(cols ...field.Expr) IAppStaticConversationDraftDo + Join(table schema.Tabler, on ...field.Expr) IAppStaticConversationDraftDo + LeftJoin(table schema.Tabler, on ...field.Expr) IAppStaticConversationDraftDo + RightJoin(table schema.Tabler, on ...field.Expr) IAppStaticConversationDraftDo + Group(cols ...field.Expr) IAppStaticConversationDraftDo + Having(conds ...gen.Condition) IAppStaticConversationDraftDo + Limit(limit int) IAppStaticConversationDraftDo + Offset(offset int) IAppStaticConversationDraftDo + Count() (count int64, err error) + Scopes(funcs ...func(gen.Dao) gen.Dao) IAppStaticConversationDraftDo + Unscoped() IAppStaticConversationDraftDo + Create(values ...*model.AppStaticConversationDraft) error + CreateInBatches(values []*model.AppStaticConversationDraft, batchSize int) error + Save(values ...*model.AppStaticConversationDraft) error + First() (*model.AppStaticConversationDraft, error) + Take() (*model.AppStaticConversationDraft, error) + Last() (*model.AppStaticConversationDraft, error) + Find() ([]*model.AppStaticConversationDraft, error) + FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AppStaticConversationDraft, err error) + FindInBatches(result *[]*model.AppStaticConversationDraft, batchSize int, fc func(tx gen.Dao, batch int) error) error + Pluck(column field.Expr, dest interface{}) error + Delete(...*model.AppStaticConversationDraft) (info gen.ResultInfo, err error) + Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + Updates(value interface{}) (info gen.ResultInfo, err error) + UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + UpdateColumns(value interface{}) (info gen.ResultInfo, err error) + UpdateFrom(q gen.SubQuery) gen.Dao + Attrs(attrs ...field.AssignExpr) IAppStaticConversationDraftDo + Assign(attrs ...field.AssignExpr) IAppStaticConversationDraftDo + Joins(fields ...field.RelationField) IAppStaticConversationDraftDo + Preload(fields ...field.RelationField) IAppStaticConversationDraftDo + FirstOrInit() (*model.AppStaticConversationDraft, error) + FirstOrCreate() (*model.AppStaticConversationDraft, error) + FindByPage(offset int, limit int) (result []*model.AppStaticConversationDraft, count int64, err error) + ScanByPage(result interface{}, offset int, limit int) (count int64, err error) + Scan(result interface{}) (err error) + Returning(value interface{}, columns ...string) IAppStaticConversationDraftDo + UnderlyingDB() *gorm.DB + schema.Tabler +} + +func (a appStaticConversationDraftDo) Debug() IAppStaticConversationDraftDo { + return a.withDO(a.DO.Debug()) +} + +func (a appStaticConversationDraftDo) WithContext(ctx context.Context) IAppStaticConversationDraftDo { + return a.withDO(a.DO.WithContext(ctx)) +} + +func (a appStaticConversationDraftDo) ReadDB() IAppStaticConversationDraftDo { + return a.Clauses(dbresolver.Read) +} + +func (a appStaticConversationDraftDo) WriteDB() IAppStaticConversationDraftDo { + return a.Clauses(dbresolver.Write) +} + +func (a appStaticConversationDraftDo) Session(config *gorm.Session) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Session(config)) +} + +func (a appStaticConversationDraftDo) Clauses(conds ...clause.Expression) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Clauses(conds...)) +} + +func (a appStaticConversationDraftDo) Returning(value interface{}, columns ...string) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Returning(value, columns...)) +} + +func (a appStaticConversationDraftDo) Not(conds ...gen.Condition) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Not(conds...)) +} + +func (a appStaticConversationDraftDo) Or(conds ...gen.Condition) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Or(conds...)) +} + +func (a appStaticConversationDraftDo) Select(conds ...field.Expr) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Select(conds...)) +} + +func (a appStaticConversationDraftDo) Where(conds ...gen.Condition) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Where(conds...)) +} + +func (a appStaticConversationDraftDo) Order(conds ...field.Expr) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Order(conds...)) +} + +func (a appStaticConversationDraftDo) Distinct(cols ...field.Expr) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Distinct(cols...)) +} + +func (a appStaticConversationDraftDo) Omit(cols ...field.Expr) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Omit(cols...)) +} + +func (a appStaticConversationDraftDo) Join(table schema.Tabler, on ...field.Expr) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Join(table, on...)) +} + +func (a appStaticConversationDraftDo) LeftJoin(table schema.Tabler, on ...field.Expr) IAppStaticConversationDraftDo { + return a.withDO(a.DO.LeftJoin(table, on...)) +} + +func (a appStaticConversationDraftDo) RightJoin(table schema.Tabler, on ...field.Expr) IAppStaticConversationDraftDo { + return a.withDO(a.DO.RightJoin(table, on...)) +} + +func (a appStaticConversationDraftDo) Group(cols ...field.Expr) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Group(cols...)) +} + +func (a appStaticConversationDraftDo) Having(conds ...gen.Condition) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Having(conds...)) +} + +func (a appStaticConversationDraftDo) Limit(limit int) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Limit(limit)) +} + +func (a appStaticConversationDraftDo) Offset(offset int) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Offset(offset)) +} + +func (a appStaticConversationDraftDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Scopes(funcs...)) +} + +func (a appStaticConversationDraftDo) Unscoped() IAppStaticConversationDraftDo { + return a.withDO(a.DO.Unscoped()) +} + +func (a appStaticConversationDraftDo) Create(values ...*model.AppStaticConversationDraft) error { + if len(values) == 0 { + return nil + } + return a.DO.Create(values) +} + +func (a appStaticConversationDraftDo) CreateInBatches(values []*model.AppStaticConversationDraft, batchSize int) error { + return a.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (a appStaticConversationDraftDo) Save(values ...*model.AppStaticConversationDraft) error { + if len(values) == 0 { + return nil + } + return a.DO.Save(values) +} + +func (a appStaticConversationDraftDo) First() (*model.AppStaticConversationDraft, error) { + if result, err := a.DO.First(); err != nil { + return nil, err + } else { + return result.(*model.AppStaticConversationDraft), nil + } +} + +func (a appStaticConversationDraftDo) Take() (*model.AppStaticConversationDraft, error) { + if result, err := a.DO.Take(); err != nil { + return nil, err + } else { + return result.(*model.AppStaticConversationDraft), nil + } +} + +func (a appStaticConversationDraftDo) Last() (*model.AppStaticConversationDraft, error) { + if result, err := a.DO.Last(); err != nil { + return nil, err + } else { + return result.(*model.AppStaticConversationDraft), nil + } +} + +func (a appStaticConversationDraftDo) Find() ([]*model.AppStaticConversationDraft, error) { + result, err := a.DO.Find() + return result.([]*model.AppStaticConversationDraft), err +} + +func (a appStaticConversationDraftDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AppStaticConversationDraft, err error) { + buf := make([]*model.AppStaticConversationDraft, 0, batchSize) + err = a.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (a appStaticConversationDraftDo) FindInBatches(result *[]*model.AppStaticConversationDraft, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return a.DO.FindInBatches(result, batchSize, fc) +} + +func (a appStaticConversationDraftDo) Attrs(attrs ...field.AssignExpr) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Attrs(attrs...)) +} + +func (a appStaticConversationDraftDo) Assign(attrs ...field.AssignExpr) IAppStaticConversationDraftDo { + return a.withDO(a.DO.Assign(attrs...)) +} + +func (a appStaticConversationDraftDo) Joins(fields ...field.RelationField) IAppStaticConversationDraftDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Joins(_f)) + } + return &a +} + +func (a appStaticConversationDraftDo) Preload(fields ...field.RelationField) IAppStaticConversationDraftDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Preload(_f)) + } + return &a +} + +func (a appStaticConversationDraftDo) FirstOrInit() (*model.AppStaticConversationDraft, error) { + if result, err := a.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*model.AppStaticConversationDraft), nil + } +} + +func (a appStaticConversationDraftDo) FirstOrCreate() (*model.AppStaticConversationDraft, error) { + if result, err := a.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*model.AppStaticConversationDraft), nil + } +} + +func (a appStaticConversationDraftDo) FindByPage(offset int, limit int) (result []*model.AppStaticConversationDraft, count int64, err error) { + result, err = a.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = a.Offset(-1).Limit(-1).Count() + return +} + +func (a appStaticConversationDraftDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = a.Count() + if err != nil { + return + } + + err = a.Offset(offset).Limit(limit).Scan(result) + return +} + +func (a appStaticConversationDraftDo) Scan(result interface{}) (err error) { + return a.DO.Scan(result) +} + +func (a appStaticConversationDraftDo) Delete(models ...*model.AppStaticConversationDraft) (result gen.ResultInfo, err error) { + return a.DO.Delete(models) +} + +func (a *appStaticConversationDraftDo) withDO(do gen.Dao) *appStaticConversationDraftDo { + a.DO = *do.(*gen.DO) + return a +} diff --git a/backend/domain/workflow/internal/repo/dal/query/app_static_conversation_online.gen.go b/backend/domain/workflow/internal/repo/dal/query/app_static_conversation_online.gen.go new file mode 100644 index 00000000..280b0f8e --- /dev/null +++ b/backend/domain/workflow/internal/repo/dal/query/app_static_conversation_online.gen.go @@ -0,0 +1,400 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/repo/dal/model" +) + +func newAppStaticConversationOnline(db *gorm.DB, opts ...gen.DOOption) appStaticConversationOnline { + _appStaticConversationOnline := appStaticConversationOnline{} + + _appStaticConversationOnline.appStaticConversationOnlineDo.UseDB(db, opts...) + _appStaticConversationOnline.appStaticConversationOnlineDo.UseModel(&model.AppStaticConversationOnline{}) + + tableName := _appStaticConversationOnline.appStaticConversationOnlineDo.TableName() + _appStaticConversationOnline.ALL = field.NewAsterisk(tableName) + _appStaticConversationOnline.ID = field.NewInt64(tableName, "id") + _appStaticConversationOnline.TemplateID = field.NewInt64(tableName, "template_id") + _appStaticConversationOnline.UserID = field.NewInt64(tableName, "user_id") + _appStaticConversationOnline.ConnectorID = field.NewInt64(tableName, "connector_id") + _appStaticConversationOnline.ConversationID = field.NewInt64(tableName, "conversation_id") + _appStaticConversationOnline.CreatedAt = field.NewInt64(tableName, "created_at") + + _appStaticConversationOnline.fillFieldMap() + + return _appStaticConversationOnline +} + +type appStaticConversationOnline struct { + appStaticConversationOnlineDo + + ALL field.Asterisk + ID field.Int64 // id + TemplateID field.Int64 // template id + UserID field.Int64 // user id + ConnectorID field.Int64 // connector id + ConversationID field.Int64 // conversation id + CreatedAt field.Int64 // create time in millisecond + + fieldMap map[string]field.Expr +} + +func (a appStaticConversationOnline) Table(newTableName string) *appStaticConversationOnline { + a.appStaticConversationOnlineDo.UseTable(newTableName) + return a.updateTableName(newTableName) +} + +func (a appStaticConversationOnline) As(alias string) *appStaticConversationOnline { + a.appStaticConversationOnlineDo.DO = *(a.appStaticConversationOnlineDo.As(alias).(*gen.DO)) + return a.updateTableName(alias) +} + +func (a *appStaticConversationOnline) updateTableName(table string) *appStaticConversationOnline { + a.ALL = field.NewAsterisk(table) + a.ID = field.NewInt64(table, "id") + a.TemplateID = field.NewInt64(table, "template_id") + a.UserID = field.NewInt64(table, "user_id") + a.ConnectorID = field.NewInt64(table, "connector_id") + a.ConversationID = field.NewInt64(table, "conversation_id") + a.CreatedAt = field.NewInt64(table, "created_at") + + a.fillFieldMap() + + return a +} + +func (a *appStaticConversationOnline) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := a.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (a *appStaticConversationOnline) fillFieldMap() { + a.fieldMap = make(map[string]field.Expr, 6) + a.fieldMap["id"] = a.ID + a.fieldMap["template_id"] = a.TemplateID + a.fieldMap["user_id"] = a.UserID + a.fieldMap["connector_id"] = a.ConnectorID + a.fieldMap["conversation_id"] = a.ConversationID + a.fieldMap["created_at"] = a.CreatedAt +} + +func (a appStaticConversationOnline) clone(db *gorm.DB) appStaticConversationOnline { + a.appStaticConversationOnlineDo.ReplaceConnPool(db.Statement.ConnPool) + return a +} + +func (a appStaticConversationOnline) replaceDB(db *gorm.DB) appStaticConversationOnline { + a.appStaticConversationOnlineDo.ReplaceDB(db) + return a +} + +type appStaticConversationOnlineDo struct{ gen.DO } + +type IAppStaticConversationOnlineDo interface { + gen.SubQuery + Debug() IAppStaticConversationOnlineDo + WithContext(ctx context.Context) IAppStaticConversationOnlineDo + WithResult(fc func(tx gen.Dao)) gen.ResultInfo + ReplaceDB(db *gorm.DB) + ReadDB() IAppStaticConversationOnlineDo + WriteDB() IAppStaticConversationOnlineDo + As(alias string) gen.Dao + Session(config *gorm.Session) IAppStaticConversationOnlineDo + Columns(cols ...field.Expr) gen.Columns + Clauses(conds ...clause.Expression) IAppStaticConversationOnlineDo + Not(conds ...gen.Condition) IAppStaticConversationOnlineDo + Or(conds ...gen.Condition) IAppStaticConversationOnlineDo + Select(conds ...field.Expr) IAppStaticConversationOnlineDo + Where(conds ...gen.Condition) IAppStaticConversationOnlineDo + Order(conds ...field.Expr) IAppStaticConversationOnlineDo + Distinct(cols ...field.Expr) IAppStaticConversationOnlineDo + Omit(cols ...field.Expr) IAppStaticConversationOnlineDo + Join(table schema.Tabler, on ...field.Expr) IAppStaticConversationOnlineDo + LeftJoin(table schema.Tabler, on ...field.Expr) IAppStaticConversationOnlineDo + RightJoin(table schema.Tabler, on ...field.Expr) IAppStaticConversationOnlineDo + Group(cols ...field.Expr) IAppStaticConversationOnlineDo + Having(conds ...gen.Condition) IAppStaticConversationOnlineDo + Limit(limit int) IAppStaticConversationOnlineDo + Offset(offset int) IAppStaticConversationOnlineDo + Count() (count int64, err error) + Scopes(funcs ...func(gen.Dao) gen.Dao) IAppStaticConversationOnlineDo + Unscoped() IAppStaticConversationOnlineDo + Create(values ...*model.AppStaticConversationOnline) error + CreateInBatches(values []*model.AppStaticConversationOnline, batchSize int) error + Save(values ...*model.AppStaticConversationOnline) error + First() (*model.AppStaticConversationOnline, error) + Take() (*model.AppStaticConversationOnline, error) + Last() (*model.AppStaticConversationOnline, error) + Find() ([]*model.AppStaticConversationOnline, error) + FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AppStaticConversationOnline, err error) + FindInBatches(result *[]*model.AppStaticConversationOnline, batchSize int, fc func(tx gen.Dao, batch int) error) error + Pluck(column field.Expr, dest interface{}) error + Delete(...*model.AppStaticConversationOnline) (info gen.ResultInfo, err error) + Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + Updates(value interface{}) (info gen.ResultInfo, err error) + UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + UpdateColumns(value interface{}) (info gen.ResultInfo, err error) + UpdateFrom(q gen.SubQuery) gen.Dao + Attrs(attrs ...field.AssignExpr) IAppStaticConversationOnlineDo + Assign(attrs ...field.AssignExpr) IAppStaticConversationOnlineDo + Joins(fields ...field.RelationField) IAppStaticConversationOnlineDo + Preload(fields ...field.RelationField) IAppStaticConversationOnlineDo + FirstOrInit() (*model.AppStaticConversationOnline, error) + FirstOrCreate() (*model.AppStaticConversationOnline, error) + FindByPage(offset int, limit int) (result []*model.AppStaticConversationOnline, count int64, err error) + ScanByPage(result interface{}, offset int, limit int) (count int64, err error) + Scan(result interface{}) (err error) + Returning(value interface{}, columns ...string) IAppStaticConversationOnlineDo + UnderlyingDB() *gorm.DB + schema.Tabler +} + +func (a appStaticConversationOnlineDo) Debug() IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Debug()) +} + +func (a appStaticConversationOnlineDo) WithContext(ctx context.Context) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.WithContext(ctx)) +} + +func (a appStaticConversationOnlineDo) ReadDB() IAppStaticConversationOnlineDo { + return a.Clauses(dbresolver.Read) +} + +func (a appStaticConversationOnlineDo) WriteDB() IAppStaticConversationOnlineDo { + return a.Clauses(dbresolver.Write) +} + +func (a appStaticConversationOnlineDo) Session(config *gorm.Session) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Session(config)) +} + +func (a appStaticConversationOnlineDo) Clauses(conds ...clause.Expression) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Clauses(conds...)) +} + +func (a appStaticConversationOnlineDo) Returning(value interface{}, columns ...string) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Returning(value, columns...)) +} + +func (a appStaticConversationOnlineDo) Not(conds ...gen.Condition) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Not(conds...)) +} + +func (a appStaticConversationOnlineDo) Or(conds ...gen.Condition) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Or(conds...)) +} + +func (a appStaticConversationOnlineDo) Select(conds ...field.Expr) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Select(conds...)) +} + +func (a appStaticConversationOnlineDo) Where(conds ...gen.Condition) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Where(conds...)) +} + +func (a appStaticConversationOnlineDo) Order(conds ...field.Expr) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Order(conds...)) +} + +func (a appStaticConversationOnlineDo) Distinct(cols ...field.Expr) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Distinct(cols...)) +} + +func (a appStaticConversationOnlineDo) Omit(cols ...field.Expr) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Omit(cols...)) +} + +func (a appStaticConversationOnlineDo) Join(table schema.Tabler, on ...field.Expr) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Join(table, on...)) +} + +func (a appStaticConversationOnlineDo) LeftJoin(table schema.Tabler, on ...field.Expr) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.LeftJoin(table, on...)) +} + +func (a appStaticConversationOnlineDo) RightJoin(table schema.Tabler, on ...field.Expr) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.RightJoin(table, on...)) +} + +func (a appStaticConversationOnlineDo) Group(cols ...field.Expr) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Group(cols...)) +} + +func (a appStaticConversationOnlineDo) Having(conds ...gen.Condition) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Having(conds...)) +} + +func (a appStaticConversationOnlineDo) Limit(limit int) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Limit(limit)) +} + +func (a appStaticConversationOnlineDo) Offset(offset int) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Offset(offset)) +} + +func (a appStaticConversationOnlineDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Scopes(funcs...)) +} + +func (a appStaticConversationOnlineDo) Unscoped() IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Unscoped()) +} + +func (a appStaticConversationOnlineDo) Create(values ...*model.AppStaticConversationOnline) error { + if len(values) == 0 { + return nil + } + return a.DO.Create(values) +} + +func (a appStaticConversationOnlineDo) CreateInBatches(values []*model.AppStaticConversationOnline, batchSize int) error { + return a.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (a appStaticConversationOnlineDo) Save(values ...*model.AppStaticConversationOnline) error { + if len(values) == 0 { + return nil + } + return a.DO.Save(values) +} + +func (a appStaticConversationOnlineDo) First() (*model.AppStaticConversationOnline, error) { + if result, err := a.DO.First(); err != nil { + return nil, err + } else { + return result.(*model.AppStaticConversationOnline), nil + } +} + +func (a appStaticConversationOnlineDo) Take() (*model.AppStaticConversationOnline, error) { + if result, err := a.DO.Take(); err != nil { + return nil, err + } else { + return result.(*model.AppStaticConversationOnline), nil + } +} + +func (a appStaticConversationOnlineDo) Last() (*model.AppStaticConversationOnline, error) { + if result, err := a.DO.Last(); err != nil { + return nil, err + } else { + return result.(*model.AppStaticConversationOnline), nil + } +} + +func (a appStaticConversationOnlineDo) Find() ([]*model.AppStaticConversationOnline, error) { + result, err := a.DO.Find() + return result.([]*model.AppStaticConversationOnline), err +} + +func (a appStaticConversationOnlineDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AppStaticConversationOnline, err error) { + buf := make([]*model.AppStaticConversationOnline, 0, batchSize) + err = a.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (a appStaticConversationOnlineDo) FindInBatches(result *[]*model.AppStaticConversationOnline, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return a.DO.FindInBatches(result, batchSize, fc) +} + +func (a appStaticConversationOnlineDo) Attrs(attrs ...field.AssignExpr) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Attrs(attrs...)) +} + +func (a appStaticConversationOnlineDo) Assign(attrs ...field.AssignExpr) IAppStaticConversationOnlineDo { + return a.withDO(a.DO.Assign(attrs...)) +} + +func (a appStaticConversationOnlineDo) Joins(fields ...field.RelationField) IAppStaticConversationOnlineDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Joins(_f)) + } + return &a +} + +func (a appStaticConversationOnlineDo) Preload(fields ...field.RelationField) IAppStaticConversationOnlineDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Preload(_f)) + } + return &a +} + +func (a appStaticConversationOnlineDo) FirstOrInit() (*model.AppStaticConversationOnline, error) { + if result, err := a.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*model.AppStaticConversationOnline), nil + } +} + +func (a appStaticConversationOnlineDo) FirstOrCreate() (*model.AppStaticConversationOnline, error) { + if result, err := a.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*model.AppStaticConversationOnline), nil + } +} + +func (a appStaticConversationOnlineDo) FindByPage(offset int, limit int) (result []*model.AppStaticConversationOnline, count int64, err error) { + result, err = a.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = a.Offset(-1).Limit(-1).Count() + return +} + +func (a appStaticConversationOnlineDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = a.Count() + if err != nil { + return + } + + err = a.Offset(offset).Limit(limit).Scan(result) + return +} + +func (a appStaticConversationOnlineDo) Scan(result interface{}) (err error) { + return a.DO.Scan(result) +} + +func (a appStaticConversationOnlineDo) Delete(models ...*model.AppStaticConversationOnline) (result gen.ResultInfo, err error) { + return a.DO.Delete(models) +} + +func (a *appStaticConversationOnlineDo) withDO(do gen.Dao) *appStaticConversationOnlineDo { + a.DO = *do.(*gen.DO) + return a +} diff --git a/backend/domain/workflow/internal/repo/dal/query/chat_flow_role_config.gen.go b/backend/domain/workflow/internal/repo/dal/query/chat_flow_role_config.gen.go new file mode 100644 index 00000000..c25a229d --- /dev/null +++ b/backend/domain/workflow/internal/repo/dal/query/chat_flow_role_config.gen.go @@ -0,0 +1,440 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package query + +import ( + "context" + + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + + "gorm.io/gen" + "gorm.io/gen/field" + + "gorm.io/plugin/dbresolver" + + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/repo/dal/model" +) + +func newChatFlowRoleConfig(db *gorm.DB, opts ...gen.DOOption) chatFlowRoleConfig { + _chatFlowRoleConfig := chatFlowRoleConfig{} + + _chatFlowRoleConfig.chatFlowRoleConfigDo.UseDB(db, opts...) + _chatFlowRoleConfig.chatFlowRoleConfigDo.UseModel(&model.ChatFlowRoleConfig{}) + + tableName := _chatFlowRoleConfig.chatFlowRoleConfigDo.TableName() + _chatFlowRoleConfig.ALL = field.NewAsterisk(tableName) + _chatFlowRoleConfig.ID = field.NewInt64(tableName, "id") + _chatFlowRoleConfig.WorkflowID = field.NewInt64(tableName, "workflow_id") + _chatFlowRoleConfig.Name = field.NewString(tableName, "name") + _chatFlowRoleConfig.Description = field.NewString(tableName, "description") + _chatFlowRoleConfig.Version = field.NewString(tableName, "version") + _chatFlowRoleConfig.Avatar = field.NewString(tableName, "avatar") + _chatFlowRoleConfig.BackgroundImageInfo = field.NewString(tableName, "background_image_info") + _chatFlowRoleConfig.OnboardingInfo = field.NewString(tableName, "onboarding_info") + _chatFlowRoleConfig.SuggestReplyInfo = field.NewString(tableName, "suggest_reply_info") + _chatFlowRoleConfig.AudioConfig = field.NewString(tableName, "audio_config") + _chatFlowRoleConfig.UserInputConfig = field.NewString(tableName, "user_input_config") + _chatFlowRoleConfig.CreatorID = field.NewInt64(tableName, "creator_id") + _chatFlowRoleConfig.CreatedAt = field.NewInt64(tableName, "created_at") + _chatFlowRoleConfig.UpdatedAt = field.NewInt64(tableName, "updated_at") + _chatFlowRoleConfig.DeletedAt = field.NewField(tableName, "deleted_at") + _chatFlowRoleConfig.ConnectorID = field.NewInt64(tableName, "connector_id") + + _chatFlowRoleConfig.fillFieldMap() + + return _chatFlowRoleConfig +} + +type chatFlowRoleConfig struct { + chatFlowRoleConfigDo + + ALL field.Asterisk + ID field.Int64 // id + WorkflowID field.Int64 // workflow id + Name field.String // role name + Description field.String // role description + Version field.String // version + Avatar field.String // avatar uri + BackgroundImageInfo field.String // background image information, object structure + OnboardingInfo field.String // intro information, object structure + SuggestReplyInfo field.String // user suggestions, object structure + AudioConfig field.String // agent audio config, object structure + UserInputConfig field.String // user input config, object structure + CreatorID field.Int64 // creator id + CreatedAt field.Int64 // create time in millisecond + UpdatedAt field.Int64 // update time in millisecond + DeletedAt field.Field // delete time in millisecond + ConnectorID field.Int64 // connector id + + fieldMap map[string]field.Expr +} + +func (c chatFlowRoleConfig) Table(newTableName string) *chatFlowRoleConfig { + c.chatFlowRoleConfigDo.UseTable(newTableName) + return c.updateTableName(newTableName) +} + +func (c chatFlowRoleConfig) As(alias string) *chatFlowRoleConfig { + c.chatFlowRoleConfigDo.DO = *(c.chatFlowRoleConfigDo.As(alias).(*gen.DO)) + return c.updateTableName(alias) +} + +func (c *chatFlowRoleConfig) updateTableName(table string) *chatFlowRoleConfig { + c.ALL = field.NewAsterisk(table) + c.ID = field.NewInt64(table, "id") + c.WorkflowID = field.NewInt64(table, "workflow_id") + c.Name = field.NewString(table, "name") + c.Description = field.NewString(table, "description") + c.Version = field.NewString(table, "version") + c.Avatar = field.NewString(table, "avatar") + c.BackgroundImageInfo = field.NewString(table, "background_image_info") + c.OnboardingInfo = field.NewString(table, "onboarding_info") + c.SuggestReplyInfo = field.NewString(table, "suggest_reply_info") + c.AudioConfig = field.NewString(table, "audio_config") + c.UserInputConfig = field.NewString(table, "user_input_config") + c.CreatorID = field.NewInt64(table, "creator_id") + c.CreatedAt = field.NewInt64(table, "created_at") + c.UpdatedAt = field.NewInt64(table, "updated_at") + c.DeletedAt = field.NewField(table, "deleted_at") + c.ConnectorID = field.NewInt64(table, "connector_id") + + c.fillFieldMap() + + return c +} + +func (c *chatFlowRoleConfig) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := c.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (c *chatFlowRoleConfig) fillFieldMap() { + c.fieldMap = make(map[string]field.Expr, 16) + c.fieldMap["id"] = c.ID + c.fieldMap["workflow_id"] = c.WorkflowID + c.fieldMap["name"] = c.Name + c.fieldMap["description"] = c.Description + c.fieldMap["version"] = c.Version + c.fieldMap["avatar"] = c.Avatar + c.fieldMap["background_image_info"] = c.BackgroundImageInfo + c.fieldMap["onboarding_info"] = c.OnboardingInfo + c.fieldMap["suggest_reply_info"] = c.SuggestReplyInfo + c.fieldMap["audio_config"] = c.AudioConfig + c.fieldMap["user_input_config"] = c.UserInputConfig + c.fieldMap["creator_id"] = c.CreatorID + c.fieldMap["created_at"] = c.CreatedAt + c.fieldMap["updated_at"] = c.UpdatedAt + c.fieldMap["deleted_at"] = c.DeletedAt + c.fieldMap["connector_id"] = c.ConnectorID +} + +func (c chatFlowRoleConfig) clone(db *gorm.DB) chatFlowRoleConfig { + c.chatFlowRoleConfigDo.ReplaceConnPool(db.Statement.ConnPool) + return c +} + +func (c chatFlowRoleConfig) replaceDB(db *gorm.DB) chatFlowRoleConfig { + c.chatFlowRoleConfigDo.ReplaceDB(db) + return c +} + +type chatFlowRoleConfigDo struct{ gen.DO } + +type IChatFlowRoleConfigDo interface { + gen.SubQuery + Debug() IChatFlowRoleConfigDo + WithContext(ctx context.Context) IChatFlowRoleConfigDo + WithResult(fc func(tx gen.Dao)) gen.ResultInfo + ReplaceDB(db *gorm.DB) + ReadDB() IChatFlowRoleConfigDo + WriteDB() IChatFlowRoleConfigDo + As(alias string) gen.Dao + Session(config *gorm.Session) IChatFlowRoleConfigDo + Columns(cols ...field.Expr) gen.Columns + Clauses(conds ...clause.Expression) IChatFlowRoleConfigDo + Not(conds ...gen.Condition) IChatFlowRoleConfigDo + Or(conds ...gen.Condition) IChatFlowRoleConfigDo + Select(conds ...field.Expr) IChatFlowRoleConfigDo + Where(conds ...gen.Condition) IChatFlowRoleConfigDo + Order(conds ...field.Expr) IChatFlowRoleConfigDo + Distinct(cols ...field.Expr) IChatFlowRoleConfigDo + Omit(cols ...field.Expr) IChatFlowRoleConfigDo + Join(table schema.Tabler, on ...field.Expr) IChatFlowRoleConfigDo + LeftJoin(table schema.Tabler, on ...field.Expr) IChatFlowRoleConfigDo + RightJoin(table schema.Tabler, on ...field.Expr) IChatFlowRoleConfigDo + Group(cols ...field.Expr) IChatFlowRoleConfigDo + Having(conds ...gen.Condition) IChatFlowRoleConfigDo + Limit(limit int) IChatFlowRoleConfigDo + Offset(offset int) IChatFlowRoleConfigDo + Count() (count int64, err error) + Scopes(funcs ...func(gen.Dao) gen.Dao) IChatFlowRoleConfigDo + Unscoped() IChatFlowRoleConfigDo + Create(values ...*model.ChatFlowRoleConfig) error + CreateInBatches(values []*model.ChatFlowRoleConfig, batchSize int) error + Save(values ...*model.ChatFlowRoleConfig) error + First() (*model.ChatFlowRoleConfig, error) + Take() (*model.ChatFlowRoleConfig, error) + Last() (*model.ChatFlowRoleConfig, error) + Find() ([]*model.ChatFlowRoleConfig, error) + FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.ChatFlowRoleConfig, err error) + FindInBatches(result *[]*model.ChatFlowRoleConfig, batchSize int, fc func(tx gen.Dao, batch int) error) error + Pluck(column field.Expr, dest interface{}) error + Delete(...*model.ChatFlowRoleConfig) (info gen.ResultInfo, err error) + Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + Updates(value interface{}) (info gen.ResultInfo, err error) + UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error) + UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error) + UpdateColumns(value interface{}) (info gen.ResultInfo, err error) + UpdateFrom(q gen.SubQuery) gen.Dao + Attrs(attrs ...field.AssignExpr) IChatFlowRoleConfigDo + Assign(attrs ...field.AssignExpr) IChatFlowRoleConfigDo + Joins(fields ...field.RelationField) IChatFlowRoleConfigDo + Preload(fields ...field.RelationField) IChatFlowRoleConfigDo + FirstOrInit() (*model.ChatFlowRoleConfig, error) + FirstOrCreate() (*model.ChatFlowRoleConfig, error) + FindByPage(offset int, limit int) (result []*model.ChatFlowRoleConfig, count int64, err error) + ScanByPage(result interface{}, offset int, limit int) (count int64, err error) + Scan(result interface{}) (err error) + Returning(value interface{}, columns ...string) IChatFlowRoleConfigDo + UnderlyingDB() *gorm.DB + schema.Tabler +} + +func (c chatFlowRoleConfigDo) Debug() IChatFlowRoleConfigDo { + return c.withDO(c.DO.Debug()) +} + +func (c chatFlowRoleConfigDo) WithContext(ctx context.Context) IChatFlowRoleConfigDo { + return c.withDO(c.DO.WithContext(ctx)) +} + +func (c chatFlowRoleConfigDo) ReadDB() IChatFlowRoleConfigDo { + return c.Clauses(dbresolver.Read) +} + +func (c chatFlowRoleConfigDo) WriteDB() IChatFlowRoleConfigDo { + return c.Clauses(dbresolver.Write) +} + +func (c chatFlowRoleConfigDo) Session(config *gorm.Session) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Session(config)) +} + +func (c chatFlowRoleConfigDo) Clauses(conds ...clause.Expression) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Clauses(conds...)) +} + +func (c chatFlowRoleConfigDo) Returning(value interface{}, columns ...string) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Returning(value, columns...)) +} + +func (c chatFlowRoleConfigDo) Not(conds ...gen.Condition) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Not(conds...)) +} + +func (c chatFlowRoleConfigDo) Or(conds ...gen.Condition) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Or(conds...)) +} + +func (c chatFlowRoleConfigDo) Select(conds ...field.Expr) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Select(conds...)) +} + +func (c chatFlowRoleConfigDo) Where(conds ...gen.Condition) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Where(conds...)) +} + +func (c chatFlowRoleConfigDo) Order(conds ...field.Expr) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Order(conds...)) +} + +func (c chatFlowRoleConfigDo) Distinct(cols ...field.Expr) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Distinct(cols...)) +} + +func (c chatFlowRoleConfigDo) Omit(cols ...field.Expr) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Omit(cols...)) +} + +func (c chatFlowRoleConfigDo) Join(table schema.Tabler, on ...field.Expr) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Join(table, on...)) +} + +func (c chatFlowRoleConfigDo) LeftJoin(table schema.Tabler, on ...field.Expr) IChatFlowRoleConfigDo { + return c.withDO(c.DO.LeftJoin(table, on...)) +} + +func (c chatFlowRoleConfigDo) RightJoin(table schema.Tabler, on ...field.Expr) IChatFlowRoleConfigDo { + return c.withDO(c.DO.RightJoin(table, on...)) +} + +func (c chatFlowRoleConfigDo) Group(cols ...field.Expr) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Group(cols...)) +} + +func (c chatFlowRoleConfigDo) Having(conds ...gen.Condition) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Having(conds...)) +} + +func (c chatFlowRoleConfigDo) Limit(limit int) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Limit(limit)) +} + +func (c chatFlowRoleConfigDo) Offset(offset int) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Offset(offset)) +} + +func (c chatFlowRoleConfigDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Scopes(funcs...)) +} + +func (c chatFlowRoleConfigDo) Unscoped() IChatFlowRoleConfigDo { + return c.withDO(c.DO.Unscoped()) +} + +func (c chatFlowRoleConfigDo) Create(values ...*model.ChatFlowRoleConfig) error { + if len(values) == 0 { + return nil + } + return c.DO.Create(values) +} + +func (c chatFlowRoleConfigDo) CreateInBatches(values []*model.ChatFlowRoleConfig, batchSize int) error { + return c.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (c chatFlowRoleConfigDo) Save(values ...*model.ChatFlowRoleConfig) error { + if len(values) == 0 { + return nil + } + return c.DO.Save(values) +} + +func (c chatFlowRoleConfigDo) First() (*model.ChatFlowRoleConfig, error) { + if result, err := c.DO.First(); err != nil { + return nil, err + } else { + return result.(*model.ChatFlowRoleConfig), nil + } +} + +func (c chatFlowRoleConfigDo) Take() (*model.ChatFlowRoleConfig, error) { + if result, err := c.DO.Take(); err != nil { + return nil, err + } else { + return result.(*model.ChatFlowRoleConfig), nil + } +} + +func (c chatFlowRoleConfigDo) Last() (*model.ChatFlowRoleConfig, error) { + if result, err := c.DO.Last(); err != nil { + return nil, err + } else { + return result.(*model.ChatFlowRoleConfig), nil + } +} + +func (c chatFlowRoleConfigDo) Find() ([]*model.ChatFlowRoleConfig, error) { + result, err := c.DO.Find() + return result.([]*model.ChatFlowRoleConfig), err +} + +func (c chatFlowRoleConfigDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.ChatFlowRoleConfig, err error) { + buf := make([]*model.ChatFlowRoleConfig, 0, batchSize) + err = c.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (c chatFlowRoleConfigDo) FindInBatches(result *[]*model.ChatFlowRoleConfig, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return c.DO.FindInBatches(result, batchSize, fc) +} + +func (c chatFlowRoleConfigDo) Attrs(attrs ...field.AssignExpr) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Attrs(attrs...)) +} + +func (c chatFlowRoleConfigDo) Assign(attrs ...field.AssignExpr) IChatFlowRoleConfigDo { + return c.withDO(c.DO.Assign(attrs...)) +} + +func (c chatFlowRoleConfigDo) Joins(fields ...field.RelationField) IChatFlowRoleConfigDo { + for _, _f := range fields { + c = *c.withDO(c.DO.Joins(_f)) + } + return &c +} + +func (c chatFlowRoleConfigDo) Preload(fields ...field.RelationField) IChatFlowRoleConfigDo { + for _, _f := range fields { + c = *c.withDO(c.DO.Preload(_f)) + } + return &c +} + +func (c chatFlowRoleConfigDo) FirstOrInit() (*model.ChatFlowRoleConfig, error) { + if result, err := c.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*model.ChatFlowRoleConfig), nil + } +} + +func (c chatFlowRoleConfigDo) FirstOrCreate() (*model.ChatFlowRoleConfig, error) { + if result, err := c.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*model.ChatFlowRoleConfig), nil + } +} + +func (c chatFlowRoleConfigDo) FindByPage(offset int, limit int) (result []*model.ChatFlowRoleConfig, count int64, err error) { + result, err = c.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = c.Offset(-1).Limit(-1).Count() + return +} + +func (c chatFlowRoleConfigDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = c.Count() + if err != nil { + return + } + + err = c.Offset(offset).Limit(limit).Scan(result) + return +} + +func (c chatFlowRoleConfigDo) Scan(result interface{}) (err error) { + return c.DO.Scan(result) +} + +func (c chatFlowRoleConfigDo) Delete(models ...*model.ChatFlowRoleConfig) (result gen.ResultInfo, err error) { + return c.DO.Delete(models) +} + +func (c *chatFlowRoleConfigDo) withDO(do gen.Dao) *chatFlowRoleConfigDo { + c.DO = *do.(*gen.DO) + return c +} diff --git a/backend/domain/workflow/internal/repo/dal/query/gen.go b/backend/domain/workflow/internal/repo/dal/query/gen.go index 7be0b28e..c7689248 100644 --- a/backend/domain/workflow/internal/repo/dal/query/gen.go +++ b/backend/domain/workflow/internal/repo/dal/query/gen.go @@ -16,20 +16,34 @@ import ( ) var ( - Q = new(Query) - ConnectorWorkflowVersion *connectorWorkflowVersion - NodeExecution *nodeExecution - WorkflowDraft *workflowDraft - WorkflowExecution *workflowExecution - WorkflowMeta *workflowMeta - WorkflowReference *workflowReference - WorkflowSnapshot *workflowSnapshot - WorkflowVersion *workflowVersion + Q = new(Query) + ConnectorWorkflowVersion *connectorWorkflowVersion + AppConversationTemplateDraft *appConversationTemplateDraft + AppConversationTemplateOnline *appConversationTemplateOnline + AppDynamicConversationDraft *appDynamicConversationDraft + AppDynamicConversationOnline *appDynamicConversationOnline + AppStaticConversationDraft *appStaticConversationDraft + AppStaticConversationOnline *appStaticConversationOnline + ChatFlowRoleConfig *chatFlowRoleConfig + NodeExecution *nodeExecution + WorkflowDraft *workflowDraft + WorkflowExecution *workflowExecution + WorkflowMeta *workflowMeta + WorkflowReference *workflowReference + WorkflowSnapshot *workflowSnapshot + WorkflowVersion *workflowVersion ) func SetDefault(db *gorm.DB, opts ...gen.DOOption) { *Q = *Use(db, opts...) ConnectorWorkflowVersion = &Q.ConnectorWorkflowVersion + AppConversationTemplateDraft = &Q.AppConversationTemplateDraft + AppConversationTemplateOnline = &Q.AppConversationTemplateOnline + AppDynamicConversationDraft = &Q.AppDynamicConversationDraft + AppDynamicConversationOnline = &Q.AppDynamicConversationOnline + AppStaticConversationDraft = &Q.AppStaticConversationDraft + AppStaticConversationOnline = &Q.AppStaticConversationOnline + ChatFlowRoleConfig = &Q.ChatFlowRoleConfig NodeExecution = &Q.NodeExecution WorkflowDraft = &Q.WorkflowDraft WorkflowExecution = &Q.WorkflowExecution @@ -41,44 +55,65 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) { func Use(db *gorm.DB, opts ...gen.DOOption) *Query { return &Query{ - db: db, - ConnectorWorkflowVersion: newConnectorWorkflowVersion(db, opts...), - NodeExecution: newNodeExecution(db, opts...), - WorkflowDraft: newWorkflowDraft(db, opts...), - WorkflowExecution: newWorkflowExecution(db, opts...), - WorkflowMeta: newWorkflowMeta(db, opts...), - WorkflowReference: newWorkflowReference(db, opts...), - WorkflowSnapshot: newWorkflowSnapshot(db, opts...), - WorkflowVersion: newWorkflowVersion(db, opts...), + db: db, + ConnectorWorkflowVersion: newConnectorWorkflowVersion(db, opts...), + AppConversationTemplateDraft: newAppConversationTemplateDraft(db, opts...), + AppConversationTemplateOnline: newAppConversationTemplateOnline(db, opts...), + AppDynamicConversationDraft: newAppDynamicConversationDraft(db, opts...), + AppDynamicConversationOnline: newAppDynamicConversationOnline(db, opts...), + AppStaticConversationDraft: newAppStaticConversationDraft(db, opts...), + AppStaticConversationOnline: newAppStaticConversationOnline(db, opts...), + ChatFlowRoleConfig: newChatFlowRoleConfig(db, opts...), + NodeExecution: newNodeExecution(db, opts...), + WorkflowDraft: newWorkflowDraft(db, opts...), + WorkflowExecution: newWorkflowExecution(db, opts...), + WorkflowMeta: newWorkflowMeta(db, opts...), + WorkflowReference: newWorkflowReference(db, opts...), + WorkflowSnapshot: newWorkflowSnapshot(db, opts...), + WorkflowVersion: newWorkflowVersion(db, opts...), } } type Query struct { db *gorm.DB - ConnectorWorkflowVersion connectorWorkflowVersion - NodeExecution nodeExecution - WorkflowDraft workflowDraft - WorkflowExecution workflowExecution - WorkflowMeta workflowMeta - WorkflowReference workflowReference - WorkflowSnapshot workflowSnapshot - WorkflowVersion workflowVersion + ConnectorWorkflowVersion connectorWorkflowVersion + AppConversationTemplateDraft appConversationTemplateDraft + AppConversationTemplateOnline appConversationTemplateOnline + AppDynamicConversationDraft appDynamicConversationDraft + AppDynamicConversationOnline appDynamicConversationOnline + AppStaticConversationDraft appStaticConversationDraft + AppStaticConversationOnline appStaticConversationOnline + ChatFlowRoleConfig chatFlowRoleConfig + NodeExecution nodeExecution + WorkflowDraft workflowDraft + WorkflowExecution workflowExecution + WorkflowMeta workflowMeta + WorkflowReference workflowReference + WorkflowSnapshot workflowSnapshot + WorkflowVersion workflowVersion } func (q *Query) Available() bool { return q.db != nil } func (q *Query) clone(db *gorm.DB) *Query { return &Query{ - db: db, - ConnectorWorkflowVersion: q.ConnectorWorkflowVersion.clone(db), - NodeExecution: q.NodeExecution.clone(db), - WorkflowDraft: q.WorkflowDraft.clone(db), - WorkflowExecution: q.WorkflowExecution.clone(db), - WorkflowMeta: q.WorkflowMeta.clone(db), - WorkflowReference: q.WorkflowReference.clone(db), - WorkflowSnapshot: q.WorkflowSnapshot.clone(db), - WorkflowVersion: q.WorkflowVersion.clone(db), + db: db, + ConnectorWorkflowVersion: q.ConnectorWorkflowVersion.clone(db), + AppConversationTemplateDraft: q.AppConversationTemplateDraft.clone(db), + AppConversationTemplateOnline: q.AppConversationTemplateOnline.clone(db), + AppDynamicConversationDraft: q.AppDynamicConversationDraft.clone(db), + AppDynamicConversationOnline: q.AppDynamicConversationOnline.clone(db), + AppStaticConversationDraft: q.AppStaticConversationDraft.clone(db), + AppStaticConversationOnline: q.AppStaticConversationOnline.clone(db), + ChatFlowRoleConfig: q.ChatFlowRoleConfig.clone(db), + NodeExecution: q.NodeExecution.clone(db), + WorkflowDraft: q.WorkflowDraft.clone(db), + WorkflowExecution: q.WorkflowExecution.clone(db), + WorkflowMeta: q.WorkflowMeta.clone(db), + WorkflowReference: q.WorkflowReference.clone(db), + WorkflowSnapshot: q.WorkflowSnapshot.clone(db), + WorkflowVersion: q.WorkflowVersion.clone(db), } } @@ -92,39 +127,60 @@ func (q *Query) WriteDB() *Query { func (q *Query) ReplaceDB(db *gorm.DB) *Query { return &Query{ - db: db, - ConnectorWorkflowVersion: q.ConnectorWorkflowVersion.replaceDB(db), - NodeExecution: q.NodeExecution.replaceDB(db), - WorkflowDraft: q.WorkflowDraft.replaceDB(db), - WorkflowExecution: q.WorkflowExecution.replaceDB(db), - WorkflowMeta: q.WorkflowMeta.replaceDB(db), - WorkflowReference: q.WorkflowReference.replaceDB(db), - WorkflowSnapshot: q.WorkflowSnapshot.replaceDB(db), - WorkflowVersion: q.WorkflowVersion.replaceDB(db), + db: db, + ConnectorWorkflowVersion: q.ConnectorWorkflowVersion.replaceDB(db), + AppConversationTemplateDraft: q.AppConversationTemplateDraft.replaceDB(db), + AppConversationTemplateOnline: q.AppConversationTemplateOnline.replaceDB(db), + AppDynamicConversationDraft: q.AppDynamicConversationDraft.replaceDB(db), + AppDynamicConversationOnline: q.AppDynamicConversationOnline.replaceDB(db), + AppStaticConversationDraft: q.AppStaticConversationDraft.replaceDB(db), + AppStaticConversationOnline: q.AppStaticConversationOnline.replaceDB(db), + ChatFlowRoleConfig: q.ChatFlowRoleConfig.replaceDB(db), + NodeExecution: q.NodeExecution.replaceDB(db), + WorkflowDraft: q.WorkflowDraft.replaceDB(db), + WorkflowExecution: q.WorkflowExecution.replaceDB(db), + WorkflowMeta: q.WorkflowMeta.replaceDB(db), + WorkflowReference: q.WorkflowReference.replaceDB(db), + WorkflowSnapshot: q.WorkflowSnapshot.replaceDB(db), + WorkflowVersion: q.WorkflowVersion.replaceDB(db), } } type queryCtx struct { - ConnectorWorkflowVersion IConnectorWorkflowVersionDo - NodeExecution INodeExecutionDo - WorkflowDraft IWorkflowDraftDo - WorkflowExecution IWorkflowExecutionDo - WorkflowMeta IWorkflowMetaDo - WorkflowReference IWorkflowReferenceDo - WorkflowSnapshot IWorkflowSnapshotDo - WorkflowVersion IWorkflowVersionDo + ConnectorWorkflowVersion IConnectorWorkflowVersionDo + AppConversationTemplateDraft IAppConversationTemplateDraftDo + AppConversationTemplateOnline IAppConversationTemplateOnlineDo + AppDynamicConversationDraft IAppDynamicConversationDraftDo + AppDynamicConversationOnline IAppDynamicConversationOnlineDo + AppStaticConversationDraft IAppStaticConversationDraftDo + AppStaticConversationOnline IAppStaticConversationOnlineDo + ChatFlowRoleConfig IChatFlowRoleConfigDo + NodeExecution INodeExecutionDo + WorkflowDraft IWorkflowDraftDo + WorkflowExecution IWorkflowExecutionDo + WorkflowMeta IWorkflowMetaDo + WorkflowReference IWorkflowReferenceDo + WorkflowSnapshot IWorkflowSnapshotDo + WorkflowVersion IWorkflowVersionDo } func (q *Query) WithContext(ctx context.Context) *queryCtx { return &queryCtx{ - ConnectorWorkflowVersion: q.ConnectorWorkflowVersion.WithContext(ctx), - NodeExecution: q.NodeExecution.WithContext(ctx), - WorkflowDraft: q.WorkflowDraft.WithContext(ctx), - WorkflowExecution: q.WorkflowExecution.WithContext(ctx), - WorkflowMeta: q.WorkflowMeta.WithContext(ctx), - WorkflowReference: q.WorkflowReference.WithContext(ctx), - WorkflowSnapshot: q.WorkflowSnapshot.WithContext(ctx), - WorkflowVersion: q.WorkflowVersion.WithContext(ctx), + ConnectorWorkflowVersion: q.ConnectorWorkflowVersion.WithContext(ctx), + AppConversationTemplateDraft: q.AppConversationTemplateDraft.WithContext(ctx), + AppConversationTemplateOnline: q.AppConversationTemplateOnline.WithContext(ctx), + AppDynamicConversationDraft: q.AppDynamicConversationDraft.WithContext(ctx), + AppDynamicConversationOnline: q.AppDynamicConversationOnline.WithContext(ctx), + AppStaticConversationDraft: q.AppStaticConversationDraft.WithContext(ctx), + AppStaticConversationOnline: q.AppStaticConversationOnline.WithContext(ctx), + ChatFlowRoleConfig: q.ChatFlowRoleConfig.WithContext(ctx), + NodeExecution: q.NodeExecution.WithContext(ctx), + WorkflowDraft: q.WorkflowDraft.WithContext(ctx), + WorkflowExecution: q.WorkflowExecution.WithContext(ctx), + WorkflowMeta: q.WorkflowMeta.WithContext(ctx), + WorkflowReference: q.WorkflowReference.WithContext(ctx), + WorkflowSnapshot: q.WorkflowSnapshot.WithContext(ctx), + WorkflowVersion: q.WorkflowVersion.WithContext(ctx), } } diff --git a/backend/domain/workflow/internal/repo/interrupt_event_store.go b/backend/domain/workflow/internal/repo/interrupt_event_store.go index 31d855df..05298cde 100644 --- a/backend/domain/workflow/internal/repo/interrupt_event_store.go +++ b/backend/domain/workflow/internal/repo/interrupt_event_store.go @@ -22,6 +22,8 @@ import ( "fmt" "time" + "github.com/redis/go-redis/v9" + "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/cache" @@ -38,6 +40,7 @@ const ( interruptEventListKeyPattern = "interrupt_event_list:%d" interruptEventTTL = 24 * time.Hour // Example: expire after 24 hours previousResumedEventKeyPattern = "previous_resumed_event:%d" + ConvToEventExecFormat = "conv_relate_info:%d" ) // SaveInterruptEvents saves multiple interrupt events to the end of a Redis list. @@ -246,3 +249,33 @@ func (i *interruptEventStoreImpl) ListInterruptEvents(ctx context.Context, wfExe return events, nil } + +func (i *interruptEventStoreImpl) BindConvRelatedInfo(ctx context.Context, convID int64, info entity.ConvRelatedInfo) error { + data, err := sonic.Marshal(info) + if err != nil { + return err + } + result := i.redis.Set(ctx, fmt.Sprintf(ConvToEventExecFormat, convID), data, interruptEventTTL) + if result.Err() != nil { + return result.Err() + } + return nil +} + +func (i *interruptEventStoreImpl) GetConvRelatedInfo(ctx context.Context, convID int64) (*entity.ConvRelatedInfo, bool, func() error, error) { + data, err := i.redis.Get(ctx, fmt.Sprintf(ConvToEventExecFormat, convID)).Bytes() + if err != nil { + if errors.Is(err, redis.Nil) { + return nil, false, nil, nil + } + return nil, false, nil, err + } + rInfo := &entity.ConvRelatedInfo{} + err = sonic.UnmarshalString(string(data), rInfo) + if err != nil { + return nil, false, nil, err + } + return rInfo, true, func() error { + return i.redis.Del(ctx, fmt.Sprintf(ConvToEventExecFormat, convID)).Err() + }, nil +} diff --git a/backend/domain/workflow/internal/repo/repository.go b/backend/domain/workflow/internal/repo/repository.go index f9fc3c68..8b98c4db 100644 --- a/backend/domain/workflow/internal/repo/repository.go +++ b/backend/domain/workflow/internal/repo/repository.go @@ -70,10 +70,15 @@ type RepositoryImpl struct { workflow.ExecuteHistoryStore builtinModel cm.BaseChatModel workflow.WorkflowConfig + workflow.Suggester } func NewRepository(idgen idgen.IDGenerator, db *gorm.DB, redis cache.Cmdable, tos storage.Storage, - cpStore einoCompose.CheckPointStore, chatModel cm.BaseChatModel, workflowConfig workflow.WorkflowConfig) workflow.Repository { + cpStore einoCompose.CheckPointStore, chatModel cm.BaseChatModel, workflowConfig workflow.WorkflowConfig) (workflow.Repository, error) { + sg, err := NewSuggester(chatModel) + if err != nil { + return nil, err + } return &RepositoryImpl{ IDGenerator: idgen, query: query.Use(db), @@ -90,9 +95,12 @@ func NewRepository(idgen idgen.IDGenerator, db *gorm.DB, redis cache.Cmdable, to query: query.Use(db), redis: redis, }, + builtinModel: chatModel, + Suggester: sg, WorkflowConfig: workflowConfig, - } + }, nil + } func (r *RepositoryImpl) CreateMeta(ctx context.Context, meta *vo.Meta) (int64, error) { @@ -320,13 +328,16 @@ func (r *RepositoryImpl) CreateVersion(ctx context.Context, id int64, info *vo.V func (r *RepositoryImpl) CreateOrUpdateDraft(ctx context.Context, id int64, draft *vo.DraftInfo) error { d := &model.WorkflowDraft{ - ID: id, - Canvas: draft.Canvas, - InputParams: draft.InputParamsStr, - OutputParams: draft.OutputParamsStr, - Modified: draft.Modified, - TestRunSuccess: draft.TestRunSuccess, - CommitID: draft.CommitID, + ID: id, + Canvas: draft.Canvas, + InputParams: draft.InputParamsStr, + OutputParams: draft.OutputParamsStr, + CommitID: draft.CommitID, + } + + if draft.DraftMeta != nil { + d.Modified = draft.DraftMeta.Modified + d.TestRunSuccess = draft.DraftMeta.TestRunSuccess } if err := r.query.WorkflowDraft.WithContext(ctx).Save(d); err != nil { @@ -500,6 +511,10 @@ func (r *RepositoryImpl) UpdateMeta(ctx context.Context, id int64, metaUpdate *v expressions = append(expressions, r.query.WorkflowMeta.LatestVersion.Value(*metaUpdate.LatestPublishedVersion)) } + if metaUpdate.WorkflowMode != nil { + expressions = append(expressions, r.query.WorkflowMeta.Mode.Value(int32(*metaUpdate.WorkflowMode))) + } + if len(expressions) == 0 { return nil } @@ -551,10 +566,13 @@ func (r *RepositoryImpl) GetEntity(ctx context.Context, policy *vo.GetPolicy) (_ draftMeta = draft.DraftMeta commitID = draft.CommitID case workflowModel.FromSpecificVersion: - v, err := r.GetVersion(ctx, policy.ID, policy.Version) + v, existed, err := r.GetVersion(ctx, policy.ID, policy.Version) if err != nil { return nil, err } + if !existed { + return nil, vo.WrapError(errno.ErrWorkflowNotFound, fmt.Errorf("workflow version %s not found for ID %d: %w", policy.Version, policy.ID, err), errorx.KV("id", strconv.FormatInt(policy.ID, 10))) + } canvas = v.Canvas inputParams = v.InputParamsStr outputParams = v.OutputParamsStr @@ -604,7 +622,117 @@ func (r *RepositoryImpl) GetEntity(ctx context.Context, policy *vo.GetPolicy) (_ }, nil } -func (r *RepositoryImpl) GetVersion(ctx context.Context, id int64, version string) (_ *vo.VersionInfo, err error) { +func (r *RepositoryImpl) CreateChatFlowRoleConfig(ctx context.Context, chatFlowRole *entity.ChatFlowRole) (int64, error) { + id, err := r.GenID(ctx) + if err != nil { + return 0, vo.WrapError(errno.ErrIDGenError, err) + } + chatFlowRoleConfig := &model.ChatFlowRoleConfig{ + ID: id, + WorkflowID: chatFlowRole.WorkflowID, + Name: chatFlowRole.Name, + Description: chatFlowRole.Description, + Avatar: chatFlowRole.AvatarUri, + AudioConfig: chatFlowRole.AudioConfig, + BackgroundImageInfo: chatFlowRole.BackgroundImageInfo, + OnboardingInfo: chatFlowRole.OnboardingInfo, + SuggestReplyInfo: chatFlowRole.SuggestReplyInfo, + UserInputConfig: chatFlowRole.UserInputConfig, + CreatorID: chatFlowRole.CreatorID, + Version: chatFlowRole.Version, + } + + if err := r.query.ChatFlowRoleConfig.WithContext(ctx).Create(chatFlowRoleConfig); err != nil { + return 0, vo.WrapError(errno.ErrDatabaseError, fmt.Errorf("create chat flow role: %w", err)) + } + + return id, nil +} + +func (r *RepositoryImpl) UpdateChatFlowRoleConfig(ctx context.Context, workflowID int64, chatFlowRole *vo.ChatFlowRoleUpdate) error { + var expressions []field.AssignExpr + if chatFlowRole.Name != nil { + expressions = append(expressions, r.query.ChatFlowRoleConfig.Name.Value(*chatFlowRole.Name)) + } + if chatFlowRole.Description != nil { + expressions = append(expressions, r.query.ChatFlowRoleConfig.Description.Value(*chatFlowRole.Description)) + } + if chatFlowRole.AvatarUri != nil { + expressions = append(expressions, r.query.ChatFlowRoleConfig.Avatar.Value(*chatFlowRole.AvatarUri)) + } + if chatFlowRole.AudioConfig != nil { + expressions = append(expressions, r.query.ChatFlowRoleConfig.AudioConfig.Value(*chatFlowRole.AudioConfig)) + } + if chatFlowRole.BackgroundImageInfo != nil { + expressions = append(expressions, r.query.ChatFlowRoleConfig.BackgroundImageInfo.Value(*chatFlowRole.BackgroundImageInfo)) + } + if chatFlowRole.OnboardingInfo != nil { + expressions = append(expressions, r.query.ChatFlowRoleConfig.OnboardingInfo.Value(*chatFlowRole.OnboardingInfo)) + } + if chatFlowRole.SuggestReplyInfo != nil { + expressions = append(expressions, r.query.ChatFlowRoleConfig.SuggestReplyInfo.Value(*chatFlowRole.SuggestReplyInfo)) + } + if chatFlowRole.UserInputConfig != nil { + expressions = append(expressions, r.query.ChatFlowRoleConfig.UserInputConfig.Value(*chatFlowRole.UserInputConfig)) + } + + if len(expressions) == 0 { + return nil + } + + _, err := r.query.ChatFlowRoleConfig.WithContext(ctx).Where(r.query.ChatFlowRoleConfig.WorkflowID.Eq(workflowID)). + UpdateColumnSimple(expressions...) + if err != nil { + return vo.WrapError(errno.ErrDatabaseError, fmt.Errorf("update chat flow role: %w", err)) + } + + return nil +} + +func (r *RepositoryImpl) GetChatFlowRoleConfig(ctx context.Context, workflowID int64, version string) (_ *entity.ChatFlowRole, err error, isExist bool) { + defer func() { + if err != nil { + err = vo.WrapIfNeeded(errno.ErrDatabaseError, err) + } + }() + role := &model.ChatFlowRoleConfig{} + if version != "" { + role, err = r.query.ChatFlowRoleConfig.WithContext(ctx).Where(r.query.ChatFlowRoleConfig.WorkflowID.Eq(workflowID), r.query.ChatFlowRoleConfig.Version.Eq(version)).First() + } else { + role, err = r.query.ChatFlowRoleConfig.WithContext(ctx).Where(r.query.ChatFlowRoleConfig.WorkflowID.Eq(workflowID)).First() + } + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, err, false + } + return nil, fmt.Errorf("failed to get chat flow role for chatflowID %d: %w", workflowID, err), true + } + res := &entity.ChatFlowRole{ + ID: role.ID, + WorkflowID: role.WorkflowID, + Name: role.Name, + Description: role.Description, + AvatarUri: role.Avatar, + AudioConfig: role.AudioConfig, + BackgroundImageInfo: role.BackgroundImageInfo, + OnboardingInfo: role.OnboardingInfo, + SuggestReplyInfo: role.SuggestReplyInfo, + UserInputConfig: role.UserInputConfig, + CreatorID: role.CreatorID, + CreatedAt: time.UnixMilli(role.CreatedAt), + } + if role.UpdatedAt > 0 { + res.UpdatedAt = time.UnixMilli(role.UpdatedAt) + } + return res, err, true +} + +func (r *RepositoryImpl) DeleteChatFlowRoleConfig(ctx context.Context, id int64, workflowID int64) error { + _, err := r.query.ChatFlowRoleConfig.WithContext(ctx).Where(r.query.ChatFlowRoleConfig.ID.Eq(id), r.query.ChatFlowRoleConfig.WorkflowID.Eq(workflowID)).Delete() + return err +} + +func (r *RepositoryImpl) GetVersion(ctx context.Context, id int64, version string) (_ *vo.VersionInfo, existed bool, err error) { defer func() { if err != nil { err = vo.WrapIfNeeded(errno.ErrDatabaseError, err) @@ -616,9 +744,9 @@ func (r *RepositoryImpl) GetVersion(ctx context.Context, id int64, version strin First() if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, vo.WrapError(errno.ErrWorkflowNotFound, fmt.Errorf("workflow version %s not found for ID %d: %w", version, id, err), errorx.KV("id", strconv.FormatInt(id, 10))) + return nil, false, nil } - return nil, fmt.Errorf("failed to get workflow version %s for ID %d: %w", version, id, err) + return nil, false, fmt.Errorf("failed to get workflow version %s for ID %d: %w", version, id, err) } return &vo.VersionInfo{ @@ -634,7 +762,29 @@ func (r *RepositoryImpl) GetVersion(ctx context.Context, id int64, version strin OutputParamsStr: wfVersion.OutputParams, }, CommitID: wfVersion.CommitID, - }, nil + }, true, nil +} + +func (r *RepositoryImpl) GetVersionListByConnectorAndWorkflowID(ctx context.Context, connectorID, workflowID int64, limit int) (_ []string, err error) { + if limit <= 0 { + return nil, vo.WrapError(errno.ErrInvalidParameter, errors.New("limit must be greater than 0")) + } + + connectorWorkflowVersion := r.query.ConnectorWorkflowVersion + vl, err := connectorWorkflowVersion.WithContext(ctx). + Where(connectorWorkflowVersion.ConnectorID.Eq(connectorID), + connectorWorkflowVersion.WorkflowID.Eq(workflowID)). + Order(connectorWorkflowVersion.CreatedAt.Desc()). + Limit(limit). + Find() + if err != nil { + return nil, vo.WrapError(errno.ErrDatabaseError, err) + } + var versionList []string + for _, v := range vl { + versionList = append(versionList, v.Version) + } + return versionList, nil } func (r *RepositoryImpl) IsApplicationConnectorWorkflowVersion(ctx context.Context, connectorID, workflowID int64, version string) (b bool, err error) { @@ -767,6 +917,10 @@ func (r *RepositoryImpl) MGetDrafts(ctx context.Context, policy *vo.MGetPolicy) conditions = append(conditions, r.query.WorkflowMeta.AppID.Eq(0)) } + if q.Mode != nil { + conditions = append(conditions, r.query.WorkflowMeta.Mode.Eq(int32(*q.Mode))) + } + type combinedDraft struct { model.WorkflowDraft Name string `gorm:"column:name"` @@ -933,6 +1087,10 @@ func (r *RepositoryImpl) MGetLatestVersion(ctx context.Context, policy *vo.MGetP conditions = append(conditions, r.query.WorkflowMeta.AppID.Eq(0)) } + if q.Mode != nil { + conditions = append(conditions, r.query.WorkflowMeta.Mode.Eq(int32(*q.Mode))) + } + type combinedVersion struct { model.WorkflowMeta Version string `gorm:"column:version"` // release version @@ -1157,6 +1315,10 @@ func (r *RepositoryImpl) MGetMetas(ctx context.Context, query *vo.MetaQuery) ( conditions = append(conditions, r.query.WorkflowMeta.AppID.Eq(0)) } + if query.Mode != nil { + conditions = append(conditions, r.query.WorkflowMeta.Mode.Eq(int32(*query.Mode))) + } + var result []*model.WorkflowMeta workflowMetaDo := r.query.WorkflowMeta.WithContext(ctx).Debug().Where(conditions...) @@ -1520,6 +1682,7 @@ func (r *RepositoryImpl) CopyWorkflow(ctx context.Context, workflowID int64, pol IconURI: wfMeta.IconURI, Desc: wfMeta.Description, AppID: ternary.IFElse(wfMeta.AppID == 0, (*int64)(nil), ptr.Of(wfMeta.AppID)), + Mode: workflowModel.WorkflowMode(wfMeta.Mode), }, CanvasInfo: &vo.CanvasInfo{ Canvas: wfDraft.Canvas, @@ -1594,6 +1757,10 @@ func (r *RepositoryImpl) GetKnowledgeRecallChatModel() cm.BaseChatModel { return r.builtinModel } +func (r *RepositoryImpl) GetObjectUrl(ctx context.Context, objectKey string, opts ...storage.GetOptFn) (string, error) { + return r.tos.GetObjectUrl(ctx, objectKey, opts...) +} + func filterDisabledAPIParameters(parametersCfg []*workflow3.APIParameter, m map[string]any) map[string]any { result := make(map[string]any, len(m)) responseParameterMap := slices.ToMap(parametersCfg, func(p *workflow3.APIParameter) (string, *workflow3.APIParameter) { diff --git a/backend/domain/workflow/internal/repo/suggest.go b/backend/domain/workflow/internal/repo/suggest.go new file mode 100644 index 00000000..e0420bb0 --- /dev/null +++ b/backend/domain/workflow/internal/repo/suggest.go @@ -0,0 +1,104 @@ +/* + * 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 repo + +import ( + "context" + "regexp" + + "github.com/cloudwego/eino/components/model" + "github.com/cloudwego/eino/components/prompt" + "github.com/cloudwego/eino/compose" + einoCompose "github.com/cloudwego/eino/compose" + "github.com/cloudwego/eino/schema" + "github.com/coze-dev/coze-studio/backend/domain/workflow" + "github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo" + "github.com/coze-dev/coze-studio/backend/pkg/logs" + "github.com/coze-dev/coze-studio/backend/pkg/sonic" +) + +const SUGGESTION_PROMPT = ` +# Role +You are an AI assistant that quickly generates 3 relevant follow-up questions. + +# Task +Analyze the user's question and the assistant's answer to suggest 3 unique, concise follow-up questions. + +**IMPORTANT**: The assistant's answer can be very long. To be fast, focus only on the main ideas and topics in the answer. Do not analyze the full text in detail. + +### Persona +{{ suggest_persona }} + +## Output Format +- Return **only** a single JSON string array. +- Example: ["What is the history?", "How does it work?", "What are the benefits?"] +- The questions must be in the same language as the user's input. +` + +type suggesterV3 struct { + r einoCompose.Runnable[*vo.SuggestInfo, []string] +} +type state struct { + userMessage *schema.Message + answer *schema.Message +} + +var suggestRegexp = regexp.MustCompile(`\[(.*?)\]`) + +func NewSuggester(chatModel model.BaseChatModel) (workflow.Suggester, error) { + chain := einoCompose.NewChain[*vo.SuggestInfo, []string](einoCompose.WithGenLocalState(func(ctx context.Context) (s *state) { + return &state{} + })) + r, err := chain.AppendLambda(einoCompose.InvokableLambda(func(ctx context.Context, input *vo.SuggestInfo) (output map[string]any, err error) { + _ = compose.ProcessState(ctx, func(ctx context.Context, s *state) error { + s.userMessage = input.UserInput + s.answer = input.AnswerInput + return nil + }) + output = map[string]any{} + if input.PersonaInput != nil { + output["persona_input"] = *input.PersonaInput + } + return + })).AppendChatTemplate(prompt.FromMessages(schema.Jinja2, schema.SystemMessage(SUGGESTION_PROMPT))).AppendChatModel(chatModel, + compose.WithStatePreHandler(func(ctx context.Context, in []*schema.Message, state *state) ([]*schema.Message, error) { + return append(in, []*schema.Message{state.userMessage, state.answer}...), nil + })).AppendLambda(einoCompose.InvokableLambda(func(ctx context.Context, input *schema.Message) (output []string, err error) { + content := suggestRegexp.FindString(input.Content) + if len(content) == 0 { + return + } + suggests := make([]string, 0) + err = sonic.UnmarshalString(content, &suggests) + if err != nil { + logs.CtxErrorf(ctx, "Failed unmarshalling suggestions: %s", input.Content) + + } + return suggests, nil + })).Compile(context.Background()) + + if err != nil { + return nil, err + } + + return &suggesterV3{r: r}, nil + +} + +func (s *suggesterV3) Suggest(ctx context.Context, info *vo.SuggestInfo) ([]string, error) { + return s.r.Invoke(ctx, info) +} diff --git a/backend/domain/workflow/service/conversation_impl.go b/backend/domain/workflow/service/conversation_impl.go new file mode 100644 index 00000000..5a17f8e0 --- /dev/null +++ b/backend/domain/workflow/service/conversation_impl.go @@ -0,0 +1,490 @@ +/* + * 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 service + +import ( + "context" + "fmt" + "github.com/coze-dev/coze-studio/backend/api/model/conversation/common" + conventity "github.com/coze-dev/coze-studio/backend/domain/conversation/conversation/entity" + + workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + "github.com/coze-dev/coze-studio/backend/pkg/taskgroup" + + workflow2 "github.com/coze-dev/coze-studio/backend/api/model/workflow" + crossconversation "github.com/coze-dev/coze-studio/backend/crossdomain/contract/conversation" + "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/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/pkg/sonic" + "github.com/coze-dev/coze-studio/backend/types/consts" + "github.com/coze-dev/coze-studio/backend/types/errno" +) + +type conversationImpl struct { + repo workflow.Repository +} + +func (c *conversationImpl) CreateDraftConversationTemplate(ctx context.Context, template *vo.CreateConversationTemplateMeta) (int64, error) { + var ( + spaceID = template.SpaceID + appID = template.AppID + name = template.Name + userID = template.UserID + ) + + existed, err := c.IsDraftConversationNameExist(ctx, appID, userID, template.Name) + if err != nil { + return 0, err + } + if existed { + return 0, vo.WrapError(errno.ErrConversationNameIsDuplicated, fmt.Errorf("conversation name %s exists", name), errorx.KV("name", name)) + } + + return c.repo.CreateDraftConversationTemplate(ctx, &vo.CreateConversationTemplateMeta{ + SpaceID: spaceID, + AppID: appID, + Name: name, + UserID: userID, + }) + +} + +func (c *conversationImpl) IsDraftConversationNameExist(ctx context.Context, appID int64, userID int64, name string) (bool, error) { + _, existed, err := c.repo.GetDynamicConversationByName(ctx, vo.Draft, appID, consts.CozeConnectorID, userID, name) + if err != nil { + return false, err + } + if existed { + return true, nil + } + + _, existed, err = c.repo.GetConversationTemplate(ctx, vo.Draft, vo.GetConversationTemplatePolicy{AppID: ptr.Of(appID), Name: ptr.Of(name)}) + if err != nil { + return false, err + } + + if existed { + return true, nil + } + + return false, nil + +} + +func (c *conversationImpl) UpdateDraftConversationTemplateName(ctx context.Context, appID int64, userID int64, templateID int64, modifiedName string) error { + template, existed, err := c.repo.GetConversationTemplate(ctx, vo.Draft, vo.GetConversationTemplatePolicy{TemplateID: ptr.Of(templateID)}) + if err != nil { + return err + } + + if existed && template.Name == modifiedName { + return nil + } + + existed, err = c.IsDraftConversationNameExist(ctx, appID, userID, modifiedName) + if err != nil { + return err + } + if existed { + return vo.WrapError(errno.ErrConversationNameIsDuplicated, fmt.Errorf("conversation name %s exists", modifiedName), errorx.KV("name", modifiedName)) + } + + wfs, err := c.findReplaceWorkflowByConversationName(ctx, appID, template.Name) + if err != nil { + return err + } + + err = c.replaceWorkflowsConversationName(ctx, wfs, slices.ToMap(wfs, func(e *entity.Workflow) (int64, string) { + return e.ID, modifiedName + })) + + if err != nil { + return err + } + + return c.repo.UpdateDraftConversationTemplateName(ctx, templateID, modifiedName) + +} + +func (c *conversationImpl) CheckWorkflowsToReplace(ctx context.Context, appID int64, templateID int64) ([]*entity.Workflow, error) { + template, existed, err := c.repo.GetConversationTemplate(ctx, vo.Draft, vo.GetConversationTemplatePolicy{TemplateID: ptr.Of(templateID)}) + if err != nil { + return nil, err + } + + if existed { + return c.findReplaceWorkflowByConversationName(ctx, appID, template.Name) + } + + return []*entity.Workflow{}, nil +} + +func (c *conversationImpl) DeleteDraftConversationTemplate(ctx context.Context, templateID int64, wfID2ConversationName map[int64]string) (int64, error) { + + if len(wfID2ConversationName) == 0 { + return c.repo.DeleteDraftConversationTemplate(ctx, templateID) + } + workflowIDs := make([]int64, 0) + for id := range wfID2ConversationName { + workflowIDs = append(workflowIDs, id) + } + + wfs, _, err := c.repo.MGetDrafts(ctx, &vo.MGetPolicy{ + MetaQuery: vo.MetaQuery{ + IDs: workflowIDs, + }, + QType: workflowModel.FromDraft, + }) + if err != nil { + return 0, err + } + + err = c.replaceWorkflowsConversationName(ctx, wfs, wfID2ConversationName) + if err != nil { + return 0, err + } + return c.repo.DeleteDraftConversationTemplate(ctx, templateID) +} + +func (c *conversationImpl) ListConversationTemplate(ctx context.Context, env vo.Env, policy *vo.ListConversationTemplatePolicy) ([]*entity.ConversationTemplate, error) { + var ( + err error + templates []*entity.ConversationTemplate + appID = policy.AppID + ) + templates, err = c.repo.ListConversationTemplate(ctx, env, &vo.ListConversationTemplatePolicy{ + AppID: appID, + Page: policy.Page, + NameLike: policy.NameLike, + Version: policy.Version, + }) + if err != nil { + return nil, err + } + return templates, nil + +} + +func (c *conversationImpl) MGetStaticConversation(ctx context.Context, env vo.Env, userID, connectorID int64, templateIDs []int64) ([]*entity.StaticConversation, error) { + return c.repo.MGetStaticConversation(ctx, env, userID, connectorID, templateIDs) +} + +func (c *conversationImpl) ListDynamicConversation(ctx context.Context, env vo.Env, policy *vo.ListConversationPolicy) ([]*entity.DynamicConversation, error) { + return c.repo.ListDynamicConversation(ctx, env, policy) +} + +func (c *conversationImpl) ReleaseConversationTemplate(ctx context.Context, appID int64, version string) error { + templates, err := c.repo.ListConversationTemplate(ctx, vo.Draft, &vo.ListConversationTemplatePolicy{ + AppID: appID, + }) + if err != nil { + return err + } + if len(templates) == 0 { + return nil + } + + return c.repo.BatchCreateOnlineConversationTemplate(ctx, templates, version) +} + +func (c *conversationImpl) InitApplicationDefaultConversationTemplate(ctx context.Context, spaceID, appID int64, userID int64) error { + _, err := c.repo.CreateDraftConversationTemplate(ctx, &vo.CreateConversationTemplateMeta{ + AppID: appID, + SpaceID: spaceID, + UserID: userID, + Name: "Default", + }) + if err != nil { + return err + } + + return nil +} + +func (c *conversationImpl) findReplaceWorkflowByConversationName(ctx context.Context, appID int64, name string) ([]*entity.Workflow, error) { + + wfs, _, err := c.repo.MGetDrafts(ctx, &vo.MGetPolicy{ + QType: workflowModel.FromDraft, + MetaQuery: vo.MetaQuery{ + AppID: ptr.Of(appID), + Mode: ptr.Of(workflow2.WorkflowMode_ChatFlow), + }, + }) + if err != nil { + return nil, err + } + + shouldReplacedWorkflow := func(nodes []*vo.Node) (bool, error) { + var startNode *vo.Node + for _, node := range nodes { + if node.Type == entity.NodeTypeEntry.IDStr() { + startNode = node + } + } + if startNode == nil { + return false, fmt.Errorf("start node not found for block type") + } + for _, vAny := range startNode.Data.Outputs { + v, err := vo.ParseVariable(vAny) + if err != nil { + return false, err + } + if v.Name == "CONVERSATION_NAME" && v.DefaultValue == name { + return true, nil + } + } + return false, nil + + } + + shouldReplacedWorkflows := make([]*entity.Workflow, 0) + for idx := range wfs { + wf := wfs[idx] + canvas := &vo.Canvas{} + err = sonic.UnmarshalString(wf.Canvas, canvas) + if err != nil { + return nil, err + } + + ok, err := shouldReplacedWorkflow(canvas.Nodes) + if err != nil { + return nil, err + } + if ok { + shouldReplacedWorkflows = append(shouldReplacedWorkflows, wf) + } + + } + + return shouldReplacedWorkflows, nil + +} + +func (c *conversationImpl) replaceWorkflowsConversationName(ctx context.Context, wfs []*entity.Workflow, workflowID2ConversionName map[int64]string) error { + + replaceConversionName := func(nodes []*vo.Node, conversionName string) error { + var startNode *vo.Node + for _, node := range nodes { + if node.Type == entity.NodeTypeEntry.IDStr() { + startNode = node + } + } + if startNode == nil { + return fmt.Errorf("start node not found for block type") + } + for idx, vAny := range startNode.Data.Outputs { + v, err := vo.ParseVariable(vAny) + if err != nil { + return err + } + if v.Name == "CONVERSATION_NAME" { + v.DefaultValue = conversionName + } + startNode.Data.Outputs[idx] = v + + } + return nil + } + tg := taskgroup.NewTaskGroup(ctx, len(wfs)) + for _, wf := range wfs { + wfEntity := wf + tg.Go(func() error { + canvas := &vo.Canvas{} + err := sonic.UnmarshalString(wfEntity.Canvas, canvas) + if err != nil { + return err + } + + conversationName := workflowID2ConversionName[wfEntity.ID] + err = replaceConversionName(canvas.Nodes, conversationName) + if err != nil { + return err + } + + replaceCanvas, err := sonic.MarshalString(canvas) + if err != nil { + return err + } + + err = c.repo.CreateOrUpdateDraft(ctx, wfEntity.ID, &vo.DraftInfo{ + DraftMeta: &vo.DraftMeta{ + TestRunSuccess: false, + Modified: true, + }, + Canvas: replaceCanvas, + }) + + if err != nil { + return err + } + return nil + + }) + } + err := tg.Wait() + if err != nil { + return err + } + return nil +} + +func (c *conversationImpl) DeleteDynamicConversation(ctx context.Context, env vo.Env, templateID int64) (int64, error) { + return c.repo.DeleteDynamicConversation(ctx, env, templateID) +} + +func (c *conversationImpl) GetOrCreateConversation(ctx context.Context, env vo.Env, appID, connectorID, userID int64, conversationName string) (int64, int64, error) { + t, existed, err := c.repo.GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{ + AppID: ptr.Of(appID), + Name: ptr.Of(conversationName), + }) + if err != nil { + return 0, 0, err + } + + conversationIDGenerator := workflow.ConversationIDGenerator(func(ctx context.Context, appID int64, userID, connectorID int64) (*conventity.Conversation, error) { + return crossconversation.DefaultSVC().CreateConversation(ctx, &conventity.CreateMeta{ + AgentID: appID, + UserID: userID, + ConnectorID: connectorID, + Scene: common.Scene_SceneWorkflow, + }) + }) + + if existed { + conversationID, sectionID, _, err := c.repo.GetOrCreateStaticConversation(ctx, env, conversationIDGenerator, &vo.CreateStaticConversation{ + AppID: appID, + ConnectorID: connectorID, + UserID: userID, + TemplateID: t.TemplateID, + }) + if err != nil { + return 0, 0, err + } + return conversationID, sectionID, nil + } + + conversationID, sectionID, _, err := c.repo.GetOrCreateDynamicConversation(ctx, env, conversationIDGenerator, &vo.CreateDynamicConversation{ + AppID: appID, + ConnectorID: connectorID, + UserID: userID, + Name: conversationName, + }) + if err != nil { + return 0, 0, err + } + return conversationID, sectionID, nil + +} + +func (c *conversationImpl) UpdateConversation(ctx context.Context, env vo.Env, appID, connectorID, userID int64, conversationName string) (int64, error) { + t, existed, err := c.repo.GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{ + AppID: ptr.Of(appID), + Name: ptr.Of(conversationName), + }) + + if err != nil { + return 0, err + } + + if existed { + conv, err := crossconversation.DefaultSVC().CreateConversation(ctx, &conventity.CreateMeta{ + AgentID: appID, + UserID: userID, + ConnectorID: connectorID, + Scene: common.Scene_SceneWorkflow, + }) + if err != nil { + return 0, err + } + if conv == nil { + return 0, fmt.Errorf("create conversation failed") + } + err = c.repo.UpdateStaticConversation(ctx, env, t.TemplateID, connectorID, userID, conv.ID) + if err != nil { + return 0, err + } + return conv.ID, nil + } + + dy, existed, err := c.repo.GetDynamicConversationByName(ctx, env, appID, connectorID, userID, conversationName) + if err != nil { + return 0, err + } + + if !existed { + return 0, fmt.Errorf("conversation name %v not found", conversationName) + } + + conv, err := crossconversation.DefaultSVC().CreateConversation(ctx, &conventity.CreateMeta{ + AgentID: appID, + UserID: userID, + ConnectorID: connectorID, + Scene: common.Scene_SceneWorkflow, + }) + if err != nil { + return 0, err + } + if conv == nil { + return 0, fmt.Errorf("create conversation failed") + } + err = c.repo.UpdateDynamicConversation(ctx, env, dy.ConversationID, conv.ID) + if err != nil { + return 0, err + } + return conv.ID, nil + +} + +func (c *conversationImpl) GetTemplateByName(ctx context.Context, env vo.Env, appID int64, templateName string) (*entity.ConversationTemplate, bool, error) { + return c.repo.GetConversationTemplate(ctx, env, vo.GetConversationTemplatePolicy{ + AppID: ptr.Of(appID), + Name: ptr.Of(templateName), + }) +} + +func (c *conversationImpl) GetDynamicConversationByName(ctx context.Context, env vo.Env, appID, connectorID, userID int64, name string) (*entity.DynamicConversation, bool, error) { + return c.repo.GetDynamicConversationByName(ctx, env, appID, connectorID, userID, name) +} + +func (c *conversationImpl) GetConversationNameByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (string, bool, error) { + sc, existed, err := c.repo.GetStaticConversationByID(ctx, env, appID, connectorID, conversationID) + if err != nil { + return "", false, err + } + if existed { + return sc, true, nil + } + + dc, existed, err := c.repo.GetDynamicConversationByID(ctx, env, appID, connectorID, conversationID) + if err != nil { + return "", false, err + } + + if existed { + return dc.Name, true, nil + } + + return "", false, nil +} + +func (c *conversationImpl) Suggest(ctx context.Context, input *vo.SuggestInfo) ([]string, error) { + return c.repo.Suggest(ctx, input) +} diff --git a/backend/domain/workflow/service/executable_impl.go b/backend/domain/workflow/service/executable_impl.go index a10bf1a8..d0734d40 100644 --- a/backend/domain/workflow/service/executable_impl.go +++ b/backend/domain/workflow/service/executable_impl.go @@ -26,6 +26,8 @@ import ( "github.com/cloudwego/eino/schema" workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + workflowapimodel "github.com/coze-dev/coze-studio/backend/api/model/workflow" + crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" "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" @@ -62,6 +64,8 @@ func (i *impl) SyncExecute(ctx context.Context, config workflowModel.ExecuteConf return nil, "", err } + config.WorkflowMode = wfEntity.Mode + isApplicationWorkflow := wfEntity.AppID != nil if isApplicationWorkflow && config.Mode == workflowModel.ExecuteModeRelease { err = i.checkApplicationWorkflowReleaseVersion(ctx, *wfEntity.AppID, config.ConnectorID, config.ID, config.Version) @@ -207,6 +211,8 @@ func (i *impl) AsyncExecute(ctx context.Context, config workflowModel.ExecuteCon return 0, err } + config.WorkflowMode = wfEntity.Mode + isApplicationWorkflow := wfEntity.AppID != nil if isApplicationWorkflow && config.Mode == workflowModel.ExecuteModeRelease { err = i.checkApplicationWorkflowReleaseVersion(ctx, *wfEntity.AppID, config.ConnectorID, config.ID, config.Version) @@ -292,6 +298,8 @@ func (i *impl) AsyncExecuteNode(ctx context.Context, nodeID string, config workf return 0, err } + config.WorkflowMode = wfEntity.Mode + isApplicationWorkflow := wfEntity.AppID != nil if isApplicationWorkflow && config.Mode == workflowModel.ExecuteModeRelease { err = i.checkApplicationWorkflowReleaseVersion(ctx, *wfEntity.AppID, config.ConnectorID, config.ID, config.Version) @@ -300,6 +308,30 @@ func (i *impl) AsyncExecuteNode(ctx context.Context, nodeID string, config workf } } + historyRounds := int64(0) + if config.WorkflowMode == workflowapimodel.WorkflowMode_ChatFlow { + + historyRounds, err = getHistoryRoundsFromNode(ctx, wfEntity, nodeID, i.repo) + if err != nil { + return 0, err + } + } + + if historyRounds > 0 { + messages, scMessages, err := i.prefetchChatHistory(ctx, config, historyRounds) + if err != nil { + logs.CtxErrorf(ctx, "failed to prefetch chat history: %v", err) + } + + if len(messages) > 0 { + config.ConversationHistory = messages + } + + if len(scMessages) > 0 { + config.ConversationHistorySchemaMessages = scMessages + } + + } c := &vo.Canvas{} if err = sonic.UnmarshalString(wfEntity.Canvas, c); err != nil { return 0, fmt.Errorf("failed to unmarshal canvas: %w", err) @@ -375,6 +407,8 @@ func (i *impl) StreamExecute(ctx context.Context, config workflowModel.ExecuteCo return nil, err } + config.WorkflowMode = wfEntity.Mode + isApplicationWorkflow := wfEntity.AppID != nil if isApplicationWorkflow && config.Mode == workflowModel.ExecuteModeRelease { err = i.checkApplicationWorkflowReleaseVersion(ctx, *wfEntity.AppID, config.ConnectorID, config.ID, config.Version) @@ -383,6 +417,29 @@ func (i *impl) StreamExecute(ctx context.Context, config workflowModel.ExecuteCo } } + historyRounds := int64(0) + if config.WorkflowMode == workflowapimodel.WorkflowMode_ChatFlow { + historyRounds, err = i.calculateMaxChatHistoryRounds(ctx, wfEntity, i.repo) + if err != nil { + return nil, err + } + } + + if historyRounds > 0 { + messages, scMessages, err := i.prefetchChatHistory(ctx, config, historyRounds) + if err != nil { + logs.CtxErrorf(ctx, "failed to prefetch chat history: %v", err) + } + + if len(messages) > 0 { + config.ConversationHistory = messages + } + + if len(scMessages) > 0 { + config.ConversationHistorySchemaMessages = scMessages + } + + } c := &vo.Canvas{} if err = sonic.UnmarshalString(wfEntity.Canvas, c); err != nil { return nil, fmt.Errorf("failed to unmarshal canvas: %w", err) @@ -718,6 +775,7 @@ func (i *impl) AsyncResume(ctx context.Context, req *entity.ResumeRequest, confi config.AppID = wfExe.AppID config.AgentID = wfExe.AgentID config.CommitID = wfExe.CommitID + config.WorkflowMode = wfEntity.Mode if config.ConnectorID == 0 { config.ConnectorID = wfExe.ConnectorID @@ -859,6 +917,7 @@ func (i *impl) StreamResume(ctx context.Context, req *entity.ResumeRequest, conf config.AppID = wfExe.AppID config.AgentID = wfExe.AgentID config.CommitID = wfExe.CommitID + config.WorkflowMode = wfEntity.Mode if config.ConnectorID == 0 { config.ConnectorID = wfExe.ConnectorID @@ -937,3 +996,73 @@ func (i *impl) checkApplicationWorkflowReleaseVersion(ctx context.Context, appID return nil } + +const maxHistoryRounds int64 = 30 + +func (i *impl) calculateMaxChatHistoryRounds(ctx context.Context, wfEntity *entity.Workflow, repo workflow.Repository) (int64, error) { + if wfEntity == nil { + return 0, nil + } + + maxRounds, err := getMaxHistoryRoundsRecursively(ctx, wfEntity, repo) + if err != nil { + return 0, err + } + return min(maxRounds, maxHistoryRounds), nil +} + +func (i *impl) prefetchChatHistory(ctx context.Context, config workflowModel.ExecuteConfig, historyRounds int64) ([]*crossmessage.WfMessage, []*schema.Message, error) { + convID := config.ConversationID + agentID := config.AgentID + appID := config.AppID + userID := config.Operator + sectionID := config.SectionID + if sectionID == nil { + logs.CtxWarnf(ctx, "SectionID is nil, skipping chat history") + return nil, nil, nil + } + + if convID == nil || *convID == 0 { + logs.CtxWarnf(ctx, "ConversationID is 0 or nil, skipping chat history") + return nil, nil, nil + } + + var resolvedAppID int64 + if appID != nil { + resolvedAppID = *appID + } else if agentID != nil { + resolvedAppID = *agentID + } else { + logs.CtxWarnf(ctx, "AppID and AgentID are both nil, skipping chat history") + return nil, nil, nil + } + + runIdsReq := &crossmessage.GetLatestRunIDsRequest{ + ConversationID: *convID, + AppID: resolvedAppID, + UserID: userID, + Rounds: historyRounds + 1, + SectionID: *sectionID, + } + + runIds, err := crossmessage.DefaultSVC().GetLatestRunIDs(ctx, runIdsReq) + if err != nil { + logs.CtxErrorf(ctx, "failed to get latest run ids: %v", err) + return nil, nil, nil + } + if len(runIds) <= 1 { + return []*crossmessage.WfMessage{}, []*schema.Message{}, nil + } + runIds = runIds[1:] + + response, err := crossmessage.DefaultSVC().GetMessagesByRunIDs(ctx, &crossmessage.GetMessagesByRunIDsRequest{ + ConversationID: *convID, + RunIDs: runIds, + }) + if err != nil { + logs.CtxErrorf(ctx, "failed to get messages by run ids: %v", err) + return nil, nil, nil + } + + return response.Messages, response.SchemaMessages, nil +} diff --git a/backend/domain/workflow/service/service_impl.go b/backend/domain/workflow/service/service_impl.go index d1ccc703..c8adf50d 100644 --- a/backend/domain/workflow/service/service_impl.go +++ b/backend/domain/workflow/service/service_impl.go @@ -20,13 +20,14 @@ import ( "context" "errors" "fmt" - "strconv" "github.com/spf13/cast" "golang.org/x/exp/maps" "golang.org/x/sync/errgroup" "gorm.io/gorm" + "strconv" + einoCompose "github.com/cloudwego/eino/compose" "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/plugin" workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" @@ -38,6 +39,7 @@ import ( "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/canvas/adaptor" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/canvas/convert" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes/intentdetector" + "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes/knowledge" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/nodes/llm" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/repo" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/schema" @@ -45,6 +47,7 @@ import ( "github.com/coze-dev/coze-studio/backend/infra/contract/chatmodel" "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/ptr" "github.com/coze-dev/coze-studio/backend/pkg/lang/slices" "github.com/coze-dev/coze-studio/backend/pkg/logs" @@ -56,6 +59,7 @@ type impl struct { repo workflow.Repository *asToolImpl *executableImpl + *conversationImpl } func NewWorkflowService(repo workflow.Repository) workflow.Service { @@ -67,12 +71,14 @@ func NewWorkflowService(repo workflow.Repository) workflow.Service { executableImpl: &executableImpl{ repo: repo, }, + conversationImpl: &conversationImpl{repo: repo}, } } func NewWorkflowRepository(idgen idgen.IDGenerator, db *gorm.DB, redis cache.Cmdable, tos storage.Storage, - cpStore einoCompose.CheckPointStore, chatModel chatmodel.BaseChatModel, workflowConfig workflow.WorkflowConfig) workflow.Repository { - return repo.NewRepository(idgen, db, redis, tos, cpStore, chatModel, workflowConfig) + cpStore einoCompose.CheckPointStore, chatModel chatmodel.BaseChatModel, cfg workflow.WorkflowConfig) (workflow.Repository, error) { + return repo.NewRepository(idgen, db, redis, tos, cpStore, chatModel, cfg) + } func (i *impl) ListNodeMeta(_ context.Context, nodeTypes map[entity.NodeType]bool) (map[string][]*entity.NodeTypeMeta, []entity.Category, error) { @@ -440,10 +446,13 @@ func (i *impl) collectNodePropertyMap(ctx context.Context, canvas *vo.Canvas) (m var canvasSchema string if n.Data.Inputs.WorkflowVersion != "" { - versionInfo, err := i.repo.GetVersion(ctx, wid, n.Data.Inputs.WorkflowVersion) + versionInfo, existed, err := i.repo.GetVersion(ctx, wid, n.Data.Inputs.WorkflowVersion) if err != nil { return nil, err } + if !existed { + return nil, vo.WrapError(errno.ErrWorkflowNotFound, fmt.Errorf("workflow version %s not found for ID %d: %w", n.Data.Inputs.WorkflowVersion, wid, err), errorx.KV("id", strconv.FormatInt(wid, 10))) + } canvasSchema = versionInfo.Canvas } else { draftInfo, err := i.repo.DraftV2(ctx, wid, "") @@ -522,6 +531,9 @@ func isEnableChatHistory(s *schema.NodeSchema) bool { case entity.NodeTypeIntentDetector: llmParam := s.Configs.(*intentdetector.Config).LLMParams return llmParam.EnableChatHistory + case entity.NodeTypeKnowledgeRetriever: + chatParam := s.Configs.(*knowledge.RetrieveConfig).ChatHistorySetting + return chatParam != nil && chatParam.EnableChatHistory default: return false } @@ -541,6 +553,103 @@ func isRefGlobalVariable(s *schema.NodeSchema) bool { return false } +func (i *impl) CreateChatFlowRole(ctx context.Context, role *vo.ChatFlowRoleCreate) (int64, error) { + id, err := i.repo.CreateChatFlowRoleConfig(ctx, &entity.ChatFlowRole{ + Name: role.Name, + Description: role.Description, + WorkflowID: role.WorkflowID, + CreatorID: role.CreatorID, + AudioConfig: role.AudioConfig, + UserInputConfig: role.UserInputConfig, + AvatarUri: role.AvatarUri, + BackgroundImageInfo: role.BackgroundImageInfo, + OnboardingInfo: role.OnboardingInfo, + SuggestReplyInfo: role.SuggestReplyInfo, + }) + + if err != nil { + return 0, err + } + + return id, nil +} + +func (i *impl) UpdateChatFlowRole(ctx context.Context, workflowID int64, role *vo.ChatFlowRoleUpdate) error { + err := i.repo.UpdateChatFlowRoleConfig(ctx, workflowID, role) + + if err != nil { + return err + } + + return nil +} + +func (i *impl) GetChatFlowRole(ctx context.Context, workflowID int64, version string) (*entity.ChatFlowRole, error) { + role, err, isExist := i.repo.GetChatFlowRoleConfig(ctx, workflowID, version) + if !isExist { + logs.CtxWarnf(ctx, "chat flow role not exist, workflow id %v, version %v", workflowID, version) + // Return (nil, nil) on 'NotExist' to align with the production behavior, + // where the GET API may be called before the CREATE API during chatflow creation. + return nil, nil + } + if err != nil { + return nil, err + } + return role, nil +} + +func (i *impl) GetWorkflowVersionsByConnector(ctx context.Context, connectorID, workflowID int64, limit int) ([]string, error) { + return i.repo.GetVersionListByConnectorAndWorkflowID(ctx, connectorID, workflowID, limit) +} + +func (i *impl) DeleteChatFlowRole(ctx context.Context, id int64, workflowID int64) error { + return i.repo.DeleteChatFlowRoleConfig(ctx, id, workflowID) +} + +func (i *impl) PublishChatFlowRole(ctx context.Context, policy *vo.PublishRolePolicy) error { + if policy.WorkflowID == 0 || policy.CreatorID == 0 || policy.Version == "" { + logs.CtxErrorf(ctx, "invalid publish role policy, workflow id %v, creator id %v should not be zero, version %v should not be empty", policy.WorkflowID, policy.CreatorID, policy.Version) + return vo.WrapError(errno.ErrInvalidParameter, fmt.Errorf("invalid publish role policy, workflow id %v, creator id %v should not be zero, version %v should not be empty", policy.WorkflowID, policy.CreatorID, policy.Version)) + } + wf, err := i.repo.GetEntity(ctx, &vo.GetPolicy{ + ID: policy.WorkflowID, + MetaOnly: true, + }) + if err != nil { + return err + } + if wf.Mode != cloudworkflow.WorkflowMode_ChatFlow { + return vo.WrapError(errno.ErrChatFlowRoleOperationFail, fmt.Errorf("workflow id %v, mode %v is not a chatflow", policy.WorkflowID, wf.Mode)) + } + role, err, isExist := i.repo.GetChatFlowRoleConfig(ctx, policy.WorkflowID, "") + if !isExist { + logs.CtxErrorf(ctx, "get draft chat flow role nil, workflow id %v", policy.WorkflowID) + return vo.WrapError(errno.ErrChatFlowRoleOperationFail, fmt.Errorf("get draft chat flow role nil, workflow id %v", policy.WorkflowID)) + } + if err != nil { + return vo.WrapIfNeeded(errno.ErrChatFlowRoleOperationFail, err) + } + + _, err = i.repo.CreateChatFlowRoleConfig(ctx, &entity.ChatFlowRole{ + Name: role.Name, + Description: role.Description, + WorkflowID: policy.WorkflowID, + CreatorID: policy.CreatorID, + AudioConfig: role.AudioConfig, + UserInputConfig: role.UserInputConfig, + AvatarUri: role.AvatarUri, + BackgroundImageInfo: role.BackgroundImageInfo, + OnboardingInfo: role.OnboardingInfo, + SuggestReplyInfo: role.SuggestReplyInfo, + Version: policy.Version, + }) + + if err != nil { + return err + } + return nil +} + func canvasToRefs(referringID int64, canvasStr string) (map[entity.WorkflowReferenceKey]struct{}, error) { var canvas vo.Canvas if err := sonic.UnmarshalString(canvasStr, &canvas); err != nil { @@ -659,6 +768,13 @@ func (i *impl) UpdateMeta(ctx context.Context, id int64, metaUpdate *vo.MetaUpda return err } + if metaUpdate.WorkflowMode != nil && *metaUpdate.WorkflowMode == cloudworkflow.WorkflowMode_ChatFlow { + err = i.adaptToChatFlow(ctx, id) + if err != nil { + return err + } + } + return nil } @@ -667,6 +783,35 @@ func (i *impl) CopyWorkflow(ctx context.Context, workflowID int64, policy vo.Cop if err != nil { return nil, err } + // chat flow should copy role config + if wf.Mode == cloudworkflow.WorkflowMode_ChatFlow { + role, err, isExist := i.repo.GetChatFlowRoleConfig(ctx, workflowID, "") + if !isExist { + logs.CtxErrorf(ctx, "get draft chat flow role nil, workflow id %v", workflowID) + return nil, vo.WrapError(errno.ErrChatFlowRoleOperationFail, fmt.Errorf("get draft chat flow role nil, workflow id %v", workflowID)) + } + + if err != nil { + return nil, vo.WrapIfNeeded(errno.ErrChatFlowRoleOperationFail, err) + } + _, err = i.repo.CreateChatFlowRoleConfig(ctx, &entity.ChatFlowRole{ + Name: role.Name, + Description: role.Description, + WorkflowID: wf.ID, + CreatorID: wf.CreatorID, + AudioConfig: role.AudioConfig, + UserInputConfig: role.UserInputConfig, + AvatarUri: role.AvatarUri, + BackgroundImageInfo: role.BackgroundImageInfo, + OnboardingInfo: role.OnboardingInfo, + SuggestReplyInfo: role.SuggestReplyInfo, + }) + + if err != nil { + return nil, err + } + + } return wf, nil @@ -677,7 +822,7 @@ func (i *impl) ReleaseApplicationWorkflows(ctx context.Context, appID int64, con return nil, fmt.Errorf("connector ids is required") } - wfs, _, err := i.MGet(ctx, &vo.MGetPolicy{ + allWorkflowsInApp, _, err := i.MGet(ctx, &vo.MGetPolicy{ MetaQuery: vo.MetaQuery{ AppID: &appID, }, @@ -688,14 +833,15 @@ func (i *impl) ReleaseApplicationWorkflows(ctx context.Context, appID int64, con } relatedPlugins := make(map[int64]*plugin.PluginEntity, len(config.PluginIDs)) - relatedWorkflow := make(map[int64]entity.IDVersionPair, len(wfs)) + relatedWorkflow := make(map[int64]entity.IDVersionPair, len(allWorkflowsInApp)) - for _, wf := range wfs { + for _, wf := range allWorkflowsInApp { relatedWorkflow[wf.ID] = entity.IDVersionPair{ ID: wf.ID, Version: config.Version, } } + for _, id := range config.PluginIDs { relatedPlugins[id] = &plugin.PluginEntity{ PluginID: id, @@ -704,7 +850,22 @@ func (i *impl) ReleaseApplicationWorkflows(ctx context.Context, appID int64, con } vIssues := make([]*vo.ValidateIssue, 0) - for _, wf := range wfs { + + willPublishWorkflows := make([]*entity.Workflow, 0) + + if len(config.WorkflowIDs) == 0 { + willPublishWorkflows = allWorkflowsInApp + } else { + willPublishWorkflows, _, err = i.MGet(ctx, &vo.MGetPolicy{ + MetaQuery: vo.MetaQuery{ + AppID: &appID, + IDs: config.WorkflowIDs, + }, + QType: workflowModel.FromDraft, + }) + } + + for _, wf := range willPublishWorkflows { issues, err := validateWorkflowTree(ctx, vo.ValidateTreeConfig{ CanvasSchema: wf.Canvas, AppID: ptr.Of(appID), @@ -723,7 +884,7 @@ func (i *impl) ReleaseApplicationWorkflows(ctx context.Context, appID int64, con return vIssues, nil } - for _, wf := range wfs { + for _, wf := range willPublishWorkflows { c := &vo.Canvas{} err := sonic.UnmarshalString(wf.Canvas, c) if err != nil { @@ -747,9 +908,8 @@ func (i *impl) ReleaseApplicationWorkflows(ctx context.Context, appID int64, con } userID := ctxutil.MustGetUIDFromCtx(ctx) - workflowsToPublish := make(map[int64]*vo.VersionInfo) - for _, wf := range wfs { + for _, wf := range willPublishWorkflows { inputStr, err := sonic.MarshalString(wf.InputParams) if err != nil { return nil, err @@ -774,8 +934,16 @@ func (i *impl) ReleaseApplicationWorkflows(ctx context.Context, appID int64, con } } - workflowIDs := make([]int64, 0, len(wfs)) + workflowIDs := make([]int64, 0, len(willPublishWorkflows)) for id, vInfo := range workflowsToPublish { + // if version existed skip + _, existed, err := i.repo.GetVersion(ctx, id, config.Version) + if err != nil { + return nil, err + } + if existed { + continue + } wfRefs, err := canvasToRefs(id, vInfo.Canvas) if err != nil { return nil, err @@ -787,6 +955,24 @@ func (i *impl) ReleaseApplicationWorkflows(ctx context.Context, appID int64, con } } + err = i.ReleaseConversationTemplate(ctx, appID, config.Version) + if err != nil { + return nil, err + } + + for _, wf := range willPublishWorkflows { + if wf.Mode == cloudworkflow.WorkflowMode_ChatFlow { + err = i.PublishChatFlowRole(ctx, &vo.PublishRolePolicy{ + WorkflowID: wf.ID, + CreatorID: wf.CreatorID, + Version: config.Version, + }) + if err != nil { + return nil, err + } + } + } + for _, connectorID := range config.ConnectorIDs { err = i.repo.BatchCreateConnectorWorkflowVersion(ctx, appID, connectorID, workflowIDs, config.Version) if err != nil { @@ -889,7 +1075,7 @@ func (i *impl) CopyWorkflowFromAppToLibrary(ctx context.Context, workflowID int6 } if node.Type == entity.NodeTypeLLM.IDStr() { - if node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.WorkflowFCParam != nil { + if node.Data.Inputs.LLM != nil && node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.WorkflowFCParam != nil { for _, w := range node.Data.Inputs.FCParam.WorkflowFCParam.WorkflowList { var ( v *vo.DraftInfo @@ -1012,7 +1198,7 @@ func (i *impl) CopyWorkflowFromAppToLibrary(ctx context.Context, workflowID int6 return err } - cwf, err := i.repo.CopyWorkflow(ctx, wf.id, vo.CopyWorkflowPolicy{ + cwf, err := i.CopyWorkflow(ctx, wf.id, vo.CopyWorkflowPolicy{ TargetAppID: ptr.Of(int64(0)), ModifiedCanvasSchema: ptr.Of(modifiedCanvasString), }) @@ -1120,7 +1306,7 @@ func (i *impl) DuplicateWorkflowsByAppID(ctx context.Context, sourceAppID, targe } if node.Type == entity.NodeTypeLLM.IDStr() { - if node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.WorkflowFCParam != nil { + if node.Data.Inputs.LLM != nil && node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.WorkflowFCParam != nil { for _, w := range node.Data.Inputs.FCParam.WorkflowFCParam.WorkflowList { var ( v *vo.DraftInfo @@ -1246,6 +1432,11 @@ func (i *impl) DuplicateWorkflowsByAppID(ctx context.Context, sourceAppID, targe } } + err = i.repo.CopyTemplateConversationByAppID(ctx, sourceAppID, targetAppID) + if err != nil { + return nil, err + } + return copiedWorkflowArray, nil } @@ -1368,7 +1559,7 @@ func (i *impl) GetWorkflowDependenceResource(ctx context.Context, workflowID int ds.DatabaseIDs = append(ds.DatabaseIDs, dsID) } case entity.NodeTypeLLM: - if node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.PluginFCParam != nil { + if node.Data.Inputs.LLM != nil && node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.PluginFCParam != nil { for idx := range node.Data.Inputs.FCParam.PluginFCParam.PluginList { if node.Data.Inputs.FCParam.PluginFCParam.PluginList[idx].IsDraft { pl := node.Data.Inputs.FCParam.PluginFCParam.PluginList[idx] @@ -1382,7 +1573,7 @@ func (i *impl) GetWorkflowDependenceResource(ctx context.Context, workflowID int } } - if node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.KnowledgeFCParam != nil { + if node.Data.Inputs.LLM != nil && node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.KnowledgeFCParam != nil { for idx := range node.Data.Inputs.FCParam.KnowledgeFCParam.KnowledgeList { kn := node.Data.Inputs.FCParam.KnowledgeFCParam.KnowledgeList[idx] kid, err := strconv.ParseInt(kn.ID, 10, 64) @@ -1394,7 +1585,7 @@ func (i *impl) GetWorkflowDependenceResource(ctx context.Context, workflowID int } } - if node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.WorkflowFCParam != nil { + if node.Data.Inputs.LLM != nil && node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.WorkflowFCParam != nil { for idx := range node.Data.Inputs.FCParam.WorkflowFCParam.WorkflowList { if node.Data.Inputs.FCParam.WorkflowFCParam.WorkflowList[idx].IsDraft { wID, err := strconv.ParseInt(node.Data.Inputs.FCParam.WorkflowFCParam.WorkflowList[idx].WorkflowID, 10, 64) @@ -1467,6 +1658,165 @@ func (i *impl) GetWorkflowDependenceResource(ctx context.Context, workflowID int } +func (i *impl) checkBotAgentNode(node *vo.Node) error { + if node.Type == entity.NodeTypeCreateConversation.IDStr() || node.Type == entity.NodeTypeConversationDelete.IDStr() || node.Type == entity.NodeTypeConversationUpdate.IDStr() || node.Type == entity.NodeTypeConversationList.IDStr() { + return errors.New("conversation-related nodes are not supported in chatflow") + } + return nil +} + +func (i *impl) validateNodesRecursively(ctx context.Context, nodes []*vo.Node, checkType cloudworkflow.CheckType, visited map[string]struct{}, repo workflow.Repository) error { + queue := make([]*vo.Node, 0, len(nodes)) + queue = append(queue, nodes...) + + for len(queue) > 0 { + node := queue[0] + queue = queue[1:] + + if node == nil { + continue + } + + var checkErr error + switch checkType { + case cloudworkflow.CheckType_BotAgent: + checkErr = i.checkBotAgentNode(node) + default: + // For now, we only handle BotAgent check, so we can do nothing here. + // In the future, if there are other check types that need to be validated on every node, this logic will need to be adjusted. + } + if checkErr != nil { + return checkErr + } + + // Enqueue nested nodes for BFS traversal. This handles Loop, Batch, and other nodes with nested blocks. + if len(node.Blocks) > 0 { + queue = append(queue, node.Blocks...) + } + + if node.Type == entity.NodeTypeSubWorkflow.IDStr() && node.Data != nil && node.Data.Inputs != nil { + workflowIDStr := node.Data.Inputs.WorkflowID + if workflowIDStr == "" { + continue + } + + workflowID, err := strconv.ParseInt(workflowIDStr, 10, 64) + if err != nil { + return fmt.Errorf("invalid workflow ID in sub-workflow node %s: %w", node.ID, err) + } + + version := node.Data.Inputs.WorkflowVersion + qType := workflowModel.FromDraft + if version != "" { + qType = workflowModel.FromSpecificVersion + } + + visitedKey := fmt.Sprintf("%d:%s", workflowID, version) + if _, ok := visited[visitedKey]; ok { + continue + } + visited[visitedKey] = struct{}{} + + subWfEntity, err := repo.GetEntity(ctx, &vo.GetPolicy{ + ID: workflowID, + QType: qType, + Version: version, + }) + if err != nil { + delete(visited, visitedKey) + if errors.Is(err, gorm.ErrRecordNotFound) { + continue + } + return fmt.Errorf("failed to get sub-workflow entity %d: %w", workflowID, err) + } + + var canvas vo.Canvas + if err := sonic.UnmarshalString(subWfEntity.Canvas, &canvas); err != nil { + return fmt.Errorf("failed to unmarshal canvas for workflow %d: %w", subWfEntity.ID, err) + } + + queue = append(queue, canvas.Nodes...) + } + + if node.Type == entity.NodeTypeLLM.IDStr() && node.Data != nil && node.Data.Inputs != nil && node.Data.Inputs.LLM != nil && node.Data.Inputs.FCParam != nil && node.Data.Inputs.FCParam.WorkflowFCParam != nil { + for _, subWfInfo := range node.Data.Inputs.FCParam.WorkflowFCParam.WorkflowList { + if subWfInfo.WorkflowID == "" { + continue + } + workflowID, err := strconv.ParseInt(subWfInfo.WorkflowID, 10, 64) + if err != nil { + return fmt.Errorf("invalid workflow ID in large model node %s: %w", node.ID, err) + } + + version := subWfInfo.WorkflowVersion + qType := workflowModel.FromDraft + if version != "" { + qType = workflowModel.FromSpecificVersion + } + + visitedKey := fmt.Sprintf("%d:%s", workflowID, version) + if _, ok := visited[visitedKey]; ok { + continue + } + visited[visitedKey] = struct{}{} + + subWfEntity, err := repo.GetEntity(ctx, &vo.GetPolicy{ + ID: workflowID, + QType: qType, + Version: version, + }) + if err != nil { + delete(visited, visitedKey) + if errors.Is(err, gorm.ErrRecordNotFound) { + continue + } + return fmt.Errorf("failed to get sub-workflow entity %d from large model node: %w", workflowID, err) + } + + var canvas vo.Canvas + if err := sonic.UnmarshalString(subWfEntity.Canvas, &canvas); err != nil { + return fmt.Errorf("failed to unmarshal canvas for workflow %d from large model node: %w", subWfEntity.ID, err) + } + + queue = append(queue, canvas.Nodes...) + } + } + } + return nil +} + +func (i *impl) WorkflowSchemaCheck(ctx context.Context, wf *entity.Workflow, checks []cloudworkflow.CheckType) ([]*cloudworkflow.CheckResult, error) { + checkResults := make([]*cloudworkflow.CheckResult, 0, len(checks)) + + var canvas vo.Canvas + if err := sonic.UnmarshalString(wf.Canvas, &canvas); err != nil { + return nil, fmt.Errorf("failed to unmarshal canvas for workflow %d: %w", wf.ID, err) + } + + for _, checkType := range checks { + visited := make(map[string]struct{}) + visitedKey := fmt.Sprintf("%d:%s", wf.ID, wf.GetVersion()) + visited[visitedKey] = struct{}{} + + err := i.validateNodesRecursively(ctx, canvas.Nodes, checkType, visited, i.repo) + + if err != nil { + checkResults = append(checkResults, &cloudworkflow.CheckResult{ + IsPass: false, + Reason: err.Error(), + Type: checkType, + }) + } else { + checkResults = append(checkResults, &cloudworkflow.CheckResult{ + IsPass: true, + Type: checkType, + Reason: "", + }) + } + } + return checkResults, nil +} + func (i *impl) MGet(ctx context.Context, policy *vo.MGetPolicy) ([]*entity.Workflow, int64, error) { if policy.MetaOnly { metas, total, err := i.repo.MGetMetas(ctx, &policy.MetaQuery) @@ -1527,11 +1877,13 @@ func (i *impl) MGet(ctx context.Context, policy *vo.MGetPolicy) ([]*entity.Workf index := 0 for id, version := range policy.Versions { - v, err := i.repo.GetVersion(ctx, id, version) + v, existed, err := i.repo.GetVersion(ctx, id, version) if err != nil { return nil, total, err } - + if !existed { + return nil, total, vo.WrapError(errno.ErrWorkflowNotFound, fmt.Errorf("workflow version %s not found for ID %d: %w", version, id, err), errorx.KV("id", strconv.FormatInt(id, 10))) + } inputs, outputs, err := ioF(v.InputParamsStr, v.OutputParamsStr) if err != nil { return nil, total, err @@ -1562,6 +1914,14 @@ func (i *impl) MGet(ctx context.Context, policy *vo.MGetPolicy) ([]*entity.Workf } } +func (i *impl) BindConvRelatedInfo(ctx context.Context, convID int64, info entity.ConvRelatedInfo) error { + return i.repo.BindConvRelatedInfo(ctx, convID, info) +} + +func (i *impl) GetConvRelatedInfo(ctx context.Context, convID int64) (*entity.ConvRelatedInfo, bool, func() error, error) { + return i.repo.GetConvRelatedInfo(ctx, convID) +} + func (i *impl) calculateTestRunSuccess(ctx context.Context, c *vo.Canvas, wid int64) (bool, error) { sc, err := adaptor.CanvasToWorkflowSchema(ctx, c) if err != nil { // not even legal, test run can't possibly be successful @@ -1766,3 +2126,58 @@ func replaceRelatedWorkflowOrExternalResourceInWorkflowNodes(nodes []*vo.Node, r func RegisterAllNodeAdaptors() { adaptor.RegisterAllNodeAdaptors() } +func (i *impl) adaptToChatFlow(ctx context.Context, wID int64) error { + wfEntity, err := i.repo.GetEntity(ctx, &vo.GetPolicy{ + ID: wID, + QType: workflowModel.FromDraft, + }) + if err != nil { + return err + } + + canvas := &vo.Canvas{} + err = sonic.UnmarshalString(wfEntity.Canvas, canvas) + if err != nil { + return err + } + + var startNode *vo.Node + for _, node := range canvas.Nodes { + if node.Type == entity.NodeTypeEntry.IDStr() { + startNode = node + break + } + } + + if startNode == nil { + return fmt.Errorf("can not find start node") + } + + vMap := make(map[string]bool) + for _, o := range startNode.Data.Outputs { + v, err := vo.ParseVariable(o) + if err != nil { + return err + } + vMap[v.Name] = true + } + + if _, ok := vMap["USER_INPUT"]; !ok { + startNode.Data.Outputs = append(startNode.Data.Outputs, &vo.Variable{ + Name: "USER_INPUT", + Type: vo.VariableTypeString, + }) + } + if _, ok := vMap["CONVERSATION_NAME"]; !ok { + startNode.Data.Outputs = append(startNode.Data.Outputs, &vo.Variable{ + Name: "CONVERSATION_NAME", + Type: vo.VariableTypeString, + DefaultValue: "Default", + }) + } + canvasStr, err := sonic.MarshalString(canvas) + if err != nil { + return err + } + return i.Save(ctx, wID, canvasStr) +} diff --git a/backend/domain/workflow/service/utils.go b/backend/domain/workflow/service/utils.go index ca6a2a62..572f1320 100644 --- a/backend/domain/workflow/service/utils.go +++ b/backend/domain/workflow/service/utils.go @@ -22,11 +22,15 @@ import ( "strconv" "strings" - cloudworkflow "github.com/coze-dev/coze-studio/backend/api/model/workflow" + workflowModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/workflow" + "github.com/coze-dev/coze-studio/backend/api/model/workflow" + wf "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/domain/workflow/internal/canvas/adaptor" "github.com/coze-dev/coze-studio/backend/domain/workflow/internal/canvas/validate" "github.com/coze-dev/coze-studio/backend/domain/workflow/variable" + "github.com/coze-dev/coze-studio/backend/pkg/lang/ternary" "github.com/coze-dev/coze-studio/backend/pkg/sonic" "github.com/coze-dev/coze-studio/backend/types/errno" ) @@ -102,17 +106,17 @@ func validateWorkflowTree(ctx context.Context, config vo.ValidateTreeConfig) ([] return issues, nil } -func convertToValidationError(issue *validate.Issue) *cloudworkflow.ValidateErrorData { - e := &cloudworkflow.ValidateErrorData{} +func convertToValidationError(issue *validate.Issue) *workflow.ValidateErrorData { + e := &workflow.ValidateErrorData{} e.Message = issue.Message if issue.NodeErr != nil { - e.Type = cloudworkflow.ValidateErrorType_BotValidateNodeErr - e.NodeError = &cloudworkflow.NodeError{ + e.Type = workflow.ValidateErrorType_BotValidateNodeErr + e.NodeError = &workflow.NodeError{ NodeID: issue.NodeErr.NodeID, } } else if issue.PathErr != nil { - e.Type = cloudworkflow.ValidateErrorType_BotValidatePathErr - e.PathError = &cloudworkflow.PathError{ + e.Type = workflow.ValidateErrorType_BotValidatePathErr + e.PathError = &workflow.PathError{ Start: issue.PathErr.StartNode, End: issue.PathErr.EndNode, } @@ -121,8 +125,8 @@ func convertToValidationError(issue *validate.Issue) *cloudworkflow.ValidateErro return e } -func toValidateErrorData(issues []*validate.Issue) []*cloudworkflow.ValidateErrorData { - validateErrors := make([]*cloudworkflow.ValidateErrorData, 0, len(issues)) +func toValidateErrorData(issues []*validate.Issue) []*workflow.ValidateErrorData { + validateErrors := make([]*workflow.ValidateErrorData, 0, len(issues)) for _, issue := range issues { validateErrors = append(validateErrors, convertToValidationError(issue)) } @@ -197,3 +201,214 @@ func isIncremental(prev version, next version) bool { return next.Patch > prev.Patch } + +func getMaxHistoryRoundsRecursively(ctx context.Context, wfEntity *entity.Workflow, repo wf.Repository) (int64, error) { + visited := make(map[string]struct{}) + maxRounds := int64(0) + err := getMaxHistoryRoundsRecursiveHelper(ctx, wfEntity, repo, visited, &maxRounds) + return maxRounds, err +} + +func getMaxHistoryRoundsRecursiveHelper(ctx context.Context, wfEntity *entity.Workflow, repo wf.Repository, visited map[string]struct{}, maxRounds *int64) error { + visitedKey := fmt.Sprintf("%d:%s", wfEntity.ID, wfEntity.GetVersion()) + if _, ok := visited[visitedKey]; ok { + return nil + } + visited[visitedKey] = struct{}{} + + var canvas vo.Canvas + if err := sonic.UnmarshalString(wfEntity.Canvas, &canvas); err != nil { + return fmt.Errorf("failed to unmarshal canvas for workflow %d: %w", wfEntity.ID, err) + } + + return collectMaxHistoryRounds(ctx, canvas.Nodes, repo, visited, maxRounds) +} + +func collectMaxHistoryRounds(ctx context.Context, nodes []*vo.Node, repo wf.Repository, visited map[string]struct{}, maxRounds *int64) error { + for _, node := range nodes { + if node == nil { + continue + } + + if node.Data != nil && node.Data.Inputs != nil && node.Data.Inputs.ChatHistorySetting != nil && node.Data.Inputs.ChatHistorySetting.EnableChatHistory { + if node.Data.Inputs.ChatHistorySetting.ChatHistoryRound > *maxRounds { + *maxRounds = node.Data.Inputs.ChatHistorySetting.ChatHistoryRound + } + } else if node.Type == entity.NodeTypeLLM.IDStr() && node.Data != nil && node.Data.Inputs != nil && node.Data.Inputs.LLMParam != nil { + param := node.Data.Inputs.LLMParam + bs, _ := sonic.Marshal(param) + llmParam := make(vo.LLMParam, 0) + if err := sonic.Unmarshal(bs, &llmParam); err != nil { + return err + } + var chatHistoryEnabled bool + var chatHistoryRound int64 + for _, param := range llmParam { + switch param.Name { + case "enableChatHistory": + if val, ok := param.Input.Value.Content.(bool); ok { + b := val + chatHistoryEnabled = b + } + case "chatHistoryRound": + if strVal, ok := param.Input.Value.Content.(string); ok { + int64Val, err := strconv.ParseInt(strVal, 10, 64) + if err != nil { + return err + } + chatHistoryRound = int64Val + } + } + } + + if chatHistoryEnabled { + if chatHistoryRound > *maxRounds { + *maxRounds = chatHistoryRound + } + } + } + + isSubWorkflow := node.Type == entity.NodeTypeSubWorkflow.IDStr() && node.Data != nil && node.Data.Inputs != nil + if isSubWorkflow { + workflowIDStr := node.Data.Inputs.WorkflowID + if workflowIDStr == "" { + continue + } + + workflowID, err := strconv.ParseInt(workflowIDStr, 10, 64) + if err != nil { + return fmt.Errorf("invalid workflow ID in sub-workflow node %s: %w", node.ID, err) + } + + subWfEntity, err := repo.GetEntity(ctx, &vo.GetPolicy{ + ID: workflowID, + QType: ternary.IFElse(len(node.Data.Inputs.WorkflowVersion) == 0, workflowModel.FromDraft, workflowModel.FromSpecificVersion), + Version: node.Data.Inputs.WorkflowVersion, + }) + if err != nil { + return fmt.Errorf("failed to get sub-workflow entity %d: %w", workflowID, err) + } + + if err := getMaxHistoryRoundsRecursiveHelper(ctx, subWfEntity, repo, visited, maxRounds); err != nil { + return err + } + } + + if len(node.Blocks) > 0 { + if err := collectMaxHistoryRounds(ctx, node.Blocks, repo, visited, maxRounds); err != nil { + return err + } + } + } + + return nil +} + +func getHistoryRoundsFromNode(ctx context.Context, wfEntity *entity.Workflow, nodeID string, repo wf.Repository) (int64, error) { + if wfEntity == nil { + return 0, nil + } + visited := make(map[string]struct{}) + visitedKey := fmt.Sprintf("%d:%s", wfEntity.ID, wfEntity.GetVersion()) + if _, ok := visited[visitedKey]; ok { + return 0, nil + } + visited[visitedKey] = struct{}{} + maxRounds := int64(0) + c := &vo.Canvas{} + if err := sonic.UnmarshalString(wfEntity.Canvas, c); err != nil { + return 0, fmt.Errorf("failed to unmarshal canvas: %w", err) + } + var ( + n *vo.Node + nodeFinder func(nodes []*vo.Node) *vo.Node + ) + nodeFinder = func(nodes []*vo.Node) *vo.Node { + for i := range nodes { + if nodes[i].ID == nodeID { + return nodes[i] + } + if len(nodes[i].Blocks) > 0 { + if n := nodeFinder(nodes[i].Blocks); n != nil { + return n + } + } + } + return nil + } + + n = nodeFinder(c.Nodes) + if n.Type == entity.NodeTypeLLM.IDStr() { + if n.Data == nil || n.Data.Inputs == nil { + return 0, nil + } + param := n.Data.Inputs.LLMParam + bs, _ := sonic.Marshal(param) + llmParam := make(vo.LLMParam, 0) + if err := sonic.Unmarshal(bs, &llmParam); err != nil { + return 0, err + } + var chatHistoryEnabled bool + var chatHistoryRound int64 + for _, param := range llmParam { + switch param.Name { + case "enableChatHistory": + if val, ok := param.Input.Value.Content.(bool); ok { + b := val + chatHistoryEnabled = b + } + case "chatHistoryRound": + if strVal, ok := param.Input.Value.Content.(string); ok { + int64Val, err := strconv.ParseInt(strVal, 10, 64) + if err != nil { + return 0, err + } + chatHistoryRound = int64Val + } + } + } + if chatHistoryEnabled { + return chatHistoryRound, nil + } + return 0, nil + } + + if n.Type == entity.NodeTypeIntentDetector.IDStr() || n.Type == entity.NodeTypeKnowledgeRetriever.IDStr() { + if n.Data != nil && n.Data.Inputs != nil && n.Data.Inputs.ChatHistorySetting != nil && n.Data.Inputs.ChatHistorySetting.EnableChatHistory { + return n.Data.Inputs.ChatHistorySetting.ChatHistoryRound, nil + } + return 0, nil + } + + if n.Type == entity.NodeTypeSubWorkflow.IDStr() { + if n.Data != nil && n.Data.Inputs != nil { + workflowIDStr := n.Data.Inputs.WorkflowID + if workflowIDStr == "" { + return 0, nil + } + workflowID, err := strconv.ParseInt(workflowIDStr, 10, 64) + if err != nil { + return 0, fmt.Errorf("invalid workflow ID in sub-workflow node %s: %w", n.ID, err) + } + subWfEntity, err := repo.GetEntity(ctx, &vo.GetPolicy{ + ID: workflowID, + QType: ternary.IFElse(len(n.Data.Inputs.WorkflowVersion) == 0, workflowModel.FromDraft, workflowModel.FromSpecificVersion), + Version: n.Data.Inputs.WorkflowVersion, + }) + if err != nil { + return 0, fmt.Errorf("failed to get sub-workflow entity %d: %w", workflowID, err) + } + if err := getMaxHistoryRoundsRecursiveHelper(ctx, subWfEntity, repo, visited, &maxRounds); err != nil { + return 0, err + } + return maxRounds, nil + } + } + + if len(n.Blocks) > 0 { + if err := collectMaxHistoryRounds(ctx, n.Blocks, repo, visited, &maxRounds); err != nil { + return 0, err + } + } + return maxRounds, nil +} diff --git a/backend/internal/mock/domain/workflow/interface.go b/backend/internal/mock/domain/workflow/interface.go index fa47ba1a..802dfc81 100644 --- a/backend/internal/mock/domain/workflow/interface.go +++ b/backend/internal/mock/domain/workflow/interface.go @@ -38,6 +38,7 @@ import ( config "github.com/coze-dev/coze-studio/backend/domain/workflow/config" entity "github.com/coze-dev/coze-studio/backend/domain/workflow/entity" vo "github.com/coze-dev/coze-studio/backend/domain/workflow/entity/vo" + storage "github.com/coze-dev/coze-studio/backend/infra/contract/storage" gomock "go.uber.org/mock/gomock" ) @@ -109,6 +110,20 @@ func (mr *MockServiceMockRecorder) AsyncResume(ctx, req, arg2 any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AsyncResume", reflect.TypeOf((*MockService)(nil).AsyncResume), ctx, req, arg2) } +// BindConvRelatedInfo mocks base method. +func (m *MockService) BindConvRelatedInfo(ctx context.Context, convID int64, info entity.ConvRelatedInfo) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BindConvRelatedInfo", ctx, convID, info) + ret0, _ := ret[0].(error) + return ret0 +} + +// BindConvRelatedInfo indicates an expected call of BindConvRelatedInfo. +func (mr *MockServiceMockRecorder) BindConvRelatedInfo(ctx, convID, info any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BindConvRelatedInfo", reflect.TypeOf((*MockService)(nil).BindConvRelatedInfo), ctx, convID, info) +} + // Cancel mocks base method. func (m *MockService) Cancel(ctx context.Context, wfExeID, wfID, spaceID int64) error { m.ctrl.T.Helper() @@ -123,6 +138,21 @@ func (mr *MockServiceMockRecorder) Cancel(ctx, wfExeID, wfID, spaceID any) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Cancel", reflect.TypeOf((*MockService)(nil).Cancel), ctx, wfExeID, wfID, spaceID) } +// CheckWorkflowsToReplace mocks base method. +func (m *MockService) CheckWorkflowsToReplace(ctx context.Context, appID, templateID int64) ([]*entity.Workflow, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckWorkflowsToReplace", ctx, appID, templateID) + ret0, _ := ret[0].([]*entity.Workflow) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CheckWorkflowsToReplace indicates an expected call of CheckWorkflowsToReplace. +func (mr *MockServiceMockRecorder) CheckWorkflowsToReplace(ctx, appID, templateID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckWorkflowsToReplace", reflect.TypeOf((*MockService)(nil).CheckWorkflowsToReplace), ctx, appID, templateID) +} + // CopyWorkflow mocks base method. func (m *MockService) CopyWorkflow(ctx context.Context, workflowID int64, policy vo.CopyWorkflowPolicy) (*entity.Workflow, error) { m.ctrl.T.Helper() @@ -168,6 +198,36 @@ func (mr *MockServiceMockRecorder) Create(ctx, meta any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockService)(nil).Create), ctx, meta) } +// CreateChatFlowRole mocks base method. +func (m *MockService) CreateChatFlowRole(ctx context.Context, role *vo.ChatFlowRoleCreate) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateChatFlowRole", ctx, role) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateChatFlowRole indicates an expected call of CreateChatFlowRole. +func (mr *MockServiceMockRecorder) CreateChatFlowRole(ctx, role any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateChatFlowRole", reflect.TypeOf((*MockService)(nil).CreateChatFlowRole), ctx, role) +} + +// CreateDraftConversationTemplate mocks base method. +func (m *MockService) CreateDraftConversationTemplate(ctx context.Context, template *vo.CreateConversationTemplateMeta) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateDraftConversationTemplate", ctx, template) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateDraftConversationTemplate indicates an expected call of CreateDraftConversationTemplate. +func (mr *MockServiceMockRecorder) CreateDraftConversationTemplate(ctx, template any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateDraftConversationTemplate", reflect.TypeOf((*MockService)(nil).CreateDraftConversationTemplate), ctx, template) +} + // Delete mocks base method. func (m *MockService) Delete(ctx context.Context, policy *vo.DeletePolicy) ([]int64, error) { m.ctrl.T.Helper() @@ -183,6 +243,50 @@ func (mr *MockServiceMockRecorder) Delete(ctx, policy any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockService)(nil).Delete), ctx, policy) } +// DeleteChatFlowRole mocks base method. +func (m *MockService) DeleteChatFlowRole(ctx context.Context, id, workflowID int64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteChatFlowRole", ctx, id, workflowID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteChatFlowRole indicates an expected call of DeleteChatFlowRole. +func (mr *MockServiceMockRecorder) DeleteChatFlowRole(ctx, id, workflowID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteChatFlowRole", reflect.TypeOf((*MockService)(nil).DeleteChatFlowRole), ctx, id, workflowID) +} + +// DeleteDraftConversationTemplate mocks base method. +func (m *MockService) DeleteDraftConversationTemplate(ctx context.Context, templateID int64, wfID2ConversationName map[int64]string) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteDraftConversationTemplate", ctx, templateID, wfID2ConversationName) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteDraftConversationTemplate indicates an expected call of DeleteDraftConversationTemplate. +func (mr *MockServiceMockRecorder) DeleteDraftConversationTemplate(ctx, templateID, wfID2ConversationName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDraftConversationTemplate", reflect.TypeOf((*MockService)(nil).DeleteDraftConversationTemplate), ctx, templateID, wfID2ConversationName) +} + +// DeleteDynamicConversation mocks base method. +func (m *MockService) DeleteDynamicConversation(ctx context.Context, env vo.Env, templateID int64) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteDynamicConversation", ctx, env, templateID) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteDynamicConversation indicates an expected call of DeleteDynamicConversation. +func (mr *MockServiceMockRecorder) DeleteDynamicConversation(ctx, env, templateID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDynamicConversation", reflect.TypeOf((*MockService)(nil).DeleteDynamicConversation), ctx, env, templateID) +} + // DuplicateWorkflowsByAppID mocks base method. func (m *MockService) DuplicateWorkflowsByAppID(ctx context.Context, sourceAPPID, targetAppID int64, related vo.ExternalResourceRelated) ([]*entity.Workflow, error) { m.ctrl.T.Helper() @@ -213,6 +317,70 @@ func (mr *MockServiceMockRecorder) Get(ctx, policy any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockService)(nil).Get), ctx, policy) } +// GetChatFlowRole mocks base method. +func (m *MockService) GetChatFlowRole(ctx context.Context, workflowID int64, version string) (*entity.ChatFlowRole, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetChatFlowRole", ctx, workflowID, version) + ret0, _ := ret[0].(*entity.ChatFlowRole) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetChatFlowRole indicates an expected call of GetChatFlowRole. +func (mr *MockServiceMockRecorder) GetChatFlowRole(ctx, workflowID, version any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatFlowRole", reflect.TypeOf((*MockService)(nil).GetChatFlowRole), ctx, workflowID, version) +} + +// GetConvRelatedInfo mocks base method. +func (m *MockService) GetConvRelatedInfo(ctx context.Context, convID int64) (*entity.ConvRelatedInfo, bool, func() error, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetConvRelatedInfo", ctx, convID) + ret0, _ := ret[0].(*entity.ConvRelatedInfo) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(func() error) + ret3, _ := ret[3].(error) + return ret0, ret1, ret2, ret3 +} + +// GetConvRelatedInfo indicates an expected call of GetConvRelatedInfo. +func (mr *MockServiceMockRecorder) GetConvRelatedInfo(ctx, convID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConvRelatedInfo", reflect.TypeOf((*MockService)(nil).GetConvRelatedInfo), ctx, convID) +} + +// GetConversationNameByID mocks base method. +func (m *MockService) GetConversationNameByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (string, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetConversationNameByID", ctx, env, appID, connectorID, conversationID) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetConversationNameByID indicates an expected call of GetConversationNameByID. +func (mr *MockServiceMockRecorder) GetConversationNameByID(ctx, env, appID, connectorID, conversationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConversationNameByID", reflect.TypeOf((*MockService)(nil).GetConversationNameByID), ctx, env, appID, connectorID, conversationID) +} + +// GetDynamicConversationByName mocks base method. +func (m *MockService) GetDynamicConversationByName(ctx context.Context, env vo.Env, appID, connectorID, userID int64, name string) (*entity.DynamicConversation, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDynamicConversationByName", ctx, env, appID, connectorID, userID, name) + ret0, _ := ret[0].(*entity.DynamicConversation) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetDynamicConversationByName indicates an expected call of GetDynamicConversationByName. +func (mr *MockServiceMockRecorder) GetDynamicConversationByName(ctx, env, appID, connectorID, userID, name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDynamicConversationByName", reflect.TypeOf((*MockService)(nil).GetDynamicConversationByName), ctx, env, appID, connectorID, userID, name) +} + // GetExecution mocks base method. func (m *MockService) GetExecution(ctx context.Context, wfExe *entity.WorkflowExecution, includeNodes bool) (*entity.WorkflowExecution, error) { m.ctrl.T.Helper() @@ -277,6 +445,38 @@ func (mr *MockServiceMockRecorder) GetNodeExecution(ctx, exeID, nodeID any) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodeExecution", reflect.TypeOf((*MockService)(nil).GetNodeExecution), ctx, exeID, nodeID) } +// GetOrCreateConversation mocks base method. +func (m *MockService) GetOrCreateConversation(ctx context.Context, env vo.Env, appID, connectorID, userID int64, conversationName string) (int64, int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOrCreateConversation", ctx, env, appID, connectorID, userID, conversationName) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(int64) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetOrCreateConversation indicates an expected call of GetOrCreateConversation. +func (mr *MockServiceMockRecorder) GetOrCreateConversation(ctx, env, appID, connectorID, userID, conversationName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOrCreateConversation", reflect.TypeOf((*MockService)(nil).GetOrCreateConversation), ctx, env, appID, connectorID, userID, conversationName) +} + +// GetTemplateByName mocks base method. +func (m *MockService) GetTemplateByName(ctx context.Context, env vo.Env, appID int64, templateName string) (*entity.ConversationTemplate, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTemplateByName", ctx, env, appID, templateName) + ret0, _ := ret[0].(*entity.ConversationTemplate) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetTemplateByName indicates an expected call of GetTemplateByName. +func (mr *MockServiceMockRecorder) GetTemplateByName(ctx, env, appID, templateName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTemplateByName", reflect.TypeOf((*MockService)(nil).GetTemplateByName), ctx, env, appID, templateName) +} + // GetWorkflowDependenceResource mocks base method. func (m *MockService) GetWorkflowDependenceResource(ctx context.Context, workflowID int64) (*vo.DependenceResource, error) { m.ctrl.T.Helper() @@ -307,6 +507,65 @@ func (mr *MockServiceMockRecorder) GetWorkflowReference(ctx, id any) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkflowReference", reflect.TypeOf((*MockService)(nil).GetWorkflowReference), ctx, id) } +// GetWorkflowVersionsByConnector mocks base method. +func (m *MockService) GetWorkflowVersionsByConnector(ctx context.Context, connectorID, workflowID int64, limit int) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetWorkflowVersionsByConnector", ctx, connectorID, workflowID, limit) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetWorkflowVersionsByConnector indicates an expected call of GetWorkflowVersionsByConnector. +func (mr *MockServiceMockRecorder) GetWorkflowVersionsByConnector(ctx, connectorID, workflowID, limit any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkflowVersionsByConnector", reflect.TypeOf((*MockService)(nil).GetWorkflowVersionsByConnector), ctx, connectorID, workflowID, limit) +} + +// InitApplicationDefaultConversationTemplate mocks base method. +func (m *MockService) InitApplicationDefaultConversationTemplate(ctx context.Context, spaceID, appID, userID int64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InitApplicationDefaultConversationTemplate", ctx, spaceID, appID, userID) + ret0, _ := ret[0].(error) + return ret0 +} + +// InitApplicationDefaultConversationTemplate indicates an expected call of InitApplicationDefaultConversationTemplate. +func (mr *MockServiceMockRecorder) InitApplicationDefaultConversationTemplate(ctx, spaceID, appID, userID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InitApplicationDefaultConversationTemplate", reflect.TypeOf((*MockService)(nil).InitApplicationDefaultConversationTemplate), ctx, spaceID, appID, userID) +} + +// ListConversationTemplate mocks base method. +func (m *MockService) ListConversationTemplate(ctx context.Context, env vo.Env, policy *vo.ListConversationTemplatePolicy) ([]*entity.ConversationTemplate, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListConversationTemplate", ctx, env, policy) + ret0, _ := ret[0].([]*entity.ConversationTemplate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListConversationTemplate indicates an expected call of ListConversationTemplate. +func (mr *MockServiceMockRecorder) ListConversationTemplate(ctx, env, policy any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListConversationTemplate", reflect.TypeOf((*MockService)(nil).ListConversationTemplate), ctx, env, policy) +} + +// ListDynamicConversation mocks base method. +func (m *MockService) ListDynamicConversation(ctx context.Context, env vo.Env, policy *vo.ListConversationPolicy) ([]*entity.DynamicConversation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListDynamicConversation", ctx, env, policy) + ret0, _ := ret[0].([]*entity.DynamicConversation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListDynamicConversation indicates an expected call of ListDynamicConversation. +func (mr *MockServiceMockRecorder) ListDynamicConversation(ctx, env, policy any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListDynamicConversation", reflect.TypeOf((*MockService)(nil).ListDynamicConversation), ctx, env, policy) +} + // ListNodeMeta mocks base method. func (m *MockService) ListNodeMeta(ctx context.Context, nodeTypes map[entity.NodeType]bool) (map[string][]*entity.NodeTypeMeta, []entity.Category, error) { m.ctrl.T.Helper() @@ -339,6 +598,21 @@ func (mr *MockServiceMockRecorder) MGet(ctx, policy any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MGet", reflect.TypeOf((*MockService)(nil).MGet), ctx, policy) } +// MGetStaticConversation mocks base method. +func (m *MockService) MGetStaticConversation(ctx context.Context, env vo.Env, userID, connectorID int64, templateIDs []int64) ([]*entity.StaticConversation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MGetStaticConversation", ctx, env, userID, connectorID, templateIDs) + ret0, _ := ret[0].([]*entity.StaticConversation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MGetStaticConversation indicates an expected call of MGetStaticConversation. +func (mr *MockServiceMockRecorder) MGetStaticConversation(ctx, env, userID, connectorID, templateIDs any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MGetStaticConversation", reflect.TypeOf((*MockService)(nil).MGetStaticConversation), ctx, env, userID, connectorID, templateIDs) +} + // Publish mocks base method. func (m *MockService) Publish(ctx context.Context, policy *vo.PublishPolicy) error { m.ctrl.T.Helper() @@ -353,6 +627,20 @@ func (mr *MockServiceMockRecorder) Publish(ctx, policy any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Publish", reflect.TypeOf((*MockService)(nil).Publish), ctx, policy) } +// PublishChatFlowRole mocks base method. +func (m *MockService) PublishChatFlowRole(ctx context.Context, policy *vo.PublishRolePolicy) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PublishChatFlowRole", ctx, policy) + ret0, _ := ret[0].(error) + return ret0 +} + +// PublishChatFlowRole indicates an expected call of PublishChatFlowRole. +func (mr *MockServiceMockRecorder) PublishChatFlowRole(ctx, policy any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishChatFlowRole", reflect.TypeOf((*MockService)(nil).PublishChatFlowRole), ctx, policy) +} + // QueryNodeProperties mocks base method. func (m *MockService) QueryNodeProperties(ctx context.Context, id int64) (map[string]*vo.NodeProperty, error) { m.ctrl.T.Helper() @@ -383,6 +671,20 @@ func (mr *MockServiceMockRecorder) ReleaseApplicationWorkflows(ctx, appID, arg2 return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReleaseApplicationWorkflows", reflect.TypeOf((*MockService)(nil).ReleaseApplicationWorkflows), ctx, appID, arg2) } +// ReleaseConversationTemplate mocks base method. +func (m *MockService) ReleaseConversationTemplate(ctx context.Context, appID int64, version string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReleaseConversationTemplate", ctx, appID, version) + ret0, _ := ret[0].(error) + return ret0 +} + +// ReleaseConversationTemplate indicates an expected call of ReleaseConversationTemplate. +func (mr *MockServiceMockRecorder) ReleaseConversationTemplate(ctx, appID, version any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReleaseConversationTemplate", reflect.TypeOf((*MockService)(nil).ReleaseConversationTemplate), ctx, appID, version) +} + // Save mocks base method. func (m *MockService) Save(ctx context.Context, id int64, arg2 string) error { m.ctrl.T.Helper() @@ -427,6 +729,21 @@ func (mr *MockServiceMockRecorder) StreamResume(ctx, req, arg2 any) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StreamResume", reflect.TypeOf((*MockService)(nil).StreamResume), ctx, req, arg2) } +// Suggest mocks base method. +func (m *MockService) Suggest(ctx context.Context, input *vo.SuggestInfo) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Suggest", ctx, input) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Suggest indicates an expected call of Suggest. +func (mr *MockServiceMockRecorder) Suggest(ctx, input any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Suggest", reflect.TypeOf((*MockService)(nil).Suggest), ctx, input) +} + // SyncExecute mocks base method. func (m *MockService) SyncExecute(ctx context.Context, arg1 workflow.ExecuteConfig, input map[string]any) (*entity.WorkflowExecution, vo.TerminatePlan, error) { m.ctrl.T.Helper() @@ -457,6 +774,49 @@ func (mr *MockServiceMockRecorder) SyncRelatedWorkflowResources(ctx, appID, rela return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncRelatedWorkflowResources", reflect.TypeOf((*MockService)(nil).SyncRelatedWorkflowResources), ctx, appID, relatedWorkflows, related) } +// UpdateChatFlowRole mocks base method. +func (m *MockService) UpdateChatFlowRole(ctx context.Context, workflowID int64, role *vo.ChatFlowRoleUpdate) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateChatFlowRole", ctx, workflowID, role) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateChatFlowRole indicates an expected call of UpdateChatFlowRole. +func (mr *MockServiceMockRecorder) UpdateChatFlowRole(ctx, workflowID, role any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateChatFlowRole", reflect.TypeOf((*MockService)(nil).UpdateChatFlowRole), ctx, workflowID, role) +} + +// UpdateConversation mocks base method. +func (m *MockService) UpdateConversation(ctx context.Context, env vo.Env, appID, connectorID, userID int64, conversationName string) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateConversation", ctx, env, appID, connectorID, userID, conversationName) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateConversation indicates an expected call of UpdateConversation. +func (mr *MockServiceMockRecorder) UpdateConversation(ctx, env, appID, connectorID, userID, conversationName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateConversation", reflect.TypeOf((*MockService)(nil).UpdateConversation), ctx, env, appID, connectorID, userID, conversationName) +} + +// UpdateDraftConversationTemplateName mocks base method. +func (m *MockService) UpdateDraftConversationTemplateName(ctx context.Context, appID, userID, templateID int64, name string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateDraftConversationTemplateName", ctx, appID, userID, templateID, name) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateDraftConversationTemplateName indicates an expected call of UpdateDraftConversationTemplateName. +func (mr *MockServiceMockRecorder) UpdateDraftConversationTemplateName(ctx, appID, userID, templateID, name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDraftConversationTemplateName", reflect.TypeOf((*MockService)(nil).UpdateDraftConversationTemplateName), ctx, appID, userID, templateID, name) +} + // UpdateMeta mocks base method. func (m *MockService) UpdateMeta(ctx context.Context, id int64, metaUpdate *vo.MetaUpdate) error { m.ctrl.T.Helper() @@ -545,6 +905,21 @@ func (mr *MockServiceMockRecorder) WorkflowAsModelTool(ctx, policies any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WorkflowAsModelTool", reflect.TypeOf((*MockService)(nil).WorkflowAsModelTool), ctx, policies) } +// WorkflowSchemaCheck mocks base method. +func (m *MockService) WorkflowSchemaCheck(ctx context.Context, wf *entity.Workflow, checks []workflow0.CheckType) ([]*workflow0.CheckResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WorkflowSchemaCheck", ctx, wf, checks) + ret0, _ := ret[0].([]*workflow0.CheckResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WorkflowSchemaCheck indicates an expected call of WorkflowSchemaCheck. +func (mr *MockServiceMockRecorder) WorkflowSchemaCheck(ctx, wf, checks any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WorkflowSchemaCheck", reflect.TypeOf((*MockService)(nil).WorkflowSchemaCheck), ctx, wf, checks) +} + // MockRepository is a mock of Repository interface. type MockRepository struct { ctrl *gomock.Controller @@ -583,6 +958,34 @@ func (mr *MockRepositoryMockRecorder) BatchCreateConnectorWorkflowVersion(ctx, a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchCreateConnectorWorkflowVersion", reflect.TypeOf((*MockRepository)(nil).BatchCreateConnectorWorkflowVersion), ctx, appID, connectorID, workflowIDs, version) } +// BatchCreateOnlineConversationTemplate mocks base method. +func (m *MockRepository) BatchCreateOnlineConversationTemplate(ctx context.Context, templates []*entity.ConversationTemplate, version string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BatchCreateOnlineConversationTemplate", ctx, templates, version) + ret0, _ := ret[0].(error) + return ret0 +} + +// BatchCreateOnlineConversationTemplate indicates an expected call of BatchCreateOnlineConversationTemplate. +func (mr *MockRepositoryMockRecorder) BatchCreateOnlineConversationTemplate(ctx, templates, version any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BatchCreateOnlineConversationTemplate", reflect.TypeOf((*MockRepository)(nil).BatchCreateOnlineConversationTemplate), ctx, templates, version) +} + +// BindConvRelatedInfo mocks base method. +func (m *MockRepository) BindConvRelatedInfo(ctx context.Context, convID int64, info entity.ConvRelatedInfo) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BindConvRelatedInfo", ctx, convID, info) + ret0, _ := ret[0].(error) + return ret0 +} + +// BindConvRelatedInfo indicates an expected call of BindConvRelatedInfo. +func (mr *MockRepositoryMockRecorder) BindConvRelatedInfo(ctx, convID, info any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BindConvRelatedInfo", reflect.TypeOf((*MockRepository)(nil).BindConvRelatedInfo), ctx, convID, info) +} + // CancelAllRunningNodes mocks base method. func (m *MockRepository) CancelAllRunningNodes(ctx context.Context, wfExeID int64) error { m.ctrl.T.Helper() @@ -597,6 +1000,20 @@ func (mr *MockRepositoryMockRecorder) CancelAllRunningNodes(ctx, wfExeID any) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelAllRunningNodes", reflect.TypeOf((*MockRepository)(nil).CancelAllRunningNodes), ctx, wfExeID) } +// CopyTemplateConversationByAppID mocks base method. +func (m *MockRepository) CopyTemplateConversationByAppID(ctx context.Context, appID, toAppID int64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CopyTemplateConversationByAppID", ctx, appID, toAppID) + ret0, _ := ret[0].(error) + return ret0 +} + +// CopyTemplateConversationByAppID indicates an expected call of CopyTemplateConversationByAppID. +func (mr *MockRepositoryMockRecorder) CopyTemplateConversationByAppID(ctx, appID, toAppID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CopyTemplateConversationByAppID", reflect.TypeOf((*MockRepository)(nil).CopyTemplateConversationByAppID), ctx, appID, toAppID) +} + // CopyWorkflow mocks base method. func (m *MockRepository) CopyWorkflow(ctx context.Context, workflowID int64, policy vo.CopyWorkflowPolicy) (*entity.Workflow, error) { m.ctrl.T.Helper() @@ -612,6 +1029,36 @@ func (mr *MockRepositoryMockRecorder) CopyWorkflow(ctx, workflowID, policy any) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CopyWorkflow", reflect.TypeOf((*MockRepository)(nil).CopyWorkflow), ctx, workflowID, policy) } +// CreateChatFlowRoleConfig mocks base method. +func (m *MockRepository) CreateChatFlowRoleConfig(ctx context.Context, chatFlowRole *entity.ChatFlowRole) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateChatFlowRoleConfig", ctx, chatFlowRole) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateChatFlowRoleConfig indicates an expected call of CreateChatFlowRoleConfig. +func (mr *MockRepositoryMockRecorder) CreateChatFlowRoleConfig(ctx, chatFlowRole any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateChatFlowRoleConfig", reflect.TypeOf((*MockRepository)(nil).CreateChatFlowRoleConfig), ctx, chatFlowRole) +} + +// CreateDraftConversationTemplate mocks base method. +func (m *MockRepository) CreateDraftConversationTemplate(ctx context.Context, template *vo.CreateConversationTemplateMeta) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateDraftConversationTemplate", ctx, template) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateDraftConversationTemplate indicates an expected call of CreateDraftConversationTemplate. +func (mr *MockRepositoryMockRecorder) CreateDraftConversationTemplate(ctx, template any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateDraftConversationTemplate", reflect.TypeOf((*MockRepository)(nil).CreateDraftConversationTemplate), ctx, template) +} + // CreateMeta mocks base method. func (m *MockRepository) CreateMeta(ctx context.Context, meta *vo.Meta) (int64, error) { m.ctrl.T.Helper() @@ -711,6 +1158,50 @@ func (mr *MockRepositoryMockRecorder) Delete(ctx, id any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockRepository)(nil).Delete), ctx, id) } +// DeleteChatFlowRoleConfig mocks base method. +func (m *MockRepository) DeleteChatFlowRoleConfig(ctx context.Context, id, workflowID int64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteChatFlowRoleConfig", ctx, id, workflowID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteChatFlowRoleConfig indicates an expected call of DeleteChatFlowRoleConfig. +func (mr *MockRepositoryMockRecorder) DeleteChatFlowRoleConfig(ctx, id, workflowID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteChatFlowRoleConfig", reflect.TypeOf((*MockRepository)(nil).DeleteChatFlowRoleConfig), ctx, id, workflowID) +} + +// DeleteDraftConversationTemplate mocks base method. +func (m *MockRepository) DeleteDraftConversationTemplate(ctx context.Context, templateID int64) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteDraftConversationTemplate", ctx, templateID) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteDraftConversationTemplate indicates an expected call of DeleteDraftConversationTemplate. +func (mr *MockRepositoryMockRecorder) DeleteDraftConversationTemplate(ctx, templateID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDraftConversationTemplate", reflect.TypeOf((*MockRepository)(nil).DeleteDraftConversationTemplate), ctx, templateID) +} + +// DeleteDynamicConversation mocks base method. +func (m *MockRepository) DeleteDynamicConversation(ctx context.Context, env vo.Env, id int64) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteDynamicConversation", ctx, env, id) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteDynamicConversation indicates an expected call of DeleteDynamicConversation. +func (mr *MockRepositoryMockRecorder) DeleteDynamicConversation(ctx, env, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDynamicConversation", reflect.TypeOf((*MockRepository)(nil).DeleteDynamicConversation), ctx, env, id) +} + // DraftV2 mocks base method. func (m *MockRepository) DraftV2(ctx context.Context, id int64, commitID string) (*vo.DraftInfo, error) { m.ctrl.T.Helper() @@ -772,6 +1263,55 @@ func (mr *MockRepositoryMockRecorder) Get(ctx, checkPointID any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockRepository)(nil).Get), ctx, checkPointID) } +// GetChatFlowRoleConfig mocks base method. +func (m *MockRepository) GetChatFlowRoleConfig(ctx context.Context, workflowID int64, version string) (*entity.ChatFlowRole, error, bool) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetChatFlowRoleConfig", ctx, workflowID, version) + ret0, _ := ret[0].(*entity.ChatFlowRole) + ret1, _ := ret[1].(error) + ret2, _ := ret[2].(bool) + return ret0, ret1, ret2 +} + +// GetChatFlowRoleConfig indicates an expected call of GetChatFlowRoleConfig. +func (mr *MockRepositoryMockRecorder) GetChatFlowRoleConfig(ctx, workflowID, version any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatFlowRoleConfig", reflect.TypeOf((*MockRepository)(nil).GetChatFlowRoleConfig), ctx, workflowID, version) +} + +// GetConvRelatedInfo mocks base method. +func (m *MockRepository) GetConvRelatedInfo(ctx context.Context, convID int64) (*entity.ConvRelatedInfo, bool, func() error, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetConvRelatedInfo", ctx, convID) + ret0, _ := ret[0].(*entity.ConvRelatedInfo) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(func() error) + ret3, _ := ret[3].(error) + return ret0, ret1, ret2, ret3 +} + +// GetConvRelatedInfo indicates an expected call of GetConvRelatedInfo. +func (mr *MockRepositoryMockRecorder) GetConvRelatedInfo(ctx, convID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConvRelatedInfo", reflect.TypeOf((*MockRepository)(nil).GetConvRelatedInfo), ctx, convID) +} + +// GetConversationTemplate mocks base method. +func (m *MockRepository) GetConversationTemplate(ctx context.Context, env vo.Env, policy vo.GetConversationTemplatePolicy) (*entity.ConversationTemplate, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetConversationTemplate", ctx, env, policy) + ret0, _ := ret[0].(*entity.ConversationTemplate) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetConversationTemplate indicates an expected call of GetConversationTemplate. +func (mr *MockRepositoryMockRecorder) GetConversationTemplate(ctx, env, policy any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConversationTemplate", reflect.TypeOf((*MockRepository)(nil).GetConversationTemplate), ctx, env, policy) +} + // GetDraftWorkflowsByAppID mocks base method. func (m *MockRepository) GetDraftWorkflowsByAppID(ctx context.Context, AppID int64) (map[int64]*vo.DraftInfo, map[int64]string, error) { m.ctrl.T.Helper() @@ -788,6 +1328,38 @@ func (mr *MockRepositoryMockRecorder) GetDraftWorkflowsByAppID(ctx, AppID any) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDraftWorkflowsByAppID", reflect.TypeOf((*MockRepository)(nil).GetDraftWorkflowsByAppID), ctx, AppID) } +// GetDynamicConversationByID mocks base method. +func (m *MockRepository) GetDynamicConversationByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (*entity.DynamicConversation, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDynamicConversationByID", ctx, env, appID, connectorID, conversationID) + ret0, _ := ret[0].(*entity.DynamicConversation) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetDynamicConversationByID indicates an expected call of GetDynamicConversationByID. +func (mr *MockRepositoryMockRecorder) GetDynamicConversationByID(ctx, env, appID, connectorID, conversationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDynamicConversationByID", reflect.TypeOf((*MockRepository)(nil).GetDynamicConversationByID), ctx, env, appID, connectorID, conversationID) +} + +// GetDynamicConversationByName mocks base method. +func (m *MockRepository) GetDynamicConversationByName(ctx context.Context, env vo.Env, appID, connectorID, userID int64, name string) (*entity.DynamicConversation, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDynamicConversationByName", ctx, env, appID, connectorID, userID, name) + ret0, _ := ret[0].(*entity.DynamicConversation) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetDynamicConversationByName indicates an expected call of GetDynamicConversationByName. +func (mr *MockRepositoryMockRecorder) GetDynamicConversationByName(ctx, env, appID, connectorID, userID, name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDynamicConversationByName", reflect.TypeOf((*MockRepository)(nil).GetDynamicConversationByName), ctx, env, appID, connectorID, userID, name) +} + // GetEntity mocks base method. func (m *MockRepository) GetEntity(ctx context.Context, policy *vo.GetPolicy) (*entity.Workflow, error) { m.ctrl.T.Helper() @@ -938,6 +1510,92 @@ func (mr *MockRepositoryMockRecorder) GetNodeOfCodeConfig() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNodeOfCodeConfig", reflect.TypeOf((*MockRepository)(nil).GetNodeOfCodeConfig)) } +// GetObjectUrl mocks base method. +func (m *MockRepository) GetObjectUrl(ctx context.Context, objectKey string, opts ...storage.GetOptFn) (string, error) { + m.ctrl.T.Helper() + varargs := []any{ctx, objectKey} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetObjectUrl", varargs...) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetObjectUrl indicates an expected call of GetObjectUrl. +func (mr *MockRepositoryMockRecorder) GetObjectUrl(ctx, objectKey any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, objectKey}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetObjectUrl", reflect.TypeOf((*MockRepository)(nil).GetObjectUrl), varargs...) +} + +// GetOrCreateDynamicConversation mocks base method. +func (m *MockRepository) GetOrCreateDynamicConversation(ctx context.Context, env vo.Env, idGen workflow1.ConversationIDGenerator, meta *vo.CreateDynamicConversation) (int64, int64, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOrCreateDynamicConversation", ctx, env, idGen, meta) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(int64) + ret2, _ := ret[2].(bool) + ret3, _ := ret[3].(error) + return ret0, ret1, ret2, ret3 +} + +// GetOrCreateDynamicConversation indicates an expected call of GetOrCreateDynamicConversation. +func (mr *MockRepositoryMockRecorder) GetOrCreateDynamicConversation(ctx, env, idGen, meta any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOrCreateDynamicConversation", reflect.TypeOf((*MockRepository)(nil).GetOrCreateDynamicConversation), ctx, env, idGen, meta) +} + +// GetOrCreateStaticConversation mocks base method. +func (m *MockRepository) GetOrCreateStaticConversation(ctx context.Context, env vo.Env, idGen workflow1.ConversationIDGenerator, meta *vo.CreateStaticConversation) (int64, int64, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOrCreateStaticConversation", ctx, env, idGen, meta) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(int64) + ret2, _ := ret[2].(bool) + ret3, _ := ret[3].(error) + return ret0, ret1, ret2, ret3 +} + +// GetOrCreateStaticConversation indicates an expected call of GetOrCreateStaticConversation. +func (mr *MockRepositoryMockRecorder) GetOrCreateStaticConversation(ctx, env, idGen, meta any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOrCreateStaticConversation", reflect.TypeOf((*MockRepository)(nil).GetOrCreateStaticConversation), ctx, env, idGen, meta) +} + +// GetStaticConversationByID mocks base method. +func (m *MockRepository) GetStaticConversationByID(ctx context.Context, env vo.Env, appID, connectorID, conversationID int64) (string, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetStaticConversationByID", ctx, env, appID, connectorID, conversationID) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetStaticConversationByID indicates an expected call of GetStaticConversationByID. +func (mr *MockRepositoryMockRecorder) GetStaticConversationByID(ctx, env, appID, connectorID, conversationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStaticConversationByID", reflect.TypeOf((*MockRepository)(nil).GetStaticConversationByID), ctx, env, appID, connectorID, conversationID) +} + +// GetStaticConversationByTemplateID mocks base method. +func (m *MockRepository) GetStaticConversationByTemplateID(ctx context.Context, env vo.Env, userID, connectorID, templateID int64) (*entity.StaticConversation, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetStaticConversationByTemplateID", ctx, env, userID, connectorID, templateID) + ret0, _ := ret[0].(*entity.StaticConversation) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// GetStaticConversationByTemplateID indicates an expected call of GetStaticConversationByTemplateID. +func (mr *MockRepositoryMockRecorder) GetStaticConversationByTemplateID(ctx, env, userID, connectorID, templateID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStaticConversationByTemplateID", reflect.TypeOf((*MockRepository)(nil).GetStaticConversationByTemplateID), ctx, env, userID, connectorID, templateID) +} + // GetTestRunLatestExeID mocks base method. func (m *MockRepository) GetTestRunLatestExeID(ctx context.Context, wfID, uID int64) (int64, error) { m.ctrl.T.Helper() @@ -954,12 +1612,13 @@ func (mr *MockRepositoryMockRecorder) GetTestRunLatestExeID(ctx, wfID, uID any) } // GetVersion mocks base method. -func (m *MockRepository) GetVersion(ctx context.Context, id int64, version string) (*vo.VersionInfo, error) { +func (m *MockRepository) GetVersion(ctx context.Context, id int64, version string) (*vo.VersionInfo, bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetVersion", ctx, id, version) ret0, _ := ret[0].(*vo.VersionInfo) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 } // GetVersion indicates an expected call of GetVersion. @@ -968,6 +1627,21 @@ func (mr *MockRepositoryMockRecorder) GetVersion(ctx, id, version any) *gomock.C return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVersion", reflect.TypeOf((*MockRepository)(nil).GetVersion), ctx, id, version) } +// GetVersionListByConnectorAndWorkflowID mocks base method. +func (m *MockRepository) GetVersionListByConnectorAndWorkflowID(ctx context.Context, connectorID, workflowID int64, limit int) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetVersionListByConnectorAndWorkflowID", ctx, connectorID, workflowID, limit) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetVersionListByConnectorAndWorkflowID indicates an expected call of GetVersionListByConnectorAndWorkflowID. +func (mr *MockRepositoryMockRecorder) GetVersionListByConnectorAndWorkflowID(ctx, connectorID, workflowID, limit any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVersionListByConnectorAndWorkflowID", reflect.TypeOf((*MockRepository)(nil).GetVersionListByConnectorAndWorkflowID), ctx, connectorID, workflowID, limit) +} + // GetWorkflowCancelFlag mocks base method. func (m *MockRepository) GetWorkflowCancelFlag(ctx context.Context, wfExeID int64) (bool, error) { m.ctrl.T.Helper() @@ -1014,6 +1688,36 @@ func (mr *MockRepositoryMockRecorder) IsApplicationConnectorWorkflowVersion(ctx, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsApplicationConnectorWorkflowVersion", reflect.TypeOf((*MockRepository)(nil).IsApplicationConnectorWorkflowVersion), ctx, connectorID, workflowID, version) } +// ListConversationTemplate mocks base method. +func (m *MockRepository) ListConversationTemplate(ctx context.Context, env vo.Env, policy *vo.ListConversationTemplatePolicy) ([]*entity.ConversationTemplate, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListConversationTemplate", ctx, env, policy) + ret0, _ := ret[0].([]*entity.ConversationTemplate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListConversationTemplate indicates an expected call of ListConversationTemplate. +func (mr *MockRepositoryMockRecorder) ListConversationTemplate(ctx, env, policy any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListConversationTemplate", reflect.TypeOf((*MockRepository)(nil).ListConversationTemplate), ctx, env, policy) +} + +// ListDynamicConversation mocks base method. +func (m *MockRepository) ListDynamicConversation(ctx context.Context, env vo.Env, policy *vo.ListConversationPolicy) ([]*entity.DynamicConversation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListDynamicConversation", ctx, env, policy) + ret0, _ := ret[0].([]*entity.DynamicConversation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListDynamicConversation indicates an expected call of ListDynamicConversation. +func (mr *MockRepositoryMockRecorder) ListDynamicConversation(ctx, env, policy any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListDynamicConversation", reflect.TypeOf((*MockRepository)(nil).ListDynamicConversation), ctx, env, policy) +} + // ListInterruptEvents mocks base method. func (m *MockRepository) ListInterruptEvents(ctx context.Context, wfExeID int64) ([]*entity.InterruptEvent, error) { m.ctrl.T.Helper() @@ -1106,6 +1810,21 @@ func (mr *MockRepositoryMockRecorder) MGetReferences(ctx, policy any) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MGetReferences", reflect.TypeOf((*MockRepository)(nil).MGetReferences), ctx, policy) } +// MGetStaticConversation mocks base method. +func (m *MockRepository) MGetStaticConversation(ctx context.Context, env vo.Env, userID, connectorID int64, templateIDs []int64) ([]*entity.StaticConversation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MGetStaticConversation", ctx, env, userID, connectorID, templateIDs) + ret0, _ := ret[0].([]*entity.StaticConversation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MGetStaticConversation indicates an expected call of MGetStaticConversation. +func (mr *MockRepositoryMockRecorder) MGetStaticConversation(ctx, env, userID, connectorID, templateIDs any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MGetStaticConversation", reflect.TypeOf((*MockRepository)(nil).MGetStaticConversation), ctx, env, userID, connectorID, templateIDs) +} + // PopFirstInterruptEvent mocks base method. func (m *MockRepository) PopFirstInterruptEvent(ctx context.Context, wfExeID int64) (*entity.InterruptEvent, bool, error) { m.ctrl.T.Helper() @@ -1192,6 +1911,21 @@ func (mr *MockRepositoryMockRecorder) SetWorkflowCancelFlag(ctx, wfExeID any) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWorkflowCancelFlag", reflect.TypeOf((*MockRepository)(nil).SetWorkflowCancelFlag), ctx, wfExeID) } +// Suggest mocks base method. +func (m *MockRepository) Suggest(ctx context.Context, input *vo.SuggestInfo) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Suggest", ctx, input) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Suggest indicates an expected call of Suggest. +func (mr *MockRepositoryMockRecorder) Suggest(ctx, input any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Suggest", reflect.TypeOf((*MockRepository)(nil).Suggest), ctx, input) +} + // TryLockWorkflowExecution mocks base method. func (m *MockRepository) TryLockWorkflowExecution(ctx context.Context, wfExeID, resumingEventID int64) (bool, entity.WorkflowExecuteStatus, error) { m.ctrl.T.Helper() @@ -1208,6 +1942,62 @@ func (mr *MockRepositoryMockRecorder) TryLockWorkflowExecution(ctx, wfExeID, res return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TryLockWorkflowExecution", reflect.TypeOf((*MockRepository)(nil).TryLockWorkflowExecution), ctx, wfExeID, resumingEventID) } +// UpdateChatFlowRoleConfig mocks base method. +func (m *MockRepository) UpdateChatFlowRoleConfig(ctx context.Context, workflowID int64, chatFlowRole *vo.ChatFlowRoleUpdate) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateChatFlowRoleConfig", ctx, workflowID, chatFlowRole) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateChatFlowRoleConfig indicates an expected call of UpdateChatFlowRoleConfig. +func (mr *MockRepositoryMockRecorder) UpdateChatFlowRoleConfig(ctx, workflowID, chatFlowRole any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateChatFlowRoleConfig", reflect.TypeOf((*MockRepository)(nil).UpdateChatFlowRoleConfig), ctx, workflowID, chatFlowRole) +} + +// UpdateDraftConversationTemplateName mocks base method. +func (m *MockRepository) UpdateDraftConversationTemplateName(ctx context.Context, templateID int64, name string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateDraftConversationTemplateName", ctx, templateID, name) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateDraftConversationTemplateName indicates an expected call of UpdateDraftConversationTemplateName. +func (mr *MockRepositoryMockRecorder) UpdateDraftConversationTemplateName(ctx, templateID, name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDraftConversationTemplateName", reflect.TypeOf((*MockRepository)(nil).UpdateDraftConversationTemplateName), ctx, templateID, name) +} + +// UpdateDynamicConversation mocks base method. +func (m *MockRepository) UpdateDynamicConversation(ctx context.Context, env vo.Env, conversationID, newConversationID int64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateDynamicConversation", ctx, env, conversationID, newConversationID) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateDynamicConversation indicates an expected call of UpdateDynamicConversation. +func (mr *MockRepositoryMockRecorder) UpdateDynamicConversation(ctx, env, conversationID, newConversationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDynamicConversation", reflect.TypeOf((*MockRepository)(nil).UpdateDynamicConversation), ctx, env, conversationID, newConversationID) +} + +// UpdateDynamicConversationNameByID mocks base method. +func (m *MockRepository) UpdateDynamicConversationNameByID(ctx context.Context, env vo.Env, templateID int64, name string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateDynamicConversationNameByID", ctx, env, templateID, name) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateDynamicConversationNameByID indicates an expected call of UpdateDynamicConversationNameByID. +func (mr *MockRepositoryMockRecorder) UpdateDynamicConversationNameByID(ctx, env, templateID, name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDynamicConversationNameByID", reflect.TypeOf((*MockRepository)(nil).UpdateDynamicConversationNameByID), ctx, env, templateID, name) +} + // UpdateFirstInterruptEvent mocks base method. func (m *MockRepository) UpdateFirstInterruptEvent(ctx context.Context, wfExeID int64, event *entity.InterruptEvent) error { m.ctrl.T.Helper() @@ -1264,6 +2054,20 @@ func (mr *MockRepositoryMockRecorder) UpdateNodeExecutionStreaming(ctx, executio return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateNodeExecutionStreaming", reflect.TypeOf((*MockRepository)(nil).UpdateNodeExecutionStreaming), ctx, execution) } +// UpdateStaticConversation mocks base method. +func (m *MockRepository) UpdateStaticConversation(ctx context.Context, env vo.Env, templateID, connectorID, userID, newConversationID int64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateStaticConversation", ctx, env, templateID, connectorID, userID, newConversationID) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateStaticConversation indicates an expected call of UpdateStaticConversation. +func (mr *MockRepositoryMockRecorder) UpdateStaticConversation(ctx, env, templateID, connectorID, userID, newConversationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStaticConversation", reflect.TypeOf((*MockRepository)(nil).UpdateStaticConversation), ctx, env, templateID, connectorID, userID, newConversationID) +} + // UpdateWorkflowDraftTestRunSuccess mocks base method. func (m *MockRepository) UpdateWorkflowDraftTestRunSuccess(ctx context.Context, id int64) error { m.ctrl.T.Helper() diff --git a/backend/pkg/lang/maps/maps.go b/backend/pkg/lang/maps/maps.go index 77c5dddc..ebd71eb4 100644 --- a/backend/pkg/lang/maps/maps.go +++ b/backend/pkg/lang/maps/maps.go @@ -32,3 +32,15 @@ func TransformKey[K1, K2 comparable, V any](m map[K1]V, f func(K1) K2) map[K2]V } return n } + +func TransformKeyWithErrorCheck[K1, K2 comparable, V any](m map[K1]V, f func(K1) (K2, error)) (map[K2]V, error) { + n := make(map[K2]V, len(m)) + for k1, v := range m { + k2, err := f(k1) + if err != nil { + return nil, err + } + n[k2] = v + } + return n, nil +} diff --git a/backend/types/ddl/gen_orm_query.go b/backend/types/ddl/gen_orm_query.go index be0004fc..ecd5fe64 100644 --- a/backend/types/ddl/gen_orm_query.go +++ b/backend/types/ddl/gen_orm_query.go @@ -57,6 +57,7 @@ var path2Table2Columns2Model = map[string]map[string]map[string]any{ "background_image_info_list": []*bot_common.BackgroundImageInfo{}, "database_config": []*bot_common.Database{}, "shortcut_command": []string{}, + "layout_info": &bot_common.LayoutInfo{}, }, "single_agent_version": { // "variable": []*bot_common.Variable{}, @@ -71,6 +72,7 @@ var path2Table2Columns2Model = map[string]map[string]map[string]any{ "background_image_info_list": []*bot_common.BackgroundImageInfo{}, "database_config": []*bot_common.Database{}, "shortcut_command": []string{}, + "layout_info": &bot_common.LayoutInfo{}, }, "single_agent_publish": { "connector_ids": []int64{}, @@ -154,6 +156,17 @@ var path2Table2Columns2Model = map[string]map[string]map[string]any{ "node_execution": {}, "workflow_snapshot": {}, "connector_workflow_version": {}, + + "chat_flow_role_config": {}, + + "app_conversation_template_draft": {}, + "app_conversation_template_online": {}, + + "app_static_conversation_draft": {}, + "app_static_conversation_online": {}, + + "app_dynamic_conversation_draft": {}, + "app_dynamic_conversation_online": {}, }, "domain/openauth/openapiauth/internal/dal/query": { @@ -191,6 +204,9 @@ var path2Table2Columns2Model = map[string]map[string]map[string]any{ "publish_config": appEntity.PublishConfig{}, }, }, + "domain/upload/internal/dal/query": { + "files": {}, + }, } var fieldNullablePath = map[string]bool{ diff --git a/backend/types/errno/conversation.go b/backend/types/errno/conversation.go index 1d8bdbf3..084a9a6a 100644 --- a/backend/types/errno/conversation.go +++ b/backend/types/errno/conversation.go @@ -35,9 +35,31 @@ const ( ErrConversationMessageNotFound = 103200001 ErrAgentRun = 103200002 + + ErrRecordNotFound = 103200003 + + ErrAgentRunWorkflowNotFound = 103200004 + ErrInProgressCanNotCancel = 103200005 ) func init() { + code.Register( + ErrInProgressCanNotCancel, + "in progress can not be cancelled", + code.WithAffectStability(false), + ) + + code.Register( + ErrAgentRunWorkflowNotFound, + "The chatflow is not configured. Please configure it and try again.", + code.WithAffectStability(false), + ) + code.Register( + ErrRecordNotFound, + "record not found or nothing to update", + code.WithAffectStability(false), + ) + code.Register( ErrAgentRun, "Interal Server Error", diff --git a/backend/types/errno/workflow.go b/backend/types/errno/workflow.go index 81c643a6..a43269a5 100644 --- a/backend/types/errno/workflow.go +++ b/backend/types/errno/workflow.go @@ -22,25 +22,34 @@ import ( ) const ( - ErrWorkflowNotPublished = 720702011 - ErrMissingRequiredParam = 720702002 - ErrInterruptNotSupported = 720702078 - ErrInvalidParameter = 720702001 - ErrArrIndexOutOfRange = 720712014 - ErrWorkflowExecuteFail = 720701013 - ErrCodeExecuteFail = 305000002 - ErrQuestionOptionsEmpty = 720712049 - ErrNodeOutputParseFail = 720712023 - ErrWorkflowTimeout = 720702085 - ErrWorkflowNotFound = 720702004 - ErrSerializationDeserializationFail = 720701011 - ErrInternalBadRequest = 720701007 - ErrSchemaConversionFail = 720702089 - ErrWorkflowCompileFail = 720701003 - ErrPluginAPIErr = 720701004 + ErrWorkflowNotPublished = 720702011 + ErrMissingRequiredParam = 720702002 + ErrInterruptNotSupported = 720702078 + ErrInvalidParameter = 720702001 + ErrArrIndexOutOfRange = 720712014 + ErrWorkflowExecuteFail = 720701013 + ErrCodeExecuteFail = 305000002 + ErrQuestionOptionsEmpty = 720712049 + ErrNodeOutputParseFail = 720712023 + ErrWorkflowTimeout = 720702085 + ErrWorkflowNotFound = 720702004 + ErrSerializationDeserializationFail = 720701011 + ErrInternalBadRequest = 720701007 + ErrSchemaConversionFail = 720702089 + ErrWorkflowCompileFail = 720701003 + ErrPluginAPIErr = 720701004 + ErrConversationNameIsDuplicated = 720702200 + ErrConversationOfAppNotFound = 720702201 + ErrConversationNodeInvalidOperation = 720702250 + ErrOnlyDefaultConversationAllowInAgentScenario = 720712033 + ErrConversationNodesNotAvailable = 702093204 ) const ( + ErrConversationNodeOperationFail = 777777782 + ErrMessageNodeOperationFail = 777777781 + ErrChatFlowRoleOperationFail = 777777780 + ErrConversationOfAppOperationFail = 777777779 ErrWorkflowSpecifiedVersionNotFound = 777777778 ErrWorkflowCanceledByUser = 777777777 ErrNodeTimeout = 777777776 @@ -57,6 +66,7 @@ const ( ErrAuthorizationRequired = 777777765 ErrVariablesAPIFail = 777777764 ErrInputFieldMissing = 777777763 + ErrConversationNotFoundForOperation = 777777762 ) // stability problems @@ -122,6 +132,24 @@ func init() { code.WithAffectStability(false), ) + code.Register( + ErrChatFlowRoleOperationFail, + "ChatFlowRole operation failure: {cause}", + code.WithAffectStability(false), + ) + + code.Register( + ErrMessageNodeOperationFail, + "Message node operation failure: {cause}", + code.WithAffectStability(false), + ) + + code.Register( + ErrConversationNodeOperationFail, + "Conversation node operation failure: {cause}", + code.WithAffectStability(false), + ) + code.Register( ErrCodeExecuteFail, "Function execution failed, please check the code of the function. Detail: {detail}", @@ -146,6 +174,12 @@ func init() { code.WithAffectStability(false), ) + code.Register( + ErrConversationOfAppOperationFail, + "Conversation management operation failure: {cause}", + code.WithAffectStability(false), + ) + code.Register( ErrNodeTimeout, "node timeout", @@ -192,6 +226,12 @@ func init() { code.WithAffectStability(false), ) + code.Register( + ErrConversationOfAppNotFound, + "conversation not found, please check if the application conversation exists", + code.WithAffectStability(false), + ) + code.Register( ErrSerializationDeserializationFail, "data serialization/deserialization fail, please contact support team", @@ -240,6 +280,12 @@ func init() { code.WithAffectStability(false), ) + code.Register( + ErrConversationNameIsDuplicated, + "conversation name {name} is duplicated", + code.WithAffectStability(false), + ) + code.Register( ErrPluginIDNotFound, "plugin {id} not found", @@ -275,6 +321,31 @@ func init() { "input field {name} not found", code.WithAffectStability(false), ) + + code.Register( + ErrConversationNotFoundForOperation, + "Conversation not found. Please create a conversation before attempting to perform any related operations.", + code.WithAffectStability(false), + ) + + code.Register( + ErrConversationNodesNotAvailable, + "Conversation nodes are unavailable in agent scenarios and require an app binding.", + code.WithAffectStability(false), + ) + + code.Register( + ErrConversationNodeInvalidOperation, + "Only conversation created through nodes are allowed to be modified or deleted.", + code.WithAffectStability(false), + ) + + code.Register( + ErrOnlyDefaultConversationAllowInAgentScenario, + "Only default conversation allow in agent scenario", + code.WithAffectStability(false), + ) + } var errnoMap = map[int]int{ diff --git a/common/config/subspaces/default/common-versions.json b/common/config/subspaces/default/common-versions.json index 3c365fbc..64a390c4 100644 --- a/common/config/subspaces/default/common-versions.json +++ b/common/config/subspaces/default/common-versions.json @@ -2,10 +2,10 @@ "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/common-versions.schema.json", "ensureConsistentVersions": true, "preferredVersions": { - "@coze-arch/coze-design": "0.0.6-alpha.101d0c" + "@coze-arch/coze-design": "0.0.6-alpha.eec92c" }, "allowedAlternativeVersions": { - "@coze-arch/coze-design": ["0.0.6-alpha.101d0c"], + "@coze-arch/coze-design": ["0.0.6-alpha.eec92c"], "@rspack/cli": ["0.6.0"], "@rspack/core": ["0.6.0", ">=0.7"], "@rspack/plugin-react-refresh": ["0.6.0"], diff --git a/common/config/subspaces/default/pnpm-config.json b/common/config/subspaces/default/pnpm-config.json index cd37ea84..7d2580d4 100644 --- a/common/config/subspaces/default/pnpm-config.json +++ b/common/config/subspaces/default/pnpm-config.json @@ -9,7 +9,7 @@ } }, "globalOverrides": { - "@coze-arch/coze-design": "0.0.6-alpha.101d0c", + "@coze-arch/coze-design": "0.0.6-alpha.eec92c", "inversify": "6.0.2", "sass@1.74.1>immutable": "3.8.2", "web-streams-polyfill": "3.3.2", diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index 7a85941f..31d3cfda 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -9,7 +9,7 @@ neverBuiltDependencies: - canvas overrides: - '@coze-arch/coze-design': 0.0.6-alpha.101d0c + '@coze-arch/coze-design': 0.0.6-alpha.eec92c inversify: 6.0.2 sass@1.74.1>immutable: 3.8.2 web-streams-polyfill: 3.3.2 @@ -61,8 +61,8 @@ importers: specifier: workspace:* version: link:../../packages/arch/bot-md-box-adapter '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/foundation-sdk': specifier: workspace:* version: link:../../packages/arch/foundation-sdk @@ -431,7 +431,7 @@ importers: version: link:../../packages/common/assets '@douyinfe/semi-rspack-plugin': specifier: 2.61.0 - version: 2.61.0(patch_hash=jgvrbizgnsxhugflluipewtgrm)(webpack@5.91.0) + version: 2.61.0(patch_hash=jgvrbizgnsxhugflluipewtgrm)(sass@1.89.2)(webpack@5.91.0) '@rsbuild/core': specifier: ~1.1.0 version: 1.1.13 @@ -1144,8 +1144,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -1408,8 +1408,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-store '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -1508,8 +1508,8 @@ importers: specifier: workspace:* version: link:../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -1787,8 +1787,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/foundation-sdk': specifier: workspace:* version: link:../../../arch/foundation-sdk @@ -2028,8 +2028,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/foundation-sdk': specifier: workspace:* version: link:../../../arch/foundation-sdk @@ -2215,8 +2215,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -2427,8 +2427,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -2536,8 +2536,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -2630,8 +2630,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -2940,8 +2940,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/eslint-config': specifier: workspace:* version: link:../../../config/eslint-config @@ -3027,8 +3027,8 @@ importers: specifier: workspace:* version: link:../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -3400,8 +3400,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -3581,8 +3581,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -3721,8 +3721,8 @@ importers: specifier: workspace:* version: link:../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -3875,8 +3875,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -4098,8 +4098,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -4279,8 +4279,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -4457,8 +4457,8 @@ importers: specifier: workspace:* version: link:../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -4539,8 +4539,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-flags '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -4676,8 +4676,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/eslint-config': specifier: workspace:* version: link:../../../config/eslint-config @@ -4912,8 +4912,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/eslint-config': specifier: workspace:* version: link:../../../config/eslint-config @@ -5178,8 +5178,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -5324,8 +5324,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-store '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -5418,8 +5418,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -5591,8 +5591,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -5700,8 +5700,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-store '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -5912,8 +5912,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/fetch-stream': specifier: workspace:* version: link:../../arch/fetch-stream @@ -6096,8 +6096,8 @@ importers: specifier: workspace:* version: link:../space-bot '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -6235,8 +6235,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/fetch-stream': specifier: workspace:* version: link:../../arch/fetch-stream @@ -6557,8 +6557,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -6718,7 +6718,7 @@ importers: version: 3.3.0(rollup@4.43.0)(typescript@5.8.2)(vite@4.5.14) vitest: specifier: ~3.0.5 - version: 3.0.9(less@4.3.0) + version: 3.0.9(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) webpack: specifier: ~5.91.0 version: 5.91.0(@swc/core@1.12.1) @@ -6826,8 +6826,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -7114,8 +7114,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-store '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -7208,8 +7208,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -8402,8 +8402,8 @@ importers: ../../../frontend/packages/arch/foundation-sdk: devDependencies: '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/eslint-config': specifier: workspace:* version: link:../../../config/eslint-config @@ -8487,8 +8487,8 @@ importers: ../../../frontend/packages/arch/i18n: dependencies: '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-studio/studio-i18n-resource-adapter': specifier: workspace:* version: link:../resources/studio-i18n-resource @@ -9235,8 +9235,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/foundation-sdk': specifier: workspace:* version: link:../../arch/foundation-sdk @@ -9414,8 +9414,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/eslint-config': specifier: workspace:* version: link:../../../../config/eslint-config @@ -9510,8 +9510,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-common/chat-area-utils': specifier: workspace:* version: link:../utils @@ -9861,8 +9861,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-typings '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -9950,13 +9950,13 @@ importers: version: 4.0.0(rollup@4.43.0) '@rspack/cli': specifier: 0.4.0 - version: 0.4.0(@rspack/core@0.4.0)(@swc/core@1.12.1)(esbuild@0.17.19)(react-refresh@0.17.0) + version: 0.4.0(@rspack/core@0.4.0)(@swc/core@1.12.1)(esbuild@0.17.19)(react-refresh@0.14.0) '@rspack/core': specifier: 0.4.0 version: 0.4.0 '@rspack/plugin-react-refresh': specifier: 0.4.0 - version: 0.4.0(react-refresh@0.17.0)(webpack@5.91.0) + version: 0.4.0(react-refresh@0.14.0)(webpack@5.91.0) '@storybook/addon-essentials': specifier: ^7.6.7 version: 7.6.20(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) @@ -10061,7 +10061,7 @@ importers: version: 3.3.7(ts-node@10.9.2) vitest: specifier: ~3.0.5 - version: 3.0.9(less@4.3.0) + version: 3.0.9(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) ../../../frontend/packages/common/chat-area/chat-uikit-shared: dependencies: @@ -10136,8 +10136,8 @@ importers: ../../../frontend/packages/common/chat-area/chat-workflow-render: dependencies: '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -10378,8 +10378,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -10520,8 +10520,8 @@ importers: specifier: workspace:* version: link:../../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -10617,8 +10617,8 @@ importers: specifier: workspace:* version: link:../../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-common/chat-area': specifier: workspace:* version: link:../chat-area @@ -10879,8 +10879,8 @@ importers: specifier: workspace:* version: link:../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -11237,8 +11237,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-api '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) classnames: specifier: ^2.3.2 version: 2.5.1 @@ -11368,8 +11368,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -11483,8 +11483,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -11741,8 +11741,8 @@ importers: specifier: workspace:* version: link:../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -11943,8 +11943,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-hooks '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -12363,8 +12363,8 @@ importers: specifier: workspace:* version: link:../bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -12463,8 +12463,8 @@ importers: specifier: workspace:* version: link:../bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -12639,8 +12639,8 @@ importers: specifier: workspace:* version: link:../bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -12953,7 +12953,7 @@ importers: version: 3.3.7(ts-node@10.9.2) vitest: specifier: ~3.0.5 - version: 3.0.9(less@4.3.0) + version: 3.0.9(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) ../../../frontend/packages/data/common/utils: dependencies: @@ -12967,8 +12967,8 @@ importers: specifier: workspace:* version: link:../../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -13079,8 +13079,8 @@ importers: specifier: workspace:* version: link:../../../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../../arch/i18n @@ -13224,8 +13224,8 @@ importers: specifier: workspace:* version: link:../../../../arch/bot-store '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../../arch/i18n @@ -13595,8 +13595,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -13767,8 +13767,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-store '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -13870,8 +13870,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -14094,8 +14094,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/foundation-sdk': specifier: workspace:* version: link:../../../arch/foundation-sdk @@ -14269,8 +14269,8 @@ importers: specifier: workspace:* version: link:../../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/pdfjs-shadow': specifier: workspace:* version: link:../../../arch/pdfjs-shadow @@ -14345,8 +14345,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -14509,7 +14509,7 @@ importers: version: 3.3.7(ts-node@10.9.2) vitest: specifier: ~3.0.5 - version: 3.0.9(less@4.3.0) + version: 3.0.9(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) ../../../frontend/packages/data/memory/database-creator: dependencies: @@ -14632,8 +14632,8 @@ importers: specifier: workspace:* version: link:../../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -14717,8 +14717,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -14835,8 +14835,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -15083,8 +15083,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -15243,8 +15243,8 @@ importers: specifier: workspace:* version: link:../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -15383,8 +15383,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-typings '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/eslint-config': specifier: workspace:* version: link:../../../../config/eslint-config @@ -15458,8 +15458,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -15544,7 +15544,7 @@ importers: version: 3.3.0(typescript@5.8.2)(vite@4.5.14) vitest: specifier: ~3.0.5 - version: 3.0.9(less@4.3.0) + version: 3.0.9(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) ../../../frontend/packages/devops/mockset-manage: devDependencies: @@ -15894,8 +15894,8 @@ importers: specifier: workspace:* version: link:../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -15985,8 +15985,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/foundation-sdk': specifier: workspace:* version: link:../../arch/foundation-sdk @@ -16070,8 +16070,8 @@ importers: ../../../frontend/packages/foundation/browser-upgrade-banner: dependencies: '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -16219,8 +16219,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-api '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-foundation/account-adapter': specifier: workspace:* version: link:../account-adapter @@ -16268,8 +16268,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -16362,8 +16362,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/foundation-sdk': specifier: workspace:* version: link:../../arch/foundation-sdk @@ -16553,8 +16553,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -16899,8 +16899,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-hooks '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -16981,8 +16981,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -17164,8 +17164,8 @@ importers: specifier: workspace:* version: link:../../components/bot-icons '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -17258,8 +17258,8 @@ importers: specifier: workspace:* version: link:../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -17385,8 +17385,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-store '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -17443,8 +17443,8 @@ importers: ../../../frontend/packages/project-ide/biz-plugin-registry-adapter: dependencies: '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-project-ide/biz-plugin': specifier: workspace:* version: link:../biz-plugin @@ -17519,8 +17519,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-typings '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -17738,8 +17738,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -17832,8 +17832,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/fetch-stream': specifier: workspace:* version: link:../../arch/fetch-stream @@ -17901,8 +17901,8 @@ importers: specifier: workspace:* version: link:../../workflow/base '@coze/api': - specifier: 1.1.0-beta.4 - version: 1.1.0-beta.4(axios@1.10.0) + specifier: 1.3.5 + version: 1.3.5(axios@1.10.0)(debug@4.3.3) ahooks: specifier: 3.7.8 version: 3.7.8(patch_hash=sa4ddrxdk2yhjzudeck6u5ww3i)(react@18.2.0) @@ -18197,13 +18197,13 @@ importers: version: 3.3.0(typescript@5.8.2)(vite@4.5.14) vitest: specifier: ~3.0.5 - version: 3.0.9(less@4.3.0) + version: 3.0.9(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) ../../../frontend/packages/studio/bot-utils: dependencies: '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -18381,8 +18381,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -18541,8 +18541,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-store '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -18888,6 +18888,187 @@ importers: specifier: ~3.0.5 version: 3.0.9(jsdom@22.1.0) + ../../../frontend/packages/studio/open-platform/chat-app-sdk: + dependencies: + '@coze-common/assets': + specifier: workspace:* + version: link:../../../common/assets + classnames: + specifier: ^2.3.2 + version: 2.5.1 + core-js: + specifier: ^3.37.1 + version: 3.43.0 + immer: + specifier: ^10.0.3 + version: 10.1.1 + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 + nanoid: + specifier: ^4.0.2 + version: 4.0.2 + react: + specifier: 18.2.0 + version: 18.2.0 + react-device-detect: + specifier: 2.2.3 + version: 2.2.3(react-dom@18.2.0)(react@18.2.0) + react-dom: + specifier: 18.2.0 + version: 18.2.0(react@18.2.0) + zustand: + specifier: ^4.4.7 + version: 4.5.7(@types/react@18.2.37)(immer@10.1.1)(react@18.2.0) + devDependencies: + '@coze-arch/bot-env': + specifier: workspace:* + version: link:../../../arch/bot-env + '@coze-arch/bot-semi': + specifier: workspace:* + version: link:../../../components/bot-semi + '@coze-arch/bot-typings': + specifier: workspace:* + version: link:../../../arch/bot-typings + '@coze-arch/bot-utils': + specifier: workspace:* + version: link:../../../arch/bot-utils + '@coze-arch/coze-design': + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + '@coze-arch/eslint-config': + specifier: workspace:* + version: link:../../../../config/eslint-config + '@coze-arch/i18n': + specifier: workspace:* + version: link:../../../arch/i18n + '@coze-arch/pkg-root-webpack-plugin': + specifier: workspace:* + version: link:../../../../infra/plugins/pkg-root-webpack-plugin + '@coze-arch/postcss-config': + specifier: workspace:* + version: link:../../../../config/postcss-config + '@coze-arch/stylelint-config': + specifier: workspace:* + version: link:../../../../config/stylelint-config + '@coze-arch/tailwind-config': + specifier: workspace:* + version: link:../../../../config/tailwind-config + '@coze-arch/ts-config': + specifier: workspace:* + version: link:../../../../config/ts-config + '@coze-arch/vitest-config': + specifier: workspace:* + version: link:../../../../config/vitest-config + '@coze-studio/bot-env-adapter': + specifier: workspace:* + version: link:../../../arch/bot-env-adapter + '@coze-studio/open-chat': + specifier: workspace:* + version: link:../open-chat + '@douyinfe/semi-rspack-plugin': + specifier: 2.61.0 + version: 2.61.0(patch_hash=jgvrbizgnsxhugflluipewtgrm)(sass@1.89.2)(webpack@5.91.0) + '@rspack/cli': + specifier: 0.6.0 + version: 0.6.0(@rspack/core@0.6.0)(debug@4.3.3)(webpack@5.91.0) + '@rspack/core': + specifier: 0.6.0 + version: 0.6.0 + '@rspack/plugin-react-refresh': + specifier: 0.6.0 + version: 0.6.0(react-refresh@0.14.0) + '@svgr/webpack': + specifier: ^8.1.0 + version: 8.1.0(typescript@5.8.2) + '@testing-library/jest-dom': + specifier: ^6.1.5 + version: 6.6.3 + '@testing-library/react': + specifier: ^14.1.2 + version: 14.3.1(react-dom@18.2.0)(react@18.2.0) + '@testing-library/react-hooks': + specifier: ^8.0.1 + version: 8.0.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@types/lodash-es': + specifier: ^4.17.10 + version: 4.17.12 + '@types/node': + specifier: 18.18.9 + version: 18.18.9 + '@types/postcss-js': + specifier: ^4.0.2 + version: 4.0.4 + '@types/react': + specifier: 18.2.37 + version: 18.2.37 + '@types/react-dom': + specifier: 18.2.15 + version: 18.2.15 + '@vitest/coverage-v8': + specifier: ~3.0.5 + version: 3.0.9(vitest@3.0.9) + autoprefixer: + specifier: ^10.4.16 + version: 10.4.21(postcss@8.5.6) + concurrently: + specifier: ~8.2.2 + version: 8.2.2 + css-loader: + specifier: ^6.10.0 + version: 6.11.0(@rspack/core@0.6.0)(webpack@5.91.0) + debug: + specifier: 4.3.3 + version: 4.3.3(supports-color@5.5.0) + file-loader: + specifier: ^6.2.0 + version: 6.2.0(webpack@5.91.0) + less: + specifier: ^4.2.0 + version: 4.3.0 + less-loader: + specifier: ~11.1.3 + version: 11.1.4(less@4.3.0)(webpack@5.91.0) + postcss: + specifier: ^8.4.32 + version: 8.5.6 + postcss-loader: + specifier: ^7.3.3 + version: 7.3.4(postcss@8.5.6)(typescript@5.8.2)(webpack@5.91.0) + react-refresh: + specifier: 0.14.0 + version: 0.14.0 + react-router-dom: + specifier: ^6.11.1 + version: 6.30.1(react-dom@18.2.0)(react@18.2.0) + rspack-plugin-dotenv: + specifier: ^0.0.3 + version: 0.0.3(@rspack/core@0.6.0) + sass: + specifier: ^1.69.5 + version: 1.89.2 + sass-loader: + specifier: ^14.1.0 + version: 14.2.1(@rspack/core@0.6.0)(sass@1.89.2)(webpack@5.91.0) + style-loader: + specifier: ^3.3.4 + version: 3.3.4(webpack@5.91.0) + tailwindcss: + specifier: ~3.3.3 + version: 3.3.7(ts-node@10.9.2) + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@18.18.9)(typescript@5.8.2) + typescript: + specifier: 5.8.2 + version: 5.8.2 + vitest: + specifier: ~3.0.5 + version: 3.0.9(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) + webpack: + specifier: ~5.91.0 + version: 5.91.0 + ../../../frontend/packages/studio/open-platform/open-auth: dependencies: '@coze-arch/bot-api': @@ -18897,8 +19078,8 @@ importers: specifier: workspace:* version: link:../../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -19026,15 +19207,114 @@ importers: ../../../frontend/packages/studio/open-platform/open-chat: dependencies: + '@coze-arch/bot-api': + specifier: workspace:* + version: link:../../../arch/bot-api + '@coze-arch/bot-semi': + specifier: workspace:* + version: link:../../../components/bot-semi + '@coze-arch/bot-utils': + specifier: workspace:* + version: link:../../../arch/bot-utils + '@coze-arch/coze-design': + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n - '@coze/chat-sdk': - specifier: 0.1.11-beta.19 - version: 0.1.11-beta.19(@types/react@18.2.37)(axios@1.10.0)(debug@4.3.3)(lodash-es@4.17.21)(react-dom@18.2.0)(react@18.2.0) + '@coze-arch/idl': + specifier: workspace:* + version: link:../../../arch/idl + '@coze-arch/logger': + specifier: workspace:* + version: link:../../../arch/logger + '@coze-common/chat-answer-action': + specifier: workspace:* + version: link:../../../common/chat-area/chat-answer-action + '@coze-common/chat-area': + specifier: workspace:* + version: link:../../../common/chat-area/chat-area + '@coze-common/chat-area-plugin-chat-background': + specifier: workspace:* + version: link:../../../common/chat-area/plugin-chat-background + '@coze-common/chat-area-plugin-message-grab': + specifier: workspace:* + version: link:../../../common/chat-area/plugin-message-grab + '@coze-common/chat-area-plugin-reasoning': + specifier: workspace:* + version: link:../../../common/chat-area/chat-area-plugin-reasoning + '@coze-common/chat-area-plugins-chat-shortcuts': + specifier: workspace:* + version: link:../../../common/chat-area/plugin-chat-shortcuts + '@coze-common/chat-core': + specifier: workspace:* + version: link:../../../common/chat-area/chat-core + '@coze-common/chat-uikit': + specifier: workspace:* + version: link:../../../common/chat-area/chat-uikit + '@coze-common/chat-uikit-shared': + specifier: workspace:* + version: link:../../../common/chat-area/chat-uikit-shared + '@coze-common/chat-workflow-render': + specifier: workspace:* + version: link:../../../common/chat-area/chat-workflow-render + '@coze-studio/file-kit': + specifier: workspace:* + version: link:../../common/file-kit + '@coze-studio/open-env-adapter': + specifier: workspace:* + version: link:../open-env-adapter + '@coze-studio/slardar-adapter': + specifier: workspace:* + version: link:../../../arch/slardar-adapter + '@coze/api': + specifier: 1.3.5 + version: 1.3.5(axios@1.10.0)(debug@4.3.3) + '@douyinfe/semi-icons': + specifier: ^2.36.0 + version: 2.81.0(react@18.2.0) + ahooks: + specifier: 3.7.8 + version: 3.7.8(patch_hash=sa4ddrxdk2yhjzudeck6u5ww3i)(react@18.2.0) + axios: + specifier: ^1.4.0 + version: 1.10.0(debug@4.3.3) + classnames: + specifier: ^2.3.2 + version: 2.5.1 + copy-to-clipboard: + specifier: ^3.3.3 + version: 3.3.3 + dayjs: + specifier: ^1.11.7 + version: 1.11.13 + eventemitter3: + specifier: ^5.0.1 + version: 5.0.1 + immer: + specifier: ^10.0.3 + version: 10.1.1 + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 + nanoid: + specifier: ^4.0.2 + version: 4.0.2 react: specifier: 18.2.0 version: 18.2.0 + react-device-detect: + specifier: 2.2.3 + version: 2.2.3(react-dom@18.2.0)(react@18.2.0) + react-dom: + specifier: 18.2.0 + version: 18.2.0(react@18.2.0) + react-router-dom: + specifier: ^6.11.1 + version: 6.30.1(react-dom@18.2.0)(react@18.2.0) + zustand: + specifier: ^4.4.7 + version: 4.5.7(@types/react@18.2.37)(immer@10.1.1)(react@18.2.0) devDependencies: '@coze-arch/bot-env': specifier: workspace:* @@ -19063,12 +19343,9 @@ importers: '@coze-arch/vitest-config': specifier: workspace:* version: link:../../../../config/vitest-config - '@coze-studio/open-env-adapter': - specifier: workspace:* - version: link:../open-env-adapter '@rspack/plugin-react-refresh': specifier: 0.6.0 - version: 0.6.0 + version: 0.6.0(react-refresh@0.14.0) '@testing-library/jest-dom': specifier: ^6.1.5 version: 6.6.3 @@ -19098,7 +19375,7 @@ importers: version: 8.2.2 css-loader: specifier: ^6.10.0 - version: 6.11.0(webpack@5.91.0) + version: 6.11.0(@rspack/core@0.6.0)(webpack@5.91.0) debug: specifier: 4.3.3 version: 4.3.3(supports-color@5.5.0) @@ -19152,7 +19429,7 @@ importers: version: link:../../../../config/vitest-config '@rspack/plugin-react-refresh': specifier: 0.6.0 - version: 0.6.0 + version: 0.6.0(react-refresh@0.14.0) '@testing-library/jest-dom': specifier: ^6.1.5 version: 6.6.3 @@ -19182,7 +19459,7 @@ importers: version: 8.2.2 css-loader: specifier: ^6.10.0 - version: 6.11.0(webpack@5.91.0) + version: 6.11.0(@rspack/core@0.6.0)(webpack@5.91.0) debug: specifier: 4.3.3 version: 4.3.3(supports-color@5.5.0) @@ -19223,8 +19500,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -19305,8 +19582,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-api '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -19837,8 +20114,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -20083,8 +20360,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-tea '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -20186,8 +20463,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -20470,8 +20747,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -20588,8 +20865,8 @@ importers: specifier: workspace:* version: link:../../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/foundation-sdk': specifier: workspace:* version: link:../../../arch/foundation-sdk @@ -20797,8 +21074,8 @@ importers: specifier: ^6.34.1 version: 6.37.2 '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -20925,8 +21202,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-hooks '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -21240,8 +21517,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/foundation-sdk': specifier: workspace:* version: link:../../arch/foundation-sdk @@ -21439,8 +21716,8 @@ importers: ../../../frontend/packages/workflow/fabric-canvas: dependencies: '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -21569,8 +21846,8 @@ importers: specifier: workspace:* version: link:../../components/bot-semi '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -21745,8 +22022,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -21953,8 +22230,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/hooks': specifier: workspace:* version: link:../../arch/hooks @@ -22404,8 +22681,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-flags '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/report-tti': specifier: workspace:* version: link:../../arch/report-tti @@ -22544,8 +22821,8 @@ importers: ../../../frontend/packages/workflow/setters: dependencies: '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -22701,8 +22978,8 @@ importers: specifier: workspace:* version: link:../../arch/bot-utils '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../arch/i18n @@ -22837,8 +23114,8 @@ importers: ../../../frontend/packages/workflow/test-run-next/form: dependencies: '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -22956,8 +23233,8 @@ importers: specifier: ^6.34.1 version: 6.37.2 '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-editor/editor': specifier: 0.1.0-alpha.dd871b version: 0.1.0-alpha.dd871b(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.1)(@codemirror/merge@6.10.2)(@codemirror/state@6.5.2)(@codemirror/view@6.37.2)(@lezer/common@1.2.3)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)(typescript@5.8.2)(vue@3.5.16) @@ -23014,8 +23291,8 @@ importers: specifier: workspace:* version: link:../../../arch/bot-api '@coze-arch/coze-design': - specifier: 0.0.6-alpha.101d0c - version: 0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) + specifier: 0.0.6-alpha.eec92c + version: 0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) '@coze-arch/i18n': specifier: workspace:* version: link:../../../arch/i18n @@ -23355,7 +23632,7 @@ packages: resolution: {integrity: sha512-F3fZga2uv09wFdEjEQIJxXALXfz0+JaOb7SabvVMmjHxeVTuGW8wgE8Vp1Hd7O+zMTYtcfEISGRzPkeiaPPsvg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.21.5 + '@babel/types': 7.27.6 '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 @@ -23828,7 +24105,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.12.9 - '@babel/helper-plugin-utils': 7.10.4 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.12.9) '@babel/plugin-transform-parameters': 7.27.1(@babel/core@7.12.9) dev: true @@ -25128,13 +25405,13 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.21.9 + '@babel/generator': 7.27.5 '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-function-name': 7.24.7 '@babel/helper-hoist-variables': 7.24.7 '@babel/helper-split-export-declaration': 7.24.7 - '@babel/parser': 7.21.9 - '@babel/types': 7.21.5 + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 debug: 4.3.3(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: @@ -25399,25 +25676,29 @@ packages: dev: true optional: true - /@coze-arch/arco-icon@0.0.6-alpha.101d0c: - resolution: {integrity: sha512-KYy7qlt5tDqPETkZNWev/yihg7/45CUpY5dKGhd/bYPkEx8Ma2gi7FSOtgEgo+oSBcw24X2tS8AU2DyPkfQmOg==} + /@coze-arch/arco-icon@0.0.6-alpha.eec92c(react@18.2.0): + resolution: {integrity: sha512-BzQdOtawSp92Uyg4LFvJSyawaFlJ4xliKvwlzbfMyK1hnFDpLLvx2UVP08o5+UrFBFllUtosErYolnNp+qF9dA==} + peerDependencies: + react: 18.2.0 dependencies: react: 18.2.0 - /@coze-arch/arco-illustration@0.0.12-alpha.101d0c: - resolution: {integrity: sha512-GglmQH0jo9D5y4V/Sc7rutxo0rswL5gJTP5oMNnNVux2IEbG+VF6DzzODXz5MUUnateiZMm1/1mcSu9GOaYZ7A==} + /@coze-arch/arco-illustration@0.0.12-alpha.eec92c(react@18.2.0): + resolution: {integrity: sha512-mTJQ9sHC/MqYxcpkmu/mYkoPOjiA3ICZ+37vysi9FlY0yXsO7aZ0aWceF4ew2vlYIPfrqJ/YTAj5HhGy64vevw==} + peerDependencies: + react: 18.2.0 dependencies: react: 18.2.0 - /@coze-arch/coze-design@0.0.6-alpha.101d0c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-2MBGiRaFQ9ZSNpnNmJSWhZGYDO67aLindJgdzC+vdI8iQZmm8SDa4R8cHsywYZiyM2aM9V6GUAxFVD8DhNT1tg==} + /@coze-arch/coze-design@0.0.6-alpha.eec92c(@types/react@18.2.37)(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-NS7rSPmTuFx+9XKm1eujh7CxOK7DSmD861rLZDDuXndTRrnub8yYlR6nbcLqjKwiZQrRgLr107vXnC51SxS9Qg==} peerDependencies: react: 18.2.0 react-dom: 18.2.0 dependencies: - '@coze-arch/arco-icon': 0.0.6-alpha.101d0c - '@coze-arch/arco-illustration': 0.0.12-alpha.101d0c - '@coze-arch/semi-theme-hand01': 0.0.6-alpha.101d0c + '@coze-arch/arco-icon': 0.0.6-alpha.eec92c(react@18.2.0) + '@coze-arch/arco-illustration': 0.0.12-alpha.eec92c(react@18.2.0) + '@coze-arch/semi-theme-hand01': 0.0.6-alpha.eec92c '@douyinfe/semi-ui': 2.72.3(acorn@8.15.0)(react-dom@18.2.0)(react@18.2.0) ahooks: 3.7.8(patch_hash=sa4ddrxdk2yhjzudeck6u5ww3i)(react@18.2.0) class-variance-authority: 0.7.1 @@ -25441,12 +25722,12 @@ packages: micromatch: 4.0.8 dev: false - /@coze-arch/semi-theme-hand01@0.0.6-alpha.101d0c: - resolution: {integrity: sha512-2eBOCTKQ8IvCqGlvV0H3A34h7nzB/LCKsh7MZuKVAWxB5vjyxtclOmTdUuuDe9yN6r6G7sQR2ibml0gsfvTG/A==} - /@coze-arch/semi-theme-hand01@0.0.6-alpha.346d77: resolution: {integrity: sha512-64YfAtHYVpssHXA4YOVu40CVUpZ+1JqESGA+esVI0lvFoNfdxF8WY5GkuIz5Xlj/73IKfgwIrxUHxM4EosAfMA==} + /@coze-arch/semi-theme-hand01@0.0.6-alpha.eec92c: + resolution: {integrity: sha512-mamZcgMbMHxr34Vp4IKqa0QESrW8OUfUJSze8tqefiUgIOvM0b1OsBvClQHCvkwByI60rENl1p6ff9FOhKyk7w==} + /@coze-editor/code-language-json@0.1.0-alpha.dd871b(@codemirror/language@6.11.1)(@codemirror/state@6.5.2)(@codemirror/view@6.37.2)(@coze-editor/code-language-shared@0.1.0-alpha.dd871b): resolution: {integrity: sha512-wMg/4HMZNKcUFtGTBhZHVb2PyZzGWwIjF7jXpBkNc2y+lKyINblpMdWGhyaovI/JVHxFUTaUysSiwuGoI55Gug==} peerDependencies: @@ -26068,25 +26349,8 @@ packages: - '@lezer/common' dev: false - /@coze/api@1.1.0-beta.4(axios@1.10.0): - resolution: {integrity: sha512-PUDZSFuv0L4UgW8Hq5YY1mSAJx83uGGER5etmFX7E4lX457pyTZb+kPL3szDfS3Hx5gikqcU/gx3w9XkSVEcfw==} - peerDependencies: - axios: ^1.7.1 - dependencies: - axios: 1.10.0(debug@4.3.3) - jsonwebtoken: 9.0.2 - node-fetch: 2.7.0 - reconnecting-websocket: 4.4.0 - uuid: 10.0.0 - ws: 8.18.2 - transitivePeerDependencies: - - bufferutil - - encoding - - utf-8-validate - dev: false - - /@coze/api@1.3.0(axios@1.10.0)(debug@4.3.3): - resolution: {integrity: sha512-kVaV4U83xXFax8yXxQSk1sUM9FdFYGB+ZzQ9eAn02/NwUOv/LpI28OxhYn7muGSJtlJ7TGyUa+deAAScSDnLIQ==} + /@coze/api@1.3.5(axios@1.10.0)(debug@4.3.3): + resolution: {integrity: sha512-/WA1KXMkUCgCKPdA7PHoSOZVlWCMGpsIqsmIcgW7fKGxLJZm7QEAERS7XX6qwflUN+oGXvKk6r3z4IdS+bdbyA==} peerDependencies: axios: ^1.7.1 dependencies: @@ -26107,45 +26371,6 @@ packages: - utf-8-validate dev: false - /@coze/chat-sdk@0.1.11-beta.19(@types/react@18.2.37)(axios@1.10.0)(debug@4.3.3)(lodash-es@4.17.21)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-DRZ1yuWhYiiOZfbAiD9ifCVA7TiC8Wia4SfkJs5O8kCgbujF2nJrekEWUD6nzt9ivY8oT6Ve/eXfq/jK4CSq6g==} - requiresBuild: true - peerDependencies: - axios: ^1.7.1 - lodash-es: ^4.17.21 - react: 18.2.0 - react-dom: 18.2.0 - dependencies: - '@coze/api': 1.3.0(axios@1.10.0)(debug@4.3.3) - axios: 1.10.0(debug@4.3.3) - classnames: 2.5.1 - lodash-es: 4.17.21 - mdast: 3.0.0-alpha.6 - mdast-util-from-markdown: 2.0.2 - mdast-util-gfm-autolink-literal: 2.0.0 - mdast-util-gfm-strikethrough: 2.0.0 - mdast-util-gfm-table: 2.0.0 - mdast-util-gfm-task-list-item: 2.0.0 - mdast-util-radio-list-item: 2.1.0 - micromark-extension-gfm-autolink-literal: 2.1.0 - micromark-extension-gfm-strikethrough: 2.1.0 - micromark-extension-gfm-table: 2.1.1 - micromark-extension-gfm-task-list-item: 2.1.0 - micromark-extension-misc-radio-list-item: 2.1.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - xss: 1.0.15 - zustand: 4.5.7(@types/react@18.2.37)(immer@10.1.1)(react@18.2.0) - transitivePeerDependencies: - - '@types/react' - - bufferutil - - debug - - encoding - - immer - - supports-color - - utf-8-validate - dev: false - /@cspotcode/source-map-support@0.8.1: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -26457,7 +26682,7 @@ packages: dependencies: jsonc-parser: 3.3.1 - /@douyinfe/semi-rspack-plugin@2.61.0(patch_hash=jgvrbizgnsxhugflluipewtgrm)(webpack@5.91.0): + /@douyinfe/semi-rspack-plugin@2.61.0(patch_hash=jgvrbizgnsxhugflluipewtgrm)(sass@1.89.2)(webpack@5.91.0): resolution: {integrity: sha512-OlE7vUMbJa87aTao8DW76aVoOb1vsr8S8pngReO9K2aqKJubF8/BwqwXmIzNb4MTHDkwMu2SizweJGLUw8K8Sw==} peerDependencies: webpack: ^5 @@ -26466,14 +26691,13 @@ packages: css-loader: 4.3.0(webpack@5.91.0) enhanced-resolve: 5.12.0 raw-loader: 4.0.2(webpack@5.91.0) - sass-loader: 10.5.2(webpack@5.91.0) + sass-loader: 10.5.2(sass@1.89.2)(webpack@5.91.0) webpack: 5.91.0 transitivePeerDependencies: - fibers - node-sass - sass - supports-color - dev: false patched: true /@douyinfe/semi-theme-default@2.53.2: @@ -29425,6 +29649,46 @@ packages: webpack: 5.91.0(@swc/core@1.12.1)(esbuild@0.17.19) dev: true + /@pmmmwh/react-refresh-webpack-plugin@0.5.10(react-refresh@0.14.0)(webpack-dev-server@4.13.1)(webpack@5.76.0): + resolution: {integrity: sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA==} + engines: {node: '>= 10.13'} + peerDependencies: + '@types/webpack': 4.x || 5.x + react-refresh: '>=0.10.0 <1.0.0' + sockjs-client: ^1.4.0 + type-fest: 3.13.1 + webpack: '>=4.43.0 <6.0.0' + webpack-dev-server: 3.x || 4.x + webpack-hot-middleware: 2.x + webpack-plugin-serve: 0.x || 1.x + peerDependenciesMeta: + '@types/webpack': + optional: true + sockjs-client: + optional: true + type-fest: + optional: true + webpack-dev-server: + optional: true + webpack-hot-middleware: + optional: true + webpack-plugin-serve: + optional: true + dependencies: + ansi-html-community: 0.0.8 + common-path-prefix: 3.0.0 + core-js-pure: 3.43.0 + error-stack-parser: 2.1.4 + find-up: 5.0.0 + html-entities: 2.6.0 + loader-utils: 2.0.4 + react-refresh: 0.14.0 + schema-utils: 3.3.0 + source-map: 0.7.4 + webpack: 5.76.0(@swc/core@1.12.1)(esbuild@0.17.19) + webpack-dev-server: 4.13.1(webpack@5.76.0) + dev: true + /@pmmmwh/react-refresh-webpack-plugin@0.5.10(react-refresh@0.14.0)(webpack-hot-middleware@2.26.1)(webpack@5.91.0): resolution: {integrity: sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA==} engines: {node: '>= 10.13'} @@ -29465,85 +29729,6 @@ packages: webpack-hot-middleware: 2.26.1 dev: true - /@pmmmwh/react-refresh-webpack-plugin@0.5.10(react-refresh@0.17.0)(webpack-dev-server@4.13.1)(webpack@5.76.0): - resolution: {integrity: sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA==} - engines: {node: '>= 10.13'} - peerDependencies: - '@types/webpack': 4.x || 5.x - react-refresh: '>=0.10.0 <1.0.0' - sockjs-client: ^1.4.0 - type-fest: 3.13.1 - webpack: '>=4.43.0 <6.0.0' - webpack-dev-server: 3.x || 4.x - webpack-hot-middleware: 2.x - webpack-plugin-serve: 0.x || 1.x - peerDependenciesMeta: - '@types/webpack': - optional: true - sockjs-client: - optional: true - type-fest: - optional: true - webpack-dev-server: - optional: true - webpack-hot-middleware: - optional: true - webpack-plugin-serve: - optional: true - dependencies: - ansi-html-community: 0.0.8 - common-path-prefix: 3.0.0 - core-js-pure: 3.43.0 - error-stack-parser: 2.1.4 - find-up: 5.0.0 - html-entities: 2.6.0 - loader-utils: 2.0.4 - react-refresh: 0.17.0 - schema-utils: 3.3.0 - source-map: 0.7.4 - webpack: 5.76.0(@swc/core@1.12.1)(esbuild@0.17.19) - webpack-dev-server: 4.13.1(webpack@5.76.0) - dev: true - - /@pmmmwh/react-refresh-webpack-plugin@0.5.10(react-refresh@0.17.0)(webpack@5.91.0): - resolution: {integrity: sha512-j0Ya0hCFZPd4x40qLzbhGsh9TMtdb+CJQiso+WxLOPNasohq9cc5SNUcwsZaRH6++Xh91Xkm/xHCkuIiIu0LUA==} - engines: {node: '>= 10.13'} - peerDependencies: - '@types/webpack': 4.x || 5.x - react-refresh: '>=0.10.0 <1.0.0' - sockjs-client: ^1.4.0 - type-fest: 3.13.1 - webpack: '>=4.43.0 <6.0.0' - webpack-dev-server: 3.x || 4.x - webpack-hot-middleware: 2.x - webpack-plugin-serve: 0.x || 1.x - peerDependenciesMeta: - '@types/webpack': - optional: true - sockjs-client: - optional: true - type-fest: - optional: true - webpack-dev-server: - optional: true - webpack-hot-middleware: - optional: true - webpack-plugin-serve: - optional: true - dependencies: - ansi-html-community: 0.0.8 - common-path-prefix: 3.0.0 - core-js-pure: 3.43.0 - error-stack-parser: 2.1.4 - find-up: 5.0.0 - html-entities: 2.6.0 - loader-utils: 2.0.4 - react-refresh: 0.17.0 - schema-utils: 3.3.0 - source-map: 0.7.4 - webpack: 5.91.0(@swc/core@1.12.1)(esbuild@0.17.19) - dev: true - /@polka/url@1.0.0-next.29: resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} dev: true @@ -31700,7 +31885,7 @@ packages: '@rspack/binding-win32-x64-msvc': 1.3.15 dev: true - /@rspack/cli@0.4.0(@rspack/core@0.4.0)(@swc/core@1.12.1)(esbuild@0.17.19)(react-refresh@0.17.0): + /@rspack/cli@0.4.0(@rspack/core@0.4.0)(@swc/core@1.12.1)(esbuild@0.17.19)(react-refresh@0.14.0): resolution: {integrity: sha512-bP/DSyUA+qLVVx99cmyXlrCwyID9jmbMfsMwDrn0BmY3IoBFFquhw9/5K1QBBpwlWnjGtntqeZPyAmweZ2loKw==} hasBin: true peerDependencies: @@ -31708,7 +31893,7 @@ packages: dependencies: '@discoveryjs/json-ext': 0.5.7 '@rspack/core': 0.4.0 - '@rspack/dev-server': 0.4.0(@rspack/core@0.4.0)(@swc/core@1.12.1)(esbuild@0.17.19)(react-refresh@0.17.0) + '@rspack/dev-server': 0.4.0(@rspack/core@0.4.0)(@swc/core@1.12.1)(esbuild@0.17.19)(react-refresh@0.14.0) colorette: 2.0.19 exit-hook: 3.2.0 interpret: 3.1.1 @@ -31734,6 +31919,32 @@ packages: - webpack-plugin-serve dev: true + /@rspack/cli@0.6.0(@rspack/core@0.6.0)(debug@4.3.3)(webpack@5.91.0): + resolution: {integrity: sha512-AH71jp0dA3RutQtZmZLWrKvjWs4bgWFHM3oVwqNpN+zG/BeEkavK1Y0KYHmKwUXXUyeFbiMl5H286Kxrj7tHxg==} + hasBin: true + peerDependencies: + '@rspack/core': '>=0.4.0' + dependencies: + '@discoveryjs/json-ext': 0.5.7 + '@rspack/core': 0.6.0 + '@rspack/dev-server': 0.6.0(@rspack/core@0.6.0)(debug@4.3.3)(webpack@5.91.0) + colorette: 2.0.19 + exit-hook: 3.2.0 + interpret: 3.1.1 + rechoir: 0.8.0 + semver: 6.3.1 + webpack-bundle-analyzer: 4.6.1 + yargs: 17.6.2 + transitivePeerDependencies: + - '@types/express' + - bufferutil + - debug + - supports-color + - utf-8-validate + - webpack + - webpack-cli + dev: true + /@rspack/core@0.1.12(webpack-hot-middleware@2.26.1)(webpack@5.91.0): resolution: {integrity: sha512-FoQP24Zp5YEsqS9FemsMuQ4XIj3oxUNjM4qHKwAmhPLx7J9D2TO0DCTcJQMiwfJ2pxWjjFaJRBpdE2iMEG+FZg==} dependencies: @@ -31957,13 +32168,13 @@ packages: - webpack-plugin-serve dev: true - /@rspack/dev-server@0.4.0(@rspack/core@0.4.0)(@swc/core@1.12.1)(esbuild@0.17.19)(react-refresh@0.17.0): + /@rspack/dev-server@0.4.0(@rspack/core@0.4.0)(@swc/core@1.12.1)(esbuild@0.17.19)(react-refresh@0.14.0): resolution: {integrity: sha512-qi70BKcGspjlNpufF+AOf5MHbEGnumMtVTtWzdw8I4xDWr2AguesrOEgACHMJx/EZks9vtbSqepf4anYglvsng==} peerDependencies: '@rspack/core': '*' dependencies: '@rspack/core': 0.4.0 - '@rspack/plugin-react-refresh': 0.4.0(react-refresh@0.17.0)(webpack-dev-server@4.13.1)(webpack@5.76.0) + '@rspack/plugin-react-refresh': 0.4.0(react-refresh@0.14.0)(webpack-dev-server@4.13.1)(webpack@5.76.0) chokidar: 3.5.3 connect-history-api-fallback: 2.0.0 express: 4.18.1 @@ -31991,6 +32202,30 @@ packages: - webpack-plugin-serve dev: true + /@rspack/dev-server@0.6.0(@rspack/core@0.6.0)(debug@4.3.3)(webpack@5.91.0): + resolution: {integrity: sha512-D/Hd8mJX0Mq4XVfr4nfPk8E0398NHaHVRzJ1nriX0uf6LJdpbgc2qSUKuoNhK3wia6Wb055hJQyRZ/e1wBDf5Q==} + peerDependencies: + '@rspack/core': '*' + dependencies: + '@rspack/core': 0.6.0 + chokidar: 3.5.3 + connect-history-api-fallback: 2.0.0 + express: 4.18.1 + http-proxy-middleware: 2.0.6(@types/express@4.17.23)(debug@4.3.3) + mime-types: 2.1.35 + webpack-dev-middleware: 6.0.2(webpack@5.91.0) + webpack-dev-server: 4.13.1(debug@4.3.3)(webpack@5.91.0) + ws: 8.8.1 + transitivePeerDependencies: + - '@types/express' + - bufferutil + - debug + - supports-color + - utf-8-validate + - webpack + - webpack-cli + dev: true + /@rspack/lite-tapable@1.0.1: resolution: {integrity: sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==} engines: {node: '>=16.0.0'} @@ -32029,7 +32264,7 @@ packages: tapable: 2.2.1 dev: true - /@rspack/plugin-react-refresh@0.4.0(react-refresh@0.17.0)(webpack-dev-server@4.13.1)(webpack@5.76.0): + /@rspack/plugin-react-refresh@0.4.0(react-refresh@0.14.0)(webpack-dev-server@4.13.1)(webpack@5.76.0): resolution: {integrity: sha512-yo2FXVj6P2HrBGIxBqqRJQzAdG6CrL0WFE+kQk/Uz+7Ct09nPvl7zRdHE1BUXHnSXIjrMJj4fRmd7hXsmtTHXQ==} peerDependencies: react-refresh: '>=0.10.0 <1.0.0' @@ -32037,8 +32272,8 @@ packages: react-refresh: optional: true dependencies: - '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(react-refresh@0.17.0)(webpack-dev-server@4.13.1)(webpack@5.76.0) - react-refresh: 0.17.0 + '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(react-refresh@0.14.0)(webpack-dev-server@4.13.1)(webpack@5.76.0) + react-refresh: 0.14.0 transitivePeerDependencies: - '@types/webpack' - sockjs-client @@ -32049,7 +32284,7 @@ packages: - webpack-plugin-serve dev: true - /@rspack/plugin-react-refresh@0.4.0(react-refresh@0.17.0)(webpack@5.91.0): + /@rspack/plugin-react-refresh@0.4.0(react-refresh@0.14.0)(webpack@5.91.0): resolution: {integrity: sha512-yo2FXVj6P2HrBGIxBqqRJQzAdG6CrL0WFE+kQk/Uz+7Ct09nPvl7zRdHE1BUXHnSXIjrMJj4fRmd7hXsmtTHXQ==} peerDependencies: react-refresh: '>=0.10.0 <1.0.0' @@ -32057,8 +32292,8 @@ packages: react-refresh: optional: true dependencies: - '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(react-refresh@0.17.0)(webpack@5.91.0) - react-refresh: 0.17.0 + '@pmmmwh/react-refresh-webpack-plugin': 0.5.10(react-refresh@0.14.0)(webpack-hot-middleware@2.26.1)(webpack@5.91.0) + react-refresh: 0.14.0 transitivePeerDependencies: - '@types/webpack' - sockjs-client @@ -32069,13 +32304,15 @@ packages: - webpack-plugin-serve dev: true - /@rspack/plugin-react-refresh@0.6.0: + /@rspack/plugin-react-refresh@0.6.0(react-refresh@0.14.0): resolution: {integrity: sha512-a8iEsSHozQ5jwRb0LjxFbi1FC2rIuDo6z4TRaZtQsaGir1E+mO4z7yff3J2DA7jxH8bp4ecqLeUXBBoRTiGzlw==} peerDependencies: react-refresh: '>=0.10.0 <1.0.0' peerDependenciesMeta: react-refresh: optional: true + dependencies: + react-refresh: 0.14.0 dev: true /@rspack/plugin-react-refresh@1.0.3(react-refresh@0.16.0): @@ -35160,6 +35397,12 @@ packages: resolution: {integrity: sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==} dev: true + /@types/postcss-js@4.0.4: + resolution: {integrity: sha512-j5+GMZVIPCJpRTwI/mO64mCzv7X+zAEq3JP0EV2lo/BrLWHAohEubUJimIAY23rH27+wKce0fXUYjAdBoqlaYw==} + dependencies: + postcss: 8.5.6 + dev: true + /@types/pretty-hrtime@1.0.3: resolution: {integrity: sha512-nj39q0wAIdhwn7DGUyT9irmsKK1tV0bd5WFEhgpqNTMFZ8cE+jieuTphCW0tfdm47S2zVT5mr09B28b1chmQMA==} dev: true @@ -36246,7 +36489,7 @@ packages: '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.4) '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.4) magic-string: 0.27.0 - react-refresh: 0.14.2 + react-refresh: 0.14.0 vite: 4.5.14(@types/node@18.18.9)(less@3.13.1) transitivePeerDependencies: - supports-color @@ -36326,7 +36569,7 @@ packages: '@vitest/spy': 3.0.9 estree-walker: 3.0.3 magic-string: 0.30.17 - vite: 6.3.5(@types/node@18.18.9)(less@3.13.1)(yaml@2.8.0) + vite: 6.3.5(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) dev: true /@vitest/pretty-format@3.0.9: @@ -38786,7 +39029,6 @@ packages: /core-js@3.43.0: resolution: {integrity: sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==} requiresBuild: true - dev: true /core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} @@ -39001,7 +39243,7 @@ packages: webpack: 5.91.0(@swc/core@1.12.1)(esbuild@0.17.19) dev: true - /css-loader@6.11.0(webpack@5.91.0): + /css-loader@6.11.0(@rspack/core@0.6.0)(webpack@5.91.0): resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==} engines: {node: '>= 12.13.0'} peerDependencies: @@ -39013,6 +39255,7 @@ packages: webpack: optional: true dependencies: + '@rspack/core': 0.6.0 icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 postcss-modules-extract-imports: 3.1.0(postcss@8.5.6) @@ -39613,6 +39856,7 @@ packages: /delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + requiresBuild: true /depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} @@ -39928,11 +40172,22 @@ packages: is-obj: 2.0.0 dev: true + /dotenv-defaults@5.0.2: + resolution: {integrity: sha512-y5z4NhblzwNk8XBIYVzjLcFkANK0rxbRDO6kGOfH9QrVYIGVEX52IqwSprKVsaLHM9pnNkCSxazZF/JPydDPvA==} + dependencies: + dotenv: 14.3.2 + dev: true + /dotenv-expand@10.0.0: resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} engines: {node: '>=12'} dev: true + /dotenv@14.3.2: + resolution: {integrity: sha512-vwEppIphpFdvaMCaHfCEv9IgwcxMljMw2TnAQBB4VWPvzXQLTb82jwmdOKzlEVUL3gNFT4l4TPKO+Bn+sqcrVQ==} + engines: {node: '>=12'} + dev: true + /dotenv@16.5.0: resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} engines: {node: '>=12'} @@ -43230,7 +43485,7 @@ packages: dependencies: '@types/express': 4.17.23 '@types/http-proxy': 1.17.16 - http-proxy: 1.18.1 + http-proxy: 1.18.1(debug@4.3.3) is-glob: 4.0.3 is-plain-obj: 3.0.0 micromatch: 4.0.8 @@ -43238,7 +43493,26 @@ packages: - debug dev: true - /http-proxy@1.18.1: + /http-proxy-middleware@2.0.6(@types/express@4.17.23)(debug@4.3.3): + resolution: {integrity: sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/express': ^4.17.13 + peerDependenciesMeta: + '@types/express': + optional: true + dependencies: + '@types/express': 4.17.23 + '@types/http-proxy': 1.17.16 + http-proxy: 1.18.1(debug@4.3.3) + is-glob: 4.0.3 + is-plain-obj: 3.0.0 + micromatch: 4.0.8 + transitivePeerDependencies: + - debug + dev: true + + /http-proxy@1.18.1(debug@4.3.3): resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} engines: {node: '>=8.0.0'} dependencies: @@ -45665,18 +45939,6 @@ packages: '@types/mdast': 4.0.3 unist-util-is: 6.0.0 - /mdast-util-radio-list-item@2.1.0: - resolution: {integrity: sha512-2lDVPrOpu/SNgt3oPuEiI5qARq/0jhvJzRshQmX4qc7EMMV0lHcZcGVN6TU2Nu0XMO4mTCojqF/EvqbhgvWH/g==} - dependencies: - '@types/mdast': 4.0.3 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - micromark-extension-misc-radio-list-item: 2.1.0 - transitivePeerDependencies: - - supports-color - dev: false - /mdast-util-to-hast@10.0.1: resolution: {integrity: sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA==} dependencies: @@ -46105,16 +46367,6 @@ packages: micromark-util-combine-extensions: 2.0.1 micromark-util-types: 2.0.2 - /micromark-extension-misc-radio-list-item@2.1.0: - resolution: {integrity: sha512-FtO7tbi3iGe1VfSqnTNzu80ajIklbWoMULyT7zn0jrZXiPN0uR0viQJ0fa77YBS4PBzSyG3G7xpJ0DuOCwx35A==} - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - dev: false - /micromark-factory-destination@1.1.0: resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==} dependencies: @@ -49187,6 +49439,17 @@ packages: react: 18.2.0 dev: false + /react-device-detect@2.2.3(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-buYY3qrCnQVlIFHrC5UcUoAj7iANs/+srdkwsnNjI7anr3Tt7UY6MqNxtMLlr0tMBied0O49UZVK8XKs3ZIiPw==} + peerDependencies: + react: 18.2.0 + react-dom: 18.2.0 + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + ua-parser-js: 1.0.40 + dev: false + /react-dnd-html5-backend@14.1.0: resolution: {integrity: sha512-6ONeqEC3XKVf4eVmMTe0oPds+c5B9Foyj8p/ZKLb7kL2qh9COYxiBHv3szd6gztqi/efkmriywLUVlPotqoJyw==} dependencies: @@ -49603,11 +49866,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} - engines: {node: '>=0.10.0'} - dev: true - /react-refresh@0.16.0: resolution: {integrity: sha512-FPvF2XxTSikpJxcr+bHut2H4gJ17+18Uy20D5/F+SKzFap62R3cM5wH6b8WN3LyGSYeQilLEcJcR1fjBSI2S1A==} engines: {node: '>=0.10.0'} @@ -50730,6 +50988,15 @@ packages: resolution: {integrity: sha512-8fnO9sQfJ4wxg1rCoden42V9A1TloS8HfgvSXZg8lZjgP74iM+PnlV8Sj4+9ouRP8juWx5qkO/+GFjTKAf2s0Q==} dev: true + /rspack-plugin-dotenv@0.0.3(@rspack/core@0.6.0): + resolution: {integrity: sha512-6LpyPw3Yyg7zHVOKqM5WGvw0ESp/j7w1tb1IrUAOkT48LUf9vbvS5Z5OUgy27kq2O+CY4oTtoHdD+8ZvnXKsCw==} + peerDependencies: + '@rspack/core': ^0.3.10 + dependencies: + '@rspack/core': 0.6.0 + dotenv-defaults: 5.0.2 + dev: true + /run-async@3.0.0: resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} engines: {node: '>=0.12.0'} @@ -50980,36 +51247,39 @@ packages: optional: true dependencies: klona: 2.0.6 - loader-utils: 2.0.0 + loader-utils: 2.0.4 neo-async: 2.6.2 sass: 1.89.2 schema-utils: 3.3.0 semver: 7.7.2 webpack: 5.91.0 - /sass-loader@10.5.2(webpack@5.91.0): - resolution: {integrity: sha512-vMUoSNOUKJILHpcNCCyD23X34gve1TS7Rjd9uXHeKqhvBG39x6XbswFDtpbTElj6XdMFezoWhkh5vtKudf2cgQ==} - engines: {node: '>= 10.13.0'} + /sass-loader@14.2.1(@rspack/core@0.6.0)(sass@1.89.2)(webpack@5.91.0): + resolution: {integrity: sha512-G0VcnMYU18a4N7VoNDegg2OuMjYtxnqzQWARVWCIVSZwJeiL9kg8QMsuIZOplsJgTzZLF6jGxI3AClj8I9nRdQ==} + engines: {node: '>= 18.12.0'} peerDependencies: - fibers: '>= 3.1.0' + '@rspack/core': 0.x || 1.x node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 sass: ^1.3.0 - webpack: ^4.36.0 || ^5.0.0 + sass-embedded: '*' + webpack: ^5.0.0 peerDependenciesMeta: - fibers: + '@rspack/core': optional: true node-sass: optional: true sass: optional: true + sass-embedded: + optional: true + webpack: + optional: true dependencies: - klona: 2.0.6 - loader-utils: 2.0.4 + '@rspack/core': 0.6.0 neo-async: 2.6.2 - schema-utils: 3.3.0 - semver: 7.7.2 + sass: 1.89.2 webpack: 5.91.0 - dev: false + dev: true /sass@1.89.2: resolution: {integrity: sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==} @@ -54459,6 +54729,31 @@ packages: - yaml dev: true + /vite-node@3.0.9(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2): + resolution: {integrity: sha512-w3Gdx7jDcuT9cNn9jExXgOyKmf5UOTb6WMHz8LGAm54eS1Elf5OuBhCxl6zJxGhEeIkgsE1WbHuoL0mj/UXqXg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.3(supports-color@5.5.0) + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.3.5(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + dev: true + /vite-node@3.0.9(@types/node@18.18.9)(sass-embedded@1.89.2): resolution: {integrity: sha512-w3Gdx7jDcuT9cNn9jExXgOyKmf5UOTb6WMHz8LGAm54eS1Elf5OuBhCxl6zJxGhEeIkgsE1WbHuoL0mj/UXqXg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -54509,31 +54804,6 @@ packages: - yaml dev: true - /vite-node@3.0.9(less@4.3.0): - resolution: {integrity: sha512-w3Gdx7jDcuT9cNn9jExXgOyKmf5UOTb6WMHz8LGAm54eS1Elf5OuBhCxl6zJxGhEeIkgsE1WbHuoL0mj/UXqXg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - dependencies: - cac: 6.7.14 - debug: 4.3.3(supports-color@5.5.0) - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 6.3.5(less@4.3.0) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - dev: true - /vite-plugin-svgr@3.3.0(rollup@4.43.0)(typescript@5.8.2)(vite@4.5.14): resolution: {integrity: sha512-vWZMCcGNdPqgziYFKQ3Y95XP0d0YGp28+MM3Dp9cTa/px5CKcHHrIoPl2Jw81rgVm6/ZUNONzjXbZQZ7Kw66og==} peerDependencies: @@ -54705,6 +54975,59 @@ packages: fsevents: 2.3.3 dev: true + /vite@6.3.5(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2): + resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': 18.18.9 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + dependencies: + '@types/node': 18.18.9 + esbuild: 0.25.5 + fdir: 6.4.6(picomatch@4.0.2) + less: 4.3.0 + picomatch: 4.0.2 + postcss: 8.5.6 + rollup: 4.43.0 + sass: 1.89.2 + tinyglobby: 0.2.14 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /vite@6.3.5(@types/node@18.18.9)(sass-embedded@1.89.2): resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -54809,57 +55132,6 @@ packages: fsevents: 2.3.3 dev: true - /vite@6.3.5(less@4.3.0): - resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@types/node': 18.18.9 - jiti: '>=1.21.0' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - dependencies: - esbuild: 0.25.5 - fdir: 6.4.6(picomatch@4.0.2) - less: 4.3.0 - picomatch: 4.0.2 - postcss: 8.5.6 - rollup: 4.43.0 - tinyglobby: 0.2.14 - optionalDependencies: - fsevents: 2.3.3 - dev: true - /vitest@3.0.9(@types/node@18.18.9)(happy-dom@12.10.3): resolution: {integrity: sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -54907,8 +55179,8 @@ packages: tinyexec: 0.3.2 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@18.18.9)(less@3.13.1)(yaml@2.8.0) - vite-node: 3.0.9(@types/node@18.18.9)(less@3.13.1)(yaml@2.8.0) + vite: 6.3.5(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) + vite-node: 3.0.9(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) why-is-node-running: 2.3.0 transitivePeerDependencies: - jiti @@ -54972,8 +55244,8 @@ packages: tinyexec: 0.3.2 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@18.18.9)(less@3.13.1)(yaml@2.8.0) - vite-node: 3.0.9(@types/node@18.18.9)(less@3.13.1)(yaml@2.8.0) + vite: 6.3.5(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) + vite-node: 3.0.9(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) why-is-node-running: 2.3.0 transitivePeerDependencies: - jiti @@ -55119,6 +55391,70 @@ packages: - yaml dev: true + /vitest@3.0.9(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2): + resolution: {integrity: sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': 18.18.9 + '@vitest/browser': 3.0.9 + '@vitest/ui': 3.0.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/node': 18.18.9 + '@vitest/expect': 3.0.9 + '@vitest/mocker': 3.0.9(vite@6.3.5) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.0.9 + '@vitest/snapshot': 3.0.9 + '@vitest/spy': 3.0.9 + '@vitest/utils': 3.0.9 + chai: 5.2.0 + debug: 4.3.3(supports-color@5.5.0) + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.3.5(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) + vite-node: 3.0.9(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + dev: true + /vitest@3.0.9(@types/node@18.18.9)(tsx@4.20.3): resolution: {integrity: sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -55293,71 +55629,8 @@ packages: tinyexec: 0.3.2 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@18.18.9)(less@3.13.1)(yaml@2.8.0) - vite-node: 3.0.9(@types/node@18.18.9)(less@3.13.1)(yaml@2.8.0) - why-is-node-running: 2.3.0 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - dev: true - - /vitest@3.0.9(less@4.3.0): - resolution: {integrity: sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': 18.18.9 - '@vitest/browser': 3.0.9 - '@vitest/ui': 3.0.9 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/debug': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - dependencies: - '@vitest/expect': 3.0.9 - '@vitest/mocker': 3.0.9(vite@6.3.5) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.0.9 - '@vitest/snapshot': 3.0.9 - '@vitest/spy': 3.0.9 - '@vitest/utils': 3.0.9 - chai: 5.2.0 - debug: 4.3.3(supports-color@5.5.0) - expect-type: 1.2.1 - magic-string: 0.30.17 - pathe: 2.0.3 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 6.3.5(less@4.3.0) - vite-node: 3.0.9(less@4.3.0) + vite: 6.3.5(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) + vite-node: 3.0.9(@types/node@18.18.9)(less@4.3.0)(sass@1.89.2) why-is-node-running: 2.3.0 transitivePeerDependencies: - jiti @@ -55536,7 +55809,7 @@ packages: peerDependencies: webpack: ^4.0.0 || ^5.0.0 dependencies: - colorette: 2.0.19 + colorette: 2.0.20 memfs: 3.5.3 mime-types: 2.1.35 range-parser: 1.2.1 @@ -55544,6 +55817,20 @@ packages: webpack: 5.76.0(@swc/core@1.12.1)(esbuild@0.17.19) dev: true + /webpack-dev-middleware@5.3.4(webpack@5.91.0): + resolution: {integrity: sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + dependencies: + colorette: 2.0.20 + memfs: 3.5.3 + mime-types: 2.1.35 + range-parser: 1.2.1 + schema-utils: 4.3.2 + webpack: 5.91.0 + dev: true + /webpack-dev-middleware@6.0.2(webpack@5.76.0): resolution: {integrity: sha512-iOddiJzPcQC6lwOIu60vscbGWth8PCRcWRCwoQcTQf9RMoOWBHg5EyzpGdtSmGMrSPd5vHEfFXmVErQEmkRngQ==} engines: {node: '>= 14.15.0'} @@ -55553,7 +55840,7 @@ packages: webpack: optional: true dependencies: - colorette: 2.0.19 + colorette: 2.0.20 memfs: 3.5.3 mime-types: 2.1.35 range-parser: 1.2.1 @@ -55570,12 +55857,12 @@ packages: webpack: optional: true dependencies: - colorette: 2.0.19 + colorette: 2.0.20 memfs: 3.5.3 mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.3.2 - webpack: 5.91.0(@swc/core@1.12.1)(esbuild@0.17.19) + webpack: 5.91.0 dev: true /webpack-dev-middleware@6.1.3(webpack@5.91.0): @@ -55595,6 +55882,57 @@ packages: webpack: 5.91.0(@swc/core@1.12.1)(esbuild@0.17.19) dev: true + /webpack-dev-server@4.13.1(debug@4.3.3)(webpack@5.91.0): + resolution: {integrity: sha512-5tWg00bnWbYgkN+pd5yISQKDejRBYGEw15RaEEslH+zdbNDxxaZvEAO2WulaSaFKb5n3YG8JXsGaDsut1D0xdA==} + engines: {node: '>= 12.13.0'} + hasBin: true + peerDependencies: + webpack: ^4.37.0 || ^5.0.0 + webpack-cli: '*' + peerDependenciesMeta: + webpack: + optional: true + webpack-cli: + optional: true + dependencies: + '@types/bonjour': 3.5.13 + '@types/connect-history-api-fallback': 1.5.4 + '@types/express': 4.17.23 + '@types/serve-index': 1.9.4 + '@types/serve-static': 1.15.8 + '@types/sockjs': 0.3.36 + '@types/ws': 8.18.1 + ansi-html-community: 0.0.8 + bonjour-service: 1.3.0 + chokidar: 3.6.0 + colorette: 2.0.20 + compression: 1.8.0 + connect-history-api-fallback: 2.0.0 + default-gateway: 6.0.3 + express: 4.18.1 + graceful-fs: 4.2.11 + html-entities: 2.6.0 + http-proxy-middleware: 2.0.6(@types/express@4.17.23)(debug@4.3.3) + ipaddr.js: 2.2.0 + launch-editor: 2.10.0 + open: 8.4.2 + p-retry: 4.6.2 + rimraf: 3.0.2 + schema-utils: 4.3.2 + selfsigned: 2.4.1 + serve-index: 1.9.1 + sockjs: 0.3.24 + spdy: 4.0.2 + webpack: 5.91.0 + webpack-dev-middleware: 5.3.4(webpack@5.91.0) + ws: 8.18.2 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + dev: true + /webpack-dev-server@4.13.1(webpack@5.76.0): resolution: {integrity: sha512-5tWg00bnWbYgkN+pd5yISQKDejRBYGEw15RaEEslH+zdbNDxxaZvEAO2WulaSaFKb5n3YG8JXsGaDsut1D0xdA==} engines: {node: '>= 12.13.0'} @@ -55618,7 +55956,7 @@ packages: ansi-html-community: 0.0.8 bonjour-service: 1.3.0 chokidar: 3.6.0 - colorette: 2.0.19 + colorette: 2.0.20 compression: 1.8.0 connect-history-api-fallback: 2.0.0 default-gateway: 6.0.3 diff --git a/common/config/subspaces/default/repo-state.json b/common/config/subspaces/default/repo-state.json index 5b401f60..26b7b44c 100644 --- a/common/config/subspaces/default/repo-state.json +++ b/common/config/subspaces/default/repo-state.json @@ -1,4 +1,4 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "preferredVersionsHash": "92954d50ed42b259ae8eb030b1cd5a4aa3b9319e" + "preferredVersionsHash": "683e562f9f3624bdf732fefc244584f915173525" } diff --git a/docker/atlas/migrations/20250718104121_update.sql b/docker/atlas/migrations/20250718104121_update.sql new file mode 100644 index 00000000..07233e39 --- /dev/null +++ b/docker/atlas/migrations/20250718104121_update.sql @@ -0,0 +1,100 @@ +-- Create "app_conversation_template_draft" table +CREATE TABLE `opencoze`.`app_conversation_template_draft` ( + `id` bigint unsigned NOT NULL COMMENT "id", + `app_id` bigint unsigned NOT NULL COMMENT "app id", + `space_id` bigint unsigned NOT NULL COMMENT "space id", + `name` varchar(256) NOT NULL COMMENT "conversation name", + `template_id` bigint unsigned NOT NULL COMMENT "template id", + `creator_id` bigint unsigned NOT NULL COMMENT "creator id", + `created_at` bigint unsigned NOT NULL COMMENT "create time in millisecond", + `updated_at` bigint unsigned NULL COMMENT "update time in millisecond", + `deleted_at` datetime(3) NULL COMMENT "delete time in millisecond", + PRIMARY KEY (`id`), + INDEX `idx_space_id_app_id_template_id` (`space_id`, `app_id`, `template_id`) +) CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; +-- Create "app_conversation_template_online" table +CREATE TABLE `opencoze`.`app_conversation_template_online` ( + `id` bigint unsigned NOT NULL COMMENT "id", + `app_id` bigint unsigned NOT NULL COMMENT "app id", + `space_id` bigint unsigned NOT NULL COMMENT "space id", + `name` varchar(256) NOT NULL COMMENT "conversation name", + `template_id` bigint unsigned NOT NULL COMMENT "template id", + `version` varchar(256) NOT NULL COMMENT "version name", + `creator_id` bigint unsigned NOT NULL COMMENT "creator id", + `created_at` bigint unsigned NOT NULL COMMENT "create time in millisecond", + PRIMARY KEY (`id`), + INDEX `idx_space_id_app_id_template_id_version` (`space_id`, `app_id`, `template_id`, `version`) +) CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; +-- Create "app_dynamic_conversation_draft" table +CREATE TABLE `opencoze`.`app_dynamic_conversation_draft` ( + `id` bigint unsigned NOT NULL COMMENT "id", + `app_id` bigint unsigned NOT NULL COMMENT "app id", + `name` varchar(256) NOT NULL COMMENT "conversation name", + `user_id` bigint unsigned NOT NULL COMMENT "user id", + `connector_id` bigint unsigned NOT NULL COMMENT "connector id", + `conversation_id` bigint unsigned NOT NULL COMMENT "conversation id", + `created_at` bigint unsigned NOT NULL COMMENT "create time in millisecond", + `deleted_at` datetime(3) NULL COMMENT "delete time in millisecond", + PRIMARY KEY (`id`), + INDEX `idx_app_id_connector_id_user_id` (`app_id`, `connector_id`, `user_id`), + INDEX `idx_connector_id_user_id_name` (`connector_id`, `user_id`, `name`) +) CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; +-- Create "app_dynamic_conversation_online" table +CREATE TABLE `opencoze`.`app_dynamic_conversation_online` ( + `id` bigint unsigned NOT NULL COMMENT "id", + `app_id` bigint unsigned NOT NULL COMMENT "app id", + `name` varchar(256) NOT NULL COMMENT "conversation name", + `user_id` bigint unsigned NOT NULL COMMENT "user id", + `connector_id` bigint unsigned NOT NULL COMMENT "connector id", + `conversation_id` bigint unsigned NOT NULL COMMENT "conversation id", + `created_at` bigint unsigned NOT NULL COMMENT "create time in millisecond", + `deleted_at` datetime(3) NULL COMMENT "delete time in millisecond", + PRIMARY KEY (`id`), + INDEX `idx_app_id_connector_id_user_id` (`app_id`, `connector_id`, `user_id`), + INDEX `idx_connector_id_user_id_name` (`connector_id`, `user_id`, `name`) +) CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; +-- Create "app_static_conversation_draft" table +CREATE TABLE `opencoze`.`app_static_conversation_draft` ( + `id` bigint unsigned NOT NULL COMMENT "id", + `template_id` bigint unsigned NOT NULL COMMENT "template id", + `user_id` bigint unsigned NOT NULL COMMENT "user id", + `connector_id` bigint unsigned NOT NULL COMMENT "connector id", + `conversation_id` bigint unsigned NOT NULL COMMENT "conversation id", + `created_at` bigint unsigned NOT NULL COMMENT "create time in millisecond", + `deleted_at` datetime(3) NULL COMMENT "delete time in millisecond", + PRIMARY KEY (`id`), + INDEX `idx_connector_id_user_id_template_id` (`connector_id`, `user_id`, `template_id`) +) CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; +-- Create "app_static_conversation_online" table +CREATE TABLE `opencoze`.`app_static_conversation_online` ( + `id` bigint unsigned NOT NULL COMMENT "id", + `template_id` bigint unsigned NOT NULL COMMENT "template id", + `user_id` bigint unsigned NOT NULL COMMENT "user id", + `connector_id` bigint unsigned NOT NULL COMMENT "connector id", + `conversation_id` bigint unsigned NOT NULL COMMENT "conversation id", + `created_at` bigint unsigned NOT NULL COMMENT "create time in millisecond", + PRIMARY KEY (`id`), + INDEX `idx_connector_id_user_id_template_id` (`connector_id`, `user_id`, `template_id`) +) CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; +-- Create "chat_flow_role_config" table +CREATE TABLE `opencoze`.`chat_flow_role_config` ( + `id` bigint unsigned NOT NULL COMMENT "id", + `workflow_id` bigint unsigned NOT NULL COMMENT "workflow id", + `connector_id` bigint unsigned NULL COMMENT "connector id", + `name` varchar(256) NOT NULL COMMENT "role name", + `description` mediumtext NOT NULL COMMENT "role description", + `version` varchar(256) NOT NULL COMMENT "version", + `avatar` varchar(256) NOT NULL COMMENT "avatar uri", + `background_image_info` mediumtext NOT NULL COMMENT "background image information, object structure", + `onboarding_info` mediumtext NOT NULL COMMENT "intro information, object structure", + `suggest_reply_info` mediumtext NOT NULL COMMENT "user suggestions, object structure", + `audio_config` mediumtext NOT NULL COMMENT "agent audio config, object structure", + `user_input_config` varchar(256) NOT NULL COMMENT "user input config, object structure", + `creator_id` bigint unsigned NOT NULL COMMENT "creator id", + `created_at` bigint unsigned NOT NULL COMMENT "create time in millisecond", + `updated_at` bigint unsigned NULL COMMENT "update time in millisecond", + `deleted_at` datetime(3) NULL COMMENT "delete time in millisecond", + PRIMARY KEY (`id`), + INDEX `idx_connector_id_version` (`connector_id`, `version`), + INDEX `idx_workflow_id_version` (`workflow_id`, `version`) +) CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/docker/atlas/migrations/20250812093734_update.sql b/docker/atlas/migrations/20250812093734_update.sql new file mode 100644 index 00000000..89e89dab --- /dev/null +++ b/docker/atlas/migrations/20250812093734_update.sql @@ -0,0 +1,2 @@ +-- Create "files" table +CREATE TABLE `opencoze`.`files` (`id` bigint unsigned NOT NULL COMMENT "id", `name` varchar(255) NOT NULL DEFAULT "" COMMENT "file name", `file_size` bigint unsigned NOT NULL DEFAULT 0 COMMENT "file size", `tos_uri` varchar(1024) NOT NULL DEFAULT "" COMMENT "TOS URI", `status` tinyint unsigned NOT NULL DEFAULT 0 COMMENT "status,0invalid,1valid", `comment` varchar(1024) NOT NULL DEFAULT "" COMMENT "file comment", `source` tinyint unsigned NOT NULL DEFAULT 0 COMMENT "source:1 from API,", `creator_id` varchar(512) NOT NULL DEFAULT "" COMMENT "creator id", `content_type` varchar(255) NOT NULL DEFAULT "" COMMENT "content type", `coze_account_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT "coze account id", `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT "Create Time in Milliseconds", `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT "Update Time in Milliseconds", `deleted_at` datetime(3) NULL COMMENT "Delete Time", PRIMARY KEY (`id`), INDEX `idx_creator_id` (`creator_id`)) CHARSET utf8mb4 COLLATE utf8mb4_general_ci COMMENT "file resource table"; diff --git a/docker/atlas/migrations/20250813081543_update.sql b/docker/atlas/migrations/20250813081543_update.sql new file mode 100644 index 00000000..f8f6a298 --- /dev/null +++ b/docker/atlas/migrations/20250813081543_update.sql @@ -0,0 +1,7 @@ +-- Modify "api_key" table +ALTER TABLE `opencoze`.`api_key` ADD COLUMN `ak_type` tinyint NOT NULL DEFAULT 0 COMMENT "api key type "; +-- Modify "single_agent_draft" table +ALTER TABLE `opencoze`.`single_agent_draft` ADD COLUMN `bot_mode` tinyint NOT NULL DEFAULT 0 COMMENT "bot mode,0:single mode 2:chatflow mode" AFTER `database_config`, ADD COLUMN `layout_info` text NULL COMMENT "chatflow layout info"; +-- Modify "single_agent_version" table +ALTER TABLE `opencoze`.`single_agent_version` ADD COLUMN `bot_mode` tinyint NOT NULL DEFAULT 0 COMMENT "bot mode,0:single mode 2:chatflow mode" AFTER `database_config`, ADD COLUMN `layout_info` text NULL COMMENT "chatflow layout info"; + diff --git a/docker/atlas/migrations/20250822060516_update.sql b/docker/atlas/migrations/20250822060516_update.sql new file mode 100644 index 00000000..bf566f52 --- /dev/null +++ b/docker/atlas/migrations/20250822060516_update.sql @@ -0,0 +1,2 @@ +-- Modify "conversation" table +ALTER TABLE `opencoze`.`conversation` ADD COLUMN `name` varchar(255) NOT NULL DEFAULT "" COMMENT "conversation name" AFTER `id`; diff --git a/docker/atlas/migrations/atlas.sum b/docker/atlas/migrations/atlas.sum index f6f338f6..43935af6 100644 --- a/docker/atlas/migrations/atlas.sum +++ b/docker/atlas/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:3ar6fnSw3e4ni74BcE2N9cIentO27OcfSt465WTv2Po= +h1:pZ9P9pFFqnfVc+o3V1CJLD8Rv3WyFM+mSwqKk1GTRRs= 20250703095335_initial.sql h1:/joaeUTMhXqAEc0KwsSve5+bYM0qPOp+9OizJtsRc+U= 20250703115304_update.sql h1:cbYo6Q6Lh96hB4hu5KW2Nn/Mr0VDpg7a1WPgpIb1SOc= 20250704040445_update.sql h1:QWmoPY//oQ+GFZwET9w/oAWa8mM0KVaD5G8Yiu9bMqY= @@ -6,5 +6,9 @@ h1:3ar6fnSw3e4ni74BcE2N9cIentO27OcfSt465WTv2Po= 20250710100212_update.sql h1:mN/3iKQDoIw2BTkMwWp3I/qOAcVGrQJ5tOJ0OqH4ZWU= 20250711034533_update.sql h1:EWeK//5urS9hJIRCeD3lwQYWNH9AIKEWG9pMLdw7KPc= 20250717125913_update.sql h1:WtPR99RlWZn0rXZsB19qp1hq0FwO5qmFhcTcV6EnFYs= -20250730131847_update.sql h1:qIutMrXtuOA98jeucTFxXck+sQNjNTtIF2apbCYt3IY= -20250802115105_update.sql h1:irreQaMAL0LtXcDlkdHP86C7/0e2HzEVsa1hP/FkZ2M= +20250718104121_update.sql h1:JY7wbfjkmqTUAvTKBm9mC1w/cobKfSGK3eqYBUpDF0k= +20250730131847_update.sql h1:3bSBm4UxtXWKSmnQHcd/T9uqw6riB0vcFNatiR6Ptj8= +20250802115105_update.sql h1:89M8rwxbidK8uZ0UDFS++HQw+m/b0vugbfrF6kQXbEI= +20250812093734_update.sql h1:27fQaPt0LYi1dA7MABvERthVR4pj4MRWFgdRVR3cd6w= +20250813081543_update.sql h1:HyBPu1LVs8oiyABbZDU3fFW0n6MeC7qOpzcHWVkwNVc= +20250822060516_update.sql h1:KoL8FPXw5/JMsJMtJsoGFIc4wYHlngBudeYSz5o4iKU= diff --git a/docker/atlas/opencoze_latest_schema.hcl b/docker/atlas/opencoze_latest_schema.hcl index 32bcf808..c56c599a 100644 --- a/docker/atlas/opencoze_latest_schema.hcl +++ b/docker/atlas/opencoze_latest_schema.hcl @@ -275,6 +275,12 @@ table "api_key" { default = 0 comment = "Used Time in Milliseconds" } + column "ak_type" { + null = false + type = tinyint + default = 0 + comment = "api key type " + } primary_key { columns = [column.id] } @@ -335,6 +341,122 @@ table "app_connector_release_ref" { columns = [column.record_id, column.connector_id] } } +table "app_conversation_template_draft" { + schema = schema.opencoze + column "id" { + null = false + type = bigint + unsigned = true + comment = "id" + } + column "app_id" { + null = false + type = bigint + unsigned = true + comment = "app id" + } + column "space_id" { + null = false + type = bigint + unsigned = true + comment = "space id" + } + column "name" { + null = false + type = varchar(256) + comment = "conversation name" + } + column "template_id" { + null = false + type = bigint + unsigned = true + comment = "template id" + } + column "creator_id" { + null = false + type = bigint + unsigned = true + comment = "creator id" + } + column "created_at" { + null = false + type = bigint + unsigned = true + comment = "create time in millisecond" + } + column "updated_at" { + null = true + type = bigint + unsigned = true + comment = "update time in millisecond" + } + column "deleted_at" { + null = true + type = datetime(3) + comment = "delete time in millisecond" + } + primary_key { + columns = [column.id] + } + index "idx_space_id_app_id_template_id" { + columns = [column.space_id, column.app_id, column.template_id] + } +} +table "app_conversation_template_online" { + schema = schema.opencoze + column "id" { + null = false + type = bigint + unsigned = true + comment = "id" + } + column "app_id" { + null = false + type = bigint + unsigned = true + comment = "app id" + } + column "space_id" { + null = false + type = bigint + unsigned = true + comment = "space id" + } + column "name" { + null = false + type = varchar(256) + comment = "conversation name" + } + column "template_id" { + null = false + type = bigint + unsigned = true + comment = "template id" + } + column "version" { + null = false + type = varchar(256) + comment = "version name" + } + column "creator_id" { + null = false + type = bigint + unsigned = true + comment = "creator id" + } + column "created_at" { + null = false + type = bigint + unsigned = true + comment = "create time in millisecond" + } + primary_key { + columns = [column.id] + } + index "idx_space_id_app_id_template_id_version" { + columns = [column.space_id, column.app_id, column.template_id, column.version] + } +} table "app_draft" { schema = schema.opencoze comment = "Draft Application" @@ -399,6 +521,122 @@ table "app_draft" { columns = [column.id] } } +table "app_dynamic_conversation_draft" { + schema = schema.opencoze + column "id" { + null = false + type = bigint + unsigned = true + comment = "id" + } + column "app_id" { + null = false + type = bigint + unsigned = true + comment = "app id" + } + column "name" { + null = false + type = varchar(256) + comment = "conversation name" + } + column "user_id" { + null = false + type = bigint + unsigned = true + comment = "user id" + } + column "connector_id" { + null = false + type = bigint + unsigned = true + comment = "connector id" + } + column "conversation_id" { + null = false + type = bigint + unsigned = true + comment = "conversation id" + } + column "created_at" { + null = false + type = bigint + unsigned = true + comment = "create time in millisecond" + } + column "deleted_at" { + null = true + type = datetime(3) + comment = "delete time in millisecond" + } + primary_key { + columns = [column.id] + } + index "idx_app_id_connector_id_user_id" { + columns = [column.app_id, column.connector_id, column.user_id] + } + index "idx_connector_id_user_id_name" { + columns = [column.connector_id, column.user_id, column.name] + } +} +table "app_dynamic_conversation_online" { + schema = schema.opencoze + column "id" { + null = false + type = bigint + unsigned = true + comment = "id" + } + column "app_id" { + null = false + type = bigint + unsigned = true + comment = "app id" + } + column "name" { + null = false + type = varchar(256) + comment = "conversation name" + } + column "user_id" { + null = false + type = bigint + unsigned = true + comment = "user id" + } + column "connector_id" { + null = false + type = bigint + unsigned = true + comment = "connector id" + } + column "conversation_id" { + null = false + type = bigint + unsigned = true + comment = "conversation id" + } + column "created_at" { + null = false + type = bigint + unsigned = true + comment = "create time in millisecond" + } + column "deleted_at" { + null = true + type = datetime(3) + comment = "delete time in millisecond" + } + primary_key { + columns = [column.id] + } + index "idx_app_id_connector_id_user_id" { + columns = [column.app_id, column.connector_id, column.user_id] + } + index "idx_connector_id_user_id_name" { + columns = [column.connector_id, column.user_id, column.name] + } +} table "app_release_record" { schema = schema.opencoze comment = "Application Release Record" @@ -506,6 +744,199 @@ table "app_release_record" { columns = [column.app_id, column.version] } } +table "app_static_conversation_draft" { + schema = schema.opencoze + column "id" { + null = false + type = bigint + unsigned = true + comment = "id" + } + column "template_id" { + null = false + type = bigint + unsigned = true + comment = "template id" + } + column "user_id" { + null = false + type = bigint + unsigned = true + comment = "user id" + } + column "connector_id" { + null = false + type = bigint + unsigned = true + comment = "connector id" + } + column "conversation_id" { + null = false + type = bigint + unsigned = true + comment = "conversation id" + } + column "created_at" { + null = false + type = bigint + unsigned = true + comment = "create time in millisecond" + } + column "deleted_at" { + null = true + type = datetime(3) + comment = "delete time in millisecond" + } + primary_key { + columns = [column.id] + } + index "idx_connector_id_user_id_template_id" { + columns = [column.connector_id, column.user_id, column.template_id] + } +} +table "app_static_conversation_online" { + schema = schema.opencoze + column "id" { + null = false + type = bigint + unsigned = true + comment = "id" + } + column "template_id" { + null = false + type = bigint + unsigned = true + comment = "template id" + } + column "user_id" { + null = false + type = bigint + unsigned = true + comment = "user id" + } + column "connector_id" { + null = false + type = bigint + unsigned = true + comment = "connector id" + } + column "conversation_id" { + null = false + type = bigint + unsigned = true + comment = "conversation id" + } + column "created_at" { + null = false + type = bigint + unsigned = true + comment = "create time in millisecond" + } + primary_key { + columns = [column.id] + } + index "idx_connector_id_user_id_template_id" { + columns = [column.connector_id, column.user_id, column.template_id] + } +} +table "chat_flow_role_config" { + schema = schema.opencoze + column "id" { + null = false + type = bigint + unsigned = true + comment = "id" + } + column "workflow_id" { + null = false + type = bigint + unsigned = true + comment = "workflow id" + } + column "connector_id" { + null = true + type = bigint + unsigned = true + comment = "connector id" + } + column "name" { + null = false + type = varchar(256) + comment = "role name" + } + column "description" { + null = false + type = mediumtext + comment = "role description" + } + column "version" { + null = false + type = varchar(256) + comment = "version" + } + column "avatar" { + null = false + type = varchar(256) + comment = "avatar uri" + } + column "background_image_info" { + null = false + type = mediumtext + comment = "background image information, object structure" + } + column "onboarding_info" { + null = false + type = mediumtext + comment = "intro information, object structure" + } + column "suggest_reply_info" { + null = false + type = mediumtext + comment = "user suggestions, object structure" + } + column "audio_config" { + null = false + type = mediumtext + comment = "agent audio config, object structure" + } + column "user_input_config" { + null = false + type = varchar(256) + comment = "user input config, object structure" + } + column "creator_id" { + null = false + type = bigint + unsigned = true + comment = "creator id" + } + column "created_at" { + null = false + type = bigint + unsigned = true + comment = "create time in millisecond" + } + column "updated_at" { + null = true + type = bigint + unsigned = true + comment = "update time in millisecond" + } + column "deleted_at" { + null = true + type = datetime(3) + comment = "delete time in millisecond" + } + primary_key { + columns = [column.id] + } + index "idx_connector_id_version" { + columns = [column.connector_id, column.version] + } + index "idx_workflow_id_version" { + columns = [column.workflow_id, column.version] + } +} table "connector_workflow_version" { schema = schema.opencoze comment = "connector workflow version" @@ -566,6 +997,12 @@ table "conversation" { comment = "id" auto_increment = true } + column "name" { + null = true + type = varchar(255) + default = "" + comment = "conversation name" + } column "connector_id" { null = false type = bigint @@ -850,6 +1287,100 @@ table "draft_database_info" { columns = [column.space_id, column.app_id, column.creator_id, column.deleted_at] } } +table "files" { + schema = schema.opencoze + comment = "file resource table" + collate = "utf8mb4_general_ci" + column "id" { + null = false + type = bigint + unsigned = true + comment = "id" + } + column "name" { + null = false + type = varchar(255) + default = "" + comment = "file name" + } + column "file_size" { + null = false + type = bigint + default = 0 + unsigned = true + comment = "file size" + } + column "tos_uri" { + null = false + type = varchar(1024) + default = "" + comment = "TOS URI" + } + column "status" { + null = false + type = tinyint + default = 0 + unsigned = true + comment = "status,0invalid,1valid" + } + column "comment" { + null = false + type = varchar(1024) + default = "" + comment = "file comment" + } + column "source" { + null = false + type = tinyint + default = 0 + unsigned = true + comment = "source:1 from API," + } + column "creator_id" { + null = false + type = varchar(512) + default = "" + comment = "creator id" + } + column "content_type" { + null = false + type = varchar(255) + default = "" + comment = "content type" + } + column "coze_account_id" { + null = false + type = bigint + default = 0 + unsigned = true + comment = "coze account id" + } + column "created_at" { + null = false + type = bigint + default = 0 + unsigned = true + comment = "Create Time in Milliseconds" + } + column "updated_at" { + null = false + type = bigint + default = 0 + unsigned = true + comment = "Update Time in Milliseconds" + } + column "deleted_at" { + null = true + type = datetime(3) + comment = "Delete Time" + } + primary_key { + columns = [column.id] + } + index "idx_creator_id" { + columns = [column.creator_id] + } +} table "knowledge" { schema = schema.opencoze comment = "knowledge tabke" @@ -2608,6 +3139,17 @@ table "single_agent_draft" { type = json comment = "Agent Database Base Configuration" } + column "bot_mode" { + null = false + type = tinyint + default = 0 + comment = "bot mode,0:single mode 2:chatflow mode" + } + column "layout_info" { + null = true + type = text + comment = "chatflow layout info" + } column "shortcut_command" { null = true type = json @@ -2769,6 +3311,17 @@ table "single_agent_version" { unsigned = true comment = "Create Time in Milliseconds" } + column "bot_mode" { + null = false + type = tinyint + default = 0 + comment = "bot mode,0:single mode 2:chatflow mode" + } + column "layout_info" { + null = true + type = text + comment = "chatflow layout info" + } column "updated_at" { null = false type = bigint diff --git a/docker/docker-compose-debug.yml b/docker/docker-compose-debug.yml index 9d9f0cd6..32004a06 100755 --- a/docker/docker-compose-debug.yml +++ b/docker/docker-compose-debug.yml @@ -18,9 +18,52 @@ services: - '3306:3306' volumes: - ./data/mysql:/var/lib/mysql - command: - - --character-set-server=utf8mb4 - - --collation-server=utf8mb4_unicode_ci + - ./volumes/mysql/schema.sql:/docker-entrypoint-initdb.d/init.sql + - ./atlas/migrations:/atlas-migrations:ro + entrypoint: + - bash + - -c + - | + /usr/local/bin/docker-entrypoint.sh mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci & + MYSQL_PID=$$! + + echo 'Waiting for MySQL to start...' + until mysqladmin ping -h localhost -u root -p$${MYSQL_ROOT_PASSWORD} --silent 2>/dev/null; do + echo 'MySQL is starting...' + sleep 2 + done + + echo 'Waiting for workflow_version table to exist...' + while true; do + if mysql -h localhost -u root -p$${MYSQL_ROOT_PASSWORD} $${MYSQL_DATABASE} -e "SHOW TABLES LIKE 'workflow_version';" 2>/dev/null | grep -q "workflow_version"; then + echo 'Found workflow_version table, continuing...' + break + else + echo 'workflow_version table not found, retrying in 2 seconds...' + sleep 2 + fi + done + + echo 'MySQL is ready, installing Atlas CLI...' + + if ! command -v atlas >/dev/null 2>&1; then + echo 'Installing Atlas CLI...' + curl -sSf https://atlasgo.sh | sh -s -- -y --community + export PATH=$$PATH:/root/.local/bin + else + echo 'Atlas CLI already installed' + fi + + if [ -d '/atlas-migrations' ] && [ "$$(ls -A /atlas-migrations)" ]; then + echo 'Running Atlas migrations...' + ATLAS_URL="mysql://$${MYSQL_USER}:$${MYSQL_PASSWORD}@localhost:3306/$${MYSQL_DATABASE}" + atlas migrate apply --url "$$ATLAS_URL" --dir "file:///atlas-migrations" --allow-dirty + echo 'Atlas migrations completed successfully' + else + echo 'No migrations found' + fi + wait $$MYSQL_PID + healthcheck: test: [ diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 9e35a951..f31c9454 100755 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -18,9 +18,50 @@ services: volumes: - ./data/mysql:/var/lib/mysql - ./volumes/mysql/schema.sql:/docker-entrypoint-initdb.d/init.sql - command: - - --character-set-server=utf8mb4 - - --collation-server=utf8mb4_unicode_ci + - ./atlas/migrations:/atlas-migrations:ro + entrypoint: + - bash + - -c + - | + /usr/local/bin/docker-entrypoint.sh mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci & + MYSQL_PID=$$! + + echo 'Waiting for MySQL to start...' + until mysqladmin ping -h localhost -u root -p$${MYSQL_ROOT_PASSWORD} --silent 2>/dev/null; do + echo 'MySQL is starting...' + sleep 2 + done + + echo 'Waiting for workflow_version table to exist...' + while true; do + if mysql -h localhost -u root -p$${MYSQL_ROOT_PASSWORD} $${MYSQL_DATABASE} -e "SHOW TABLES LIKE 'workflow_version';" 2>/dev/null | grep -q "workflow_version"; then + echo 'Found workflow_version table, continuing...' + break + else + echo 'workflow_version table not found, retrying in 2 seconds...' + sleep 2 + fi + done + + echo 'MySQL is ready, installing Atlas CLI...' + + if ! command -v atlas >/dev/null 2>&1; then + echo 'Installing Atlas CLI...' + curl -sSf https://atlasgo.sh | sh -s -- -y --community + export PATH=$$PATH:/root/.local/bin + else + echo 'Atlas CLI already installed' + fi + + if [ -d '/atlas-migrations' ] && [ "$$(ls -A /atlas-migrations)" ]; then + echo 'Running Atlas migrations...' + ATLAS_URL="mysql://$${MYSQL_USER}:$${MYSQL_PASSWORD}@localhost:3306/$${MYSQL_DATABASE}" + atlas migrate apply --url "$$ATLAS_URL" --dir "file:///atlas-migrations" --allow-dirty + echo 'Atlas migrations completed successfully' + else + echo 'No migrations found' + fi + wait $$MYSQL_PID healthcheck: test: [ diff --git a/docker/volumes/mysql/schema.sql b/docker/volumes/mysql/schema.sql index 897aede1..9f0ba135 100755 --- a/docker/volumes/mysql/schema.sql +++ b/docker/volumes/mysql/schema.sql @@ -1,95 +1,963 @@ SET NAMES utf8mb4; CREATE DATABASE IF NOT EXISTS opencoze COLLATE utf8mb4_unicode_ci; -- Create 'agent_to_database' table -CREATE TABLE IF NOT EXISTS `agent_to_database` (`id` bigint unsigned NOT NULL COMMENT 'ID', `agent_id` bigint unsigned NOT NULL COMMENT 'Agent ID', `database_id` bigint unsigned NOT NULL COMMENT 'ID of database_info', `is_draft` bool NOT NULL COMMENT 'Is draft', `prompt_disable` bool NOT NULL DEFAULT 0 COMMENT 'Support prompt calls: 1 not supported, 0 supported', PRIMARY KEY (`id`), UNIQUE INDEX `uniq_agent_db_draft` (`agent_id`, `database_id`, `is_draft`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'agent_to_database info'; +CREATE TABLE IF NOT EXISTS `agent_to_database` ( + `id` bigint unsigned NOT NULL COMMENT 'ID', + `agent_id` bigint unsigned NOT NULL COMMENT 'Agent ID', + `database_id` bigint unsigned NOT NULL COMMENT 'ID of database_info', + `is_draft` bool NOT NULL COMMENT 'Is draft', + `prompt_disable` bool NOT NULL DEFAULT 0 COMMENT 'Support prompt calls: 1 not supported, 0 supported', + PRIMARY KEY (`id`), + UNIQUE INDEX `uniq_agent_db_draft` (`agent_id`, `database_id`, `is_draft`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'agent_to_database info'; -- Create 'agent_tool_draft' table -CREATE TABLE IF NOT EXISTS `agent_tool_draft` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Primary Key ID', `agent_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Agent ID', `plugin_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', `tool_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Tool ID', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `sub_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Sub URL Path', `method` varchar(64) NOT NULL DEFAULT '' COMMENT 'HTTP Request Method', `tool_name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Tool Name', `tool_version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Tool Version, e.g. v1.0.0', `operation` json NULL COMMENT 'Tool Openapi Operation Schema', PRIMARY KEY (`id`), INDEX `idx_agent_plugin_tool` (`agent_id`, `plugin_id`, `tool_id`), INDEX `idx_agent_tool_bind` (`agent_id`, `created_at`), UNIQUE INDEX `uniq_idx_agent_tool_id` (`agent_id`, `tool_id`), UNIQUE INDEX `uniq_idx_agent_tool_name` (`agent_id`, `tool_name`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Draft Agent Tool'; +CREATE TABLE IF NOT EXISTS `agent_tool_draft` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Primary Key ID', + `agent_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Agent ID', + `plugin_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', + `tool_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Tool ID', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `sub_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Sub URL Path', + `method` varchar(64) NOT NULL DEFAULT '' COMMENT 'HTTP Request Method', + `tool_name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Tool Name', + `tool_version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Tool Version, e.g. v1.0.0', + `operation` json NULL COMMENT 'Tool Openapi Operation Schema', + PRIMARY KEY (`id`), + INDEX `idx_agent_plugin_tool` (`agent_id`, `plugin_id`, `tool_id`), + INDEX `idx_agent_tool_bind` (`agent_id`, `created_at`), + UNIQUE INDEX `uniq_idx_agent_tool_id` (`agent_id`, `tool_id`), + UNIQUE INDEX `uniq_idx_agent_tool_name` (`agent_id`, `tool_name`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Draft Agent Tool'; -- Create 'agent_tool_version' table -CREATE TABLE IF NOT EXISTS `agent_tool_version` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Primary Key ID', `agent_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Agent ID', `plugin_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', `tool_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Tool ID', `agent_version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Agent Tool Version', `tool_name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Tool Name', `tool_version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Tool Version, e.g. v1.0.0', `sub_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Sub URL Path', `method` varchar(64) NOT NULL DEFAULT '' COMMENT 'HTTP Request Method', `operation` json NULL COMMENT 'Tool Openapi Operation Schema', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', PRIMARY KEY (`id`), INDEX `idx_agent_tool_id_created_at` (`agent_id`, `tool_id`, `created_at`), INDEX `idx_agent_tool_name_created_at` (`agent_id`, `tool_name`, `created_at`), UNIQUE INDEX `uniq_idx_agent_tool_id_agent_version` (`agent_id`, `tool_id`, `agent_version`), UNIQUE INDEX `uniq_idx_agent_tool_name_agent_version` (`agent_id`, `tool_name`, `agent_version`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Agent Tool Version'; +CREATE TABLE IF NOT EXISTS `agent_tool_version` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Primary Key ID', + `agent_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Agent ID', + `plugin_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', + `tool_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Tool ID', + `agent_version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Agent Tool Version', + `tool_name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Tool Name', + `tool_version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Tool Version, e.g. v1.0.0', + `sub_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Sub URL Path', + `method` varchar(64) NOT NULL DEFAULT '' COMMENT 'HTTP Request Method', + `operation` json NULL COMMENT 'Tool Openapi Operation Schema', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + PRIMARY KEY (`id`), + INDEX `idx_agent_tool_id_created_at` (`agent_id`, `tool_id`, `created_at`), + INDEX `idx_agent_tool_name_created_at` (`agent_id`, `tool_name`, `created_at`), + UNIQUE INDEX `uniq_idx_agent_tool_id_agent_version` (`agent_id`, `tool_id`, `agent_version`), + UNIQUE INDEX `uniq_idx_agent_tool_name_agent_version` (`agent_id`, `tool_name`, `agent_version`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Agent Tool Version'; -- Create 'api_key' table -CREATE TABLE IF NOT EXISTS `api_key` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key ID', `api_key` varchar(255) NOT NULL DEFAULT '' COMMENT 'API Key hash', `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'API Key Name', `status` tinyint NOT NULL DEFAULT 0 COMMENT '0 normal, 1 deleted', `user_id` bigint NOT NULL DEFAULT 0 COMMENT 'API Key Owner', `expired_at` bigint NOT NULL DEFAULT 0 COMMENT 'API Key Expired Time', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `last_used_at` bigint NOT NULL DEFAULT 0 COMMENT 'Used Time in Milliseconds', PRIMARY KEY (`id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'api key table'; +CREATE TABLE IF NOT EXISTS `api_key` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key ID', + `api_key` varchar(255) NOT NULL DEFAULT '' COMMENT 'API Key hash', + `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'API Key Name', + `status` tinyint NOT NULL DEFAULT 0 COMMENT '0 normal, 1 deleted', + `user_id` bigint NOT NULL DEFAULT 0 COMMENT 'API Key Owner', + `expired_at` bigint NOT NULL DEFAULT 0 COMMENT 'API Key Expired Time', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `last_used_at` bigint NOT NULL DEFAULT 0 COMMENT 'Used Time in Milliseconds', + `ak_type` tinyint NOT NULL DEFAULT 0 COMMENT 'api key type ', + PRIMARY KEY (`id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'api key table'; -- Create 'app_connector_release_ref' table -CREATE TABLE IF NOT EXISTS `app_connector_release_ref` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Primary Key', `record_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Publish Record ID', `connector_id` bigint unsigned NULL COMMENT 'Publish Connector ID', `publish_config` json NULL COMMENT 'Publish Configuration', `publish_status` tinyint NOT NULL DEFAULT 0 COMMENT 'Publish Status', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', PRIMARY KEY (`id`), UNIQUE INDEX `uniq_record_connector` (`record_id`, `connector_id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Connector Release Record Reference'; +CREATE TABLE IF NOT EXISTS `app_connector_release_ref` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Primary Key', + `record_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Publish Record ID', + `connector_id` bigint unsigned NULL COMMENT 'Publish Connector ID', + `publish_config` json NULL COMMENT 'Publish Configuration', + `publish_status` tinyint NOT NULL DEFAULT 0 COMMENT 'Publish Status', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + PRIMARY KEY (`id`), + UNIQUE INDEX `uniq_record_connector` (`record_id`, `connector_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Connector Release Record Reference'; +-- Create 'app_conversation_template_draft' table +CREATE TABLE IF NOT EXISTS `app_conversation_template_draft` ( + `id` bigint unsigned NOT NULL COMMENT 'id', + `app_id` bigint unsigned NOT NULL COMMENT 'app id', + `space_id` bigint unsigned NOT NULL COMMENT 'space id', + `name` varchar(256) NOT NULL COMMENT 'conversation name', + `template_id` bigint unsigned NOT NULL COMMENT 'template id', + `creator_id` bigint unsigned NOT NULL COMMENT 'creator id', + `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', + `updated_at` bigint unsigned NULL COMMENT 'update time in millisecond', + `deleted_at` datetime(3) NULL COMMENT 'delete time in millisecond', + PRIMARY KEY (`id`), + INDEX `idx_space_id_app_id_template_id` (`space_id`, `app_id`, `template_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; +-- Create 'app_conversation_template_online' table +CREATE TABLE IF NOT EXISTS `app_conversation_template_online` ( + `id` bigint unsigned NOT NULL COMMENT 'id', + `app_id` bigint unsigned NOT NULL COMMENT 'app id', + `space_id` bigint unsigned NOT NULL COMMENT 'space id', + `name` varchar(256) NOT NULL COMMENT 'conversation name', + `template_id` bigint unsigned NOT NULL COMMENT 'template id', + `version` varchar(256) NOT NULL COMMENT 'version name', + `creator_id` bigint unsigned NOT NULL COMMENT 'creator id', + `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', + PRIMARY KEY (`id`), + INDEX `idx_space_id_app_id_template_id_version` (`space_id`, `app_id`, `template_id`, `version`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; -- Create 'app_draft' table -CREATE TABLE IF NOT EXISTS `app_draft` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'APP ID', `space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Space ID', `owner_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner ID', `icon_uri` varchar(512) NOT NULL DEFAULT '' COMMENT 'Icon URI', `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Application Name', `description` text NULL COMMENT 'Application Description', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `deleted_at` datetime NULL COMMENT 'Delete Time', PRIMARY KEY (`id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Draft Application'; +CREATE TABLE IF NOT EXISTS `app_draft` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'APP ID', + `space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Space ID', + `owner_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner ID', + `icon_uri` varchar(512) NOT NULL DEFAULT '' COMMENT 'Icon URI', + `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Application Name', + `description` text NULL COMMENT 'Application Description', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `deleted_at` datetime NULL COMMENT 'Delete Time', + PRIMARY KEY (`id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Draft Application'; +-- Create 'app_dynamic_conversation_draft' table +CREATE TABLE IF NOT EXISTS `app_dynamic_conversation_draft` ( + `id` bigint unsigned NOT NULL COMMENT 'id', + `app_id` bigint unsigned NOT NULL COMMENT 'app id', + `name` varchar(256) NOT NULL COMMENT 'conversation name', + `user_id` bigint unsigned NOT NULL COMMENT 'user id', + `connector_id` bigint unsigned NOT NULL COMMENT 'connector id', + `conversation_id` bigint unsigned NOT NULL COMMENT 'conversation id', + `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', + `deleted_at` datetime(3) NULL COMMENT 'delete time in millisecond', + PRIMARY KEY (`id`), + INDEX `idx_app_id_connector_id_user_id` (`app_id`, `connector_id`, `user_id`), + INDEX `idx_connector_id_user_id_name` (`connector_id`, `user_id`, `name`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; +-- Create 'app_dynamic_conversation_online' table +CREATE TABLE IF NOT EXISTS `app_dynamic_conversation_online` ( + `id` bigint unsigned NOT NULL COMMENT 'id', + `app_id` bigint unsigned NOT NULL COMMENT 'app id', + `name` varchar(256) NOT NULL COMMENT 'conversation name', + `user_id` bigint unsigned NOT NULL COMMENT 'user id', + `connector_id` bigint unsigned NOT NULL COMMENT 'connector id', + `conversation_id` bigint unsigned NOT NULL COMMENT 'conversation id', + `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', + `deleted_at` datetime(3) NULL COMMENT 'delete time in millisecond', + PRIMARY KEY (`id`), + INDEX `idx_app_id_connector_id_user_id` (`app_id`, `connector_id`, `user_id`), + INDEX `idx_connector_id_user_id_name` (`connector_id`, `user_id`, `name`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; -- Create 'app_release_record' table -CREATE TABLE IF NOT EXISTS `app_release_record` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Publish Record ID', `app_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Application ID', `space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Space ID', `owner_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner ID', `icon_uri` varchar(512) NOT NULL DEFAULT '' COMMENT 'Icon URI', `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Application Name', `description` text NULL COMMENT 'Application Description', `connector_ids` json NULL COMMENT 'Publish Connector IDs', `extra_info` json NULL COMMENT 'Publish Extra Info', `version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Release Version', `version_desc` text NULL COMMENT 'Version Description', `publish_status` tinyint NOT NULL DEFAULT 0 COMMENT 'Publish Status', `publish_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Publish Time in Milliseconds', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', PRIMARY KEY (`id`), INDEX `idx_app_publish_at` (`app_id`, `publish_at`), UNIQUE INDEX `uniq_idx_app_version_connector` (`app_id`, `version`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Application Release Record'; +CREATE TABLE IF NOT EXISTS `app_release_record` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Publish Record ID', + `app_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Application ID', + `space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Space ID', + `owner_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner ID', + `icon_uri` varchar(512) NOT NULL DEFAULT '' COMMENT 'Icon URI', + `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Application Name', + `description` text NULL COMMENT 'Application Description', + `connector_ids` json NULL COMMENT 'Publish Connector IDs', + `extra_info` json NULL COMMENT 'Publish Extra Info', + `version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Release Version', + `version_desc` text NULL COMMENT 'Version Description', + `publish_status` tinyint NOT NULL DEFAULT 0 COMMENT 'Publish Status', + `publish_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Publish Time in Milliseconds', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + PRIMARY KEY (`id`), + INDEX `idx_app_publish_at` (`app_id`, `publish_at`), + UNIQUE INDEX `uniq_idx_app_version_connector` (`app_id`, `version`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Application Release Record'; +-- Create 'app_static_conversation_draft' table +CREATE TABLE IF NOT EXISTS `app_static_conversation_draft` ( + `id` bigint unsigned NOT NULL COMMENT 'id', + `template_id` bigint unsigned NOT NULL COMMENT 'template id', + `user_id` bigint unsigned NOT NULL COMMENT 'user id', + `connector_id` bigint unsigned NOT NULL COMMENT 'connector id', + `conversation_id` bigint unsigned NOT NULL COMMENT 'conversation id', + `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', + `deleted_at` datetime(3) NULL COMMENT 'delete time in millisecond', + PRIMARY KEY (`id`), + INDEX `idx_connector_id_user_id_template_id` (`connector_id`, `user_id`, `template_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; +-- Create 'app_static_conversation_online' table +CREATE TABLE IF NOT EXISTS `app_static_conversation_online` ( + `id` bigint unsigned NOT NULL COMMENT 'id', + `template_id` bigint unsigned NOT NULL COMMENT 'template id', + `user_id` bigint unsigned NOT NULL COMMENT 'user id', + `connector_id` bigint unsigned NOT NULL COMMENT 'connector id', + `conversation_id` bigint unsigned NOT NULL COMMENT 'conversation id', + `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', + PRIMARY KEY (`id`), + INDEX `idx_connector_id_user_id_template_id` (`connector_id`, `user_id`, `template_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; +-- Create 'chat_flow_role_config' table +CREATE TABLE IF NOT EXISTS `chat_flow_role_config` ( + `id` bigint unsigned NOT NULL COMMENT 'id', + `workflow_id` bigint unsigned NOT NULL COMMENT 'workflow id', + `connector_id` bigint unsigned NULL COMMENT 'connector id', + `name` varchar(256) NOT NULL COMMENT 'role name', + `description` mediumtext NOT NULL COMMENT 'role description', + `version` varchar(256) NOT NULL COMMENT 'version', + `avatar` varchar(256) NOT NULL COMMENT 'avatar uri', + `background_image_info` mediumtext NOT NULL COMMENT 'background image information, object structure', + `onboarding_info` mediumtext NOT NULL COMMENT 'intro information, object structure', + `suggest_reply_info` mediumtext NOT NULL COMMENT 'user suggestions, object structure', + `audio_config` mediumtext NOT NULL COMMENT 'agent audio config, object structure', + `user_input_config` varchar(256) NOT NULL COMMENT 'user input config, object structure', + `creator_id` bigint unsigned NOT NULL COMMENT 'creator id', + `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', + `updated_at` bigint unsigned NULL COMMENT 'update time in millisecond', + `deleted_at` datetime(3) NULL COMMENT 'delete time in millisecond', + PRIMARY KEY (`id`), + INDEX `idx_connector_id_version` (`connector_id`, `version`), + INDEX `idx_workflow_id_version` (`workflow_id`, `version`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; -- Create 'connector_workflow_version' table -CREATE TABLE IF NOT EXISTS `connector_workflow_version` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', `app_id` bigint unsigned NOT NULL COMMENT 'app id', `connector_id` bigint unsigned NOT NULL COMMENT 'connector id', `workflow_id` bigint unsigned NOT NULL COMMENT 'workflow id', `version` varchar(256) NOT NULL COMMENT 'version', `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', PRIMARY KEY (`id`), INDEX `idx_connector_id_workflow_id_create_at` (`connector_id`, `workflow_id`, `created_at`), UNIQUE INDEX `uniq_connector_id_workflow_id_version` (`connector_id`, `workflow_id`, `version`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'connector workflow version'; +CREATE TABLE IF NOT EXISTS `connector_workflow_version` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `app_id` bigint unsigned NOT NULL COMMENT 'app id', + `connector_id` bigint unsigned NOT NULL COMMENT 'connector id', + `workflow_id` bigint unsigned NOT NULL COMMENT 'workflow id', + `version` varchar(256) NOT NULL COMMENT 'version', + `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', + PRIMARY KEY (`id`), + INDEX `idx_connector_id_workflow_id_create_at` (`connector_id`, `workflow_id`, `created_at`), + UNIQUE INDEX `uniq_connector_id_workflow_id_version` (`connector_id`, `workflow_id`, `version`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'connector workflow version'; -- Create 'conversation' table -CREATE TABLE IF NOT EXISTS `conversation` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', `connector_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Publish Connector ID', `agent_id` bigint NOT NULL DEFAULT 0 COMMENT 'agent_id', `scene` tinyint NOT NULL DEFAULT 0 COMMENT 'conversation scene', `section_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'section_id', `creator_id` bigint unsigned NULL DEFAULT 0 COMMENT 'creator_id', `ext` text NULL COMMENT 'ext', `status` tinyint NOT NULL DEFAULT 1 COMMENT 'status: 1-normal 2-deleted', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', PRIMARY KEY (`id`), INDEX `idx_connector_bot_status` (`connector_id`, `agent_id`, `creator_id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'conversation info record'; +CREATE TABLE IF NOT EXISTS `conversation` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'conversation name', + `connector_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Publish Connector ID', + `agent_id` bigint NOT NULL DEFAULT 0 COMMENT 'agent_id', + `scene` tinyint NOT NULL DEFAULT 0 COMMENT 'conversation scene', + `section_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'section_id', + `creator_id` bigint unsigned NULL DEFAULT 0 COMMENT 'creator_id', + `ext` text NULL COMMENT 'ext', + `status` tinyint NOT NULL DEFAULT 1 COMMENT 'status: 1-normal 2-deleted', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + PRIMARY KEY (`id`), + INDEX `idx_connector_bot_status` (`connector_id`, `agent_id`, `creator_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'conversation info record'; -- Create 'data_copy_task' table -CREATE TABLE IF NOT EXISTS `data_copy_task` (`master_task_id` varchar(128) NULL DEFAULT '' COMMENT 'task id', `origin_data_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'origin data id', `target_data_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'target data id', `origin_space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'origin space id', `target_space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'target space id', `origin_user_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'origin user id', `target_user_id` bigint unsigned NULL DEFAULT 0 COMMENT 'target user id', `origin_app_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'origin app id', `target_app_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'target app id', `data_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'data type 1:knowledge, 2:database', `ext_info` varchar(255) NOT NULL DEFAULT '' COMMENT 'ext', `start_time` bigint NULL DEFAULT 0 COMMENT 'task start time', `finish_time` bigint NULL COMMENT 'task finish time', `status` tinyint NOT NULL DEFAULT 1 COMMENT '1: Create 2: Running 3: Success 4: Failure', `error_msg` varchar(128) NULL COMMENT 'error msg', `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', PRIMARY KEY (`id`), UNIQUE INDEX `uniq_master_task_id_origin_data_id_data_type` (`master_task_id`, `origin_data_id`, `data_type`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'data copy task record'; +CREATE TABLE IF NOT EXISTS `data_copy_task` ( + `master_task_id` varchar(128) NULL DEFAULT '' COMMENT 'task id', + `origin_data_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'origin data id', + `target_data_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'target data id', + `origin_space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'origin space id', + `target_space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'target space id', + `origin_user_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'origin user id', + `target_user_id` bigint unsigned NULL DEFAULT 0 COMMENT 'target user id', + `origin_app_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'origin app id', + `target_app_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'target app id', + `data_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'data type 1:knowledge, 2:database', + `ext_info` varchar(255) NOT NULL DEFAULT '' COMMENT 'ext', + `start_time` bigint NULL DEFAULT 0 COMMENT 'task start time', + `finish_time` bigint NULL COMMENT 'task finish time', + `status` tinyint NOT NULL DEFAULT 1 COMMENT '1: Create 2: Running 3: Success 4: Failure', + `error_msg` varchar(128) NULL COMMENT 'error msg', + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', + PRIMARY KEY (`id`), + UNIQUE INDEX `uniq_master_task_id_origin_data_id_data_type` (`master_task_id`, `origin_data_id`, `data_type`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'data copy task record'; -- Create 'draft_database_info' table -CREATE TABLE IF NOT EXISTS `draft_database_info` (`id` bigint unsigned NOT NULL COMMENT 'ID', `app_id` bigint unsigned NULL COMMENT 'App ID', `space_id` bigint unsigned NOT NULL COMMENT 'Space ID', `related_online_id` bigint unsigned NOT NULL COMMENT 'The primary key ID of online_database_info table', `is_visible` tinyint NOT NULL DEFAULT 1 COMMENT 'Visibility: 0 invisible, 1 visible', `prompt_disabled` tinyint NOT NULL DEFAULT 0 COMMENT 'Support prompt calls: 1 not supported, 0 supported', `table_name` varchar(255) NOT NULL COMMENT 'Table name', `table_desc` varchar(256) NULL COMMENT 'Table description', `table_field` text NULL COMMENT 'Table field info', `creator_id` bigint NOT NULL DEFAULT 0 COMMENT 'Creator ID', `icon_uri` varchar(255) NOT NULL COMMENT 'Icon Uri', `physical_table_name` varchar(255) NULL COMMENT 'The name of the real physical table', `rw_mode` bigint NOT NULL DEFAULT 1 COMMENT 'Read and write permission modes: 1. Limited read and write mode 2. Read-only mode 3. Full read and write mode', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `deleted_at` datetime NULL COMMENT 'Delete Time', PRIMARY KEY (`id`), INDEX `idx_space_app_creator_deleted` (`space_id`, `app_id`, `creator_id`, `deleted_at`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'draft database info'; +CREATE TABLE IF NOT EXISTS `draft_database_info` ( + `id` bigint unsigned NOT NULL COMMENT 'ID', + `app_id` bigint unsigned NULL COMMENT 'App ID', + `space_id` bigint unsigned NOT NULL COMMENT 'Space ID', + `related_online_id` bigint unsigned NOT NULL COMMENT 'The primary key ID of online_database_info table', + `is_visible` tinyint NOT NULL DEFAULT 1 COMMENT 'Visibility: 0 invisible, 1 visible', + `prompt_disabled` tinyint NOT NULL DEFAULT 0 COMMENT 'Support prompt calls: 1 not supported, 0 supported', + `table_name` varchar(255) NOT NULL COMMENT 'Table name', + `table_desc` varchar(256) NULL COMMENT 'Table description', + `table_field` text NULL COMMENT 'Table field info', + `creator_id` bigint NOT NULL DEFAULT 0 COMMENT 'Creator ID', + `icon_uri` varchar(255) NOT NULL COMMENT 'Icon Uri', + `physical_table_name` varchar(255) NULL COMMENT 'The name of the real physical table', + `rw_mode` bigint NOT NULL DEFAULT 1 COMMENT 'Read and write permission modes: 1. Limited read and write mode 2. Read-only mode 3. Full read and write mode', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `deleted_at` datetime NULL COMMENT 'Delete Time', + PRIMARY KEY (`id`), + INDEX `idx_space_app_creator_deleted` (`space_id`, `app_id`, `creator_id`, `deleted_at`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'draft database info'; +-- Create 'files' table +CREATE TABLE IF NOT EXISTS `files` ( + `id` bigint unsigned NOT NULL COMMENT 'id', + `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'file name', + `file_size` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'file size', + `tos_uri` varchar(1024) NOT NULL DEFAULT '' COMMENT 'TOS URI', + `status` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'status,0invalid,1valid', + `comment` varchar(1024) NOT NULL DEFAULT '' COMMENT 'file comment', + `source` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'source:1 from API,', + `creator_id` varchar(512) NOT NULL DEFAULT '' COMMENT 'creator id', + `content_type` varchar(255) NOT NULL DEFAULT '' COMMENT 'content type', + `coze_account_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'coze account id', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `deleted_at` datetime(3) NULL COMMENT 'Delete Time', + PRIMARY KEY (`id`), + INDEX `idx_creator_id` (`creator_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'file resource table'; -- Create 'knowledge' table -CREATE TABLE IF NOT EXISTS `knowledge` (`id` bigint unsigned NOT NULL COMMENT 'id', `name` varchar(150) NOT NULL DEFAULT '' COMMENT 'knowledge_s name', `app_id` bigint NOT NULL DEFAULT 0 COMMENT 'app id', `creator_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'creator id', `space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'space id', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `deleted_at` datetime(3) NULL COMMENT 'Delete Time', `status` tinyint NOT NULL DEFAULT 1 COMMENT '0 initialization, 1 effective, 2 invalid', `description` text NULL COMMENT 'description', `icon_uri` varchar(150) NULL COMMENT 'icon uri', `format_type` tinyint NOT NULL DEFAULT 0 COMMENT '0: Text 1: Table 2: Images', PRIMARY KEY (`id`), INDEX `idx_app_id` (`app_id`), INDEX `idx_creator_id` (`creator_id`), INDEX `idx_space_id_deleted_at_updated_at` (`space_id`, `deleted_at`, `updated_at`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'knowledge tabke'; +CREATE TABLE IF NOT EXISTS `knowledge` ( + `id` bigint unsigned NOT NULL COMMENT 'id', + `name` varchar(150) NOT NULL DEFAULT '' COMMENT 'knowledge_s name', + `app_id` bigint NOT NULL DEFAULT 0 COMMENT 'app id', + `creator_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'creator id', + `space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'space id', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `deleted_at` datetime(3) NULL COMMENT 'Delete Time', + `status` tinyint NOT NULL DEFAULT 1 COMMENT '0 initialization, 1 effective, 2 invalid', + `description` text NULL COMMENT 'description', + `icon_uri` varchar(150) NULL COMMENT 'icon uri', + `format_type` tinyint NOT NULL DEFAULT 0 COMMENT '0: Text 1: Table 2: Images', + PRIMARY KEY (`id`), + INDEX `idx_app_id` (`app_id`), + INDEX `idx_creator_id` (`creator_id`), + INDEX `idx_space_id_deleted_at_updated_at` (`space_id`, `deleted_at`, `updated_at`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'knowledge tabke'; -- Create 'knowledge_document' table -CREATE TABLE IF NOT EXISTS `knowledge_document` (`id` bigint unsigned NOT NULL COMMENT 'id', `knowledge_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'knowledge id', `name` varchar(150) NOT NULL DEFAULT '' COMMENT 'document name', `file_extension` varchar(20) NOT NULL DEFAULT '0' COMMENT 'Document type, txt/pdf/csv etc..', `document_type` int NOT NULL DEFAULT 0 COMMENT 'Document type: 0: Text 1: Table 2: Image', `uri` text NULL COMMENT 'uri', `size` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'document size', `slice_count` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'slice count', `char_count` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'number of characters', `creator_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'creator id', `space_id` bigint NOT NULL DEFAULT 0 COMMENT 'space id', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `deleted_at` datetime(3) NULL COMMENT 'Delete Time', `source_type` int NULL DEFAULT 0 COMMENT '0: Local file upload, 2: Custom text, 103: Feishu 104: Lark', `status` int NOT NULL DEFAULT 0 COMMENT 'status', `fail_reason` text NULL COMMENT 'fail reason', `parse_rule` json NULL COMMENT 'parse rule', `table_info` json NULL COMMENT 'table info', PRIMARY KEY (`id`), INDEX `idx_creator_id` (`creator_id`), INDEX `idx_knowledge_id_deleted_at_updated_at` (`knowledge_id`, `deleted_at`, `updated_at`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'knowledge document info'; +CREATE TABLE IF NOT EXISTS `knowledge_document` ( + `id` bigint unsigned NOT NULL COMMENT 'id', + `knowledge_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'knowledge id', + `name` varchar(150) NOT NULL DEFAULT '' COMMENT 'document name', + `file_extension` varchar(20) NOT NULL DEFAULT '0' COMMENT 'Document type, txt/pdf/csv etc..', + `document_type` int NOT NULL DEFAULT 0 COMMENT 'Document type: 0: Text 1: Table 2: Image', + `uri` text NULL COMMENT 'uri', + `size` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'document size', + `slice_count` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'slice count', + `char_count` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'number of characters', + `creator_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'creator id', + `space_id` bigint NOT NULL DEFAULT 0 COMMENT 'space id', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `deleted_at` datetime(3) NULL COMMENT 'Delete Time', + `source_type` int NULL DEFAULT 0 COMMENT '0: Local file upload, 2: Custom text, 103: Feishu 104: Lark', + `status` int NOT NULL DEFAULT 0 COMMENT 'status', + `fail_reason` text NULL COMMENT 'fail reason', + `parse_rule` json NULL COMMENT 'parse rule', + `table_info` json NULL COMMENT 'table info', + PRIMARY KEY (`id`), + INDEX `idx_creator_id` (`creator_id`), + INDEX `idx_knowledge_id_deleted_at_updated_at` (`knowledge_id`, `deleted_at`, `updated_at`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'knowledge document info'; -- Create 'knowledge_document_review' table -CREATE TABLE IF NOT EXISTS `knowledge_document_review` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'id', `knowledge_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'knowledge id', `space_id` bigint NOT NULL DEFAULT 0 COMMENT 'space id', `name` varchar(150) NOT NULL DEFAULT '' COMMENT 'name', `type` varchar(10) NOT NULL DEFAULT '0' COMMENT 'document type', `uri` text NULL COMMENT 'uri', `format_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0 text, 1 table, 2 images', `status` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0 Processing 1 Completed 2 Failed 3 Expired', `chunk_resp_uri` text NULL COMMENT 'pre-sliced uri', `deleted_at` datetime(3) NULL COMMENT 'Delete Time', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `creator_id` bigint NOT NULL DEFAULT 0 COMMENT 'creator id', PRIMARY KEY (`id`), INDEX `idx_dataset_id` (`knowledge_id`, `status`, `updated_at`), INDEX `idx_uri` (`uri` (100))) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Document slice preview info'; +CREATE TABLE IF NOT EXISTS `knowledge_document_review` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'id', + `knowledge_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'knowledge id', + `space_id` bigint NOT NULL DEFAULT 0 COMMENT 'space id', + `name` varchar(150) NOT NULL DEFAULT '' COMMENT 'name', + `type` varchar(10) NOT NULL DEFAULT '0' COMMENT 'document type', + `uri` text NULL COMMENT 'uri', + `format_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0 text, 1 table, 2 images', + `status` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0 Processing 1 Completed 2 Failed 3 Expired', + `chunk_resp_uri` text NULL COMMENT 'pre-sliced uri', + `deleted_at` datetime(3) NULL COMMENT 'Delete Time', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `creator_id` bigint NOT NULL DEFAULT 0 COMMENT 'creator id', + PRIMARY KEY (`id`), + INDEX `idx_dataset_id` (`knowledge_id`, `status`, `updated_at`), + INDEX `idx_uri` (`uri` (100)) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Document slice preview info'; -- Create 'knowledge_document_slice' table -CREATE TABLE IF NOT EXISTS `knowledge_document_slice` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'id', `knowledge_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'knowledge id', `document_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'document_id', `content` text NULL COMMENT 'content', `sequence` decimal(20,5) NOT NULL COMMENT 'slice sequence number, starting from 1', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `deleted_at` datetime(3) NULL COMMENT 'Delete Time', `creator_id` bigint NOT NULL DEFAULT 0 COMMENT 'creator id', `space_id` bigint NOT NULL DEFAULT 0 COMMENT 'space id', `status` int NOT NULL DEFAULT 0 COMMENT 'status', `fail_reason` text NULL COMMENT 'fail reason', `hit` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'hit counts ', PRIMARY KEY (`id`), INDEX `idx_document_id_deleted_at_sequence` (`document_id`, `deleted_at`, `sequence`), INDEX `idx_knowledge_id_document_id` (`knowledge_id`, `document_id`), INDEX `idx_sequence` (`sequence`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'knowledge document slice'; +CREATE TABLE IF NOT EXISTS `knowledge_document_slice` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'id', + `knowledge_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'knowledge id', + `document_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'document_id', + `content` text NULL COMMENT 'content', + `sequence` decimal(20,5) NOT NULL COMMENT 'slice sequence number, starting from 1', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `deleted_at` datetime(3) NULL COMMENT 'Delete Time', + `creator_id` bigint NOT NULL DEFAULT 0 COMMENT 'creator id', + `space_id` bigint NOT NULL DEFAULT 0 COMMENT 'space id', + `status` int NOT NULL DEFAULT 0 COMMENT 'status', + `fail_reason` text NULL COMMENT 'fail reason', + `hit` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'hit counts ', + PRIMARY KEY (`id`), + INDEX `idx_document_id_deleted_at_sequence` (`document_id`, `deleted_at`, `sequence`), + INDEX `idx_knowledge_id_document_id` (`knowledge_id`, `document_id`), + INDEX `idx_sequence` (`sequence`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'knowledge document slice'; -- Create 'message' table -CREATE TABLE IF NOT EXISTS `message` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', `run_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'run_id', `conversation_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'conversation id', `user_id` varchar(60) NOT NULL DEFAULT '' COMMENT 'user id', `agent_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'agent_id', `role` varchar(100) NOT NULL DEFAULT '' COMMENT 'role: user、assistant、system', `content_type` varchar(100) NOT NULL DEFAULT '' COMMENT 'content type 1 text', `content` mediumtext NULL COMMENT 'content', `message_type` varchar(100) NOT NULL DEFAULT '' COMMENT 'message_type', `display_content` text NULL COMMENT 'display content', `ext` text NULL COMMENT 'message ext' COLLATE utf8mb4_general_ci, `section_id` bigint unsigned NULL COMMENT 'section_id', `broken_position` int NULL DEFAULT -1 COMMENT 'broken position', `status` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'message status: 1 Available 2 Deleted 3 Replaced 4 Broken 5 Failed 6 Streaming 7 Pending', `model_content` mediumtext NULL COMMENT 'model content', `meta_info` text NULL COMMENT 'text tagging information such as citation and highlighting', `reasoning_content` text NULL COMMENT 'reasoning content' COLLATE utf8mb4_general_ci, `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', PRIMARY KEY (`id`), INDEX `idx_conversation_id` (`conversation_id`), INDEX `idx_run_id` (`run_id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'message record'; +CREATE TABLE IF NOT EXISTS `message` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `run_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'run_id', + `conversation_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'conversation id', + `user_id` varchar(60) NOT NULL DEFAULT '' COMMENT 'user id', + `agent_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'agent_id', + `role` varchar(100) NOT NULL DEFAULT '' COMMENT 'role: user、assistant、system', + `content_type` varchar(100) NOT NULL DEFAULT '' COMMENT 'content type 1 text', + `content` mediumtext NULL COMMENT 'content', + `message_type` varchar(100) NOT NULL DEFAULT '' COMMENT 'message_type', + `display_content` text NULL COMMENT 'display content', + `ext` text NULL COMMENT 'message ext' COLLATE utf8mb4_general_ci, + `section_id` bigint unsigned NULL COMMENT 'section_id', + `broken_position` int NULL DEFAULT -1 COMMENT 'broken position', + `status` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'message status: 1 Available 2 Deleted 3 Replaced 4 Broken 5 Failed 6 Streaming 7 Pending', + `model_content` mediumtext NULL COMMENT 'model content', + `meta_info` text NULL COMMENT 'text tagging information such as citation and highlighting', + `reasoning_content` text NULL COMMENT 'reasoning content' COLLATE utf8mb4_general_ci, + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + PRIMARY KEY (`id`), + INDEX `idx_conversation_id` (`conversation_id`), + INDEX `idx_run_id` (`run_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'message record'; -- Create 'model_entity' table -CREATE TABLE IF NOT EXISTS `model_entity` (`id` bigint unsigned NOT NULL COMMENT 'id', `meta_id` bigint unsigned NOT NULL COMMENT 'model metadata id', `name` varchar(128) NOT NULL COMMENT 'name', `description` text NULL COMMENT 'description', `default_params` json NULL COMMENT 'default params', `scenario` bigint unsigned NOT NULL COMMENT 'scenario', `status` int NOT NULL DEFAULT 1 COMMENT 'model status', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `deleted_at` bigint unsigned NULL COMMENT 'Delete Time', PRIMARY KEY (`id`), INDEX `idx_scenario` (`scenario`), INDEX `idx_status` (`status`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Model information'; +CREATE TABLE IF NOT EXISTS `model_entity` ( + `id` bigint unsigned NOT NULL COMMENT 'id', + `meta_id` bigint unsigned NOT NULL COMMENT 'model metadata id', + `name` varchar(128) NOT NULL COMMENT 'name', + `description` text NULL COMMENT 'description', + `default_params` json NULL COMMENT 'default params', + `scenario` bigint unsigned NOT NULL COMMENT 'scenario', + `status` int NOT NULL DEFAULT 1 COMMENT 'model status', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `deleted_at` bigint unsigned NULL COMMENT 'Delete Time', + PRIMARY KEY (`id`), + INDEX `idx_scenario` (`scenario`), + INDEX `idx_status` (`status`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Model information'; -- Create 'model_meta' table -CREATE TABLE IF NOT EXISTS `model_meta` (`id` bigint unsigned NOT NULL COMMENT 'id', `model_name` varchar(128) NOT NULL COMMENT 'model name', `protocol` varchar(128) NOT NULL COMMENT 'model protocol', `icon_uri` varchar(255) NOT NULL DEFAULT '' COMMENT 'Icon URI', `capability` json NULL COMMENT 'capability', `conn_config` json NULL COMMENT 'model conn config', `status` int NOT NULL DEFAULT 1 COMMENT 'model status', `description` varchar(2048) NOT NULL DEFAULT '' COMMENT 'description', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `deleted_at` bigint unsigned NULL COMMENT 'Delete Time', `icon_url` varchar(255) NOT NULL DEFAULT '' COMMENT 'Icon URL', PRIMARY KEY (`id`), INDEX `idx_status` (`status`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Model metadata'; +CREATE TABLE IF NOT EXISTS `model_meta` ( + `id` bigint unsigned NOT NULL COMMENT 'id', + `model_name` varchar(128) NOT NULL COMMENT 'model name', + `protocol` varchar(128) NOT NULL COMMENT 'model protocol', + `icon_uri` varchar(255) NOT NULL DEFAULT '' COMMENT 'Icon URI', + `capability` json NULL COMMENT 'capability', + `conn_config` json NULL COMMENT 'model conn config', + `status` int NOT NULL DEFAULT 1 COMMENT 'model status', + `description` varchar(2048) NOT NULL DEFAULT '' COMMENT 'description', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `deleted_at` bigint unsigned NULL COMMENT 'Delete Time', + `icon_url` varchar(255) NOT NULL DEFAULT '' COMMENT 'Icon URL', + PRIMARY KEY (`id`), + INDEX `idx_status` (`status`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Model metadata'; -- Create 'node_execution' table -CREATE TABLE IF NOT EXISTS `node_execution` (`id` bigint unsigned NOT NULL COMMENT 'node execution id', `execute_id` bigint unsigned NOT NULL COMMENT 'the workflow execute id this node execution belongs to', `node_id` varchar(128) NOT NULL COMMENT 'node key' COLLATE utf8mb4_unicode_ci, `node_name` varchar(128) NOT NULL COMMENT 'name of the node' COLLATE utf8mb4_unicode_ci, `node_type` varchar(128) NOT NULL COMMENT 'the type of the node, in string' COLLATE utf8mb4_unicode_ci, `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', `status` tinyint unsigned NOT NULL COMMENT '1=waiting 2=running 3=success 4=fail', `duration` bigint unsigned NULL COMMENT 'execution duration in millisecond', `input` mediumtext NULL COMMENT 'actual input of the node' COLLATE utf8mb4_unicode_ci, `output` mediumtext NULL COMMENT 'actual output of the node' COLLATE utf8mb4_unicode_ci, `raw_output` mediumtext NULL COMMENT 'the original output of the node' COLLATE utf8mb4_unicode_ci, `error_info` mediumtext NULL COMMENT 'error info' COLLATE utf8mb4_unicode_ci, `error_level` varchar(32) NULL COMMENT 'level of the error' COLLATE utf8mb4_unicode_ci, `input_tokens` bigint unsigned NULL COMMENT 'number of input tokens', `output_tokens` bigint unsigned NULL COMMENT 'number of output tokens', `updated_at` bigint unsigned NULL COMMENT 'update time in millisecond', `composite_node_index` bigint unsigned NULL COMMENT 'loop or batch_s execution index', `composite_node_items` mediumtext NULL COMMENT 'the items extracted from parent composite node for this index' COLLATE utf8mb4_unicode_ci, `parent_node_id` varchar(128) NULL COMMENT 'when as inner node for loop or batch, this is the parent node_s key' COLLATE utf8mb4_unicode_ci, `sub_execute_id` bigint unsigned NULL COMMENT 'if this node is sub_workflow, the exe id of the sub workflow', `extra` mediumtext NULL COMMENT 'extra info' COLLATE utf8mb4_unicode_ci, PRIMARY KEY (`id`), INDEX `idx_execute_id_node_id` (`execute_id`, `node_id`), INDEX `idx_execute_id_parent_node_id` (`execute_id`, `parent_node_id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT 'Node run record, used to record the status information of each node during each workflow execution'; +CREATE TABLE IF NOT EXISTS `node_execution` ( + `id` bigint unsigned NOT NULL COMMENT 'node execution id', + `execute_id` bigint unsigned NOT NULL COMMENT 'the workflow execute id this node execution belongs to', + `node_id` varchar(128) NOT NULL COMMENT 'node key' COLLATE utf8mb4_unicode_ci, + `node_name` varchar(128) NOT NULL COMMENT 'name of the node' COLLATE utf8mb4_unicode_ci, + `node_type` varchar(128) NOT NULL COMMENT 'the type of the node, in string' COLLATE utf8mb4_unicode_ci, + `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', + `status` tinyint unsigned NOT NULL COMMENT '1=waiting 2=running 3=success 4=fail', + `duration` bigint unsigned NULL COMMENT 'execution duration in millisecond', + `input` mediumtext NULL COMMENT 'actual input of the node' COLLATE utf8mb4_unicode_ci, + `output` mediumtext NULL COMMENT 'actual output of the node' COLLATE utf8mb4_unicode_ci, + `raw_output` mediumtext NULL COMMENT 'the original output of the node' COLLATE utf8mb4_unicode_ci, + `error_info` mediumtext NULL COMMENT 'error info' COLLATE utf8mb4_unicode_ci, + `error_level` varchar(32) NULL COMMENT 'level of the error' COLLATE utf8mb4_unicode_ci, + `input_tokens` bigint unsigned NULL COMMENT 'number of input tokens', + `output_tokens` bigint unsigned NULL COMMENT 'number of output tokens', + `updated_at` bigint unsigned NULL COMMENT 'update time in millisecond', + `composite_node_index` bigint unsigned NULL COMMENT 'loop or batch_s execution index', + `composite_node_items` mediumtext NULL COMMENT 'the items extracted from parent composite node for this index' COLLATE utf8mb4_unicode_ci, + `parent_node_id` varchar(128) NULL COMMENT 'when as inner node for loop or batch, this is the parent node_s key' COLLATE utf8mb4_unicode_ci, + `sub_execute_id` bigint unsigned NULL COMMENT 'if this node is sub_workflow, the exe id of the sub workflow', + `extra` mediumtext NULL COMMENT 'extra info' COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`id`), + INDEX `idx_execute_id_node_id` (`execute_id`, `node_id`), + INDEX `idx_execute_id_parent_node_id` (`execute_id`, `parent_node_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT 'Node run record, used to record the status information of each node during each workflow execution'; -- Create 'online_database_info' table -CREATE TABLE IF NOT EXISTS `online_database_info` (`id` bigint unsigned NOT NULL COMMENT 'ID', `app_id` bigint unsigned NULL COMMENT 'App ID', `space_id` bigint unsigned NOT NULL COMMENT 'Space ID', `related_draft_id` bigint unsigned NOT NULL COMMENT 'The primary key ID of draft_database_info table', `is_visible` tinyint NOT NULL DEFAULT 1 COMMENT 'Visibility: 0 invisible, 1 visible', `prompt_disabled` tinyint NOT NULL DEFAULT 0 COMMENT 'Support prompt calls: 1 not supported, 0 supported', `table_name` varchar(255) NOT NULL COMMENT 'Table name', `table_desc` varchar(256) NULL COMMENT 'Table description', `table_field` text NULL COMMENT 'Table field info', `creator_id` bigint NOT NULL DEFAULT 0 COMMENT 'Creator ID', `icon_uri` varchar(255) NOT NULL COMMENT 'Icon Uri', `physical_table_name` varchar(255) NULL COMMENT 'The name of the real physical table', `rw_mode` bigint NOT NULL DEFAULT 1 COMMENT 'Read and write permission modes: 1. Limited read and write mode 2. Read-only mode 3. Full read and write mode', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `deleted_at` datetime NULL COMMENT 'Delete Time', PRIMARY KEY (`id`), INDEX `idx_space_app_creator_deleted` (`space_id`, `app_id`, `creator_id`, `deleted_at`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'online database info'; +CREATE TABLE IF NOT EXISTS `online_database_info` ( + `id` bigint unsigned NOT NULL COMMENT 'ID', + `app_id` bigint unsigned NULL COMMENT 'App ID', + `space_id` bigint unsigned NOT NULL COMMENT 'Space ID', + `related_draft_id` bigint unsigned NOT NULL COMMENT 'The primary key ID of draft_database_info table', + `is_visible` tinyint NOT NULL DEFAULT 1 COMMENT 'Visibility: 0 invisible, 1 visible', + `prompt_disabled` tinyint NOT NULL DEFAULT 0 COMMENT 'Support prompt calls: 1 not supported, 0 supported', + `table_name` varchar(255) NOT NULL COMMENT 'Table name', + `table_desc` varchar(256) NULL COMMENT 'Table description', + `table_field` text NULL COMMENT 'Table field info', + `creator_id` bigint NOT NULL DEFAULT 0 COMMENT 'Creator ID', + `icon_uri` varchar(255) NOT NULL COMMENT 'Icon Uri', + `physical_table_name` varchar(255) NULL COMMENT 'The name of the real physical table', + `rw_mode` bigint NOT NULL DEFAULT 1 COMMENT 'Read and write permission modes: 1. Limited read and write mode 2. Read-only mode 3. Full read and write mode', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `deleted_at` datetime NULL COMMENT 'Delete Time', + PRIMARY KEY (`id`), + INDEX `idx_space_app_creator_deleted` (`space_id`, `app_id`, `creator_id`, `deleted_at`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'online database info'; -- Create 'plugin' table -CREATE TABLE IF NOT EXISTS `plugin` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', `space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Space ID', `developer_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Developer ID', `app_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Application ID', `icon_uri` varchar(512) NOT NULL DEFAULT '' COMMENT 'Icon URI', `server_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Server URL', `plugin_type` tinyint NOT NULL DEFAULT 0 COMMENT 'Plugin Type, 1:http, 6:local', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Plugin Version, e.g. v1.0.0', `version_desc` text NULL COMMENT 'Plugin Version Description', `manifest` json NULL COMMENT 'Plugin Manifest', `openapi_doc` json NULL COMMENT 'OpenAPI Document, only stores the root', PRIMARY KEY (`id`), INDEX `idx_space_created_at` (`space_id`, `created_at`), INDEX `idx_space_updated_at` (`space_id`, `updated_at`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Latest Plugin'; +CREATE TABLE IF NOT EXISTS `plugin` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', + `space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Space ID', + `developer_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Developer ID', + `app_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Application ID', + `icon_uri` varchar(512) NOT NULL DEFAULT '' COMMENT 'Icon URI', + `server_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Server URL', + `plugin_type` tinyint NOT NULL DEFAULT 0 COMMENT 'Plugin Type, 1:http, 6:local', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Plugin Version, e.g. v1.0.0', + `version_desc` text NULL COMMENT 'Plugin Version Description', + `manifest` json NULL COMMENT 'Plugin Manifest', + `openapi_doc` json NULL COMMENT 'OpenAPI Document, only stores the root', + PRIMARY KEY (`id`), + INDEX `idx_space_created_at` (`space_id`, `created_at`), + INDEX `idx_space_updated_at` (`space_id`, `updated_at`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Latest Plugin'; -- Create 'plugin_draft' table -CREATE TABLE IF NOT EXISTS `plugin_draft` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', `space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Space ID', `developer_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Developer ID', `app_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Application ID', `icon_uri` varchar(512) NOT NULL DEFAULT '' COMMENT 'Icon URI', `server_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Server URL', `plugin_type` tinyint NOT NULL DEFAULT 0 COMMENT 'Plugin Type, 1:http, 6:local', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `deleted_at` datetime NULL COMMENT 'Delete Time', `manifest` json NULL COMMENT 'Plugin Manifest', `openapi_doc` json NULL COMMENT 'OpenAPI Document, only stores the root', PRIMARY KEY (`id`), INDEX `idx_app_id` (`app_id`, `id`), INDEX `idx_space_app_created_at` (`space_id`, `app_id`, `created_at`), INDEX `idx_space_app_updated_at` (`space_id`, `app_id`, `updated_at`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Draft Plugin'; +CREATE TABLE IF NOT EXISTS `plugin_draft` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', + `space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Space ID', + `developer_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Developer ID', + `app_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Application ID', + `icon_uri` varchar(512) NOT NULL DEFAULT '' COMMENT 'Icon URI', + `server_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Server URL', + `plugin_type` tinyint NOT NULL DEFAULT 0 COMMENT 'Plugin Type, 1:http, 6:local', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `deleted_at` datetime NULL COMMENT 'Delete Time', + `manifest` json NULL COMMENT 'Plugin Manifest', + `openapi_doc` json NULL COMMENT 'OpenAPI Document, only stores the root', + PRIMARY KEY (`id`), + INDEX `idx_app_id` (`app_id`, `id`), + INDEX `idx_space_app_created_at` (`space_id`, `app_id`, `created_at`), + INDEX `idx_space_app_updated_at` (`space_id`, `app_id`, `updated_at`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Draft Plugin'; -- Create 'plugin_oauth_auth' table -CREATE TABLE IF NOT EXISTS `plugin_oauth_auth` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Primary Key', `user_id` varchar(255) NOT NULL DEFAULT '' COMMENT 'User ID', `plugin_id` bigint NOT NULL DEFAULT 0 COMMENT 'Plugin ID', `is_draft` bool NOT NULL DEFAULT 0 COMMENT 'Is Draft Plugin', `oauth_config` json NULL COMMENT 'Authorization Code OAuth Config', `access_token` text NULL COMMENT 'Access Token', `refresh_token` text NULL COMMENT 'Refresh Token', `token_expired_at` bigint NULL COMMENT 'Token Expired in Milliseconds', `next_token_refresh_at` bigint NULL COMMENT 'Next Token Refresh Time in Milliseconds', `last_active_at` bigint NULL COMMENT 'Last active time in Milliseconds', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', PRIMARY KEY (`id`), INDEX `idx_last_active_at` (`last_active_at`), INDEX `idx_last_token_expired_at` (`token_expired_at`), INDEX `idx_next_token_refresh_at` (`next_token_refresh_at`), UNIQUE INDEX `uniq_idx_user_plugin_is_draft` (`user_id`, `plugin_id`, `is_draft`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Plugin OAuth Authorization Code Info'; +CREATE TABLE IF NOT EXISTS `plugin_oauth_auth` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Primary Key', + `user_id` varchar(255) NOT NULL DEFAULT '' COMMENT 'User ID', + `plugin_id` bigint NOT NULL DEFAULT 0 COMMENT 'Plugin ID', + `is_draft` bool NOT NULL DEFAULT 0 COMMENT 'Is Draft Plugin', + `oauth_config` json NULL COMMENT 'Authorization Code OAuth Config', + `access_token` text NULL COMMENT 'Access Token', + `refresh_token` text NULL COMMENT 'Refresh Token', + `token_expired_at` bigint NULL COMMENT 'Token Expired in Milliseconds', + `next_token_refresh_at` bigint NULL COMMENT 'Next Token Refresh Time in Milliseconds', + `last_active_at` bigint NULL COMMENT 'Last active time in Milliseconds', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + PRIMARY KEY (`id`), + INDEX `idx_last_active_at` (`last_active_at`), + INDEX `idx_last_token_expired_at` (`token_expired_at`), + INDEX `idx_next_token_refresh_at` (`next_token_refresh_at`), + UNIQUE INDEX `uniq_idx_user_plugin_is_draft` (`user_id`, `plugin_id`, `is_draft`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Plugin OAuth Authorization Code Info'; -- Create 'plugin_version' table -CREATE TABLE IF NOT EXISTS `plugin_version` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Primary Key ID', `space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Space ID', `developer_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Developer ID', `plugin_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', `app_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Application ID', `icon_uri` varchar(512) NOT NULL DEFAULT '' COMMENT 'Icon URI', `server_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Server URL', `plugin_type` tinyint NOT NULL DEFAULT 0 COMMENT 'Plugin Type, 1:http, 6:local', `version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Plugin Version, e.g. v1.0.0', `version_desc` text NULL COMMENT 'Plugin Version Description', `manifest` json NULL COMMENT 'Plugin Manifest', `openapi_doc` json NULL COMMENT 'OpenAPI Document, only stores the root', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `deleted_at` datetime NULL COMMENT 'Delete Time', PRIMARY KEY (`id`), UNIQUE INDEX `uniq_idx_plugin_version` (`plugin_id`, `version`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Plugin Version'; +CREATE TABLE IF NOT EXISTS `plugin_version` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Primary Key ID', + `space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Space ID', + `developer_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Developer ID', + `plugin_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', + `app_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Application ID', + `icon_uri` varchar(512) NOT NULL DEFAULT '' COMMENT 'Icon URI', + `server_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Server URL', + `plugin_type` tinyint NOT NULL DEFAULT 0 COMMENT 'Plugin Type, 1:http, 6:local', + `version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Plugin Version, e.g. v1.0.0', + `version_desc` text NULL COMMENT 'Plugin Version Description', + `manifest` json NULL COMMENT 'Plugin Manifest', + `openapi_doc` json NULL COMMENT 'OpenAPI Document, only stores the root', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `deleted_at` datetime NULL COMMENT 'Delete Time', + PRIMARY KEY (`id`), + UNIQUE INDEX `uniq_idx_plugin_version` (`plugin_id`, `version`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Plugin Version'; -- Create 'prompt_resource' table -CREATE TABLE IF NOT EXISTS `prompt_resource` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', `space_id` bigint NOT NULL COMMENT 'space id', `name` varchar(255) NOT NULL COMMENT 'name', `description` varchar(255) NOT NULL COMMENT 'description', `prompt_text` mediumtext NULL COMMENT 'prompt text', `status` int NOT NULL COMMENT 'status, 0 is invalid, 1 is valid', `creator_id` bigint NOT NULL COMMENT 'creator id', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', PRIMARY KEY (`id`), INDEX `idx_creator_id` (`creator_id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT 'prompt_resource'; +CREATE TABLE IF NOT EXISTS `prompt_resource` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `space_id` bigint NOT NULL COMMENT 'space id', + `name` varchar(255) NOT NULL COMMENT 'name', + `description` varchar(255) NOT NULL COMMENT 'description', + `prompt_text` mediumtext NULL COMMENT 'prompt text', + `status` int NOT NULL COMMENT 'status, 0 is invalid, 1 is valid', + `creator_id` bigint NOT NULL COMMENT 'creator id', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + PRIMARY KEY (`id`), + INDEX `idx_creator_id` (`creator_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT 'prompt_resource'; -- Create 'run_record' table -CREATE TABLE IF NOT EXISTS `run_record` (`id` bigint unsigned NOT NULL COMMENT 'id', `conversation_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'conversation id', `section_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'section ID', `agent_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'agent_id', `user_id` varchar(255) NOT NULL DEFAULT '' COMMENT 'user id', `source` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Execute source 0 API', `status` varchar(255) NOT NULL DEFAULT '' COMMENT 'status,0 Unknown, 1-Created,2-InProgress,3-Completed,4-Failed,5-Expired,6-Cancelled,7-RequiresAction', `creator_id` bigint NOT NULL DEFAULT 0 COMMENT 'creator id', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `failed_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Fail Time in Milliseconds', `last_error` text NULL COMMENT 'error message' COLLATE utf8mb4_general_ci, `completed_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Finish Time in Milliseconds', `chat_request` text NULL COMMENT 'Original request field' COLLATE utf8mb4_general_ci, `ext` text NULL COMMENT 'ext' COLLATE utf8mb4_general_ci, `usage` json NULL COMMENT 'usage', PRIMARY KEY (`id`), INDEX `idx_c_s` (`conversation_id`, `section_id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'run record'; +CREATE TABLE IF NOT EXISTS `run_record` ( + `id` bigint unsigned NOT NULL COMMENT 'id', + `conversation_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'conversation id', + `section_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'section ID', + `agent_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'agent_id', + `user_id` varchar(255) NOT NULL DEFAULT '' COMMENT 'user id', + `source` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Execute source 0 API', + `status` varchar(255) NOT NULL DEFAULT '' COMMENT 'status,0 Unknown, 1-Created,2-InProgress,3-Completed,4-Failed,5-Expired,6-Cancelled,7-RequiresAction', + `creator_id` bigint NOT NULL DEFAULT 0 COMMENT 'creator id', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `failed_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Fail Time in Milliseconds', + `last_error` text NULL COMMENT 'error message' COLLATE utf8mb4_general_ci, + `completed_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Finish Time in Milliseconds', + `chat_request` text NULL COMMENT 'Original request field' COLLATE utf8mb4_general_ci, + `ext` text NULL COMMENT 'ext' COLLATE utf8mb4_general_ci, + `usage` json NULL COMMENT 'usage', + PRIMARY KEY (`id`), + INDEX `idx_c_s` (`conversation_id`, `section_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'run record'; -- Create 'shortcut_command' table -CREATE TABLE IF NOT EXISTS `shortcut_command` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', `object_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Entity ID, this command can be used for this entity', `command_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'command id', `command_name` varchar(255) NOT NULL DEFAULT '' COMMENT 'command name', `shortcut_command` varchar(255) NOT NULL DEFAULT '' COMMENT 'shortcut command', `description` varchar(2000) NOT NULL DEFAULT '' COMMENT 'description', `send_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'send type 0:query 1:panel', `tool_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Type 1 of tool used: WorkFlow 2: Plugin', `work_flow_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'workflow id', `plugin_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'plugin id', `plugin_tool_name` varchar(255) NOT NULL DEFAULT '' COMMENT 'plugin tool name', `template_query` text NULL COMMENT 'template query', `components` json NULL COMMENT 'Panel parameters', `card_schema` text NULL COMMENT 'card schema', `tool_info` json NULL COMMENT 'Tool information includes name+variable list', `status` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Status, 0 is invalid, 1 is valid', `creator_id` bigint unsigned NULL DEFAULT 0 COMMENT 'creator id', `is_online` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Is online information: 0 draft 1 online', `created_at` bigint NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `agent_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'When executing a multi instruction, which node executes the instruction', `shortcut_icon` json NULL COMMENT 'shortcut icon', `plugin_tool_id` bigint NOT NULL DEFAULT 0 COMMENT 'tool_id', PRIMARY KEY (`id`), UNIQUE INDEX `uniq_object_command_id_type` (`object_id`, `command_id`, `is_online`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'bot shortcut command table'; +CREATE TABLE IF NOT EXISTS `shortcut_command` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `object_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Entity ID, this command can be used for this entity', + `command_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'command id', + `command_name` varchar(255) NOT NULL DEFAULT '' COMMENT 'command name', + `shortcut_command` varchar(255) NOT NULL DEFAULT '' COMMENT 'shortcut command', + `description` varchar(2000) NOT NULL DEFAULT '' COMMENT 'description', + `send_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'send type 0:query 1:panel', + `tool_type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Type 1 of tool used: WorkFlow 2: Plugin', + `work_flow_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'workflow id', + `plugin_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'plugin id', + `plugin_tool_name` varchar(255) NOT NULL DEFAULT '' COMMENT 'plugin tool name', + `template_query` text NULL COMMENT 'template query', + `components` json NULL COMMENT 'Panel parameters', + `card_schema` text NULL COMMENT 'card schema', + `tool_info` json NULL COMMENT 'Tool information includes name+variable list', + `status` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Status, 0 is invalid, 1 is valid', + `creator_id` bigint unsigned NULL DEFAULT 0 COMMENT 'creator id', + `is_online` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Is online information: 0 draft 1 online', + `created_at` bigint NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `agent_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'When executing a multi instruction, which node executes the instruction', + `shortcut_icon` json NULL COMMENT 'shortcut icon', + `plugin_tool_id` bigint NOT NULL DEFAULT 0 COMMENT 'tool_id', + PRIMARY KEY (`id`), + UNIQUE INDEX `uniq_object_command_id_type` (`object_id`, `command_id`, `is_online`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'bot shortcut command table'; -- Create 'single_agent_draft' table -CREATE TABLE IF NOT EXISTS `single_agent_draft` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key ID', `agent_id` bigint NOT NULL DEFAULT 0 COMMENT 'Agent ID', `creator_id` bigint NOT NULL DEFAULT 0 COMMENT 'Creator ID', `space_id` bigint NOT NULL DEFAULT 0 COMMENT 'Space ID', `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Agent Name', `description` text NULL COMMENT 'Agent Description', `icon_uri` varchar(255) NOT NULL DEFAULT '' COMMENT 'Icon URI', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `deleted_at` datetime(3) NULL COMMENT 'delete time in millisecond', `variables_meta_id` bigint NULL COMMENT 'variables meta table ID', `model_info` json NULL COMMENT 'Model Configuration Information', `onboarding_info` json NULL COMMENT 'Onboarding Information', `prompt` json NULL COMMENT 'Agent Prompt Configuration', `plugin` json NULL COMMENT 'Agent Plugin Base Configuration', `knowledge` json NULL COMMENT 'Agent Knowledge Base Configuration', `workflow` json NULL COMMENT 'Agent Workflow Configuration', `suggest_reply` json NULL COMMENT 'Suggested Replies', `jump_config` json NULL COMMENT 'Jump Configuration', `background_image_info_list` json NULL COMMENT 'Background image', `database_config` json NULL COMMENT 'Agent Database Base Configuration', `shortcut_command` json NULL COMMENT 'shortcut command', PRIMARY KEY (`id`), INDEX `idx_creator_id` (`creator_id`), UNIQUE INDEX `uniq_agent_id` (`agent_id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Single Agent Draft Copy Table'; +CREATE TABLE IF NOT EXISTS `single_agent_draft` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key ID', + `agent_id` bigint NOT NULL DEFAULT 0 COMMENT 'Agent ID', + `creator_id` bigint NOT NULL DEFAULT 0 COMMENT 'Creator ID', + `space_id` bigint NOT NULL DEFAULT 0 COMMENT 'Space ID', + `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Agent Name', + `description` text NULL COMMENT 'Agent Description', + `icon_uri` varchar(255) NOT NULL DEFAULT '' COMMENT 'Icon URI', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `deleted_at` datetime(3) NULL COMMENT 'delete time in millisecond', + `variables_meta_id` bigint NULL COMMENT 'variables meta table ID', + `model_info` json NULL COMMENT 'Model Configuration Information', + `onboarding_info` json NULL COMMENT 'Onboarding Information', + `prompt` json NULL COMMENT 'Agent Prompt Configuration', + `plugin` json NULL COMMENT 'Agent Plugin Base Configuration', + `knowledge` json NULL COMMENT 'Agent Knowledge Base Configuration', + `workflow` json NULL COMMENT 'Agent Workflow Configuration', + `suggest_reply` json NULL COMMENT 'Suggested Replies', + `jump_config` json NULL COMMENT 'Jump Configuration', + `background_image_info_list` json NULL COMMENT 'Background image', + `database_config` json NULL COMMENT 'Agent Database Base Configuration', + `bot_mode` tinyint NOT NULL DEFAULT 0 COMMENT 'bot mode,0:single mode 2:chatflow mode', + `layout_info` text NULL COMMENT 'chatflow layout info', + `shortcut_command` json NULL COMMENT 'shortcut command', + PRIMARY KEY (`id`), + INDEX `idx_creator_id` (`creator_id`), + UNIQUE INDEX `uniq_agent_id` (`agent_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Single Agent Draft Copy Table'; -- Create 'single_agent_publish' table -CREATE TABLE IF NOT EXISTS `single_agent_publish` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', `agent_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'agent_id', `publish_id` varchar(50) NOT NULL DEFAULT '' COMMENT 'publish id' COLLATE utf8mb4_general_ci, `connector_ids` json NULL COMMENT 'connector_ids', `version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Agent Version', `publish_info` text NULL COMMENT 'publish info' COLLATE utf8mb4_general_ci, `publish_time` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'publish time', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `creator_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'creator id', `status` tinyint NOT NULL DEFAULT 0 COMMENT 'Status 0: In use 1: Delete 3: Disabled', `extra` json NULL COMMENT 'extra', PRIMARY KEY (`id`), INDEX `idx_agent_id_version` (`agent_id`, `version`), INDEX `idx_creator_id` (`creator_id`), INDEX `idx_publish_id` (`publish_id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Bot connector and release version info'; +CREATE TABLE IF NOT EXISTS `single_agent_publish` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `agent_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'agent_id', + `publish_id` varchar(50) NOT NULL DEFAULT '' COMMENT 'publish id' COLLATE utf8mb4_general_ci, + `connector_ids` json NULL COMMENT 'connector_ids', + `version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Agent Version', + `publish_info` text NULL COMMENT 'publish info' COLLATE utf8mb4_general_ci, + `publish_time` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'publish time', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `creator_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'creator id', + `status` tinyint NOT NULL DEFAULT 0 COMMENT 'Status 0: In use 1: Delete 3: Disabled', + `extra` json NULL COMMENT 'extra', + PRIMARY KEY (`id`), + INDEX `idx_agent_id_version` (`agent_id`, `version`), + INDEX `idx_creator_id` (`creator_id`), + INDEX `idx_publish_id` (`publish_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Bot connector and release version info'; -- Create 'single_agent_version' table -CREATE TABLE IF NOT EXISTS `single_agent_version` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key ID', `agent_id` bigint NOT NULL DEFAULT 0 COMMENT 'Agent ID', `creator_id` bigint NOT NULL DEFAULT 0 COMMENT 'Creator ID', `space_id` bigint NOT NULL DEFAULT 0 COMMENT 'Space ID', `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Agent Name', `description` text NULL COMMENT 'Agent Description', `icon_uri` varchar(255) NOT NULL DEFAULT '' COMMENT 'Icon URI', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `deleted_at` datetime(3) NULL COMMENT 'delete time in millisecond', `variables_meta_id` bigint NULL COMMENT 'variables meta table ID', `model_info` json NULL COMMENT 'Model Configuration Information', `onboarding_info` json NULL COMMENT 'Onboarding Information', `prompt` json NULL COMMENT 'Agent Prompt Configuration', `plugin` json NULL COMMENT 'Agent Plugin Base Configuration', `knowledge` json NULL COMMENT 'Agent Knowledge Base Configuration', `workflow` json NULL COMMENT 'Agent Workflow Configuration', `suggest_reply` json NULL COMMENT 'Suggested Replies', `jump_config` json NULL COMMENT 'Jump Configuration', `connector_id` bigint unsigned NOT NULL COMMENT 'Connector ID', `version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Agent Version', `background_image_info_list` json NULL COMMENT 'Background image', `database_config` json NULL COMMENT 'Agent Database Base Configuration', `shortcut_command` json NULL COMMENT 'shortcut command', PRIMARY KEY (`id`), INDEX `idx_creator_id` (`creator_id`), UNIQUE INDEX `uniq_agent_id_and_version_connector_id` (`agent_id`, `version`, `connector_id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Single Agent Version Copy Table'; +CREATE TABLE IF NOT EXISTS `single_agent_version` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key ID', + `agent_id` bigint NOT NULL DEFAULT 0 COMMENT 'Agent ID', + `creator_id` bigint NOT NULL DEFAULT 0 COMMENT 'Creator ID', + `space_id` bigint NOT NULL DEFAULT 0 COMMENT 'Space ID', + `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Agent Name', + `description` text NULL COMMENT 'Agent Description', + `icon_uri` varchar(255) NOT NULL DEFAULT '' COMMENT 'Icon URI', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `bot_mode` tinyint NOT NULL DEFAULT 0 COMMENT 'bot mode,0:single mode 2:chatflow mode', + `layout_info` text NULL COMMENT 'chatflow layout info', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `deleted_at` datetime(3) NULL COMMENT 'delete time in millisecond', + `variables_meta_id` bigint NULL COMMENT 'variables meta table ID', + `model_info` json NULL COMMENT 'Model Configuration Information', + `onboarding_info` json NULL COMMENT 'Onboarding Information', + `prompt` json NULL COMMENT 'Agent Prompt Configuration', + `plugin` json NULL COMMENT 'Agent Plugin Base Configuration', + `knowledge` json NULL COMMENT 'Agent Knowledge Base Configuration', + `workflow` json NULL COMMENT 'Agent Workflow Configuration', + `suggest_reply` json NULL COMMENT 'Suggested Replies', + `jump_config` json NULL COMMENT 'Jump Configuration', + `connector_id` bigint unsigned NOT NULL COMMENT 'Connector ID', + `version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Agent Version', + `background_image_info_list` json NULL COMMENT 'Background image', + `database_config` json NULL COMMENT 'Agent Database Base Configuration', + `shortcut_command` json NULL COMMENT 'shortcut command', + PRIMARY KEY (`id`), + INDEX `idx_creator_id` (`creator_id`), + UNIQUE INDEX `uniq_agent_id_and_version_connector_id` (`agent_id`, `version`, `connector_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Single Agent Version Copy Table'; -- Create 'space' table -CREATE TABLE IF NOT EXISTS `space` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key ID, Space ID', `owner_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner ID', `name` varchar(200) NOT NULL DEFAULT '' COMMENT 'Space Name', `description` varchar(2000) NOT NULL DEFAULT '' COMMENT 'Space Description', `icon_uri` varchar(200) NOT NULL DEFAULT '' COMMENT 'Icon URI', `creator_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Creator ID', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Creation Time (Milliseconds)', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time (Milliseconds)', `deleted_at` bigint unsigned NULL COMMENT 'Deletion Time (Milliseconds)', PRIMARY KEY (`id`), INDEX `idx_creator_id` (`creator_id`), INDEX `idx_owner_id` (`owner_id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Space Table'; +CREATE TABLE IF NOT EXISTS `space` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key ID, Space ID', + `owner_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner ID', + `name` varchar(200) NOT NULL DEFAULT '' COMMENT 'Space Name', + `description` varchar(2000) NOT NULL DEFAULT '' COMMENT 'Space Description', + `icon_uri` varchar(200) NOT NULL DEFAULT '' COMMENT 'Icon URI', + `creator_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Creator ID', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Creation Time (Milliseconds)', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time (Milliseconds)', + `deleted_at` bigint unsigned NULL COMMENT 'Deletion Time (Milliseconds)', + PRIMARY KEY (`id`), + INDEX `idx_creator_id` (`creator_id`), + INDEX `idx_owner_id` (`owner_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Space Table'; -- Create 'space_user' table -CREATE TABLE IF NOT EXISTS `space_user` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key ID, Auto Increment', `space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Space ID', `user_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'User ID', `role_type` int NOT NULL DEFAULT 3 COMMENT 'Role Type: 1.owner 2.admin 3.member', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Creation Time (Milliseconds)', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time (Milliseconds)', PRIMARY KEY (`id`), INDEX `idx_user_id` (`user_id`), UNIQUE INDEX `uniq_space_user` (`space_id`, `user_id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Space Member Table'; +CREATE TABLE IF NOT EXISTS `space_user` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key ID, Auto Increment', + `space_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Space ID', + `user_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'User ID', + `role_type` int NOT NULL DEFAULT 3 COMMENT 'Role Type: 1.owner 2.admin 3.member', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Creation Time (Milliseconds)', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time (Milliseconds)', + PRIMARY KEY (`id`), + INDEX `idx_user_id` (`user_id`), + UNIQUE INDEX `uniq_space_user` (`space_id`, `user_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Space Member Table'; -- Create 'template' table -CREATE TABLE IF NOT EXISTS `template` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key ID', `agent_id` bigint NOT NULL DEFAULT 0 COMMENT 'Agent ID', `workflow_id` bigint NOT NULL DEFAULT 0 COMMENT 'Workflow ID', `space_id` bigint NOT NULL DEFAULT 0 COMMENT 'Space ID', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `heat` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Heat', `product_entity_type` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Product Entity Type', `meta_info` json NULL COMMENT 'Meta Info', `agent_extra` json NULL COMMENT 'Agent Extra Info', `workflow_extra` json NULL COMMENT 'Workflow Extra Info', `project_extra` json NULL COMMENT 'Project Extra Info', PRIMARY KEY (`id`), UNIQUE INDEX `uniq_agent_id` (`agent_id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Template Info Table'; +CREATE TABLE IF NOT EXISTS `template` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key ID', + `agent_id` bigint NOT NULL DEFAULT 0 COMMENT 'Agent ID', + `workflow_id` bigint NOT NULL DEFAULT 0 COMMENT 'Workflow ID', + `space_id` bigint NOT NULL DEFAULT 0 COMMENT 'Space ID', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `heat` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Heat', + `product_entity_type` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Product Entity Type', + `meta_info` json NULL COMMENT 'Meta Info', + `agent_extra` json NULL COMMENT 'Agent Extra Info', + `workflow_extra` json NULL COMMENT 'Workflow Extra Info', + `project_extra` json NULL COMMENT 'Project Extra Info', + PRIMARY KEY (`id`), + UNIQUE INDEX `uniq_agent_id` (`agent_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Template Info Table'; -- Create 'tool' table -CREATE TABLE IF NOT EXISTS `tool` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Tool ID', `plugin_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Tool Version, e.g. v1.0.0', `sub_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Sub URL Path', `method` varchar(64) NOT NULL DEFAULT '' COMMENT 'HTTP Request Method', `operation` json NULL COMMENT 'Tool Openapi Operation Schema', `activated_status` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0:activated; 1:deactivated', PRIMARY KEY (`id`), INDEX `idx_plugin_activated_status` (`plugin_id`, `activated_status`), UNIQUE INDEX `uniq_idx_plugin_sub_url_method` (`plugin_id`, `sub_url`, `method`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Latest Tool'; +CREATE TABLE IF NOT EXISTS `tool` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Tool ID', + `plugin_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Tool Version, e.g. v1.0.0', + `sub_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Sub URL Path', + `method` varchar(64) NOT NULL DEFAULT '' COMMENT 'HTTP Request Method', + `operation` json NULL COMMENT 'Tool Openapi Operation Schema', + `activated_status` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0:activated; 1:deactivated', + PRIMARY KEY (`id`), + INDEX `idx_plugin_activated_status` (`plugin_id`, `activated_status`), + UNIQUE INDEX `uniq_idx_plugin_sub_url_method` (`plugin_id`, `sub_url`, `method`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Latest Tool'; -- Create 'tool_draft' table -CREATE TABLE IF NOT EXISTS `tool_draft` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Tool ID', `plugin_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `sub_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Sub URL Path', `method` varchar(64) NOT NULL DEFAULT '' COMMENT 'HTTP Request Method', `operation` json NULL COMMENT 'Tool Openapi Operation Schema', `debug_status` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0:not pass; 1:pass', `activated_status` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0:activated; 1:deactivated', PRIMARY KEY (`id`), INDEX `idx_plugin_created_at_id` (`plugin_id`, `created_at`, `id`), UNIQUE INDEX `uniq_idx_plugin_sub_url_method` (`plugin_id`, `sub_url`, `method`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Draft Tool'; +CREATE TABLE IF NOT EXISTS `tool_draft` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Tool ID', + `plugin_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `sub_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Sub URL Path', + `method` varchar(64) NOT NULL DEFAULT '' COMMENT 'HTTP Request Method', + `operation` json NULL COMMENT 'Tool Openapi Operation Schema', + `debug_status` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0:not pass; 1:pass', + `activated_status` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0:activated; 1:deactivated', + PRIMARY KEY (`id`), + INDEX `idx_plugin_created_at_id` (`plugin_id`, `created_at`, `id`), + UNIQUE INDEX `uniq_idx_plugin_sub_url_method` (`plugin_id`, `sub_url`, `method`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Draft Tool'; -- Create 'tool_version' table -CREATE TABLE IF NOT EXISTS `tool_version` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Primary Key ID', `tool_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Tool ID', `plugin_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', `version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Tool Version, e.g. v1.0.0', `sub_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Sub URL Path', `method` varchar(64) NOT NULL DEFAULT '' COMMENT 'HTTP Request Method', `operation` json NULL COMMENT 'Tool Openapi Operation Schema', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `deleted_at` datetime NULL COMMENT 'Delete Time', PRIMARY KEY (`id`), UNIQUE INDEX `uniq_idx_tool_version` (`tool_id`, `version`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Tool Version'; +CREATE TABLE IF NOT EXISTS `tool_version` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Primary Key ID', + `tool_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Tool ID', + `plugin_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Plugin ID', + `version` varchar(255) NOT NULL DEFAULT '' COMMENT 'Tool Version, e.g. v1.0.0', + `sub_url` varchar(512) NOT NULL DEFAULT '' COMMENT 'Sub URL Path', + `method` varchar(64) NOT NULL DEFAULT '' COMMENT 'HTTP Request Method', + `operation` json NULL COMMENT 'Tool Openapi Operation Schema', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `deleted_at` datetime NULL COMMENT 'Delete Time', + PRIMARY KEY (`id`), + UNIQUE INDEX `uniq_idx_tool_version` (`tool_id`, `version`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Tool Version'; -- Create 'user' table -CREATE TABLE IF NOT EXISTS `user` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key ID', `name` varchar(128) NOT NULL DEFAULT '' COMMENT 'User Nickname', `unique_name` varchar(128) NOT NULL DEFAULT '' COMMENT 'User Unique Name', `email` varchar(128) NOT NULL DEFAULT '' COMMENT 'Email', `password` varchar(128) NOT NULL DEFAULT '' COMMENT 'Password (Encrypted)', `description` varchar(512) NOT NULL DEFAULT '' COMMENT 'User Description', `icon_uri` varchar(512) NOT NULL DEFAULT '' COMMENT 'Avatar URI', `user_verified` bool NOT NULL DEFAULT 0 COMMENT 'User Verification Status', `locale` varchar(128) NOT NULL DEFAULT '' COMMENT 'Locale', `session_key` varchar(256) NOT NULL DEFAULT '' COMMENT 'Session Key', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Creation Time (Milliseconds)', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time (Milliseconds)', `deleted_at` bigint unsigned NULL COMMENT 'Deletion Time (Milliseconds)', PRIMARY KEY (`id`), INDEX `idx_session_key` (`session_key`), UNIQUE INDEX `uniq_email` (`email`), UNIQUE INDEX `uniq_unique_name` (`unique_name`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'User Table'; +CREATE TABLE IF NOT EXISTS `user` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key ID', + `name` varchar(128) NOT NULL DEFAULT '' COMMENT 'User Nickname', + `unique_name` varchar(128) NOT NULL DEFAULT '' COMMENT 'User Unique Name', + `email` varchar(128) NOT NULL DEFAULT '' COMMENT 'Email', + `password` varchar(128) NOT NULL DEFAULT '' COMMENT 'Password (Encrypted)', + `description` varchar(512) NOT NULL DEFAULT '' COMMENT 'User Description', + `icon_uri` varchar(512) NOT NULL DEFAULT '' COMMENT 'Avatar URI', + `user_verified` bool NOT NULL DEFAULT 0 COMMENT 'User Verification Status', + `locale` varchar(128) NOT NULL DEFAULT '' COMMENT 'Locale', + `session_key` varchar(256) NOT NULL DEFAULT '' COMMENT 'Session Key', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Creation Time (Milliseconds)', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time (Milliseconds)', + `deleted_at` bigint unsigned NULL COMMENT 'Deletion Time (Milliseconds)', + PRIMARY KEY (`id`), + INDEX `idx_session_key` (`session_key`), + UNIQUE INDEX `uniq_email` (`email`), + UNIQUE INDEX `uniq_unique_name` (`unique_name`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'User Table'; -- Create 'variable_instance' table -CREATE TABLE IF NOT EXISTS `variable_instance` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'id', `biz_type` tinyint unsigned NOT NULL COMMENT '1 for agent,2 for app', `biz_id` varchar(128) NOT NULL DEFAULT '' COMMENT '1 for agent_id,2 for app_id', `version` varchar(255) NOT NULL COMMENT 'agent or project version empty represents draft status', `keyword` varchar(255) NOT NULL COMMENT 'Keyword to Memory', `type` tinyint NOT NULL COMMENT 'Memory type 1 KV 2 list', `content` text NULL COMMENT 'content', `connector_uid` varchar(255) NOT NULL COMMENT 'connector_uid', `connector_id` bigint NOT NULL COMMENT 'connector_id, e.g. coze = 10000010', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', PRIMARY KEY (`id`), INDEX `idx_connector_key` (`biz_id`, `biz_type`, `version`, `connector_uid`, `connector_id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT 'KV Memory'; +CREATE TABLE IF NOT EXISTS `variable_instance` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'id', + `biz_type` tinyint unsigned NOT NULL COMMENT '1 for agent,2 for app', + `biz_id` varchar(128) NOT NULL DEFAULT '' COMMENT '1 for agent_id,2 for app_id', + `version` varchar(255) NOT NULL COMMENT 'agent or project version empty represents draft status', + `keyword` varchar(255) NOT NULL COMMENT 'Keyword to Memory', + `type` tinyint NOT NULL COMMENT 'Memory type 1 KV 2 list', + `content` text NULL COMMENT 'content', + `connector_uid` varchar(255) NOT NULL COMMENT 'connector_uid', + `connector_id` bigint NOT NULL COMMENT 'connector_id, e.g. coze = 10000010', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + PRIMARY KEY (`id`), + INDEX `idx_connector_key` (`biz_id`, `biz_type`, `version`, `connector_uid`, `connector_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT 'KV Memory'; -- Create 'variables_meta' table -CREATE TABLE IF NOT EXISTS `variables_meta` (`id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'id', `creator_id` bigint unsigned NOT NULL COMMENT 'creator id', `biz_type` tinyint unsigned NOT NULL COMMENT '1 for agent,2 for app', `biz_id` varchar(128) NOT NULL DEFAULT '' COMMENT '1 for agent_id,2 for app_id', `variable_list` json NULL COMMENT 'JSON data for variable configuration', `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', `version` varchar(255) NOT NULL COMMENT 'Project version, empty represents draft status', PRIMARY KEY (`id`), INDEX `idx_user_key` (`creator_id`), UNIQUE INDEX `uniq_project_key` (`biz_id`, `biz_type`, `version`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT 'KV Memory meta'; +CREATE TABLE IF NOT EXISTS `variables_meta` ( + `id` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'id', + `creator_id` bigint unsigned NOT NULL COMMENT 'creator id', + `biz_type` tinyint unsigned NOT NULL COMMENT '1 for agent,2 for app', + `biz_id` varchar(128) NOT NULL DEFAULT '' COMMENT '1 for agent_id,2 for app_id', + `variable_list` json NULL COMMENT 'JSON data for variable configuration', + `created_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Create Time in Milliseconds', + `updated_at` bigint unsigned NOT NULL DEFAULT 0 COMMENT 'Update Time in Milliseconds', + `version` varchar(255) NOT NULL COMMENT 'Project version, empty represents draft status', + PRIMARY KEY (`id`), + INDEX `idx_user_key` (`creator_id`), + UNIQUE INDEX `uniq_project_key` (`biz_id`, `biz_type`, `version`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT 'KV Memory meta'; -- Create 'workflow_draft' table -CREATE TABLE IF NOT EXISTS `workflow_draft` (`id` bigint unsigned NOT NULL COMMENT 'workflow ID', `canvas` mediumtext NULL COMMENT 'Front end schema', `input_params` mediumtext NULL COMMENT 'Input schema', `output_params` mediumtext NULL COMMENT 'Output parameter schema', `test_run_success` bool NOT NULL DEFAULT 0 COMMENT '0 not running, 1 running successfully', `modified` bool NOT NULL DEFAULT 0 COMMENT '0 has not been modified, 1 has been modified', `updated_at` bigint unsigned NULL COMMENT 'Update Time in Milliseconds', `deleted_at` datetime(3) NULL COMMENT 'Delete Time', `commit_id` varchar(255) NOT NULL COMMENT 'used to uniquely identify a draft snapshot', PRIMARY KEY (`id`), INDEX `idx_updated_at` (`updated_at` DESC)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Workflow canvas draft table, used to record the latest draft canvas information of workflow'; +CREATE TABLE IF NOT EXISTS `workflow_draft` ( + `id` bigint unsigned NOT NULL COMMENT 'workflow ID', + `canvas` mediumtext NULL COMMENT 'Front end schema', + `input_params` mediumtext NULL COMMENT 'Input schema', + `output_params` mediumtext NULL COMMENT 'Output parameter schema', + `test_run_success` bool NOT NULL DEFAULT 0 COMMENT '0 not running, 1 running successfully', + `modified` bool NOT NULL DEFAULT 0 COMMENT '0 has not been modified, 1 has been modified', + `updated_at` bigint unsigned NULL COMMENT 'Update Time in Milliseconds', + `deleted_at` datetime(3) NULL COMMENT 'Delete Time', + `commit_id` varchar(255) NOT NULL COMMENT 'used to uniquely identify a draft snapshot', + PRIMARY KEY (`id`), + INDEX `idx_updated_at` (`updated_at` DESC) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Workflow canvas draft table, used to record the latest draft canvas information of workflow'; -- Create 'workflow_execution' table -CREATE TABLE IF NOT EXISTS `workflow_execution` (`id` bigint unsigned NOT NULL COMMENT 'execute id', `workflow_id` bigint unsigned NOT NULL COMMENT 'workflow_id', `version` varchar(50) NULL COMMENT 'workflow version. empty if is draft', `space_id` bigint unsigned NOT NULL COMMENT 'the space id the workflow belongs to', `mode` tinyint unsigned NOT NULL COMMENT 'the execution mode: 1. debug run 2. release run 3. node debug', `operator_id` bigint unsigned NOT NULL COMMENT 'the user id that runs this workflow', `connector_id` bigint unsigned NULL COMMENT 'the connector on which this execution happened', `connector_uid` varchar(64) NULL COMMENT 'user id of the connector', `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', `log_id` varchar(128) NULL COMMENT 'log id', `status` tinyint unsigned NULL COMMENT '1=running 2=success 3=fail 4=interrupted', `duration` bigint unsigned NULL COMMENT 'execution duration in millisecond', `input` mediumtext NULL COMMENT 'actual input of this execution', `output` mediumtext NULL COMMENT 'the actual output of this execution', `error_code` varchar(255) NULL COMMENT 'error code if any', `fail_reason` mediumtext NULL COMMENT 'the reason for failure', `input_tokens` bigint unsigned NULL COMMENT 'number of input tokens', `output_tokens` bigint unsigned NULL COMMENT 'number of output tokens', `updated_at` bigint unsigned NULL COMMENT 'update time in millisecond', `root_execution_id` bigint unsigned NULL COMMENT 'the top level execution id. Null if this is the root', `parent_node_id` varchar(128) NULL COMMENT 'the node key for the sub_workflow node that executes this workflow', `app_id` bigint unsigned NULL COMMENT 'app id this workflow execution belongs to', `node_count` mediumint unsigned NULL COMMENT 'the total node count of the workflow', `resume_event_id` bigint unsigned NULL COMMENT 'the current event ID which is resuming', `agent_id` bigint unsigned NULL COMMENT 'the agent that this execution binds to', `sync_pattern` tinyint unsigned NULL COMMENT 'the sync pattern 1. sync 2. async 3. stream', `commit_id` varchar(255) NULL COMMENT 'draft commit id this execution belongs to', PRIMARY KEY (`id`), INDEX `idx_workflow_id_version_mode_created_at` (`workflow_id`, `version`, `mode`, `created_at`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Workflow Execution Record Table, used to record the status of each workflow execution'; +CREATE TABLE IF NOT EXISTS `workflow_execution` ( + `id` bigint unsigned NOT NULL COMMENT 'execute id', + `workflow_id` bigint unsigned NOT NULL COMMENT 'workflow_id', + `version` varchar(50) NULL COMMENT 'workflow version. empty if is draft', + `space_id` bigint unsigned NOT NULL COMMENT 'the space id the workflow belongs to', + `mode` tinyint unsigned NOT NULL COMMENT 'the execution mode: 1. debug run 2. release run 3. node debug', + `operator_id` bigint unsigned NOT NULL COMMENT 'the user id that runs this workflow', + `connector_id` bigint unsigned NULL COMMENT 'the connector on which this execution happened', + `connector_uid` varchar(64) NULL COMMENT 'user id of the connector', + `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', + `log_id` varchar(128) NULL COMMENT 'log id', + `status` tinyint unsigned NULL COMMENT '1=running 2=success 3=fail 4=interrupted', + `duration` bigint unsigned NULL COMMENT 'execution duration in millisecond', + `input` mediumtext NULL COMMENT 'actual input of this execution', + `output` mediumtext NULL COMMENT 'the actual output of this execution', + `error_code` varchar(255) NULL COMMENT 'error code if any', + `fail_reason` mediumtext NULL COMMENT 'the reason for failure', + `input_tokens` bigint unsigned NULL COMMENT 'number of input tokens', + `output_tokens` bigint unsigned NULL COMMENT 'number of output tokens', + `updated_at` bigint unsigned NULL COMMENT 'update time in millisecond', + `root_execution_id` bigint unsigned NULL COMMENT 'the top level execution id. Null if this is the root', + `parent_node_id` varchar(128) NULL COMMENT 'the node key for the sub_workflow node that executes this workflow', + `app_id` bigint unsigned NULL COMMENT 'app id this workflow execution belongs to', + `node_count` mediumint unsigned NULL COMMENT 'the total node count of the workflow', + `resume_event_id` bigint unsigned NULL COMMENT 'the current event ID which is resuming', + `agent_id` bigint unsigned NULL COMMENT 'the agent that this execution binds to', + `sync_pattern` tinyint unsigned NULL COMMENT 'the sync pattern 1. sync 2. async 3. stream', + `commit_id` varchar(255) NULL COMMENT 'draft commit id this execution belongs to', + PRIMARY KEY (`id`), + INDEX `idx_workflow_id_version_mode_created_at` (`workflow_id`, `version`, `mode`, `created_at`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Workflow Execution Record Table, used to record the status of each workflow execution'; -- Create 'workflow_meta' table -CREATE TABLE IF NOT EXISTS `workflow_meta` (`id` bigint unsigned NOT NULL COMMENT 'workflow id', `name` varchar(256) NOT NULL COMMENT 'workflow name', `description` varchar(2000) NOT NULL COMMENT 'workflow description', `icon_uri` varchar(256) NOT NULL COMMENT 'icon uri', `status` tinyint unsigned NOT NULL COMMENT '0: Not published, 1: Published', `content_type` tinyint unsigned NOT NULL COMMENT '0 Users 1 Official', `mode` tinyint unsigned NOT NULL COMMENT '0:workflow, 3:chat_flow', `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', `updated_at` bigint unsigned NULL COMMENT 'update time in millisecond', `deleted_at` datetime(3) NULL COMMENT 'delete time in millisecond', `creator_id` bigint unsigned NOT NULL COMMENT 'user id for creator', `tag` tinyint unsigned NULL COMMENT 'template tag: Tag: 1=All, 2=Hot, 3=Information, 4=Music, 5=Picture, 6=UtilityTool, 7=Life, 8=Traval, 9=Network, 10=System, 11=Movie, 12=Office, 13=Shopping, 14=Education, 15=Health, 16=Social, 17=Entertainment, 18=Finance, 100=Hidden', `author_id` bigint unsigned NOT NULL COMMENT 'Original author user ID', `space_id` bigint unsigned NOT NULL COMMENT 'space id', `updater_id` bigint unsigned NULL COMMENT 'User ID for updating metadata', `source_id` bigint unsigned NULL COMMENT 'Workflow ID of source', `app_id` bigint unsigned NULL COMMENT 'app id', `latest_version` varchar(50) NULL COMMENT 'the version of the most recent publish', `latest_version_ts` bigint unsigned NULL COMMENT 'create time of latest version', PRIMARY KEY (`id`), INDEX `idx_app_id` (`app_id`), INDEX `idx_latest_version_ts` (`latest_version_ts` DESC), INDEX `idx_space_id_app_id_status_latest_version_ts` (`space_id`, `app_id`, `status`, `latest_version_ts`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'The workflow metadata table,used to record the basic metadata of workflow'; +CREATE TABLE IF NOT EXISTS `workflow_meta` ( + `id` bigint unsigned NOT NULL COMMENT 'workflow id', + `name` varchar(256) NOT NULL COMMENT 'workflow name', + `description` varchar(2000) NOT NULL COMMENT 'workflow description', + `icon_uri` varchar(256) NOT NULL COMMENT 'icon uri', + `status` tinyint unsigned NOT NULL COMMENT '0: Not published, 1: Published', + `content_type` tinyint unsigned NOT NULL COMMENT '0 Users 1 Official', + `mode` tinyint unsigned NOT NULL COMMENT '0:workflow, 3:chat_flow', + `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', + `updated_at` bigint unsigned NULL COMMENT 'update time in millisecond', + `deleted_at` datetime(3) NULL COMMENT 'delete time in millisecond', + `creator_id` bigint unsigned NOT NULL COMMENT 'user id for creator', + `tag` tinyint unsigned NULL COMMENT 'template tag: Tag: 1=All, 2=Hot, 3=Information, 4=Music, 5=Picture, 6=UtilityTool, 7=Life, 8=Traval, 9=Network, 10=System, 11=Movie, 12=Office, 13=Shopping, 14=Education, 15=Health, 16=Social, 17=Entertainment, 18=Finance, 100=Hidden', + `author_id` bigint unsigned NOT NULL COMMENT 'Original author user ID', + `space_id` bigint unsigned NOT NULL COMMENT 'space id', + `updater_id` bigint unsigned NULL COMMENT 'User ID for updating metadata', + `source_id` bigint unsigned NULL COMMENT 'Workflow ID of source', + `app_id` bigint unsigned NULL COMMENT 'app id', + `latest_version` varchar(50) NULL COMMENT 'the version of the most recent publish', + `latest_version_ts` bigint unsigned NULL COMMENT 'create time of latest version', + PRIMARY KEY (`id`), + INDEX `idx_app_id` (`app_id`), + INDEX `idx_latest_version_ts` (`latest_version_ts` DESC), + INDEX `idx_space_id_app_id_status_latest_version_ts` (`space_id`, `app_id`, `status`, `latest_version_ts`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'The workflow metadata table,used to record the basic metadata of workflow'; -- Create 'workflow_reference' table -CREATE TABLE IF NOT EXISTS `workflow_reference` (`id` bigint unsigned NOT NULL COMMENT 'workflow id', `referred_id` bigint unsigned NOT NULL COMMENT 'the id of the workflow that is referred by other entities', `referring_id` bigint unsigned NOT NULL COMMENT 'the entity id that refers this workflow', `refer_type` tinyint unsigned NOT NULL COMMENT '1 subworkflow 2 tool', `referring_biz_type` tinyint unsigned NOT NULL COMMENT 'the biz type the referring entity belongs to: 1. workflow 2. agent', `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', `status` tinyint unsigned NOT NULL COMMENT 'whether this reference currently takes effect. 0: disabled 1: enabled', `deleted_at` datetime(3) NULL COMMENT 'Delete Time', PRIMARY KEY (`id`), INDEX `idx_referred_id_referring_biz_type_status` (`referred_id`, `referring_biz_type`, `status`), INDEX `idx_referring_id_status` (`referring_id`, `status`), UNIQUE INDEX `uniq_referred_id_referring_id_refer_type` (`referred_id`, `referring_id`, `refer_type`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'The workflow association table,used to record the direct mutual reference relationship between workflows'; +CREATE TABLE IF NOT EXISTS `workflow_reference` ( + `id` bigint unsigned NOT NULL COMMENT 'workflow id', + `referred_id` bigint unsigned NOT NULL COMMENT 'the id of the workflow that is referred by other entities', + `referring_id` bigint unsigned NOT NULL COMMENT 'the entity id that refers this workflow', + `refer_type` tinyint unsigned NOT NULL COMMENT '1 subworkflow 2 tool', + `referring_biz_type` tinyint unsigned NOT NULL COMMENT 'the biz type the referring entity belongs to: 1. workflow 2. agent', + `created_at` bigint unsigned NOT NULL COMMENT 'create time in millisecond', + `status` tinyint unsigned NOT NULL COMMENT 'whether this reference currently takes effect. 0: disabled 1: enabled', + `deleted_at` datetime(3) NULL COMMENT 'Delete Time', + PRIMARY KEY (`id`), + INDEX `idx_referred_id_referring_biz_type_status` (`referred_id`, `referring_biz_type`, `status`), + INDEX `idx_referring_id_status` (`referring_id`, `status`), + UNIQUE INDEX `uniq_referred_id_referring_id_refer_type` (`referred_id`, `referring_id`, `refer_type`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'The workflow association table,used to record the direct mutual reference relationship between workflows'; -- Create 'workflow_snapshot' table -CREATE TABLE IF NOT EXISTS `workflow_snapshot` (`workflow_id` bigint unsigned NOT NULL COMMENT 'workflow id this snapshot belongs to', `commit_id` varchar(255) NOT NULL COMMENT 'the commit id of the workflow draft', `canvas` mediumtext NULL COMMENT 'frontend schema for this snapshot', `input_params` mediumtext NULL COMMENT 'input parameter info', `output_params` mediumtext NULL COMMENT 'output parameter info', `created_at` bigint unsigned NOT NULL COMMENT 'Create Time in Milliseconds', `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', PRIMARY KEY (`id`), UNIQUE INDEX `uniq_workflow_id_commit_id` (`workflow_id`, `commit_id`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'snapshot for executed workflow draft'; +CREATE TABLE IF NOT EXISTS `workflow_snapshot` ( + `workflow_id` bigint unsigned NOT NULL COMMENT 'workflow id this snapshot belongs to', + `commit_id` varchar(255) NOT NULL COMMENT 'the commit id of the workflow draft', + `canvas` mediumtext NULL COMMENT 'frontend schema for this snapshot', + `input_params` mediumtext NULL COMMENT 'input parameter info', + `output_params` mediumtext NULL COMMENT 'output parameter info', + `created_at` bigint unsigned NOT NULL COMMENT 'Create Time in Milliseconds', + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', + PRIMARY KEY (`id`), + UNIQUE INDEX `uniq_workflow_id_commit_id` (`workflow_id`, `commit_id`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'snapshot for executed workflow draft'; -- Create 'workflow_version' table -CREATE TABLE IF NOT EXISTS `workflow_version` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', `workflow_id` bigint unsigned NOT NULL COMMENT 'workflow id', `version` varchar(50) NOT NULL COMMENT 'Published version', `version_description` varchar(2000) NOT NULL COMMENT 'Version Description', `canvas` mediumtext NULL COMMENT 'Front end schema', `input_params` mediumtext NULL COMMENT 'input params', `output_params` mediumtext NULL COMMENT 'output params', `creator_id` bigint unsigned NOT NULL COMMENT 'creator id', `created_at` bigint unsigned NOT NULL COMMENT 'Create Time in Milliseconds', `deleted_at` datetime(3) NULL COMMENT 'Delete Time', `commit_id` varchar(255) NOT NULL COMMENT 'the commit id corresponding to this version', PRIMARY KEY (`id`), INDEX `idx_id_created_at` (`workflow_id`, `created_at`), UNIQUE INDEX `uniq_workflow_id_version` (`workflow_id`, `version`)) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Workflow Canvas Version Information Table, used to record canvas information for different versions'; +CREATE TABLE IF NOT EXISTS `workflow_version` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID', + `workflow_id` bigint unsigned NOT NULL COMMENT 'workflow id', + `version` varchar(50) NOT NULL COMMENT 'Published version', + `version_description` varchar(2000) NOT NULL COMMENT 'Version Description', + `canvas` mediumtext NULL COMMENT 'Front end schema', + `input_params` mediumtext NULL COMMENT 'input params', + `output_params` mediumtext NULL COMMENT 'output params', + `creator_id` bigint unsigned NOT NULL COMMENT 'creator id', + `created_at` bigint unsigned NOT NULL COMMENT 'Create Time in Milliseconds', + `deleted_at` datetime(3) NULL COMMENT 'Delete Time', + `commit_id` varchar(255) NOT NULL COMMENT 'the commit id corresponding to this version', + PRIMARY KEY (`id`), + INDEX `idx_id_created_at` (`workflow_id`, `created_at`), + UNIQUE INDEX `uniq_workflow_id_version` (`workflow_id`, `version`) +) ENGINE=InnoDB CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Workflow Canvas Version Information Table, used to record canvas information for different versions'; -- 初始化用户表数据 -- 使用 INSERT ON DUPLICATE KEY UPDATE 语句 -- 当主键或唯一键冲突时,不会插入新记录,而是更新指定字段 @@ -142,12 +1010,12 @@ INSERT INTO template (agent_id, space_id, product_entity_type, meta_info) VALUES ON DUPLICATE KEY UPDATE agent_id = VALUES(agent_id), meta_info = VALUES(meta_info); -INSERT INTO opencoze.workflow_meta(id,space_id, name, description, icon_uri, created_at,status, content_type, mode, creator_id, tag, author_id) VALUES +INSERT INTO workflow_meta(id,space_id, name, description, icon_uri, created_at,status, content_type, mode, creator_id, tag, author_id) VALUES (1, 999999,'split_messages', '示例:把较长的文本消息拆分多个,适合拟人发消息场景', 'default_icon/default_workflow_icon.png', 1750254785913,3, 0, 0, 0, 0, 0) ON DUPLICATE KEY UPDATE id = VALUES(id); -INSERT INTO opencoze.workflow_draft (id, canvas, input_params, output_params, test_run_success, modified, updated_at, deleted_at, commit_id) VALUES (1, '{ +INSERT INTO workflow_draft (id, canvas, input_params, output_params, test_run_success, modified, updated_at, deleted_at, commit_id) VALUES (1, '{ "nodes": [ { "id": "100001", diff --git a/frontend/apps/coze-studio/rsbuild.config.ts b/frontend/apps/coze-studio/rsbuild.config.ts index 0dc9b1e2..05e47fc3 100644 --- a/frontend/apps/coze-studio/rsbuild.config.ts +++ b/frontend/apps/coze-studio/rsbuild.config.ts @@ -33,6 +33,12 @@ const mergedConfig = defineConfig({ secure: false, changeOrigin: true, }, + { + context: ['/v1'], + target: API_PROXY_TARGET, + secure: false, + changeOrigin: true, + }, ], }, html: { diff --git a/frontend/config/rsbuild-config/src/index.ts b/frontend/config/rsbuild-config/src/index.ts index 1572c55a..b2cca803 100644 --- a/frontend/config/rsbuild-config/src/index.ts +++ b/frontend/config/rsbuild-config/src/index.ts @@ -21,9 +21,9 @@ import { pluginSass } from '@rsbuild/plugin-sass'; import { pluginReact } from '@rsbuild/plugin-react'; import { pluginLess } from '@rsbuild/plugin-less'; import { type RsbuildConfig, mergeRsbuildConfig } from '@rsbuild/core'; +import { SemiRspackPlugin } from '@douyinfe/semi-rspack-plugin'; import { PkgRootWebpackPlugin } from '@coze-arch/pkg-root-webpack-plugin'; import { GLOBAL_ENVS } from '@coze-arch/bot-env'; -import { SemiRspackPlugin } from '@douyinfe/semi-rspack-plugin'; const getDefine = () => { const define = {}; diff --git a/frontend/config/tailwind-config/package.json b/frontend/config/tailwind-config/package.json index 6a52661b..f173abc8 100644 --- a/frontend/config/tailwind-config/package.json +++ b/frontend/config/tailwind-config/package.json @@ -6,6 +6,7 @@ "exports": { ".": "./src/index.js", "./coze": "./src/coze.js", + "./util": "./src/util.js", "./design-token": "./src/design-token.ts" }, "main": "src/index.js", diff --git a/frontend/config/tailwind-config/src/util.js b/frontend/config/tailwind-config/src/util.js new file mode 100644 index 00000000..81ba8b84 --- /dev/null +++ b/frontend/config/tailwind-config/src/util.js @@ -0,0 +1,282 @@ +/* + * 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. + */ + +const plugin = require('tailwindcss/plugin'); + +const lightModeVariables = require('./light'); +const darkModeVariables = require('./dark'); + +// 用于生成 CSS 变量的帮助函数 +function generateCssVariables(variables, theme) { + return Object.entries(variables).reduce((acc, [key, value]) => { + acc[`--${key}`] = theme ? theme(value) : value; + return acc; + }, {}); +} + +// 样式语义化 +function generateSemanticVariables(semantics, theme, property) { + return Object.entries(semantics).map(([key, value]) => ({ + [`.${key}`]: { + [property]: theme(value), + }, + })); +} + +const semanticForeground = { + /* Theme */ + 'coz-fg-hglt-plus': 'colors.foreground.5', + 'coz-fg-hglt-plus-dim': 'colors.foreground.5', + 'coz-fg-hglt': 'colors.brand.5', + 'coz-fg-hglt-dim': 'colors.brand.3', + 'coz-fg-plus': 'colors.foreground.4', + 'coz-fg': 'colors.foreground.3', + 'coz-fg-primary': 'colors.foreground.3', + 'coz-fg-secondary': 'colors.foreground.2', + 'coz-fg-dim': 'colors.foreground.1', + 'coz-fg-white': 'colors.foreground.7', + 'coz-fg-white-dim': 'colors.foreground.white', + 'coz-fg-hglt-ai': 'colors.purple.5', + 'coz-fg-hglt-ai-dim': 'colors.purple.3', + /* Functional Color */ + 'coz-fg-hglt-red': 'colors.red.5', + 'coz-fg-hglt-red-dim': 'colors.red.3', + 'coz-fg-hglt-yellow': 'colors.yellow.5', + 'coz-fg-hglt-yellow-dim': 'colors.yellow.3', + 'coz-fg-hglt-green': 'colors.green.5', + 'coz-fg-hglt-green-dim': 'colors.green.3', + /* Chart, Tag Only */ + 'coz-fg-color-orange': 'colors.yellow.5', + 'coz-fg-color-orange-dim': 'colors.yellow.3', + 'coz-fg-color-emerald': 'colors.green.5', + 'coz-fg-color-emerald-dim': 'colors.green.3', + 'coz-fg-color-cyan': 'colors.cyan.50', + 'coz-fg-color-cyan-dim': 'colors.cyan.30', + 'coz-fg-color-blue': 'colors.blue.50', + 'coz-fg-color-blue-dim': 'colors.blue.30', + 'coz-fg-color-purple': 'colors.purple.50', + 'coz-fg-color-purple-dim': 'colors.purple.30', + 'coz-fg-color-magenta': 'colors.magenta.50', + 'coz-fg-color-magenta-dim': 'colors.magenta.3', + 'coz-fg-color-yellow': 'colors.yellow.50', + 'coz-fg-color-yellow-dim': 'colors.yellow.30', + /* Code Only */ + 'coz-fg-hglt-orange': 'colors.orange.5', + 'coz-fg-hglt-orange-dim': 'colors.orange.3', + 'coz-fg-hglt-emerald': 'colors.emerald.5', + 'coz-fg-hglt-emerald-dim': 'colors.emerald.3', + 'coz-fg-hglt-cyan': 'colors.cyan.5', + 'coz-fg-hglt-cyan-dim': 'colors.cyan.3', + 'coz-fg-hglt-blue': 'colors.blue.5', + 'coz-fg-hglt-blue-dim': 'colors.blue.3', + 'coz-fg-hglt-purple': 'colors.purple.5', + 'coz-fg-hglt-purple-dim': 'colors.purple.3', + 'coz-fg-hglt-magenta': 'colors.magenta.5', + 'coz-fg-hglt-magenta-dim': 'colors.magenta.3', + /* branding Only */ + 'coz-fg-color-brand': 'colors.brand.50', + 'coz-fg-color-brand-dim': 'colors.brand.30', + 'coz-fg-color-alternative': 'colors.alternative.50', + 'coz-fg-color-alternative-dim': 'colors.alternative.30', +}; + +const semanticMiddleground = { + /* Theme */ + 'coz-mg-hglt-plus-pressed': 'colors.brand.7', + 'coz-mg-hglt-plus-hovered': 'colors.brand.6', + 'coz-mg-hglt-plus': 'colors.brand.5', + 'coz-mg-hglt-plus-dim': 'colors.brand.3', + 'coz-mg-hglt-secondary-pressed': 'colors.brand.2', + 'coz-mg-hglt-secondary-hovered': 'colors.brand.1', + 'coz-mg-hglt-secondary': 'colors.brand.0', + 'coz-mg-hglt-secondary-red': 'colors.red.0', + 'coz-mg-hglt-secondary-yellow': 'colors.yellow.0', + 'coz-mg-hglt-secondary-green': 'colors.green.0', + 'coz-mg-plus-pressed': 'colors.background.8', + 'coz-mg-plus-hovered': 'colors.background.7', + 'coz-mg-plus': 'colors.background.6', + 'coz-mg-hglt-pressed': 'colors.brand.3', + 'coz-mg-hglt-hovered': 'colors.brand.2', + 'coz-mg-hglt-plus-ai-pressed': 'colors.purple.7', + 'coz-mg-hglt-plus-ai-hovered': 'colors.purple.6', + 'coz-mg-hglt-plus-ai': 'colors.purple.5', + 'coz-mg-hglt-plus-ai-dim': 'colors.purple.3', + 'coz-mg-hglt': 'colors.brand.1', + 'coz-mg-hglt-ai-pressed': 'colors.purple.3', + 'coz-mg-hglt-ai-hovered': 'colors.purple.2', + 'coz-mg-hglt-ai': 'colors.purple.1', + /* Functional Color */ + 'coz-mg-hglt-plus-red-pressed': 'colors.red.7', + 'coz-mg-hglt-plus-red-hovered': 'colors.red.6', + 'coz-mg-hglt-plus-red': 'colors.red.5', + 'coz-mg-hglt-plus-red-dim': 'colors.red.3', + 'coz-mg-hglt-plus-yellow-pressed': 'colors.yellow.7', + 'coz-mg-hglt-plus-yellow-hovered': 'colors.yellow.6', + 'coz-mg-hglt-plus-yellow': 'colors.yellow.5', + 'coz-mg-hglt-plus-yellow-dim': 'colors.yellow.3', + 'coz-mg-hglt-plus-green-pressed': 'colors.green.7', + 'coz-mg-hglt-plus-green-hovered': 'colors.green.6', + 'coz-mg-hglt-plus-green': 'colors.green.5', + 'coz-mg-hglt-plus-green-dim': 'colors.green.3', + 'coz-mg-hglt-red-pressed': 'colors.red.3', + 'coz-mg-hglt-red-hovered': 'colors.red.2', + 'coz-mg-hglt-red': 'colors.red.1', + 'coz-mg-hglt-yellow-pressed': 'colors.yellow.3', + 'coz-mg-hglt-yellow-hovered': 'colors.yellow.2', + 'coz-mg-hglt-yellow': 'colors.yellow.1', + 'coz-mg-hglt-green-pressed': 'colors.green.3', + 'coz-mg-hglt-green-hovered': 'colors.green.2', + 'coz-mg-hglt-green': 'colors.green.1', + /* Card, Tag, Avatar Only */ + 'coz-mg-color-plus-orange': 'colors.yellow.5', + 'coz-mg-color-plus-emerald': 'colors.green.5', + 'coz-mg-color-plus-cyan': 'colors.cyan.50', + 'coz-mg-color-plus-blue': 'colors.blue.50', + 'coz-mg-color-plus-purple': 'colors.purple.50', + 'coz-mg-color-plus-magenta': 'colors.magenta.50', + 'coz-mg-color-plus-yellow': 'colors.yellow.50', + 'coz-mg-color-orange-pressed': 'colors.yellow.3', + 'coz-mg-color-orange-hovered': 'colors.yellow.2', + 'coz-mg-color-orange': 'colors.yellow.1', + 'coz-mg-color-emerald-pressed': 'colors.green.3', + 'coz-mg-color-emerald-hovered': 'colors.green.2', + 'coz-mg-color-emerald': 'colors.green.1', + 'coz-mg-color-cyan-pressed': 'colors.cyan.30', + 'coz-mg-color-cyan-hovered': 'colors.cyan.20', + 'coz-mg-color-cyan': 'colors.cyan.10', + 'coz-mg-color-blue-pressed': 'colors.blue.30', + 'coz-mg-color-blue-hovered': 'colors.blue.20', + 'coz-mg-color-blue': 'colors.blue.10', + 'coz-mg-color-purple-pressed': 'colors.purple.30', + 'coz-mg-color-purple-hovered': 'colors.purple.20', + 'coz-mg-color-purple': 'colors.purple.10', + 'coz-mg-color-magenta-pressed': 'colors.magenta.30', + 'coz-mg-color-magenta-hovered': 'colors.magenta.20', + 'coz-mg-color-magenta': 'colors.magenta.10', + 'coz-mg-primary-pressed': 'colors.background.7', + 'coz-mg-primary-hovered': 'colors.background.6', + 'coz-mg-primary': 'colors.background.5', + 'coz-mg-secondary-pressed': 'colors.background.6', + 'coz-mg-secondary-hovered': 'colors.background.5', + 'coz-mg-secondary': 'colors.background.4', + 'coz-mg': 'colors.background.4', + 'coz-mg-mask': 'colors.mask.5', + 'coz-mg-table-fixed-hovered': 'colors.background.0', + 'coz-mg-card-pressed': 'colors.background.3', + 'coz-mg-card-hovered': 'colors.background.3', + 'coz-mg-card': 'colors.background.3', + /** brand */ + 'coz-mg-color-plus-brand': 'colors.brand.50', +}; + +const semanticBackground = { + 'coz-bg-max': 'colors.background.3', + 'coz-bg-plus': 'colors.background.2', + 'coz-bg-primary': 'colors.background.1', + 'coz-bg': 'colors.background.1', + 'coz-bg-secondary': 'colors.background.0', +}; + +const semanticShadow = { + 'coz-shadow': 'boxShadow.normal', + 'coz-shadow-large': 'boxShadow.large', + 'coz-shadow-default': 'boxShadow.normal', + 'coz-shadow-small': 'boxShadow.small', +}; + +// Add button rounded definitions +const buttonRounded = { + 'coz-btn-rounded-large': 'btnBorderRadius.large', + 'coz-btn-rounded-normal': 'btnBorderRadius.normal', + 'coz-btn-rounded-small': 'btnBorderRadius.small', + 'coz-btn-rounded-mini': 'btnBorderRadius.mini', +}; + +const inputRounded = { + 'coz-input-rounded-large': 'inputBorderRadius.large', + 'coz-input-rounded-normal': 'inputBorderRadius.normal', + 'coz-input-rounded-small': 'inputBorderRadius.small', +}; + +const inputHeight = { + 'coz-input-height-large': 'inputHeight.large', + 'coz-input-height-normal': 'inputHeight.normal', + 'coz-input-height-small': 'inputHeight.small', +}; + +const semanticStroke = { + 'coz-stroke-hglt': 'colors.brand.5', + 'coz-stroke-plus': 'colors.stroke.6', + 'coz-stroke-primary': 'colors.stroke.5', + 'coz-stroke-hglt-red': 'colors.red.5', + 'coz-stroke-hglt-yellow': 'colors.yellow.5', + 'coz-stroke-hglt-green': 'colors.green.5', + 'coz-stroke-color-orange': 'colors.yellow.5', + 'coz-stroke-color-emerald': 'colors.green.5', + 'coz-stroke-color-cyan': 'colors.cyan.50', + 'coz-stroke-color-blue': 'colors.blue.50', + 'coz-stroke-color-purple': 'colors.purple.50', + 'coz-stroke-color-magenta': 'colors.magenta.50', + 'coz-stroke-color-yellow': 'colors.yellow.50', + 'coz-stroke-color-brand': 'colors.brand.50', + 'coz-stroke-opaque': 'colors.stroke.opaque', + 'coz-stroke-max': 'colors.stroke.max', +}; + +function genTailwindPlugin(defaultCls, darkCls) { + return plugin(function ({ addBase, addUtilities, theme }) { + addBase({ + [defaultCls]: generateCssVariables(lightModeVariables), + [darkCls]: generateCssVariables(darkModeVariables), + }); + + addBase({ + [defaultCls]: { + ...generateCssVariables(semanticForeground, theme), + ...generateCssVariables(semanticMiddleground, theme), + ...generateCssVariables(semanticBackground, theme), + ...generateCssVariables(semanticStroke, theme), + ...generateCssVariables(semanticShadow, theme), + ...generateCssVariables(buttonRounded, theme), + ...generateCssVariables(inputRounded, theme), + ...generateCssVariables(inputHeight, theme), + }, + }); + + addUtilities([ + ...generateSemanticVariables(semanticForeground, theme, 'color'), + ...generateSemanticVariables( + semanticMiddleground, + theme, + 'background-color', + ), + ...generateSemanticVariables( + semanticBackground, + theme, + 'background-color', + ), + ...generateSemanticVariables(semanticStroke, theme, 'border-color'), + ...generateSemanticVariables(semanticShadow, theme, 'box-shadow'), + ...generateSemanticVariables(buttonRounded, theme, 'border-radius'), + ...generateSemanticVariables(inputRounded, theme, 'border-radius'), + ...generateSemanticVariables(inputHeight, theme, 'height'), + ]); + }); +} + +module.exports = { + genTailwindPlugin, +}; diff --git a/frontend/packages/agent-ide/layout/src/components/header/index.tsx b/frontend/packages/agent-ide/layout/src/components/header/index.tsx index 3d114856..14643f77 100644 --- a/frontend/packages/agent-ide/layout/src/components/header/index.tsx +++ b/frontend/packages/agent-ide/layout/src/components/header/index.tsx @@ -129,10 +129,8 @@ export const BotHeader: React.FC = props => { editBotInfoFn={editBotInfoFn} deployButton={props.deployButton} /> - {/** mode selector */} - {diffTask || IS_OPEN_SOURCE ? null : ( - - )} + {/** 模式选择器 */} + {diffTask ? null : } {/* 2. Middle bot menu area - offline */} diff --git a/frontend/packages/agent-ide/space-bot/src/hook/use-create-bot/form-switch/index.tsx b/frontend/packages/agent-ide/space-bot/src/hook/use-create-bot/form-switch/index.tsx index 43ae6508..16c11274 100644 --- a/frontend/packages/agent-ide/space-bot/src/hook/use-create-bot/form-switch/index.tsx +++ b/frontend/packages/agent-ide/space-bot/src/hook/use-create-bot/form-switch/index.tsx @@ -45,4 +45,6 @@ function SwitchWithDesc({ ); } -export const FormSwitch = withField(SwitchWithDesc); +export const FormSwitch = withField(SwitchWithDesc) as ReturnType< + typeof withField +>; diff --git a/frontend/packages/arch/bot-env-adapter/package.json b/frontend/packages/arch/bot-env-adapter/package.json index 84676a10..4c66435a 100644 --- a/frontend/packages/arch/bot-env-adapter/package.json +++ b/frontend/packages/arch/bot-env-adapter/package.json @@ -7,6 +7,7 @@ "maintainers": [], "exports": { ".": "./src/index.ts", + "./configs": "./src/configs.ts", "./build": "./scripts/build.ts", "./typings": "./src/typings.d.ts", "./runtime": "./src/runtime/index.ts" @@ -14,6 +15,9 @@ "main": "src/index.ts", "typesVersions": { "*": { + "configs": [ + "./src/configs.ts" + ], "build": [ "./scripts/build.ts" ], diff --git a/frontend/packages/arch/resources/studio-i18n-resource/src/locales/en.json b/frontend/packages/arch/resources/studio-i18n-resource/src/locales/en.json index a6bfe24a..1f3189b5 100644 --- a/frontend/packages/arch/resources/studio-i18n-resource/src/locales/en.json +++ b/frontend/packages/arch/resources/studio-i18n-resource/src/locales/en.json @@ -3769,5 +3769,29 @@ "workspace_develop": "Development", "workspace_develop_search_project": "Search for projects", "workspace_library_search": "Search resources", - "workspace_no_permission_access": "No permission to access this workspace." + "workspace_no_permission_access": "No permission to access this workspace.", + "web_sdk_add_new_conversation": "Create a new conversation", + "store_start_new_chat": "Add chat", + "sendFailed": "Send failed", + "chat_voice_input_need_focus": "Click to activate input", + "web_sdk_create_conversation": "A new session has been created", + "web_sdk_conversation_history": "conversation history", + "web_sdk_conversation_default_name": "Newly created session", + "web_sdk_delete": "delete", + "web_sdk_delete_conversation": "Delete session", + "web_sdk_rename_conversation": "rename session", + "web_sdk_confirm": "OK", + "web_sdk_cancel": "cancel", + "web_sdk_open_conversations": "Expand Sidebar", + "unbind_notification": "Notification: Agent has been unbinded.", + "profile_history_today": "Today", + "log_pay_wall_date_filter_30_days": "Past 30 days", + "web_sdk_past": "past", + "404_title": "Sorry, this page doesn't exist", + "404_content": "Please check your link or try again", + "overview_bi_assistant_system_error": "System error. Please try again later.", + "web_sdk_retry_notification": "Please try again later.", + "chatInputPlaceholder": "Send message", + "web_sdk_official_banner": "Powered by {docs_link}. AI-generated content for reference only.", + "web_sdk_official_banner_link": "coze" } diff --git a/frontend/packages/arch/resources/studio-i18n-resource/src/locales/zh-CN.json b/frontend/packages/arch/resources/studio-i18n-resource/src/locales/zh-CN.json index aa08969e..9ee69987 100644 --- a/frontend/packages/arch/resources/studio-i18n-resource/src/locales/zh-CN.json +++ b/frontend/packages/arch/resources/studio-i18n-resource/src/locales/zh-CN.json @@ -3817,5 +3817,29 @@ "workspace_develop": "项目开发", "workspace_develop_search_project": "搜索项目", "workspace_library_search": "搜索资源", - "workspace_no_permission_access": "无法查看空间" + "workspace_no_permission_access": "无法查看空间", + "web_sdk_add_new_conversation": "创建新会话", + "store_start_new_chat": "新增对话", + "sendFailed": "发送失败", + "chat_voice_input_need_focus": "点击当前对话区激活输入框", + "web_sdk_create_conversation": "已创建新会话", + "web_sdk_conversation_history": "会话历史", + "web_sdk_conversation_default_name": "新创建的会话", + "web_sdk_delete": "删除", + "web_sdk_delete_conversation": "删除会话", + "web_sdk_rename_conversation": "重命名会话", + "web_sdk_confirm": "确定", + "web_sdk_cancel": "取消", + "web_sdk_open_conversations": "展开侧栏", + "unbind_notification": "提示:智能体已经被解绑", + "profile_history_today": "今天", + "log_pay_wall_date_filter_30_days": "过去30天", + "web_sdk_past": "过往", + "404_title": "抱歉,该页面不存在", + "404_content": "请检查链接或重试", + "overview_bi_assistant_system_error": "系统错误,请稍后再试", + "web_sdk_retry_notification": "请稍后重试。", + "chatInputPlaceholder": "发送消息", + "web_sdk_official_banner": "由 {docs_link} 提供支持,AI生成仅供参考", + "web_sdk_official_banner_link": "扣子" } diff --git a/frontend/packages/common/chat-area/chat-answer-action/src/components/action-bar-hover-container/index.tsx b/frontend/packages/common/chat-area/chat-answer-action/src/components/action-bar-hover-container/index.tsx index 46641b4c..6f72894d 100644 --- a/frontend/packages/common/chat-area/chat-answer-action/src/components/action-bar-hover-container/index.tsx +++ b/frontend/packages/common/chat-area/chat-answer-action/src/components/action-bar-hover-container/index.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { type PropsWithChildren } from 'react'; +import { forwardRef, type PropsWithChildren } from 'react'; import classNames from 'classnames'; @@ -25,14 +25,16 @@ interface ActionBarHoverContainerProps { style?: React.CSSProperties; } -export const ActionBarHoverContainer: React.FC< +export const ActionBarHoverContainer = forwardRef< + HTMLDivElement, PropsWithChildren -> = ({ children, style }) => ( +>(({ children, style }, ref) => (
{children}
-); +)); diff --git a/frontend/packages/common/chat-area/chat-answer-action/src/components/copy-text-message/index.tsx b/frontend/packages/common/chat-area/chat-answer-action/src/components/copy-text-message/index.tsx index 1d25fc91..0806a179 100644 --- a/frontend/packages/common/chat-area/chat-answer-action/src/components/copy-text-message/index.tsx +++ b/frontend/packages/common/chat-area/chat-answer-action/src/components/copy-text-message/index.tsx @@ -34,15 +34,27 @@ import { useTooltipTrigger } from '../../hooks/use-tooltip-trigger'; type CopyTextMessageProps = Omit< ComponentProps, 'icon' | 'iconSize' | 'onClick' ->; +> & { + isMustGroupLastAnswerMessage?: boolean; + isUseExternalContent?: boolean; + externalContent?: string; +}; export const CopyTextMessage: React.FC< PropsWithChildren -> = ({ className, ...props }) => { +> = ({ + className, + isMustGroupLastAnswerMessage = true, + isUseExternalContent = false, + externalContent, + ...props +}) => { const { reporter } = useChatArea(); const { message, meta } = useMessageBoxContext(); - const { content } = message; + const content = isUseExternalContent + ? externalContent || '' + : message.content; const [isCopySuccessful, setIsCopySuccessful] = useState(false); const trigger = useTooltipTrigger('hover'); @@ -87,7 +99,7 @@ export const CopyTextMessage: React.FC< return null; } - if (!meta.isGroupLastAnswerMessage) { + if (!meta.isGroupLastAnswerMessage && isMustGroupLastAnswerMessage) { return null; } diff --git a/frontend/packages/common/chat-area/chat-answer-action/tsconfig.dev.json b/frontend/packages/common/chat-area/chat-answer-action/tsconfig.dev.json new file mode 100644 index 00000000..61e003e1 --- /dev/null +++ b/frontend/packages/common/chat-area/chat-answer-action/tsconfig.dev.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.build.json", + "compilerOptions": { + "noEmit": true, + "declaration": false, + "declarationMap": false, + "sourceMap": false, + "outDir": null, + "tsBuildInfoFile": null + }, + "exclude": ["dist", "node_modules", "__tests__"] +} diff --git a/frontend/packages/common/chat-area/chat-answer-action/tsconfig.json b/frontend/packages/common/chat-area/chat-answer-action/tsconfig.json index 27f70196..2d1a23ca 100644 --- a/frontend/packages/common/chat-area/chat-answer-action/tsconfig.json +++ b/frontend/packages/common/chat-area/chat-answer-action/tsconfig.json @@ -9,6 +9,9 @@ }, { "path": "./tsconfig.misc.json" + }, + { + "path": "./tsconfig.dev.json" } ], "exclude": ["**/*"] diff --git a/frontend/packages/common/chat-area/chat-area/src/chat-area-main/index.tsx b/frontend/packages/common/chat-area/chat-area/src/chat-area-main/index.tsx index 284428f6..b38837a3 100644 --- a/frontend/packages/common/chat-area/chat-area/src/chat-area-main/index.tsx +++ b/frontend/packages/common/chat-area/chat-area/src/chat-area-main/index.tsx @@ -222,6 +222,7 @@ export const ChatArea = forwardRef((props, ref) => { selectable, showClearContextDivider, messageWidth, + messageMaxWidth, readonly, uiKitChatInputButtonConfig, uikitChatInputButtonStatus, @@ -237,6 +238,7 @@ export const ChatArea = forwardRef((props, ref) => { isOnboardingCentered, fileLimit, stopRespondOverrideWaiting, + isMiniScreen, } = props; const getScrollViewRef = useRef<() => ScrollViewController>(null); const { @@ -278,6 +280,7 @@ export const ChatArea = forwardRef((props, ref) => { enableSelectOnboarding, showClearContextDivider, messageWidth, + messageMaxWidth, readonly: readonly || isClearMessageHistoryLock, uiKitChatInputButtonConfig, theme, @@ -304,6 +307,7 @@ export const ChatArea = forwardRef((props, ref) => { onboardingSuggestionsShowMode, showBackground, stopRespondOverrideWaiting, + isMiniScreen, }} > ( wrapperClassName, inputNativeCallbacks, safeAreaClassName, + ...restInputProps } = useChatInputProps(); const showBackground = useShowBackGround(); @@ -395,6 +396,7 @@ export const ChatInput: ( showBackground={showBackground} limitFileCount={fileLimit} onPaste={handlePaste} + {...restInputProps} {...componentProps} />
{ + const usedFooter = customMessageGroupFooterPlugin; + + if (!usedFooter) { + return null; + } + + const { Component } = usedFooter; + return ; + }; return ( <> @@ -152,6 +165,7 @@ export const MessageGroupWrapper: React.FC< isSendingMessage={isSendingMessage} messageGroup={messageGroup} > + {renderFooter?.()} {isLatest ? ( <> {!showContextDividerWithOnboarding && } diff --git a/frontend/packages/common/chat-area/chat-area/src/context/chat-area-context/chat-area-callback.ts b/frontend/packages/common/chat-area/chat-area/src/context/chat-area-context/chat-area-callback.ts index 15ab3b3a..66fff31d 100644 --- a/frontend/packages/common/chat-area/chat-area/src/context/chat-area-context/chat-area-callback.ts +++ b/frontend/packages/common/chat-area/chat-area/src/context/chat-area-context/chat-area-callback.ts @@ -16,6 +16,10 @@ import { type MouseEvent } from 'react'; +import { + type IEventCallbacks, + type IOnLinkClickParams, +} from '@coze-common/chat-uikit-shared'; import { type MessageBoxTheme } from '@coze-common/chat-uikit'; import { type ClearMessageContextParams, @@ -25,7 +29,6 @@ import { type ChatCoreError, type GetHistoryMessageResponse, } from '@coze-common/chat-core'; -import { type IOnLinkClickParams } from '@coze-common/chat-uikit-shared'; import { type Message as BuiltInMessage, @@ -150,6 +153,7 @@ export interface ChatAreaMessageEventMap { event: MouseEvent, ) => void; onBeforeStopResponding: () => void; + onCopyUpload: IEventCallbacks['onCopyUpload']; } export type ChatAreaEventCallback = Partial & diff --git a/frontend/packages/common/chat-area/chat-area/src/context/chat-area-context/default-props.ts b/frontend/packages/common/chat-area/chat-area/src/context/chat-area-context/default-props.ts index cf8f4fb9..e1154492 100644 --- a/frontend/packages/common/chat-area/chat-area/src/context/chat-area-context/default-props.ts +++ b/frontend/packages/common/chat-area/chat-area/src/context/chat-area-context/default-props.ts @@ -22,4 +22,5 @@ export const defaultConfigs: ChatAreaConfigs = { ignoreMessageConfigList: [], groupUserMessage: false, uploadPlugin: UploadPlugin, + isShowFunctionCallBox: true, }; diff --git a/frontend/packages/common/chat-area/chat-area/src/context/chat-area-context/type.ts b/frontend/packages/common/chat-area/chat-area/src/context/chat-area-context/type.ts index f130f679..9e343337 100644 --- a/frontend/packages/common/chat-area/chat-area/src/context/chat-area-context/type.ts +++ b/frontend/packages/common/chat-area/chat-area/src/context/chat-area-context/type.ts @@ -106,6 +106,7 @@ export const allIgnorableMessageTypes = [ export interface ChatAreaConfigs { ignoreMessageConfigList: IgnoreMessageType[]; showFunctionCallDetail: boolean; + isShowFunctionCallBox: boolean; // Whether to group user messages (merge avatars) groupUserMessage: boolean; uploadPlugin: typeof UploadPlugin; diff --git a/frontend/packages/common/chat-area/chat-area/src/context/chat-input-props/context.tsx b/frontend/packages/common/chat-area/chat-area/src/context/chat-input-props/context.tsx index b4d38915..bbfd5a1b 100644 --- a/frontend/packages/common/chat-area/chat-area/src/context/chat-input-props/context.tsx +++ b/frontend/packages/common/chat-area/chat-area/src/context/chat-input-props/context.tsx @@ -23,7 +23,8 @@ import { type OnBeforeSubmit = IChatInputProps['onBeforeSubmit']; -export interface ChatInputProps { +export interface ChatInputProps + extends Pick { /** * {@link OnBeforeSubmit} */ diff --git a/frontend/packages/common/chat-area/chat-area/src/context/copywriting/copywriting-context.tsx b/frontend/packages/common/chat-area/chat-area/src/context/copywriting/copywriting-context.tsx index 32961b90..cec05ddf 100644 --- a/frontend/packages/common/chat-area/chat-area/src/context/copywriting/copywriting-context.tsx +++ b/frontend/packages/common/chat-area/chat-area/src/context/copywriting/copywriting-context.tsx @@ -26,6 +26,7 @@ const getDefaultCopywriting = (): CopywritingContextInterface => ({ textareaBottomTips: '', clearContextDividerText: '', clearContextTooltipContent: '', + audioButtonTooltipContent: '', }); export const CopywritingContext = createContext( diff --git a/frontend/packages/common/chat-area/chat-area/src/context/copywriting/types.ts b/frontend/packages/common/chat-area/chat-area/src/context/copywriting/types.ts index 2770dd55..cc8c2898 100644 --- a/frontend/packages/common/chat-area/chat-area/src/context/copywriting/types.ts +++ b/frontend/packages/common/chat-area/chat-area/src/context/copywriting/types.ts @@ -19,4 +19,5 @@ export interface CopywritingContextInterface { textareaBottomTips: string; clearContextDividerText: string; clearContextTooltipContent: string; + audioButtonTooltipContent: string; } diff --git a/frontend/packages/common/chat-area/chat-area/src/context/preference/preference-context.tsx b/frontend/packages/common/chat-area/chat-area/src/context/preference/preference-context.tsx index 126da64e..b65baf36 100644 --- a/frontend/packages/common/chat-area/chat-area/src/context/preference/preference-context.tsx +++ b/frontend/packages/common/chat-area/chat-area/src/context/preference/preference-context.tsx @@ -17,9 +17,9 @@ import { createContext, type PropsWithChildren, useContext } from 'react'; import { isUndefined, merge, omitBy } from 'lodash-es'; +import { Layout } from '@coze-common/chat-uikit-shared'; import { type MakeValueUndefinable } from '@coze-common/chat-area-utils'; import { SuggestedQuestionsShowMode } from '@coze-arch/bot-api/developer_api'; -import { Layout } from '@coze-common/chat-uikit-shared'; import { type PreferenceContextInterface, @@ -49,6 +49,7 @@ const getDefaultPreference = (): Required => ({ selectable: false, showClearContextDivider: true, messageWidth: '100%', + messageMaxWidth: '', readonly: false, uiKitChatInputButtonConfig: { isSendButtonVisible: true, @@ -68,6 +69,7 @@ const getDefaultPreference = (): Required => ({ forceShowOnboardingMessage: false, showStopRespond: true, layout: Layout.PC, + isMiniScreen: false, isOnboardingCentered: false, stopRespondOverrideWaiting: undefined, }); diff --git a/frontend/packages/common/chat-area/chat-area/src/context/preference/types.ts b/frontend/packages/common/chat-area/chat-area/src/context/preference/types.ts index 75e17f36..b71765b7 100644 --- a/frontend/packages/common/chat-area/chat-area/src/context/preference/types.ts +++ b/frontend/packages/common/chat-area/chat-area/src/context/preference/types.ts @@ -96,6 +96,10 @@ export interface PreferenceContextInterface { * message list width */ messageWidth: string; + /** + * message list max width + */ + messageMaxWidth: string; /** * Is it read-only? */ @@ -156,6 +160,11 @@ export interface PreferenceContextInterface { */ layout: Layout; + /** + * Whether to enable mini screen mode + */ + isMiniScreen: boolean; + /** * Whether to force the stop reply button to be displayed */ diff --git a/frontend/packages/common/chat-area/chat-area/src/context/upload-controller-context/provider.tsx b/frontend/packages/common/chat-area/chat-area/src/context/upload-controller-context/provider.tsx index 212af683..73d1617e 100644 --- a/frontend/packages/common/chat-area/chat-area/src/context/upload-controller-context/provider.tsx +++ b/frontend/packages/common/chat-area/chat-area/src/context/upload-controller-context/provider.tsx @@ -17,6 +17,7 @@ import { type PropsWithChildren, useRef, useEffect } from 'react'; import { UploadController } from '../../service/upload-controller'; +import { useChatAreaContext } from '../../hooks/context/use-chat-area-context'; import { UploadControllerContext, type UploadControllerContextProps, @@ -25,12 +26,16 @@ import { export const UploadControllerProvider: React.FC = ({ children, }) => { + const { configs } = useChatAreaContext(); const uploadControllerMap = useRef< UploadControllerContextProps['uploadControllerMap'] >({}); const createControllerAndUpload: UploadControllerContextProps['createControllerAndUpload'] = param => { - uploadControllerMap.current[param.fileId] = new UploadController(param); + uploadControllerMap.current[param.fileId] = new UploadController({ + ...param, + multiUploadPlugin: configs.uploadPlugin, + }); }; const cancelUploadById: UploadControllerContextProps['cancelUploadById'] = id => { diff --git a/frontend/packages/common/chat-area/chat-area/src/index.tsx b/frontend/packages/common/chat-area/chat-area/src/index.tsx index 9f17ec2d..69ce2edc 100644 --- a/frontend/packages/common/chat-area/chat-area/src/index.tsx +++ b/frontend/packages/common/chat-area/chat-area/src/index.tsx @@ -301,3 +301,5 @@ export { } from '@coze-common/chat-core'; export { type OnboardingSelectChangeCallback } from './context/chat-area-context/chat-area-callback'; export { ChatInputArea } from './components/chat-input'; +export { type ChatMessage } from '@coze-arch/bot-api/developer_api'; +export { useBuiltinButtonStatus } from './hooks/uikit/use-builtin-button-status'; diff --git a/frontend/packages/common/chat-area/chat-area/src/plugin/types/plugin-component/index.ts b/frontend/packages/common/chat-area/chat-area/src/plugin/types/plugin-component/index.ts index aee46828..42f2fe8c 100644 --- a/frontend/packages/common/chat-area/chat-area/src/plugin/types/plugin-component/index.ts +++ b/frontend/packages/common/chat-area/chat-area/src/plugin/types/plugin-component/index.ts @@ -16,6 +16,7 @@ import { type ComponentType } from 'react'; +import { type MessageGroup } from '../../../store/types'; import { type MessageBoxProps } from '../../../components/types'; import { type CustomSendMessageBox, @@ -44,6 +45,8 @@ export interface CustomComponent { MessageBox: ComponentType; MessageBoxFooter: CustomMessageBoxFooter; MessageBoxHoverSlot: ComponentType; + MessageGroupFooter: ComponentType<{ messageGroup: MessageGroup }>; + UIKitMessageBoxPlugin: ComponentType; UIKitOnBoardingPlugin: ComponentType; } diff --git a/frontend/packages/common/chat-area/chat-area/src/service/upload-controller.ts b/frontend/packages/common/chat-area/chat-area/src/service/upload-controller.ts index e9cc5e5d..a8490672 100644 --- a/frontend/packages/common/chat-area/chat-area/src/service/upload-controller.ts +++ b/frontend/packages/common/chat-area/chat-area/src/service/upload-controller.ts @@ -21,6 +21,7 @@ export interface UploadControllerProps { fileId: string; file: File; userId: string; + multiUploadPlugin?: typeof UploadPlugin; onProgress: (event: EventPayloadMap['progress'], fileId: string) => void; onComplete: (event: EventPayloadMap['complete'], fileId: string) => void; onError: (event: EventPayloadMap['error'], fileId: string) => void; @@ -39,9 +40,10 @@ export class UploadController { onComplete, onError, onReady, + multiUploadPlugin = UploadPlugin, }: UploadControllerProps) { this.fileId = fileId; - this.uploadPlugin = new UploadPlugin({ + this.uploadPlugin = new multiUploadPlugin({ file, userId, type: isImage(file) ? 'image' : 'object', diff --git a/frontend/packages/common/chat-area/chat-core/src/chat-sdk/index.ts b/frontend/packages/common/chat-area/chat-core/src/chat-sdk/index.ts index ac8353bb..6250e038 100644 --- a/frontend/packages/common/chat-area/chat-core/src/chat-sdk/index.ts +++ b/frontend/packages/common/chat-area/chat-core/src/chat-sdk/index.ts @@ -42,7 +42,6 @@ import { MessageManager } from '@/message/message-manager'; import { ChunkProcessor, PreSendLocalMessageFactory } from '@/message'; import { HttpChunk } from '@/channel/http-chunk'; -import { type TokenManager } from '../credential'; import { type ChatASRParams, type BreakMessageParams, @@ -70,6 +69,7 @@ import { MessageManagerService } from './services/message-manager-service'; import { HttpChunkService } from './services/http-chunk-service'; import { CreateMessageService } from './services/create-message-service'; import { ReportEventsTracer, SlardarEvents } from './events/slardar-events'; +import { type TokenManager } from '../credential'; export default class ChatSDK { private static instances: Map = new Map(); @@ -523,4 +523,10 @@ export default class ChatSDK { } return this.messageManagerService.chatASR(params); } + + updateConversationId(conversationId: string) { + this.conversation_id = conversationId; + this.messageManagerService.conversation_id = conversationId; + this.preSendLocalMessageFactory.conversation_id = conversationId; + } } diff --git a/frontend/packages/common/chat-area/chat-core/src/request-manager/index.ts b/frontend/packages/common/chat-area/chat-core/src/request-manager/index.ts index 9a5816f1..7f5c3198 100644 --- a/frontend/packages/common/chat-area/chat-core/src/request-manager/index.ts +++ b/frontend/packages/common/chat-area/chat-core/src/request-manager/index.ts @@ -137,7 +137,7 @@ export class RequestManager { // Execute incoming unified hooks const onCommonAfterResponse = async ( response: AxiosResponse, - hooksName: 'onAfterResponse' | 'onErrrorResponse' = 'onAfterResponse', + hooksName: 'onAfterResponse' | 'onErrorResponse' = 'onAfterResponse', ): Promise => { // eslint-disable-next-line @typescript-eslint/naming-convention -- temporary variable, quite normal let _response: AxiosResponse | Promise = response; @@ -154,7 +154,7 @@ export class RequestManager { // Execute hooks for each scene const onSceneAfterResponse = async ( response: AxiosResponse, - hooksName: 'onAfterResponse' | 'onErrrorResponse' = 'onAfterResponse', + hooksName: 'onAfterResponse' | 'onErrorResponse' = 'onAfterResponse', ): Promise => { const { scenes } = this.mergedBaseOptions; // eslint-disable-next-line @typescript-eslint/naming-convention -- temporary variable, quite normal @@ -188,9 +188,9 @@ export class RequestManager { // eslint-disable-next-line @typescript-eslint/naming-convention -- temporary variable, quite normal const _response = await onCommonAfterResponse( response, - 'onErrrorResponse', + 'onErrorResponse', ); - return await onSceneAfterResponse(_response, 'onErrrorResponse'); + return await onSceneAfterResponse(_response, 'onErrorResponse'); }, ); } diff --git a/frontend/packages/common/chat-area/chat-core/src/request-manager/types.ts b/frontend/packages/common/chat-area/chat-core/src/request-manager/types.ts index f2805f00..16b3f8c3 100644 --- a/frontend/packages/common/chat-area/chat-core/src/request-manager/types.ts +++ b/frontend/packages/common/chat-area/chat-core/src/request-manager/types.ts @@ -66,7 +66,7 @@ interface Hooks { onGetMessageStreamParser?: ( requestMessageRawBody: Record, ) => FetchSteamConfig['streamParser']; - onErrrorResponse?: Array<(response: AxiosResponse) => Promise>; + onErrorResponse?: Array<(response: AxiosResponse) => Promise>; } export enum RequestScene { diff --git a/frontend/packages/common/chat-area/chat-uikit-shared/src/types/chat-input/index.ts b/frontend/packages/common/chat-area/chat-uikit-shared/src/types/chat-input/index.ts index 1916220d..83819408 100644 --- a/frontend/packages/common/chat-area/chat-uikit-shared/src/types/chat-input/index.ts +++ b/frontend/packages/common/chat-area/chat-uikit-shared/src/types/chat-input/index.ts @@ -127,10 +127,15 @@ export interface IChatInputProps { leftActions?: ReactNode; /** - * Right Slot + * Right Actions */ rightActions?: ReactNode; + /** + * right slot + */ + rightSlot?: ReactNode; + /** * Custom send button */ diff --git a/frontend/packages/common/chat-area/chat-uikit/src/components/chat/chat-upload/index.tsx b/frontend/packages/common/chat-area/chat-uikit/src/components/chat/chat-upload/index.tsx index 283c41b1..b61e077e 100644 --- a/frontend/packages/common/chat-area/chat-uikit/src/components/chat/chat-upload/index.tsx +++ b/frontend/packages/common/chat-area/chat-uikit/src/components/chat/chat-upload/index.tsx @@ -16,16 +16,16 @@ import { type FC } from 'react'; -import { - FILE_TYPE_CONFIG, - FileTypeEnum, -} from '@coze-common/chat-core/shared/const'; -import { Toast, Upload } from '@coze-arch/coze-design'; import { type IChatUploadCopywritingConfig, DEFAULT_MAX_FILE_SIZE, UploadType, } from '@coze-common/chat-uikit-shared'; +import { + FILE_TYPE_CONFIG, + FileTypeEnum, +} from '@coze-common/chat-core/shared/const'; +import { Toast, Upload } from '@coze-arch/coze-design'; interface IChatUploadProps { /** @@ -132,6 +132,7 @@ export const ChatUpload: FC = props => { onFileChange={handleUpload} disabled={isDisabled} multiple={limitFileCount > 1} + uploadTrigger={'custom'} > {children} diff --git a/frontend/packages/common/chat-area/chat-uikit/src/components/common/message-box/index.tsx b/frontend/packages/common/chat-area/chat-uikit/src/components/common/message-box/index.tsx index f4212258..6d12bece 100644 --- a/frontend/packages/common/chat-area/chat-uikit/src/components/common/message-box/index.tsx +++ b/frontend/packages/common/chat-area/chat-uikit/src/components/common/message-box/index.tsx @@ -40,7 +40,8 @@ export const MessageBox: FC< messageBubbleClassname, messageBubbleWrapperClassname, - messageBoxWraperClassname, + messageBoxWrapperClassname, + messageHoverWrapperClassName, messageErrorWrapperClassname, isHoverShowUserInfo, @@ -69,7 +70,8 @@ export const MessageBox: FC< classname={classname} messageBubbleWrapperClassname={messageBubbleWrapperClassname} messageBubbleClassname={messageBubbleClassname} - messageBoxWraperClassname={messageBoxWraperClassname} + messageBoxWrapperClassname={messageBoxWrapperClassname} + messageHoverWrapperClassName={messageHoverWrapperClassName} messageErrorWrapperClassname={messageErrorWrapperClassname} isHoverShowUserInfo={isHoverShowUserInfo} layout={layout} diff --git a/frontend/packages/common/chat-area/chat-uikit/src/components/common/message-box/message-box-wrap.tsx b/frontend/packages/common/chat-area/chat-uikit/src/components/common/message-box/message-box-wrap.tsx index 73a15a95..410dbd4f 100644 --- a/frontend/packages/common/chat-area/chat-uikit/src/components/common/message-box/message-box-wrap.tsx +++ b/frontend/packages/common/chat-area/chat-uikit/src/components/common/message-box/message-box-wrap.tsx @@ -24,13 +24,13 @@ import { import classnames from 'classnames'; import { useClickAway, useHover, useUpdateEffect } from 'ahooks'; -import { ErrorBoundary } from '@coze-arch/logger'; import { Layout, UIKitEvents, useUiKitEventCenter, } from '@coze-common/chat-uikit-shared'; import { useEventCallback } from '@coze-common/chat-hooks'; +import { ErrorBoundary } from '@coze-arch/logger'; import { Avatar, Typography } from '@coze-arch/coze-design'; import { UserLabel, UserName } from '../user-label'; @@ -67,7 +67,8 @@ export const MessageBoxWrap: FC< classname, messageBubbleClassname, messageBubbleWrapperClassname, - messageBoxWraperClassname, + messageBoxWrapperClassname, + messageHoverWrapperClassName, messageErrorWrapperClassname, isHoverShowUserInfo = true, layout, @@ -165,7 +166,7 @@ export const MessageBoxWrap: FC< // chat-uikit-message-box-container chat-uikit-message-box-container-pc className={classnames( messageBoxContainerVariants({ isMobileLayout }), - messageBoxWraperClassname, + messageBoxWrapperClassname, )} >
{right}
+ {isHovering || hoverContentVisible ? ( +
+ {hoverContent} +
+ ) : null}
{/* Please read the refreshContainerWidthConditionally above before changing the style of this dom */}
{renderFooter?.(refreshContainerWidthConditionally)}
- {isHovering || hoverContentVisible ? ( -
- {hoverContent} -
- ) : null} diff --git a/frontend/packages/common/chat-area/chat-uikit/src/components/common/message-box/type.ts b/frontend/packages/common/chat-area/chat-uikit/src/components/common/message-box/type.ts index 96e92ad6..f3fb0636 100644 --- a/frontend/packages/common/chat-area/chat-uikit/src/components/common/message-box/type.ts +++ b/frontend/packages/common/chat-area/chat-uikit/src/components/common/message-box/type.ts @@ -91,7 +91,8 @@ interface MessageBoxBasicProps { classname?: string; messageBubbleWrapperClassname?: string; - messageBoxWraperClassname?: string; // Direct father style of message box + messageBoxWrapperClassname?: string; // Direct father style of message box + messageHoverWrapperClassName?: string; // Direct hover style of message box messageBubbleClassname?: string; // Message The style of the message bubble messageErrorWrapperClassname?: string; // Message wrong father style isHoverShowUserInfo?: boolean; // Whether to display user details when hovering @@ -160,7 +161,9 @@ export interface MessageBoxWrapProps { contentTime: number | undefined; classname?: string; - messageBoxWraperClassname?: string; // Direct father style of message box + messageBoxWrapperClassname?: string; // Direct father style of message box + messageHoverWrapperClassName?: string; // Direct hover style of message box + messageBubbleClassname?: string; // Message The style of the message bubble messageBubbleWrapperClassname?: string; // Message message bubble father style messageErrorWrapperClassname?: string; // Message wrong father style diff --git a/frontend/packages/common/chat-area/chat-uikit/src/components/contents/text-content/index.tsx b/frontend/packages/common/chat-area/chat-uikit/src/components/contents/text-content/index.tsx index d974b00f..cf6cf954 100644 --- a/frontend/packages/common/chat-area/chat-uikit/src/components/contents/text-content/index.tsx +++ b/frontend/packages/common/chat-area/chat-uikit/src/components/contents/text-content/index.tsx @@ -54,7 +54,6 @@ export const TextContent: FC = props => { } = props; const MdBoxLazy = LazyCozeMdBox; const contentRef = useRef(null); - const { content } = message; if (!isText(content)) { @@ -63,7 +62,6 @@ export const TextContent: FC = props => { const isStreaming = !message.is_finish; const text = content.slice(0, message.broken_pos ?? Infinity); - return (
= ({ + message, + ...restProps +}) => { + const chatflowNodeData: ChatflowNodeData | undefined = useMemo( + () => extractChatflowMessage(message), + [message], + ); + if (!chatflowNodeData) { + return null; + } + if (chatflowNodeData.card_type === 'INPUT') { + return ( + + ); + } else if (chatflowNodeData.card_type === 'QUESTION') { + return ( + + ); + } else { + return 'content type is not supported'; + } +}; + +export const WorkflowRenderEntry = memo(BaseComponent, (prevProps, nextProps) => + isEqual(omitBy(prevProps, isFunction), omitBy(nextProps, isFunction)), +); diff --git a/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/input-node-render.tsx b/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/input-node-render.tsx new file mode 100644 index 00000000..ab85a6fc --- /dev/null +++ b/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/input-node-render.tsx @@ -0,0 +1,102 @@ +/* + * 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. + */ + +import { useState } from 'react'; + +import { produce } from 'immer'; +import { + type IEventCallbacks, + type IMessage, +} from '@coze-common/chat-uikit-shared'; +import { I18n } from '@coze-arch/i18n'; +import { Button, Input, Space, Typography } from '@coze-arch/coze-design'; + +import { type ChatflowNodeData } from './type'; +import { NodeWrapperUI } from './node-wrapper-ui'; + +export const InputNodeRender = ({ + data, + onCardSendMsg, + readonly, + isDisable, + message, +}: { + data: ChatflowNodeData; + onCardSendMsg?: IEventCallbacks['onCardSendMsg']; + readonly?: boolean; + isDisable?: boolean; + message: IMessage; +}) => { + const [inputData, setInputData] = useState>({}); + const [hasSend, setHasSend] = useState(false); + const disabled = readonly || isDisable || hasSend; + + return ( + + + {data.input_card_data?.map((item, index) => ( + + + {item?.name} + + { + setInputData( + produce(draft => { + draft[item.name] = value; + }), + ); + }} + /> + + ))} + + + + + ); +}; diff --git a/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/node-wrapper-ui.tsx b/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/node-wrapper-ui.tsx new file mode 100644 index 00000000..aee3166c --- /dev/null +++ b/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/node-wrapper-ui.tsx @@ -0,0 +1,23 @@ +/* + * 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. + */ + +import { type PropsWithChildren } from 'react'; + +export const NodeWrapperUI: React.FC = ({ children }) => ( +
+ {children} +
+); diff --git a/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/question-node-render.tsx b/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/question-node-render.tsx new file mode 100644 index 00000000..a85fe639 --- /dev/null +++ b/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/question-node-render.tsx @@ -0,0 +1,67 @@ +/* + * 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. + */ + +import { + type IEventCallbacks, + type IMessage, +} from '@coze-common/chat-uikit-shared'; +import { Button, Space, Typography } from '@coze-arch/coze-design'; + +import { type ChatflowNodeData } from './type'; +import { NodeWrapperUI } from './node-wrapper-ui'; + +export const QuestionNodeRender = ({ + data, + onCardSendMsg, + readonly, + isDisable, + message, +}: { + data: ChatflowNodeData; + onCardSendMsg?: IEventCallbacks['onCardSendMsg']; + readonly?: boolean; + isDisable?: boolean; + message: IMessage; +}) => { + const disabled = readonly || isDisable; + return ( + + + + {data.question_card_data?.Title} + + + {data.question_card_data?.Options?.map((option, index) => ( + + ))} + + + + ); +}; diff --git a/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/type.ts b/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/type.ts new file mode 100644 index 00000000..786f6f1c --- /dev/null +++ b/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/type.ts @@ -0,0 +1,54 @@ +/* + * 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. + */ + +import { + type IEventCallbacks, + type IMessage, +} from '@coze-common/chat-uikit-shared'; + +interface RenderNodeBaseProps extends Pick { + isDisable: boolean | undefined; + readonly: boolean | undefined; +} +export interface RenderNodeEntryProps extends RenderNodeBaseProps { + message: IMessage; +} +export interface ChatflowNodeData { + card_type: 'QUESTION' | 'INPUT'; + input_card_data?: { + type: string; + name: string; + }[]; + question_card_data?: { + // eslint-disable-next-line @typescript-eslint/naming-convention + Title: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + Options: { name: string }[]; + }; +} +export interface ChatflowNodeData { + card_type: 'QUESTION' | 'INPUT'; + input_card_data?: { + type: string; + name: string; + }[]; + question_card_data?: { + // eslint-disable-next-line @typescript-eslint/naming-convention + Title: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + Options: { name: string }[]; + }; +} diff --git a/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/utils.ts b/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/utils.ts new file mode 100644 index 00000000..edea64ab --- /dev/null +++ b/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/components/utils.ts @@ -0,0 +1,40 @@ +/* + * 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. + */ + +import { type IMessage } from '@coze-common/chat-uikit-shared'; +import { safeJSONParse } from '@coze-common/chat-uikit'; + +import { type ChatflowNodeData } from './type'; + +export const extractChatflowMessage = (message: IMessage) => { + if (message.content_type === 'card') { + const contentStruct = safeJSONParse(message.content) as { + x_properties: { + workflow_card_info: string; + }; + }; + const workflowDataStr = contentStruct?.x_properties?.workflow_card_info; + if (workflowDataStr) { + const cardData = safeJSONParse(workflowDataStr) as ChatflowNodeData; + if (cardData?.card_type === 'QUESTION' && cardData?.question_card_data) { + return cardData; + } + if (cardData?.card_type === 'INPUT' && cardData?.input_card_data) { + return cardData; + } + } + } +}; diff --git a/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/index.tsx b/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/index.tsx new file mode 100644 index 00000000..ea76cd3b --- /dev/null +++ b/frontend/packages/common/chat-area/chat-workflow-render/src/components/chat-flow-render/index.tsx @@ -0,0 +1,87 @@ +/* + * 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. + */ + +import { ContentBoxType } from '@coze-common/chat-uikit-shared'; +import { + ContentBox, + type EnhancedContentConfig, + ContentType, +} from '@coze-common/chat-uikit'; +import { + PluginScopeContextProvider, + usePluginCustomComponents, + type ComponentTypesMap, +} from '@coze-common/chat-area'; + +import { WorkflowRenderEntry } from './components'; + +const defaultEnable = (value?: boolean) => { + if (typeof value === 'undefined') { + return true; + } + return value; +}; + +export const ChatFlowRender: ComponentTypesMap['contentBox'] = props => { + const customTextMessageInnerTopSlotList = usePluginCustomComponents( + 'TextMessageInnerTopSlot', + ); + const enhancedContentConfigList: EnhancedContentConfig[] = [ + { + rule: ({ contentType, contentConfigs }) => { + const isCardEnable = defaultEnable( + contentConfigs?.[ContentBoxType.CARD]?.enable, + ); + return contentType === ContentType.Card && isCardEnable; + }, + render: ({ message, eventCallbacks, options }) => { + const { isCardDisabled, readonly } = options; + + const { onCardSendMsg } = eventCallbacks ?? {}; + + return ( + + ); + }, + }, + ]; + return ( + + {customTextMessageInnerTopSlotList.map( + // eslint-disable-next-line @typescript-eslint/naming-convention -- matches the expected naming + ({ pluginName, Component }, index) => ( + + + + ), + )} + + } + {...props} + /> + ); +}; diff --git a/frontend/packages/common/chat-area/chat-workflow-render/src/index.ts b/frontend/packages/common/chat-area/chat-workflow-render/src/index.ts index 2108dad9..9e3a42a3 100644 --- a/frontend/packages/common/chat-area/chat-workflow-render/src/index.ts +++ b/frontend/packages/common/chat-area/chat-workflow-render/src/index.ts @@ -15,3 +15,4 @@ */ export { WorkflowRender } from './components/workflow-render'; +export { ChatFlowRender } from './components/chat-flow-render'; diff --git a/frontend/packages/common/chat-area/plugin-chat-background/src/index.ts b/frontend/packages/common/chat-area/plugin-chat-background/src/index.ts index 37a378e5..69602028 100644 --- a/frontend/packages/common/chat-area/plugin-chat-background/src/index.ts +++ b/frontend/packages/common/chat-area/plugin-chat-background/src/index.ts @@ -48,5 +48,6 @@ export const createChatBackgroundPlugin = () => { }; return { ChatBackgroundPlugin, + chatBackgroundEvent, }; }; diff --git a/frontend/packages/common/chat-area/plugin-chat-shortcuts/src/hooks/shortcut.ts b/frontend/packages/common/chat-area/plugin-chat-shortcuts/src/hooks/shortcut.ts index 79d0337e..f21dc7e2 100644 --- a/frontend/packages/common/chat-area/plugin-chat-shortcuts/src/hooks/shortcut.ts +++ b/frontend/packages/common/chat-area/plugin-chat-shortcuts/src/hooks/shortcut.ts @@ -117,7 +117,9 @@ export const useSendUseToolMessage = () => { componentsFormValues: Record; options?: SendMessageOptions; onBeforeSendTemplateShortcut?: ( - params: OnBeforeSendTemplateShortcutParams, + params: OnBeforeSendTemplateShortcutParams & { + shortcut: ShortCutCommand; + }, ) => OnBeforeSendTemplateShortcutParams; withoutComponentsList?: boolean; }) => { @@ -170,6 +172,7 @@ export const useSendUseToolMessage = () => { const handledParams = onBeforeSendTemplateShortcut?.({ message: cloneDeep(message), options: cloneDeep(options), + shortcut, }) || { message, options, diff --git a/frontend/packages/common/chat-area/plugin-chat-shortcuts/src/shortcut-bar/index.tsx b/frontend/packages/common/chat-area/plugin-chat-shortcuts/src/shortcut-bar/index.tsx index ef4db706..7a6b8d70 100644 --- a/frontend/packages/common/chat-area/plugin-chat-shortcuts/src/shortcut-bar/index.tsx +++ b/frontend/packages/common/chat-area/plugin-chat-shortcuts/src/shortcut-bar/index.tsx @@ -18,10 +18,10 @@ import { type CSSProperties, type FC, useRef, useState } from 'react'; import cls from 'classnames'; -import { type ShortCutCommand } from '@coze-agent-ide/tool-config'; import { useMessageWidth } from '@coze-common/chat-area'; import { OverflowList, Popover } from '@coze-arch/bot-semi'; import { SendType } from '@coze-arch/bot-api/playground_api'; +import { type ShortCutCommand } from '@coze-agent-ide/tool-config'; import { enableSendTypePanelHideTemplate, @@ -53,7 +53,9 @@ interface ChatShortCutBarProps { wrapperStyle?: CSSProperties; toolTipFooterSlot?: React.ReactNode; onBeforeSendTemplateShortcut?: ( - params: OnBeforeSendTemplateShortcutParams, + params: OnBeforeSendTemplateShortcutParams & { + shortcut: ShortCutCommand; + }, ) => OnBeforeSendTemplateShortcutParams; onBeforeSendTextMessage?: ( params: OnBeforeSendQueryShortcutParams, diff --git a/frontend/packages/common/chat-area/plugin-chat-shortcuts/src/utils/get-ui-mode-by-biz-scene.ts b/frontend/packages/common/chat-area/plugin-chat-shortcuts/src/utils/get-ui-mode-by-biz-scene.ts index 61f936b0..7f43e185 100644 --- a/frontend/packages/common/chat-area/plugin-chat-shortcuts/src/utils/get-ui-mode-by-biz-scene.ts +++ b/frontend/packages/common/chat-area/plugin-chat-shortcuts/src/utils/get-ui-mode-by-biz-scene.ts @@ -19,7 +19,7 @@ import { exhaustiveCheckSimple } from '@coze-common/chat-area-utils'; import { type UIMode } from '../shortcut-bar/types'; export const getUIModeByBizScene: (props: { - bizScene: 'debug' | 'store' | 'home' | 'agentApp'; + bizScene: 'debug' | 'store' | 'home' | 'agentApp' | 'websdk'; showBackground: boolean; }) => UIMode = ({ bizScene, showBackground }) => { if (bizScene === 'agentApp') { @@ -32,7 +32,7 @@ export const getUIModeByBizScene: (props: { return 'white'; } - if (bizScene === 'store' || bizScene === 'debug') { + if (bizScene === 'store' || bizScene === 'debug' || bizScene === 'websdk') { if (showBackground) { return 'blur'; } diff --git a/frontend/packages/common/md-editor-adapter/src/editor.tsx b/frontend/packages/common/md-editor-adapter/src/editor.tsx index a5f7f127..a61249d5 100644 --- a/frontend/packages/common/md-editor-adapter/src/editor.tsx +++ b/frontend/packages/common/md-editor-adapter/src/editor.tsx @@ -41,6 +41,7 @@ export const EditorFullInputInner = forwardRef( ...restProps } = props; const [value, setValue] = useState(propsValue); + const [isComposing, setIsComposing] = useState(false); // Create a mutable reference to store the latest value const valueRef = useRef(value); @@ -104,8 +105,16 @@ export const EditorFullInputInner = forwardRef( value={value} onChange={v => { setValue(v); + if (isComposing) { + return; + } propsOnChange?.(v); }} + onCompositionStart={() => setIsComposing(true)} + onCompositionEnd={e => { + setIsComposing(false); + propsOnChange?.(e.currentTarget.value); + }} /> ); }, diff --git a/frontend/packages/project-ide/biz-workflow/src/conversation/chat-history/index.tsx b/frontend/packages/project-ide/biz-workflow/src/conversation/chat-history/index.tsx index 283b079a..aea697e1 100644 --- a/frontend/packages/project-ide/biz-workflow/src/conversation/chat-history/index.tsx +++ b/frontend/packages/project-ide/biz-workflow/src/conversation/chat-history/index.tsx @@ -17,12 +17,12 @@ import React, { Suspense, lazy, useMemo } from 'react'; import { userStoreService } from '@coze-studio/user-store'; +import type { IProject } from '@coze-studio/open-chat'; +import { useIDEGlobalStore } from '@coze-project-ide/framework'; import { I18n } from '@coze-arch/i18n'; import { IconCozIllusAdd } from '@coze-arch/coze-design/illustrations'; import { EmptyState } from '@coze-arch/coze-design'; import { CreateEnv } from '@coze-arch/bot-api/workflow_api'; -import type { IProject } from '@coze-studio/open-chat'; -import { useIDEGlobalStore } from '@coze-project-ide/framework'; import { DISABLED_CONVERSATION } from '../constants'; import { useSkeleton } from './use-skeleton'; @@ -81,8 +81,8 @@ export const ChatHistory: React.FC = ({ const chatUserInfo = { id: userInfo?.user_id_str || '', - name: userInfo?.name || '', - avatar: userInfo?.avatar_url || '', + nickname: userInfo?.name || '', + url: userInfo?.avatar_url || '', }; if ( diff --git a/frontend/packages/project-ide/biz-workflow/src/conversation/constants/index.ts b/frontend/packages/project-ide/biz-workflow/src/conversation/constants/index.ts index b3b698f7..c176a4c2 100644 --- a/frontend/packages/project-ide/biz-workflow/src/conversation/constants/index.ts +++ b/frontend/packages/project-ide/biz-workflow/src/conversation/constants/index.ts @@ -16,9 +16,6 @@ import { I18n } from '@coze-arch/i18n'; -// Default session unique_id -export const DEFAULT_UNIQUE_ID = '0'; - export const DEFAULT_CONVERSATION_NAME = 'Default'; export const MAX_LIMIT = 1000; diff --git a/frontend/packages/project-ide/biz-workflow/src/conversation/hooks/use-delete-chat/index.tsx b/frontend/packages/project-ide/biz-workflow/src/conversation/hooks/use-delete-chat/index.tsx index 48d21201..6836d58e 100644 --- a/frontend/packages/project-ide/biz-workflow/src/conversation/hooks/use-delete-chat/index.tsx +++ b/frontend/packages/project-ide/biz-workflow/src/conversation/hooks/use-delete-chat/index.tsx @@ -16,6 +16,7 @@ import React, { useMemo, useState } from 'react'; +import { useIDEGlobalStore } from '@coze-project-ide/framework'; import { I18n } from '@coze-arch/i18n'; import { IconCozChat } from '@coze-arch/coze-design/icons'; import { Modal, Select, Typography, Toast } from '@coze-arch/coze-design'; @@ -24,9 +25,8 @@ import { type ProjectConversation, } from '@coze-arch/bot-api/workflow_api'; import { workflowApi } from '@coze-arch/bot-api'; -import { useIDEGlobalStore } from '@coze-project-ide/framework'; -import { DEFAULT_UNIQUE_ID, DEFAULT_CONVERSATION_NAME } from '../../constants'; +import { DEFAULT_CONVERSATION_NAME } from '../../constants'; import s from './index.module.less'; @@ -154,7 +154,7 @@ export const useDeleteChat = ({ style={{ width: '50%' }} dropdownStyle={{ width: 220 }} size="small" - defaultValue={DEFAULT_UNIQUE_ID} + defaultValue={optionList[0]?.value} optionList={optionList} onChange={value => { const selectItem = staticList.find( diff --git a/frontend/packages/project-ide/biz-workflow/src/conversation/static-chat-list/index.tsx b/frontend/packages/project-ide/biz-workflow/src/conversation/static-chat-list/index.tsx index 152bd4a8..1c10aa6d 100644 --- a/frontend/packages/project-ide/biz-workflow/src/conversation/static-chat-list/index.tsx +++ b/frontend/packages/project-ide/biz-workflow/src/conversation/static-chat-list/index.tsx @@ -30,7 +30,7 @@ import { type ProjectConversation } from '@coze-arch/bot-api/workflow_api'; import { TitleWithTooltip } from '../title-with-tooltip'; import commonStyles from '../conversation-content/index.module.less'; import { EditInput } from '../conversation-content/edit-input'; -import { DEFAULT_UNIQUE_ID, type ErrorCode } from '../constants'; +import { type ErrorCode } from '../constants'; import s from './index.module.less'; @@ -140,9 +140,7 @@ export const StaticChatList = ({ {item.conversation_name} )} - {editingUniqueId === item.unique_id || - item.unique_id === DEFAULT_UNIQUE_ID || - !canEdit ? null : ( + {editingUniqueId === item.unique_id || !canEdit ? null : (
{ subType: WorkflowMode.Workflow, tooltip: , }, - // The open-source version does not currently support conversation streaming - IS_OPEN_SOURCE - ? null - : { - icon: WORKFLOW_SUB_TYPE_ICON_MAP[WorkflowMode.ChatFlow], - label: I18n.t('project_resource_sidebar_create_new_resource', { - resource: I18n.t('wf_chatflow_76'), - }), - subType: WorkflowMode.ChatFlow, - tooltip: , - }, + { + icon: WORKFLOW_SUB_TYPE_ICON_MAP[WorkflowMode.ChatFlow], + label: I18n.t('project_resource_sidebar_create_new_resource', { + resource: I18n.t('wf_chatflow_76'), + }), + subType: WorkflowMode.ChatFlow, + tooltip: , + }, ].filter(Boolean) as ResourceFolderCozeProps['createResourceConfig'], [], ); const iconRender: ResourceFolderCozeProps['iconRender'] = useMemo( () => - ({ resource }) => - ( - <> - { - WORKFLOW_SUB_TYPE_ICON_MAP[ - resource.res_sub_type || WorkflowMode.Workflow - ] - } - - ), + ({ resource }) => ( + <> + { + WORKFLOW_SUB_TYPE_ICON_MAP[ + resource.res_sub_type || WorkflowMode.Workflow + ] + } + + ), [], ); diff --git a/frontend/packages/project-ide/main/package.json b/frontend/packages/project-ide/main/package.json index 5f41133c..b451fd42 100644 --- a/frontend/packages/project-ide/main/package.json +++ b/frontend/packages/project-ide/main/package.json @@ -36,7 +36,7 @@ "@coze-studio/publish-manage-hooks": "workspace:*", "@coze-studio/user-store": "workspace:*", "@coze-workflow/base": "workspace:*", - "@coze/api": "1.1.0-beta.4", + "@coze/api": "1.3.5", "ahooks": "^3.7.8", "classnames": "^2.3.2", "dayjs": "^1.11.7", diff --git a/frontend/packages/project-ide/main/src/components/configuration/index.tsx b/frontend/packages/project-ide/main/src/components/configuration/index.tsx index c0f7cb21..2152b361 100644 --- a/frontend/packages/project-ide/main/src/components/configuration/index.tsx +++ b/frontend/packages/project-ide/main/src/components/configuration/index.tsx @@ -90,22 +90,19 @@ export const Configuration = () => { onClick={handleSwitchExpand} />
- {/* will support soon */} - {IS_OPEN_SOURCE ? null : ( -
- - {I18n.t('wf_chatflow_101')} -
- )} +
+ + {I18n.t('wf_chatflow_101')} +
= memo( () => ({ view: { widgetRegistries: [ - // will support soon - ...(IS_OPEN_SOURCE ? [] : [ConversationRegistry]), + ConversationRegistry, WorkflowWidgetRegistry, DatabaseWidgetRegistry, KnowledgeWidgetRegistry, diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/.env.default b/frontend/packages/studio/open-platform/chat-app-sdk/.env.default new file mode 100644 index 00000000..8eb11935 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/.env.default @@ -0,0 +1,6 @@ +CHAT_APP_CHATFLOW_COZE_APP_ID="" +CHAT_APP_CHATFLOW_COZE_WORKFLOW_ID="" +CHAT_APP_INDEX_COZE_BOT_ID="" + +CHAT_APP_COZE_TOKEN="" +CHAT_APP_COZE_BOT_USER_URL="" diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/.env.local b/frontend/packages/studio/open-platform/chat-app-sdk/.env.local new file mode 100644 index 00000000..081ce081 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/.env.local @@ -0,0 +1,6 @@ +CHAT_APP_CHATFLOW_COZE_APP_ID="7542447949096157184" +CHAT_APP_CHATFLOW_COZE_WORKFLOW_ID="7542447968176046080" +CHAT_APP_INDEX_COZE_BOT_ID="" + +CHAT_APP_COZE_TOKEN="pat_aec5209b90cdac8883547dff09fe0000e6c1b296347c977c16cdb70f094a55ca" +CHAT_APP_COZE_BOT_USER_URL="" diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/.gitignore b/frontend/packages/studio/open-platform/chat-app-sdk/.gitignore new file mode 100644 index 00000000..257adbf0 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/.gitignore @@ -0,0 +1,4 @@ +inhouse +libs +dist_ignore +.env.local* diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/.stylelintrc.js b/frontend/packages/studio/open-platform/chat-app-sdk/.stylelintrc.js new file mode 100644 index 00000000..dcb34838 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/.stylelintrc.js @@ -0,0 +1,24 @@ +/* + * 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. + */ + +const { defineConfig } = require('@coze-arch/stylelint-config'); + +module.exports = defineConfig({ + extends: [], + rules: { + 'order/properties-order': null, + }, +}); diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/OWNERS b/frontend/packages/studio/open-platform/chat-app-sdk/OWNERS new file mode 100644 index 00000000..79ceaa97 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/OWNERS @@ -0,0 +1,9 @@ +reviewers: + - sunkuo + - gaoyuanhan.duty + - gaoding.devingao + - shenxiaojie.316 + - zhangyingdong + - shanrenkai + - yangyu.1 +approvals_required: 1 diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/README.md b/frontend/packages/studio/open-platform/chat-app-sdk/README.md new file mode 100644 index 00000000..21a8989e --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/README.md @@ -0,0 +1,3 @@ +# @flow-platform/chat-app-sdk + +https://bytedance.larkoffice.com/wiki/IdTkw7Kd5iahWLkv6FKcIo53nBe \ No newline at end of file diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/config/rush-project.json b/frontend/packages/studio/open-platform/chat-app-sdk/config/rush-project.json new file mode 100644 index 00000000..9213dd76 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/config/rush-project.json @@ -0,0 +1,16 @@ +{ + "operationSettings": [ + { + "operationName": "build", + "outputFolderNames": ["dist", "inhouse", "libs"] + }, + { + "operationName": "test:cov", + "outputFolderNames": ["coverage"] + }, + { + "operationName": "ts-check", + "outputFolderNames": ["./dist_ignore"] + } + ] +} diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/config/rushx-config.json b/frontend/packages/studio/open-platform/chat-app-sdk/config/rushx-config.json new file mode 100644 index 00000000..622b8825 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/config/rushx-config.json @@ -0,0 +1,6 @@ +{ + "codecov": { + "coverage": 30, + "incrementCoverage": 60 + } +} diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/eslint.config.js b/frontend/packages/studio/open-platform/chat-app-sdk/eslint.config.js new file mode 100644 index 00000000..6ce53ca8 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/eslint.config.js @@ -0,0 +1,32 @@ +/* + * 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. + */ + +const { defineConfig } = require('@coze-arch/eslint-config'); + +module.exports = defineConfig({ + packageRoot: __dirname, + preset: 'web', + rules: { + '@coze-arch/max-line-per-function': [ + 'error', + { + max: 300, + }, + ], + 'no-restricted-imports': 'off', + }, + ignores: ['**/inhouse', '**/libs', '**/dist_*'], +}); diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/package.json b/frontend/packages/studio/open-platform/chat-app-sdk/package.json new file mode 100644 index 00000000..36018b11 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/package.json @@ -0,0 +1,126 @@ +{ + "name": "@coze-studio/chat-app-sdk", + "version": "1.2.0-beta.17", + "description": "Coze Web ChatApp SDK", + "license": "Apache-2.0", + "author": "yangyu.1@bytedance.com", + "maintainers": [ + "liuyuhang.0@bytedance.com", + "yangyu.1@bytedance.com" + ], + "sideEffects": [ + "**/*.css", + "**/*.less", + "**/*.scss" + ], + "exports": { + ".": "./src/index.ts", + "./rspack": "./rspack-config/export.ts" + }, + "main": "src/index.ts", + "unpkg": true, + "types": "./src/index.ts", + "typesVersions": { + "*": { + "rspack": [ + "./rspack-config/export.ts" + ] + } + }, + "files": [ + "inhouse", + "libs", + "README.md" + ], + "scripts": { + "analyze": "ANALYZE_MODE=true pnpm build:inhouse:cn --analyze", + "analyze:perf": "PERFSEE=true npm run build:release:cn", + "bam": "bam update", + "build": "IS_OPEN_SOURCE=true rm -rf dist_ignore inhouse libs && concurrently \"npm:build:*\"", + "build:inhouse:boe": "CUSTOM_VERSION=inhouse BUILD_TYPE=offline REGION=cn npm run rsbuild", + "build:inhouse:cn": "CUSTOM_VERSION=inhouse BUILD_TYPE=online REGION=cn npm run rsbuild", + "build:inhouse:sg": "CUSTOM_VERSION=inhouse BUILD_TYPE=online REGION=sg npm run rsbuild", + "build:release:cn": "CUSTOM_VERSION=release BUILD_TYPE=online REGION=cn npm run rsbuild", + "build:release:oversea": "CUSTOM_VERSION=release BUILD_TYPE=online REGION=sg npm run rsbuild", + "build:ts": "tsc -b tsconfig.build.json", + "dev": "IS_OPEN_SOURCE=true npm run dev:cn:rl", + "dev:boe": "CUSTOM_VERSION=inhouse REGION=cn BUILD_TYPE=offline pnpm rsdev", + "dev:cn": "CUSTOM_VERSION=inhouse REGION=cn BUILD_TYPE=online pnpm rsdev", + "dev:cn:rl": "CUSTOM_VERSION=release REGION=cn BUILD_TYPE=online pnpm rsdev", + "dev:sg": "CUSTOM_VERSION=inhouse REGION=sg BUILD_TYPE=online pnpm rsdev", + "dev:sg:rl": "CUSTOM_VERSION=release REGION=sg BUILD_TYPE=online pnpm rsdev", + "lint": "eslint ./ --cache", + "rsbuild": "NODE_ENV=production rspack build -c ./rspack-config/build.config.ts", + "rsdev": "NODE_ENV=development rspack serve -c ./rspack-config/dev.config.ts", + "test": "vitest --run --passWithNoTests", + "test:cov": "vitest run --coverage" + }, + "dependencies": { + "@coze-common/assets": "workspace:*", + "classnames": "^2.3.2", + "core-js": "^3.37.1", + "immer": "^10.0.3", + "lodash-es": "^4.17.21", + "nanoid": "^4.0.2", + "react": "~18.2.0", + "react-device-detect": "2.2.3", + "react-dom": "~18.2.0", + "zustand": "^4.4.7" + }, + "devDependencies": { + "@coze-arch/bot-env": "workspace:*", + "@coze-arch/bot-semi": "workspace:*", + "@coze-arch/bot-typings": "workspace:*", + "@coze-arch/bot-utils": "workspace:*", + "@coze-arch/coze-design": "0.0.6-alpha.346d77", + "@coze-arch/eslint-config": "workspace:*", + "@coze-arch/i18n": "workspace:*", + "@coze-arch/pkg-root-webpack-plugin": "workspace:*", + "@coze-arch/postcss-config": "workspace:*", + "@coze-arch/stylelint-config": "workspace:*", + "@coze-arch/tailwind-config": "workspace:*", + "@coze-arch/ts-config": "workspace:*", + "@coze-arch/vitest-config": "workspace:*", + "@coze-studio/bot-env-adapter": "workspace:*", + "@coze-studio/open-chat": "workspace:*", + "@douyinfe/semi-rspack-plugin": "2.61.0", + "@rspack/cli": "0.6.0", + "@rspack/core": "0.6.0", + "@rspack/plugin-react-refresh": "0.6.0", + "@svgr/webpack": "^8.1.0", + "@testing-library/jest-dom": "^6.1.5", + "@testing-library/react": "^14.1.2", + "@testing-library/react-hooks": "^8.0.1", + "@types/lodash-es": "^4.17.10", + "@types/node": "^18", + "@types/postcss-js": "^4.0.2", + "@types/react": "18.2.37", + "@types/react-dom": "18.2.15", + "@vitest/coverage-v8": "~3.0.5", + "autoprefixer": "^10.4.16", + "concurrently": "~8.2.2", + "css-loader": "^6.10.0", + "debug": "^4.3.4", + "file-loader": "^6.2.0", + "less": "^4.2.0", + "less-loader": "~11.1.3", + "postcss": "^8.4.32", + "postcss-loader": "^7.3.3", + "react-refresh": "0.14.0", + "react-router-dom": "^6.11.1", + "rspack-plugin-dotenv": "^0.0.3", + "sass": "^1.69.5", + "sass-loader": "^14.1.0", + "style-loader": "^3.3.4", + "tailwindcss": "~3.3.3", + "ts-node": "^10.9.1", + "typescript": "~5.8.2", + "vitest": "~3.0.5", + "webpack": "~5.91.0" + }, + "// deps": "debug@^4.3.4 为脚本自动补齐,请勿改动", + "botPublishConfig": { + "main": "dist/index.js" + } +} + diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/app.ts b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/app.ts new file mode 100644 index 00000000..e0b4cf83 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/app.ts @@ -0,0 +1,36 @@ +/* + * 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. + */ + +import { configs as GLOBAL_ENVS } from '@coze-studio/bot-env-adapter/configs'; + +import { openSdkDefineEnvs } from './env'; +import { IS_OVERSEA } from './base'; + +export const getRspackAppDefineEnvs = () => ({ + ...openSdkDefineEnvs, + /** + * ChatArea 依赖 + */ + IS_OVERSEA, + CARD_BUILDER_ENV_STR: JSON.stringify(GLOBAL_ENVS.CARD_BUILDER_ENV_STR), + SAMI_WS_ORIGIN: JSON.stringify(GLOBAL_ENVS.SAMI_WS_ORIGIN), + SAMI_APP_KEY: JSON.stringify(GLOBAL_ENVS.SAMI_APP_KEY), + SAMI_CHAT_WS_URL: JSON.stringify(GLOBAL_ENVS.SAMI_CHAT_WS_URL), + COZE_API_TTS_BASE_URL: JSON.stringify(GLOBAL_ENVS.COZE_API_TTS_BASE_URL), + FEATURE_ENABLE_MSG_DEBUG: false, + APP_ID: '""', + COZE_DOMAIN: JSON.stringify(GLOBAL_ENVS.COZE_DOMAIN), +}); diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/base.ts b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/base.ts new file mode 100644 index 00000000..522c89a9 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/base.ts @@ -0,0 +1,67 @@ +/* + * 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. + */ + +const { + REGION, + BUILD_TYPE, + CUSTOM_VERSION, + NODE_ENV: ENV, + ANALYZE_MODE, + PERFSEE, + IS_OPEN_SOURCE, +} = process.env; + +const NODE_ENV = ENV as 'development' | 'production'; +const IS_DEV_MODE = NODE_ENV !== 'production'; +const IS_BOE = BUILD_TYPE === 'offline'; +const IS_RELEASE_VERSION = CUSTOM_VERSION === 'release'; +const IS_OVERSEA = REGION !== 'cn'; +const IS_ANALYZE_MODE = ANALYZE_MODE === 'true'; +const IS_PERFSEE = PERFSEE === 'true'; + +export { + IS_PERFSEE, + IS_DEV_MODE, + IS_BOE, + IS_RELEASE_VERSION, + IS_OVERSEA, + CUSTOM_VERSION, + NODE_ENV, + REGION, + IS_ANALYZE_MODE, + IS_OPEN_SOURCE, +}; + +type EnvVar = boolean | string; + +export const getEnvConfig = ( + config: { + cn: { + boe?: EnvVar; + inhouse?: EnvVar; + release?: EnvVar; + }; + sg: { + inhouse: EnvVar; + release: EnvVar; + }; + va: { + release: EnvVar; + }; + }, + defaultVal: EnvVar = '', + // @ts-expect-error -- linter-disable-autofix +): EnvVar => config[REGION]?.[IS_BOE ? 'boe' : CUSTOM_VERSION] ?? defaultVal; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/build.config.ts b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/build.config.ts new file mode 100644 index 00000000..2fa820c8 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/build.config.ts @@ -0,0 +1,166 @@ +/* + * 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. + */ + +import path from 'path'; + +import { DefinePlugin, ProgressPlugin, type Configuration } from '@rspack/core'; +import { SemiRspackPlugin } from '@douyinfe/semi-rspack-plugin'; +import PkgRootWebpackPlugin from '@coze-arch/pkg-root-webpack-plugin'; + +import { PREFIX_CLASS } from './semi-css-var-postcss-plugin'; +import { cssLoaders, sideEffectsRules, swcTsLoader } from './rules'; +import { openSdkUnPkgDirName } from './env'; +import { IS_ANALYZE_MODE } from './base'; +import { getRspackAppDefineEnvs } from './app'; +// eslint-disable-next-line @typescript-eslint/naming-convention -- __dirname +const __rootName = path.resolve(__dirname, '../'); + +const config: Configuration = { + mode: 'production', + context: __rootName, + optimization: { + splitChunks: false, + ...(IS_ANALYZE_MODE + ? { + minimize: false, + chunkIds: 'named', + } + : {}), + }, + entry: { + main: ['./src/index.ts'], + ui: './src/export-ui/index.ts', + }, + experiments: { + css: false, + }, + output: { + path: openSdkUnPkgDirName, + filename: pathData => + pathData.chunk?.name === 'main' ? 'index.js' : '[name].js', + library: { + name: 'CozeWebSDK[name]', + type: 'umd', + }, + }, + target: ['web'], + resolve: { + tsConfigPath: path.resolve(__rootName, './tsconfig.json'), // https://www.rspack.dev/config/resolve.html#resolvetsconfigpath + alias: { + '@coze-arch/i18n$': path.resolve( + __rootName, + './node_modules/@coze-arch/i18n/src/raw/index.ts', + ), + /** + * swc.env.mode='usage' + */ + 'core-js': path.dirname(require.resolve('core-js')), + }, + extensions: ['...', '.tsx', '.ts', '.jsx'], + }, + module: { + rules: [ + ...sideEffectsRules, + { + test: /\.svg$/, + issuer: /\.[jt]sx?$/, + use: [ + { + loader: '@svgr/webpack', + options: { + svgoConfig: { + plugins: [ + { + name: 'preset-default', + params: { + overrides: { + removeViewBox: false, + }, + }, + }, + ], + }, + native: false, + }, + }, + 'file-loader', + ], + }, + { + test: /\.(png|gif|jpg|jpeg|woff2)$/, + type: 'asset', + }, + { + test: /\.less$/, + use: [ + ...cssLoaders, + { + loader: 'less-loader', + options: {}, + }, + ], + }, + { + test: /\.scss$/, + use: [ + ...cssLoaders, + { + loader: 'sass-loader', + options: { + sassOptions: { + silenceDeprecations: [ + 'mixed-decls', + 'import', + 'function-units', + ], + }, + }, + }, + ], + }, + { + test: /\.css$/, + use: cssLoaders, + }, + { + test: /\.tsx?$/, + exclude: { + and: [/\/node_modules\//, /^((?!@byted\/mojito-safe-fund).)*$/], + }, + use: swcTsLoader, + }, + ], + }, + builtins: { + treeShaking: true, + }, + plugins: [ + new DefinePlugin(getRspackAppDefineEnvs()), + new ProgressPlugin({}), + new PkgRootWebpackPlugin({}), + new SemiRspackPlugin({ + prefixCls: PREFIX_CLASS, + }), + ].filter(Boolean) as Configuration['plugins'], + devServer: { + allowedHosts: 'all', + historyApiFallback: true, + hot: true, + }, + devtool: false, +}; + +export default config; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/dev.config.ts b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/dev.config.ts new file mode 100644 index 00000000..69beb309 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/dev.config.ts @@ -0,0 +1,199 @@ +/* + * 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. + */ + +import path from 'path'; + +import { DotenvPlugin } from 'rspack-plugin-dotenv'; +import refreshPlugin from '@rspack/plugin-react-refresh'; +import { + DefinePlugin, + ProgressPlugin, + type Configuration, + HtmlRspackPlugin, +} from '@rspack/core'; +import { SemiRspackPlugin } from '@douyinfe/semi-rspack-plugin'; +import PkgRootWebpackPlugin from '@coze-arch/pkg-root-webpack-plugin'; + +import { devCssLoaders, swcTsLoader } from './rules'; +import { devDefineEnvs } from './dev'; +// eslint-disable-next-line @typescript-eslint/naming-convention -- __dirname +const __rootName = path.resolve(__dirname, '../'); + +const config: Configuration = { + mode: 'development', + context: __rootName, + entry: { + main: ['./src/dev-app/index.tsx'], + }, + experiments: { + css: true, + }, + target: ['web'], + resolve: { + tsConfigPath: path.resolve(__rootName, 'tsconfig.json'), // https://www.rspack.dev/config/resolve.html#resolvetsconfigpath + alias: { + '@coze-arch/i18n$': path.resolve( + __rootName, + './node_modules/@coze-arch/i18n/src/raw/index.ts', + ), + /** + * swc.env.mode='usage' + */ + 'core-js': path.dirname(require.resolve('core-js')), + }, + extensions: ['...', '.tsx', '.ts', '.jsx'], + }, + module: { + parser: { + 'css/auto': { + namedExports: false, + }, + }, + generator: { + 'css/auto': { + exportsConvention: 'camel-case', + localIdentName: '[hash]-[local]', + }, + }, + rules: [ + { + test: /\.less$/, + use: [ + ...devCssLoaders, + { + loader: 'less-loader', + options: {}, + }, + ], + type: 'css/auto', + }, + { + test: /\.scss$/, + use: [ + ...devCssLoaders, + { + loader: 'sass-loader', + options: { + sassOptions: { + silenceDeprecations: [ + 'mixed-decls', + 'import', + 'function-units', + ], + }, + }, + }, + ], + type: 'css/auto', + }, + { + test: /\.css$/, + use: devCssLoaders, + }, + { + test: /\.svg$/, + issuer: /\.[jt]sx?$/, + use: [ + { + loader: '@svgr/webpack', + options: { + native: false, + svgoConfig: { + plugins: [ + { + name: 'preset-default', + params: { + overrides: { + removeViewBox: false, + }, + }, + }, + ], + }, + }, + }, + 'file-loader', + ], + }, + { + test: /\.(png|gif|jpg|jpeg|woff2)$/, + use: 'file-loader', + }, + { + test: /\.tsx?$/, + exclude: { + and: [/\/node_modules\//, /^((?!@byted\/mojito-safe-fund).)*$/], + }, + use: swcTsLoader, + }, + ], + }, + builtins: { + treeShaking: true, + }, + plugins: [ + new DotenvPlugin({ + path: path.resolve( + __rootName, + devDefineEnvs.IS_BOE ? '.env.local.boe' : '.env.local', + ), + systemvars: false, + defaults: true, + allowEmptyValues: true, + }), + new DefinePlugin({ + ...devDefineEnvs, + IS_PROD: !devDefineEnvs.IS_BOE, + }), + new ProgressPlugin({}), + new PkgRootWebpackPlugin({}), + new SemiRspackPlugin({ + prefixCls: 'coze-chat-sdk-semi', + }), + new HtmlRspackPlugin(), + new refreshPlugin(), + ] as Configuration['plugins'], + stats: false, + devServer: { + allowedHosts: 'all', + compress: false, + historyApiFallback: true, + port: '8081', + hot: true, + proxy: [ + { + context: ['/api'], + target: 'http://localhost:8888', + secure: false, + changeOrigin: true, + }, + { + context: ['/v1'], + target: 'http://localhost:8888', + secure: false, + changeOrigin: true, + }, + { + context: ['/v3'], + target: 'http://localhost:8888', + secure: false, + changeOrigin: true, + }, + ], + }, +}; + +export default config; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/dev.ts b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/dev.ts new file mode 100644 index 00000000..2eaaae63 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/dev.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +import { type DefinePluginOptions } from '@rspack/core'; + +import { getRspackAppDefineEnvs } from './app'; + +export const devDefineEnvs: DefinePluginOptions = getRspackAppDefineEnvs(); diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/env.ts b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/env.ts new file mode 100644 index 00000000..e7d913cc --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/env.ts @@ -0,0 +1,90 @@ +/* + * 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. + */ + +import { + IS_RELEASE_VERSION, + IS_DEV_MODE, + NODE_ENV, + REGION, + IS_BOE, + getEnvConfig, + IS_OVERSEA, + IS_OPEN_SOURCE, +} from './base'; + +export const openSdkDefineEnvs = { + IS_BOE, + IS_DEV_MODE, + REGION: JSON.stringify(REGION), + IS_RELEASE_VERSION, + IS_OVERSEA, + FEATURE_ENABLE_TEA_UG: false, + IS_PROD: !IS_BOE, + IS_OPEN_SOURCE, +}; + +const getUnPkgDirName = () => { + if (IS_BOE) { + return 'inhouse/boe'; + } + + let name = ''; + + if (IS_RELEASE_VERSION) { + switch (REGION) { + case 'sg': + case 'va': + name = 'oversea'; + break; + case 'cn': + name = 'cn'; + break; + default: + name = ''; + } + + return `libs/${name}`; + } + + return `inhouse/${REGION}`; +}; +export const openSdkUnPkgDirName = getUnPkgDirName(); + +const slardarVaPath = '/maliva'; +const slardarSgPath = '/sg'; +export const openSdkSlardarRegion = getEnvConfig({ + cn: { + boe: '', + inhouse: '', + release: '', + }, + sg: { + inhouse: slardarSgPath, + release: slardarSgPath, + }, + va: { + release: slardarVaPath, + }, +}); + +console.debug( + 'open-sdk', + NODE_ENV, + '\nopenSdkDefineEnvs:', + openSdkDefineEnvs, + '\nopenSdkSlardarRegion:', + openSdkSlardarRegion, +); diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/export.ts b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/export.ts new file mode 100644 index 00000000..72bdc281 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/export.ts @@ -0,0 +1,27 @@ +/* + * 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. + */ + +export { swcTsLoader, devCssLoaders } from './rules'; +export { getRspackAppDefineEnvs } from './app'; +export { openSdkSlardarRegion } from './env'; +export { + IS_DEV_MODE, + IS_RELEASE_VERSION, + CUSTOM_VERSION, + NODE_ENV, + REGION, + IS_BOE, +} from './base'; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/rules.ts b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/rules.ts new file mode 100644 index 00000000..7bb6f3cb --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/rules.ts @@ -0,0 +1,129 @@ +/* + * 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. + */ + +import { type RuleSetRule } from '@rspack/core'; + +import semiCssVarPrefixPlugin from './semi-css-var-postcss-plugin'; +import { IS_DEV_MODE } from './base'; + +type UseLoaders = Extract; + +export const cssLoaders: UseLoaders = [ + 'style-loader', + { + loader: 'css-loader', + options: { + sourceMap: IS_DEV_MODE, + modules: { + auto: true, + exportLocalsConvention: 'camelCase', + localIdentName: !IS_DEV_MODE ? '[hash]' : '[path][name][ext]__[local]', + }, + }, + }, + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: [ + // eslint-disable-next-line @typescript-eslint/no-require-imports + require('tailwindcss')(), + // eslint-disable-next-line @typescript-eslint/no-require-imports + require('autoprefixer')(), + + semiCssVarPrefixPlugin(), + ], + }, + }, + }, +]; + +/** + * 已经标记 sideEffects: false,无需覆盖 的pkg: + * chat-open + */ +export const sideEffectsRules: RuleSetRule[] = [ + { + test: /packages\/components\/bot-icons/, + sideEffects: false, + }, + { + test: /packages\/components\/bot-semi/, + sideEffects: false, + }, + { + test: /packages\/studio\/chat-area/, + sideEffects: false, + }, + { + test: /packages\/studio\/chat-core/, + sideEffects: false, + }, + { + test: /packages\/arch\/i18n/, + sideEffects: false, + }, +].filter(r => r); + +export const swcTsLoader: UseLoaders = [ + { + loader: 'builtin:swc-loader', + options: { + sourceMap: IS_DEV_MODE, + jsc: { + parser: { + syntax: 'typescript', + tsx: true, + }, + transform: { + react: { + runtime: 'automatic', + development: IS_DEV_MODE, + refresh: IS_DEV_MODE, + }, + }, + }, + env: { + mode: 'usage', + coreJs: '3.37.1', + targets: [ + 'chrome >= 87', + 'edge >= 88', + 'firefox >= 78', + 'safari >= 14', + ], + }, + }, + }, +]; + +export const devCssLoaders: UseLoaders = [ + { + loader: 'postcss-loader', + options: { + postcssOptions: { + plugins: [ + // eslint-disable-next-line @typescript-eslint/no-require-imports + require('tailwindcss')(), + // eslint-disable-next-line @typescript-eslint/no-require-imports + require('autoprefixer')(), + + semiCssVarPrefixPlugin(), + ], + }, + }, + }, +]; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/semi-css-var-postcss-plugin.ts b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/semi-css-var-postcss-plugin.ts new file mode 100644 index 00000000..179cbdff --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/rspack-config/semi-css-var-postcss-plugin.ts @@ -0,0 +1,90 @@ +/* + * 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. + */ + +/** + * PostCSS 插件:为 Semi 组件类名和 CSS 变量添加前缀 + * 解决 coze-design 里 hardcode 的 .semi-xxx 类名与 prefixCls 不匹配导致样式失效问题 + * 兼容多类名、嵌套、伪类、组合选择器等复杂情况 + * + * 注意:本插件应在 coze-design 的样式被引入后生效,确保所有 .semi-xxx 都能被正确加前缀 + * + * 已添加调试代码,可通过环境变量 DEBUG_SEMI_CSS_VAR_PLUGIN 控制输出 + */ + +import type { PluginCreator } from 'postcss'; + +export const PREFIX_CLASS = 'coze-chat-sdk-semi'; +const CSS_VAR_PREFIX = `${PREFIX_CLASS}-`; +const SEMI_CLASS_PREFIX = 'semi-'; +const CUSTOM_CLASS_PREFIX = `${PREFIX_CLASS}-`; + +// 处理选择器,将 .semi-xxx 替换为 .coze-chat-sdk-semi-xxx +function processSelector(selector: string): string { + // 只处理 .semi-xxx(不管前面有无其它类名、伪类、组合等) + // 例如:.semi-button、.semi-button-primary:hover、.foo .semi-button.bar + // 注意:不要重复加前缀 + // 兼容 :is(.semi-button), :not(.semi-button), .semi-button:hover, .semi-button.foo + // 兼容多个选择器用逗号分隔的情况 + const replaced = selector.replace( + /\.semi-([a-zA-Z0-9_-]+)/g, + (match, className) => { + // 已经有前缀的不处理 + if (match.includes(`.${CUSTOM_CLASS_PREFIX}`)) { + return match; + } + return `.${CUSTOM_CLASS_PREFIX}${className}`; + }, + ); + return replaced; +} + +const semiCssVarPrefixPlugin: PluginCreator = () => ({ + postcssPlugin: 'semi-css-var-prefix', + // eslint-disable-next-line @typescript-eslint/naming-convention + Rule(rule) { + // 只要选择器里有 .semi-,就处理 + if (rule.selector && rule.selector.includes(`.${SEMI_CLASS_PREFIX}`)) { + rule.selector = processSelector(rule.selector); + } + }, + // eslint-disable-next-line @typescript-eslint/naming-convention + Declaration(decl) { + // 处理 CSS 变量定义 + if (decl.prop && decl.prop.startsWith('--semi-')) { + decl.prop = decl.prop.replace(/^--semi-/, `--${CSS_VAR_PREFIX}`); + } + + // 处理 CSS 变量引用 + if (decl.value && decl.value.includes('var(--semi-')) { + decl.value = decl.value.replace( + /var\(--semi-([a-zA-Z0-9_-]+)\)/g, + `var(--${CSS_VAR_PREFIX}$1)`, + ); + } + + // 处理 rgba(var(--semi-xxx), ...) + if (decl.value && decl.value.includes('rgba(var(--semi-')) { + decl.value = decl.value.replace( + /rgba\(var\(--semi-([a-zA-Z0-9_-]+)\)/g, + `rgba(var(--${CSS_VAR_PREFIX}$1)`, + ); + } + }, +}); + +semiCssVarPrefixPlugin.postcss = true; + +export default semiCssVarPrefixPlugin; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/assets/widget.png b/frontend/packages/studio/open-platform/chat-app-sdk/src/assets/widget.png new file mode 100644 index 0000000000000000000000000000000000000000..a26c942879aa619803a6c1e4cf9acbbf28306869 GIT binary patch literal 25830 zcmV)-K!?AHP);AhBWY$dcQmD^)jfUt z_U+}or|PY$Q-^3=#${Z_Wn9K(AW<|f$1GZF#iFrJeK~_A~R-I#w?NGWl)y`p9Yg2>?0Q zlmO5`_bJeChLW3uFySlnsx3{-@RaI3Q+vlBk9&OmLL0^6K)%3_C zj}X=ZBmhUQTYi_%W6sULsVN&MLSp(opR^3bkH)Huc~A_{` zj~E0R`{kUoXV2EWU*~eU=y$QbaN$ClFkyn;ym_+$4xd*8H1B)g`!qBQz`?)>sJ|)a zYDyYN2J)hfRd9?+Pz=I*qa3?ir%fST*LP5+jVbmuWD#$2%dfRS)*O1OqmjC z6_|6-AXxLg1Os@_#6*ZjvuMGB1zw?0 zP+0Q$yob-vKL01@)>KSAN0WE1rz)3uUT!J>Vgrqgv};{ht0})$7kbweRL7cKoBm`C zOg1*tpe5+Qfdeq#0@hLrW|)8kyai?*G#{IQI>nC!1VZ>p{vJf9Q`S=UpvdqDWgbQY z->Z4qX!NoJngERm+uV@+ty{MeFaEM+%V@^z?_A+kHZAkLikp?nEv1nIz@P-0C7|M( zVxjBaVqe>uy}SPO(Ym_2V9%aCMx%hMTz1)I8s-+biE9o*EN6-w3=a3`_Z&+-`*H?H zS%V@Yn3f2TmFW4R-p?h-+?$$hm>U4YjvYI^RjXEc3<6Kp{qdyfQ{HfMMXve^rK)eD zQ3irR2u!cX*zCHeP}uh9y47#rR#{mY2uESgQj`O6{|pLfm@JpCxSa=b`?}vLOJI~G zCcuu0*qUcK6^VJ?9a%$|Ph+Z0v3QF*z`X$*-DijNfv z9p5|B^~z(fu7BTFa1!=%1uIsp2(G>MT9}CvJ2B>$c-zQvN!vfju}w>Q_{t3&Wd#bI z5md_XjP#2vljD7XM+QK!gwuQa>8A}xXliQmSVMTPydT~cp?P>QhPq_S4 z00cyNRew<7H~;rVSHAGlIm>_X_Ku$SF5nnR1$(Y4*o|zS7x)!i&*pd*aY*2~%3}X6 zdq6-&y`~sUOu-%(*|-uK1*>&kHzwl?vDk`SE&GYNtN~Z`21|z!I z^WTTsp1l2kANjp4Amh*;aP`82C2r&KOcEV1QQ9S z($!c0c;1Aj3;%f>496w_$Du>d-M-<)cWvXydx2v$0o=OmlrTVG-VwORyD<+DRYqy+ zn6C==a(EJ8vB3unMSNr1yP+^X+-*Sk!j z9TP=A0>hLyzt{Kb-b>@0;e>@_G;f_a@zR?vxb(??e)6#kZf9>*@xJ@+3v84;07&?- z0DR6#p>e*SZSsybtug8}g&sJCAQ6Ovn-yLW$vQ>OPghr$&o*!FwHK_sqGrPBcTnlv zqH#F^(G+a&KD_n)58Z$1ub7M%LB{v*-)}U=%{Sj{5)*&<%U>4VtYS(HvdRJnjRF}J zpvZ_wll?oLsAqs6xk+}Mfx7d~JAIf+b#-;Q+R5)|oYMGPpB^`rMn-~S_xD@de)GkR zFMV(;;v9{QjRmB;BgTOoD|r9xJJVSaaK{{u_R~q0lMad$Afkk`bSiwQ5xbzbha4V|Y)JV+N66s(sg5-g(-M zJCGSY+;Y->SRZ}#8zgeK0uabaf{z@7cQ9b5udmNPW63v8pE~vGaWIS$fz~*5@E5nf zyyl~u*^DZ1r&VN4ZnOm!C(R~sI=*yTP9!HoW1Lh_IG%~f)8zIT5}Af)2|W(>-+%x8 zerIQAMX^})F1g~#_xTm|f5~5~r*Rog(%#o|@GDP0am6=%-w(un3)2cb!_A}02*^g` z-Q{E>4U7>7MMkgS#vmg8fF6a(v>5~g4FT=|GQD-{*4(5?lX90{_0*UAT>ZOgTt=-F z@*Q7!;?WD?x-B3g-q6rc6cWy6Fi1GrNVs-@kQm&gPeSh0h;j;tKns_^?ub{UkGI|> zAAn#|or8v0zWlEyU$$b+J>!5F{gSJw|HS1h);_lQoUfn3u5$2K%X?iJt99? z%4A9fZ=zrv{=i@#tRH>!(F!Kix!0Wkz1P%DJomdw`KQsil%MG0jw78fzU{fE-m->G zsr>x;^8pSPx)i1wWjT7qf74FRHCN8^s>GJ=+b?xf6zQ8;Yz{s%!rGWHwTQtsX6Isf@#;uKl2ZK4!aDijkIfcU?`q_vG>cIN|zctb|u+Ig=g_k}2hvR@4ds107 z{r_Ba`9p7GK;)X6n=6pOiOgpC9NtHF#H4NfQDu=*j+vp7Vbc^PQomjID*$1(B`Ak< zZxAt%>WeOC4N)}?h_Ni)UAsQ`%oEq#Ic?gsVpCI7p`)Xth)xn?!tIDj?WTk15~=4y z4UmyApUm&1lEKUhpg>>_y#$E$`t|F5aEKYFef70X&6hn)<1*&uP}^@-y!7nbSHqOz zcVD>v`s<4*3+`*{L&E1u$fQdRLjVxa5Hsie z)AGjVOMgn^a!N?!qziv~_IdxgoXuu`^5n_hefQm$Lu?A4xy2Ifnw(rY$)6hXj(tc^ zs7sWrl$&ykk6{h72}yk@KM6oASn{>gXUw?qXEg41J*7mmyY}FoRkyzU^2awL6K)!5-v9)w&Wh9H7ef-)qaNaZpaakoV6MSaud;gT7ie6@#Y#`l-oIJ@!W+NU z)zyXZ7-2_TqMqg-XhnM=PkzoL4| z$7o!}#gVG&X&-Bz@v$@5ipZg@z^cG_8dw>^FH~}BvXUM1@yH2%xr8m3C{QLNtwc-k zu1XLoRsH%YOV0fCFO|y8rc+4pE~T#sw-XsK&T)$Lu&2I_VquTex)=Ut#i2t#J^+u@ zk|j&bKqY7lBo9iiTt^^&xDhv3dEuoGy{&e_oPVS-B(4UC_i0LI zK@-)~C~BVU(VWwAR9oXwbF)u1wO*pFw;U{*zjy4+Q&+d99XkqU4e0cL@M9Gfp>(#d z{@sl)|Jftlspc`}`;tp8DPrPFOt+Sr{WVH6&x)7PG01U)qQsB)3(&EcWG^It?bxxS z@{BY8zxgv~|MpM3F{UMwporg9O%2gym((x_Drm{#N&|xALo0DYo!s2cz&OxSpw+8; z=;aN01DsGPD;m?lU^8mty3bv^XU|;+n2+Y?&6`)a;DQTGH9{0ZaH^h{EP`;Xpg897 z6e+h?qT-t!U!90pGs3C0wbf(0Fc$=YKW+N!KRz}Ap)D)W1_;-!sHHO(SJ9F)D{Tg3 zc$~X!H#5b>7q(BIo1?jNa&-RkY6HG)JM#3vgGUTN4ju{|4l>37!e-RGg&+NRTieP{ z<4?9YaF+lDNVm3=Sw^N)Diq+>mKyv|(lLt8E<+M+V@N4HO(=oSU{I*`&F*k5ZJW^f=7*Di; zs~zw!3mh4e))e7dG_w8ME`eRIf4PtT{Xg63;6XR3RHgh{$lw=)1;(JgPwpTgXS<(YSSt8o8{``0XxiG^{L5E7 z`LVG82=ON2-{dAmfAUB5^p_u-OlPr~O){nEX@m{t8WgmM<=yUN+8 zRPvno+mDXL?^ot-&&oz7VNH>X!r)W4a^ zQludTW;t!PN1yw2Go6244dra(%lV;s6qz|Lr*O<9*{{;zh!*86DKMk~;hxhIP&y`? zy&hcSRhQS&CqB|dO_RyogPj_?Y^9>I=}+gJ@$tC_4<5{+bSlR5!!iMbM=IZVw+5Z;A=GvmKjJ+L%}P`|#l+{o8-E)AKL%@nyJu z;qhM4$&(=HdEz&Zo&P2_pL!6~+RH8W8XNlZFrwe%Kou?I69rpJC?M-rvj60LQg> zu{QCdb%y4#*JCu79}{xsru3orH5x9Yb&&*Ms{Am)_fvhznP>m|WpLnN8n?A;*Lv3T zsoVic5OD^%VS}S-i{mn%w5OrL)1N2tuiZ6tW0mt0&$@BHJ2 z*j|Tul1tkDE*QdwSpo#+2aXdIl(k>k-%5hRy&mB^zSV5dcGteHCN!lqW!c|lA@xnm zK7x)HnY5VB4dX5V40O`q7H<1=IB*pC9_ZWyf+EA?6m26=;tu%WZQ8bNo5JwYLx&Dk zEMENA^KupSZ=y1jC^F5B=F*F*=+FLSQq<#Oo%5Xr=-B(=zW!jKEMPbyAdHTu?B098 zb1G$j+tDfsjX@6Y0WcaE?A3gtqTDr=oA_SM#pj&+&1L9(+3k;N^d9CdL3%kf2GTzS z7%pFf=%Wl>9hCWG*XI1P-}MK>bVAuf0HUR(#dw^ipZ2D*)e->%V)A4~zt4?Fz#v|x z1R#`<-V_)p8w6Pz!2Ml!yVSAfGwQEFNV(=!tT9}Y?mPGL*4yf7)-2zWZ%Fw8qp|sd zx4`YcfB*g*O090(xKTcGC%UIp<&UG@Xvy5Cbi|q{G4wAQCjrxmql(_lnKKcWK=Pk| z&TIbe^zzdZ5-kqcQ={k;AD+xH2xVQZF(6dJbENt;>~t?bls0?Pv`g(vc;(}Fk6usX zKg95SIn-l*MD&68HquwWevl5CGE2wgogWoaQQ7>vlcwJE_j|Yh=m63_PCM;1J$35T z!pfB^@v%{Z+L<>I;$(1_1jkX$Cpoc8u%VU&5O9ahn>P<-)s2>DZf?#sPrK?8Dl@^Y znFq(9c*h?#(A24Z#3RtNiVM__OeQsOZ?1+G5Lwro^?M1vp;Tl_$r4B16-}F(IO^?{B`}~Z&R+I!mvaO6VW}XC0{hvdr^d!c&qld3%i)wdXC^3I zcJ6)|sFPuUmP?o_;aF;FYI6LYTy4$V8>mbqY(!x^A_$6f%S{tPBI`jHa`n|$`|vpJ+qch$rdYJ-lXG*GjlWA}A~Fsd4dIfDs_C*z zYD06w3Ct;#C!DvKKM>2))rcIj^@=tidB#nOKwAWosWhUC@Hr@ON zYc~1)U9O&ECyjPMh-3nT9AKz88ZRj;Z{%lCjxJh$YxQR=`S_dB4$(P--^%oG5ES!H zrG3Q1RR*Ie1`2G5*9r3Q!w-X4D=c9Dq3Lv58M$G@F%df8sP#oPG-XPTe8}gy+LUTdr*4Bl39!Cgj%pzv-?$n@xzvzzv$z0{9NrtR8C?#3^tuEtcfy9J>dVQ}6L|cTm;02l5hF3^O z4pYhr;zbG|tpu{nC+o9_%_td6B$KRw#DHAG{e=`>)N`6P{kpO+om_HFcJv$y47fmu z2&oJoEfbTeltdaX+F^k~4lwL}FJ4q-hSwrqqsbkEG5TfRy!T#jtrqZH4~c?s&&qfU z9OLFhSs7SUqz4bVsDhTj$*~O*gY6lIA z5^H;WxoQhY!t^6Y1Ex|}@GO=utD&&URPwu{U8<_4UJNrHZFbI_Iq*h-_qyIwx1wNF z0VI8%1SrJ5veD3f2m-kVqsKN(C)N_E3XKd|-_K3FkjhB3HJ#2}Sm~r)JEjwrx7m~> z{W*q80VM1gF20z9c>#DuU^_-C3t`XawY);QQLeqc-L%mK3l^B!vS8f+6j{6?Ev4cV ziIidq*ug%->@wK5XU`tvb%KBR;w!&*36;TcQ!$++dcE+BN}AkU5t~gB8CCtjFm}PA zlK0Nrb+ta5P?a{d2S=3$DiFw9e&Kg6=8oD-`U2AlUa0fVJI~C|?v~mlha#lU)6aZT z$(o#z>4XS2N?_i3=bb(x*gCHE%%;=av#1OtPNECzv!2UlLga~2{YiCtJr*SGL5s~P zl^Fhd?$TNt%qy?KNYyoS&SsNdVd59@LIEJKaKMR$Y`c=qh01ivQp`kU_83B;Wc#r3s;bJ&fP}x-tXZSx z&6{U(p{`e_PhFh=H& z?%1)zBwXPTxMgeV7M)FHAUd(7O-cNS6gdRjm9cI@B|CsP+0p#vQDj~Y!8CFsDh-p) z2FLXfyhD8`nESA7z$;LB#?=&A?K~QJ=Tz%Z&OWooM0^iE_@EgFiyy!~L=M^Y<<(S8 zEeq30M0&m1Gb<9kVz~$Cj#Y4vQ4KvEYs)Y`6F#KLlYPen<$Pz9OJ#NQ5>&b8McS%j zPn2G#*)yhe#0$J}8IsIlrQ#gRo;+BQF5t6-R$r24d!9QZY2QQRgIdKVzKXU?3d7cN{#yfz1LaKwOO`m8sV7oQ-qB_=f`y2Mk> zW-1}dRhP{(GP)ZZvq-iC))C2dYc-Z@R>Ht~z{s_GF(!)AMXyD^K92PM1$CX_XwlxsTSvvit=c38RbJi&Z= z{PD+4RB+m~X<~nw^bXIfEDJsn+r-I4zf;iJlqp%AfIORsf0Y^Ss;Fw139^m0iVokF z*2LtbT82QlS+lMfP&f%ua&UAcNH*%k;)MJ5?K3}uJqOuT&PNypC=?t=2 zP7&N8lMvcT@zJ~MQ$bY9B~4kH)KRJB@@%|Z z?M&{t&FD~@l4R-$a2P?u1BJG&N~CQhBHGR9qhN3-_=BlTrC%?o%HdTHzsNI@7*2n4sXU&NbCsTi z5a2Lcy?V9zh$Gu>nHwq@yfY`W>EgHDcAJ@s6(@qAIs$uPrNjefD^i{+7fs=aV+KUk zI*7)@6(TA*3a?yEO0;IQQM1i}Xl!iM>_Lq_hZTf&E&;rLX&#nnBx-KL=0ctb@0V^k%_ zXP9*h9X>!|)5v4Hk@}~3MCOx_ZJYpWMj2{6A)0Nb^1|G9R3;MLvW^3H{pOfB0YsQd z7j--VV|j~;s;W!f*(CoeudV|c0Hg4b#L96Bvk%@>sYp)bq`O%YDrdh)PJJfNg5vOK-0v+AZdj zM6*RxKz8ojDVrEqL1ih9rtrLEiF%R2fJ4A=q)St4TR;a62Gr70q_*~eUxKDWJ_Jm; zh|3+q3guzNM45&->AEW`fu^92j*ckwfHn=Hjo@_JE!!_4kSKQDx>K*h1q%etoH^4( z2jPvyZcV#(<>~AHw3Ghm9qZ`bA9#iS_V#VGYfqjZ zyf&JotcG$rRn~nwEpqq)Iany48>*OIMT3}}dkh6Yb!8OTv58vT=`sf-sXThVMpo@-S0*BV)ZN0etx(A6E#dq22=R^Hn} z`}gPB5z#{rJ={tk{lq5zOdCxy+@Qeui^cGIpuF*$$I5LI0X&Cfd*XmdXo~U?=Dp~^ z`@Ky#k646LBwVwRrckLtxX=>tK7ms}=FOX@+uGVRR1k6|)@dHC8xdjm6b}2GuyZ&ZXGc#3MVftD1o79#OzZIjPYWu(gQ{*MHm1+M*a;$45SN8vV|1HAmA6 z9FrzgaVIfxqE8=u&(zT69Ef=ld$xsuhc0PN2M!cz{VRQR&wWSe;33qZ4IlE!00%S$ z%qPzadpo#fTUuHOX-b%c9&rlmmNnrAOhzAmPpe2%sN~#slJ{g>vTQn;3ggI2$Kf!a zSQYWowHHAWItilrEiGGGNq_u@4TOPJzV()Pj$d6s5dtJGp6t16=bXpde8UM(lw*`q z#I#_Ki~Dz|MKsm69ewm~-`T_FKoDvyt2qDZn>*>u#kF+)YirpR=?xkbQ9Q@#B#Z_P z#64>#YPa_lg?`|T=Fc$A^5n@irj-8=et4MHyofP99>x9b$cYa6I?<-te1gf(8nrWM zZf-W8?ccvYVB+2HJK}v6qd{w@48L0d4WtAEjIa$Agf)JMU9!4Z=pHdn;c^COh-K$g z(0l%DlF<&)#EpK^$L`q~dW?I~rzDRsC#9GV9EZrXyE#lADT|#|^;pcYV;ANO%vYX# z>aZc#6&$!V3j+WS7c39`vh|o;!s+D5ex%ze)$chSkjyRa+t09`(wsBz=EC!kCcMS#GP%c$>n0deccej4EcU>BGC$10JJcBn zFmhB%-9S@-BDj*)6mFKRBubgTF?+0$Ps3bXyC6saBWf%|4+~{96j3$Dr9S=_v*^Pgm`Sf$ zR!5g!GLin^_omP{|KW7@8hXd%j&;wST&qm2k@fuCm{3H*2_|?Yg~J_34jB2qgNNDE zWWopBk!$9|@Hzz>H*RE*R%^>Cw9O1pWP9|>>T{9;wakJl12Z5Nc>#RD2M;{9=ZwXF zPQx#8;BDsgoXJSliA7siXy^is`Ua1pzB+ozL?>i2$vJ#}a`p9om{4f1QR~ZM%E1O{$q8;x^7e1mhXo@t(^Tj;gWUlpRC)K zg4k6%sll^^+mFCB+|by*^M@P7CCgz1lfcTH7-P20RKiRJ%&^pS@q(;kXwj#LY70Xb z;fS{GLbe-!7e9FT@UtDHi^FU}>F9s+fG7Ep-(0d(qiq)`fRJ$^5~Ufn*2%T`=olLi z%$Hklne2FCl;Iax!@qy|#S@LTC~!Oh6s(6AM4#a;Iu@^xiJlk0-?Yi6!nu&rQM6NC z0|1pE+m-Qh+-8%?{(Wl^#ry2DRZb8uumC99_fgc^{H@vHGig%JcDC{^hZhnYqv-57 z^h^gistKLB9TQ~CCnN;Qu>@6lD=15crWbadAVhdJF0o=0r{NZ6Uq2iuO z-b_F}9I$c$fyRM33yZQX;0VHrL*kJ40|FT|J%l zeWH>+htUq$NNeXnoBPx9E{c@wpzM1wiIWH{iY6HW^Ql;9-$bD`qhTshXeMb6-suGk z7NEn|Lb7FMqkiU-Q?$VJtCnPI$&`g1He<#NjHSSYEdh>)roa#P^&Q$&Q90?gGz=m^ zA4T!YKiHZ#0Pu?Fopng0@ytc(;W%HLs&X-~_og|Lw-#S)pfgbuMHeD{eq@G^g z(oJ1Q0$RGX)@TYeh)(|dH&5s3rz^WmZqh~PSJDmF)zI{5KGoLY``PZ>P!np_4#>m} zXz#v&e*Due+Pt-3I)}?Hs^Z_Rq56r6?PGOZ+@X}XK-Fm=5IG%;AhXCjS1&*AwhVbkv(D7 zZbaLxGbZ0QsU8U8u`Om0$97q8{(t~*F`tq)Wi>*8&ff~ zZ!K)X7w3m2Q|wdM5ddPBqcSfm%`J`v7;+pm2He9F+nk#<1e-el_pUDb-VeHL zuTHe??uF=|fFJk!Tf z*?`{l&N}|Sj=0N-ifdM;(>c&?Bo*Fh0}BY*hSmxPTcM}c78oJ8Us`{JOfNP3cren{ zy?rCJ1ak?rRkT?+igbN_y&>8wR;)mZN5C;S;eZh!65c1VsaTyv7B5UF5E5)(a>*qi zEgIqR?(S}~CkfgPKC_WVM8cNd+E%p5cHGbkp}ie0+AJo8x}f9zMZ4J1aiQ6z`;qnI z5m)P=VdiY(M8nv#r%0=Q(L>d2C$HvXtC-Q(av*QQ>u=ur04aY04U1TEhRGXsftNhn@;<%$ zoioGq0tCx)VR`{PLSaBHnUdhUX$tOVN@e)iPs}z)L+b4d=&{Fo>5hLnY)l~2m;ltk z=}CFJags+jTw6_Z=jLeYlrU*)-~Iw?hax@wY#+V+NyiVJDv!+wQg6Kewl6}vQ zXI;md;s~4gT^zV$-%wX?b8~Z^&8Yn3$&-uH(KFdLpm7U|M_IC|QFM{vC!B)Urf?L} z#%<*fGqx4M5sYNY7dkdpR5UK9VGwa~h7HxjNC<8;z4X9CEp*+A$$dh#4zBULMi*o3TsO6??^)tGozPHtRMfZJL-ka z62!w;T?G(3e(iPgMnkNDq6x^NH{Ze%IoI8nMs9qqH@j0&7B%>7lXC zThj{K6so>xRlDg4108XZ4&US1zX!!!+WoL)HshEDd%CkhULqJd4Lcl^l&nmNOB8a)}1Oh^MEu~-n< zZGfbvDg+74LcAuN!44Jv#X673FIxN=w#hV7wr zCaN`~SS|C59FrLEiYg{xb-dK__v&uEW!uk(k5`zhq|n!mzTNVRAkQzaV73v|TQI+d z&OdJwuj^v(lg;ErgTjJFggl{~Nu&$^mXvQd3VfoyuNfJ~z31M&Mf%=%JE^TTh{&e_ zu<(nbw)@EeY)ons$MAP1H+l4xFHWL)b1OJdM?+>h$?#QZ2)Pc?0^+zxns_<_(5B5t z=*5=~(=*Stv!{)WCg-Qi&odW+VkC$*_{7iep8IvTy;rNd z{RyTTqIU*>A&xO~MvmV7o{99>uXi+@jO`o6rJE`fj6*!sIdoFV}TbN^O3lqA}c5GX1JyyA%` zof3;;i^B#33d%dUWGWHFN<7-JWDO>-u#ZfONqS zsMyaR#QU)g(`3n#3Im99&Z&&z8bb+)$Sp0NC+!ex`>Z-Ui?nTfH?3LAKzJqug6WcA z%9>GCeh3WZSs?g!dt}_Okk0mXkD)DsKiG~t0Lcdb1)t!~U2oX1LECEMVjV@!91#^i znB=7LPC0%vo)yV~%-;RFy1E<(?s6R2h6z=}7g59JRo#>+7frkP%Aeg!!!9u^4%7ca zb1FT`9!+#f=Q+)&sBsbRL+kFB@I=|MB=KQ09Px= zA$Dx%c*~{&wYS;ykFFr}n!&_l{&3pt9L?nb-`VF>7!Cn*Ph8DIeO+!$LdxYxzSUYb zi+=rh8$G|~uo?CWt?c=p1we>S7Yvxw$1_88!~L-gKQLZ+?l0c5XXpQH;`S@16P7-X zP2|~OR6vXZK|5GHcDztpSx`SsA!ZZ~M1=zg0(dxtHH8_i3suCvUm3hnWPm_Z6k1!K zZSU(l_(Emn7jI}%1vn&4Kt}|dx4@EsfHn(PyD8hJ~bgn zOP5ZJtjlB76s5>`DKJUC6dLx_sX1zH&e6HAsZ1m@%5iaQB|DCCEY~#9chbk(R${hs zJ^K&z(bvAd&6u8^kBnf9WJ_xUmEa4W5{F1+jnha-U;fbZyLbF>7jp8rO$N*%iiluC zOQ7O7TQy|!pp2lymtqBJi8uw;DKm=FIZ#L;2a`w0oYzJ6_rePWTdm0JLg(R)ziyg5 z+@K&z7bjHKaW84jhHRKW?6#Q^6+K*FO5q>?Xmm^<*`0ipodr~QMVem>-f)Y^86Q2+dEDPV*3V0w(t?8n6!7f#mo9}nmTb998Jn$a28VJKg35I0DQ6HjBHJ>f z(emZXy{Dgk+GHD|h=El}#V+W6evTVny8XV3ulVuXht~_$?`q{biQM?FQ63?|Hcd_i zp*dxb3nG8Kil)Ui?pa?YfZ@tOf4jg)e|I2&BtIW~LrVX4)^=9LYu52${*ujWDkjU> zknti_IBwOLC&ZbhGS=ec)q2{yYvpRP@d+W>=(uOjoEc!q1ACpc%(a40!(DgXg-vNN z*q{{=ooaE1)TWP(G~C*}0~pFW>emodk(9 zYQq;7qQZ`(H<9o#=m!r8NMUS8JMPE~Z&Whvq5pRh$2i-=f3N+2JAF+9U29^Y>{_-qG$)y8jv!u z;!$-1)(M^JMzj-yh~!I9HEt1u?c2ATW5D21C=Ebhn~i-HWR_Q6`uualKN{~?C8_SQ zN;IG#PGpM)Dg`=DcW^-v1U9&(qjk|lbOZDKD5#YJ#!yRtu!h&*0-_+-7z8Ph#P=4* zA;J_GiI0Tm4B36syo4jJtbT7tJR(GX00Q#%%}Sf&7otts2dhsJg&+V#F- zo!}JdZ8uR$Y%!Qm80U&iL_`KbvMr~Gz_IRyPjR@gaD+yam;iR%GJ{l_MM2huIEN}; zY$MBuOFo~@0kXg-#2Uw>U8gw4i2x&uN0|2$IO7~rxk>qgbA72X5bnJt$D~-GmI&VJ^Tqa~uXBnRRT zn`Nj^JMA=6`Ua}X0t0BxLtC`8JlD~Ac+>qfN+fbjk+mcFIkX%s#0@2H2gyjDPu}Ta zT_D(iR(Na>W{ukH+kC;zRf@-pr-}FR03(xFTdgE%-GxFjNzY^j_l0Y7&3$23tK6Hb z>GF~VTZpwGuK(bnLRe@{yjI~8q|qWrj%>aEz}~xefnf8W$gnV>0eD7XIE;Hws+T$enF#5 zRMgiglL8WtB#R@sXz;R@?eK3jt{~oDaC>7XTU5-Dh_vbxBHn302_VD-5W#sD!R<@(}#%-j1C@C&$KeYJ8kS z(+Ps1)JSZVERCZ{y2MT7l9j)u+5tq|TG(E)WQm!V*aE0nS65eH=AXyK;W{^M_~#9M zeJyKf)Je!GUftY72M^}$SPZAfltz>ShiNQ*$#Dd(`4sokI$=hofsxhgXGz-ZaSvt@Vk&OPAVE&ObrdCtsxLZeALh#cwM^1%K*_wIz*gf=H_ z4<6CUlP6<9oau&OI>B>l%kH~;LJUK4{x)*7Oqz&W0AW)01|>oe3;~hhA1|^OstD4} zUMLfvPt!?T#a`gKE~l+)3a1 zmo2n)Teodop&1gTeh1mW$kG5hgM*|^Cf8jWlIj?=`}XzGSO4KvTF2K^u-B27L05h8 z_4hg87L8}js>pLiW&jr@;t^z1I%rhLhV@_mF93qVfL(#xpWzcQn~g|HsOz=4CW0M3M)K+_;db+DF&CuAXkVHd)jxk!Pf1u1QkxfHyb2dW3%QzyV&H ziq`ao*H5KeZklR_y87;z4PVkaoVlr0$XdOlW!^8CklchZwjx0wrE za7HcNaNQJM*F|X~h96M>^p(J#Uxt$2|KLG-_>p!#zKVPmj){m%n4f%9KubLO&=p_g z=h)4jr6cV8>1F_6+(fU8oj_imBmly5N|YEwp_~|nS@#=cr z^PU}eWI(~-sI~)-P~4zy+RR(#z4n4TMuo;OpwN<0i~3OoZVv%h0iRYi4%MS3V^B)4X{Z( zr6@ye9Tze7efIA*n(82A5?eyjF@JsygMJE~0dQCkQi3>Z2N*#JhzEb!%GX=PIFJ+% zH(+=2<+-Qd_ecA7-@OIdu#yqWycXH8ecUGd5Ww5Hb7#PTyTF?JCX$V5>+pRQjr;HK zjy2m^Kk6K4|BR3&70EsRK~s|x;9wKV=O{NoQGs-F1_c07W0$(iuYTZz6C0NN7M)}X z^KvXOD6pgU`nc`nsbP|$+kSV38MPO=A04mNtDC#nq}Xr3VA37@oQ;lq(YbE8eiGeu z!_+YSyCMWePJrQ4R8=I@7z0lO5T|eGuZX}W0w)FC~R`B&P7!p~rijF6nc&MeT zYs&+_df&0MqNY0+0B<+b?c4 zN@>J@biou&o;9D4V=)YeuYWe4G##pF_nNf8c@t*k22Xa4eZ))u)K z7^d`2s4a4y>Nl~8fX}hkqL!CVFG=v9Amjl6$$^$W`sTmvTv(4pUz*89*h6kmiUI*{~;95c_)mnGd~Z@9v*%v!HNPFv=pE4j2m&kKlgSmvbM3Fn>A_KyZY?Quy6EwIC<;1X=OVa4l|HkruL z7)R9w#?YngpZl3AT7 zHbelD2?(=qfa_2$LzlH%fEgMCNmd}+97Xg{T9eHvpG_ul&q7;NS#41Z5`N{i55B*l z;hbA()JXVD%tKS!ami5hbYvceq-`BGVCjLhEt(*ucdDuibn{Kkbm94vY`IS5ln@hbQ*x(ZIIH6-kSxkI~&3Yl%S+KURGkYHR0qgD90kxcS0s7~Qq?^-G&CzVb(Rj0Q7GM9isJ z;_a}fwT*^U3Ga)TDIqWd1_l{ZDo@v5)5z}LDbX~-1{C;;1&6!=iCKvI*LYOzj&OWO zTxaR0_w1uzKi+QLr4aNL2A)DIEOTMdXecO&L-~Bp`BC$<`VzVe7Oyha6kG@npiS2LC zlGU7PtcrW^2OnyqFMeecy|S?@6<>%G3b8LrDT$)xL3oUlI<>waw^yq{TLYjx^?UPIdkTy7hZV5XWLIfTYz-)wlSmJNH@%=%dh_Vdnb-6 zGwMXe_QFxQ9qS6!g=m?7dKI1j+DUW)n^YmeCC5Z_&k}i5sjIt4yLO|Xv<(>gVfDS( zeJUi`qtRsY425*GzjXKGkKAwvZ+6>2T{p}oUc>zg)5)Y1A@2?yG6*)C^u}zGJSXAA zLIvd918It*K1E%kB00pSewgzhZh?~hZj_rFKlnu@yih3L&!0*9M^$6v(&h^;`_Z>X zlNoiQk_-+e%_*#v1xh7*cNd;fOEWlD0770V>j^FjqihMPN2P`S|aApriH0gD{)e90#8;pkQw&qSk#iH8lkgY?Q;q zh)@d%2*41ysT^q+Y$-rM23wvt^4`hc64jG3II!Um%tQPFHr@L5>x1y-iYPGxGUW5h zXMpr#@{HEjwXJKO`Ov3dbKyV#(`X3l4TXr=5f%jtdnjeSIwlTc%1RcE+4R!-9$vZ) zNk~tO7{&*r_s@mJ0^rlt;W{oi6Tgzt!~;%L5TslC{9pX#k&g8ojwDp4#Q-9Bp88Zc z!V{z#u?ge##CM#UPEjVT%duT&(LjKJ3>FkB<%Nmr-`=}-)sB_}zyAJ|saL;~#)wGcjAIyK^X$+6G@;@03z;T zH30c9S7|-;%=e#o^w#fkTR}=eH-n63`YP+_rp;l*1&~6`RE= z29l$Vr-&q#_7S;hMGz=~dlsH3L=!Q|%aSEaOz~xmwU{(%QXYO~73RQW!C)k)!`@#$ zaLu=Sd-lIbV?^S#Z|m)`UM;D=8s|^tB2Sg)hu$P5o-0-19P32VRqPQPYkCbPw?KT52OvQ@&Itb`7I6S0(ur@H-gZEYNO;jRb5SjuWDOQVygf}&vGx_2x@BYSE zBHr1OjE7{!mSPhu{g_W)1-{`H5bNik{otqe@A<`c{Bs|zJt*g$K!?H`Q(an3LA*H5C!RhHvj}^ z)v8s5n_zW^A%n$Pvt}9A#Kaqf37!S0NAv*bej|Kg}Ck27qd9O4|8p`@b( z;l`5SU2k0f33`qk4w~+a_F_xRXQKonr1H;^uEmDPS#y;y}>t*w**8-$>+vfj&g)4y$?(DmAjcm&zVHDu<@nT1`ucESA$Zfc5~p*_j8zqW!n zIRy#i)KSlJELs9`OrTI1Iq0bXiMcpjC=iS~p3oL{0zLo(H&nD`4WTKF*5C)x2hvSp zPtmjG)qj6w&#smKhsI@0%idipzp-u0cQ%T}fEX5ckVxa`VG`ZP4c~xa!GZ-Qonz6W zMVNQOc$@HjYc_>he-4+B4dv7^8xix#uH_;kdk(oK)(c?J3^NLZh&6_0)Wf%I%qZkW zfjmJ);-T;{1`vFnUZe=GUHyTd@7Zc%v{) z7_WVJqgq;8kSA5aq`QiHiAp}E+9BTXN>!bI@wac7G3%BO(zrNsv`$YA;+Vl77sf+| z&gBIwVD;1Q1t1WMfThsGog}twoVK^nW zkr_N+J?Q!87Pi2 z=qBO_j{$BFxlahdAel>uHv%-QCVTnDp-Rnzf=3DuZQGXr+^}x#XFmhi9gRy_3f>(Y zD4RF^yKsmea0t9-fC2BCJxwsBivkD~M~2CSg5DTS2MvKBoseoP5uea{Xd*brr70jT z>DH-QQC3qZ=i-f?=9>aQbZ2L0z^+;~ZQ3-2o5ul6yfNP~u_*<&gJNJv?mJSsRqnf-fyz8fju3bmRvMSGklx;XFa7#gZ~Vxfou=+9G=vGx zS!+PNO?i$*z?8z2aTq*giX{UO7)d8%uB~VX4YI>@y2s;b%90Z{pVAj%*O4ZZ5qjdP zg$l%t+G15^4R;$%Ka63B`MHUW$PI1PJ8F_d1A$Tn@3_)TN=v*PNLL@jRIZjS? z#R;2GLl!O4sj@iEe%WWdQP@Trh+Uh~o7^+_%sD{3J@!cDm{h?dmE)xnS+%@W*`=C$ zk4g+uLdNYymw)$`DO0X~%QyiaT>?UVVE>~(c;<;eyqheUhIU}i(8uq#CvWW~xZ{z& z!S5Xz4w&-<^}+(oeS*0q5d5Vu9XtXB*0yAg&LY!80gNFHq3VoLdMa73Q;MmX{3y)0 z3Rf*Ef#7pg17Y=v#6h@f0SZ(cG%4r&3@aHBRm?Z4_)xO|FjJ>qJ9GI(cQDVWn?mDq z(xq5Da^Qt$Kk(VTyYJrx{=kAHz8*U+qStzx`fSEDVpWCd)2A0OT^!75N%VuK2@V>Z zQ@~(TIg%Nb#~HOANQT7w1koji`nf93oiIb@(8JBcgu*_|&y7G0^5yZcz%j74X&_Lu zk_KSB7s!#>zyHCV|NF=rKibjy!hJL@Cso?p*4+EVuWtTG%l?OVfCC~3%i6#s_6Z>1 zVYKEkxMUuHU=RetAzVO!P)qG5>&=Wt*V#6v)Bu^=$16jwDKb0(aY0%g6;_J^d-fVI zL7&kWaMyD00!`r|s}>rAzvuI3NIS0*a|&7mYZP_HK0Zg~@(b>`X6p3oZX5SbjjX`C zwE5M4{no~HU&AClX8Z%vb`XeTlL;M}#3Rg@8kw68RsG{J<$Djx>ZJaHN467oLg5K%)gHruUU=FGIblh=@Qj(?4+?#Mh z@@7DikSL^oV2n2i7akJ#8kh$;Iv?T~7?+DoN#+$uMaOPaDiIS=;yVBiyKQ&!@{NT{ zKKjV~1@C_UIM)~+0e9HE>0j@7`Nglizzy3JJmz;GBjqH?(?qD1ow*bX(!g!TE4+{y%SNY&!p@aSzqdhz{~yt%shu zckK(GzK68~s%;d&9ZuGob~D3Ce)9cR3{>B?96!Kxexz&KGU#H976WtQ{?pF-&)8Wq8kSB?BC;D=on_ z_`!JK7uc5KYRInLwr!h|%7Y-_0EOils9RgX$BNc4+JXU5VM#c&NDhBDH?Nq!`0T%W z^Efz;O?rCvu6t?qUw&)fz6bV_ErBZB0dqxI?I4;#wlO>)KWEMylePiV7wfrm=VDe} zotkOLa0vN5r*wY5an@=OC2CGoMhp}sQbMAtAQ*=s+5#dP1LPaWu&PB)lqrJ1BwX#= zw@+dj&=>$og$0KIL%dSZGF8^3!jhXd?Ye1;&tc6manV&YE+qwE*tX@)AHMp^KftSG zW+8=Xgo$|IPDSGfk+$9?Gi5K<7(zOG_H0baV9X`Fd(63xNHvoC$Qgq}*25%~I3zs? zl0~RR#snxR)(%-?FcR8AW?@CnJiq~sfg%X}uoYZ)FsFoPz?|afrzx(+cB+YjW7}#C z0|ad65J6(Vu*QICWtM3(ZbbI2160%k{xBKF}o1ID>Ky=_<}no0>3G-h;&YqT#`PH;sYll zBLWJD?q5)?A2`x;UtkRn705S$fxi*UU|?XXVnf0up21-7EZ0ChBgeo2FbuZ(vZ3E3QZPpvlnKA3eH=U~9Dk0EoUij2~U57VfR(Uf=wQ}lo*=;E}VSs?K z_LyEvyi0h;Al)-&%m`XrTg?zsbYyV3;&)PBrf7ca^yQEfsdf_nXe4CBKp~P9`L^UK zVLM9kX33Wlb4qvy_dZH$O5vOU2lqahsnw*1put;hk>g$s`34oJg3hPF^kX?o#w#rM zFq#J7dd+LUbm64Qmt1fvXb%9Px9{NA9b3QmKO0~E`it-iQRq#I06?L3fL6e^m`Ral zz=HAi_$~?zG&eUFQ5s(WA=&|XOc=8d4Z&7GKmK5@Cs}ziG2;+q%xMZ&T;ylCY!i`h z{3o@fY%GJ^>gGru!}yEs+qWC70gZvi4N$;!%Vf{9u3Lbkg1=|L0L_8@AmjYEZ?%dn z{*-CgPMgi|E7!q)Wj<&VWyz zd)7#SA^Lqf2{J$o!!6vb(q1V%P^myD5%2{I7MOSjt7mW7vSmu5;5au(xLr&`he^dR z2ATvODUV;Um{s9~(IF8RfS7IUpEm8NrbVvJ3 z>t5aPPtSC9Z8^;Mj^G&@zR5uXDk#(nn9&w72jf8kBDi52u7H_HEpISEIJ_bYh-hX~ z)cCT&S4r70m*AR9!XH$sF0snUc0FZ4;YtRHN{qA3BH{2>q|GS+gY8%a*RAWJg681w zdrZQ822unLUYnE=vfw~9bPJH2Xb~JErWHBU8*#M!tonv?PH&jB?EI?A$@4}VNW?6H zHfV2OyJho+e_W3#5v)eQb2gd*aRxi`F@R=p%p^2^jE^>zzBy)8#9R=FIbZ+~#=c;R zXnYT886eY=;fmjv8HEskNRw$II$93Xh;fHfGURC<$0BZTpyO&_{RW@|5Kylc80K>X zfNVi$Io_1lV>=cBAq0sq)YYViuq#kAcmf+q1t?$&7#E|BbD$yk{BnK19&ihbk9;y%MpLxu^f({KlpW=GE5Cn^#>sb8b~-^W2dFN(g|KZQWhF zw;egWd22`e^INuV{*NuVSER!rRfa+j5`cifm}&GdktpO3#!P}X;QNi%DopA#ngIVytE}_9 z>RQjMto6OB+EU;t6gpet?ZXH1`L>oq{zzAE?}3(lU+0n5)~B|&wfts(XXmS(6gGNy z0pLwTg~?+ZZNct`d1Oo@3Oz_>pMV9&BL%~fXz+!ApPMNrz$7|gK$vk4xGo9A*8JyBsjW3PLJa81k(7;Hq0Vv+|UO0I5CS%o)P9aw_E|`OY=4j!+cKw!E0!eBJ(bj zw8zg<0}O16B3@)BYtPaSAktzgS%*lX;zq{g?g?u^9mCXHBO#*#6e2Fa$l9jV*bH~h zJ^W{TCllAe-;#=sZ4hx(55cws{;;EV8v;b50SEv9?|?fO4X+aNm2gh$W+id2B2~kk zBO*OYE^Q{2vJOr*Aj~z;D3gJ-h5T)gi?oLOeR!#`mi#G*c&S*$oA?I6!7o=?7p`ax zv$o&!xq$#EI0gxa0tf>X41$80g|riHfWG}1V@^Sffau%51rWY%+~(RuD_9Ld)*MTK z0elFWPU>XwI@19Cuz-mECJPFAlTrW zNzIUGw0M+|qQTD*MA}@7ov+&DE!p>wNY!u%bLMb@_?4erb15xDHXL=RL}icMON`6a zaq+7}A{hj!$WY@T;_!=OK@=5go_jarH9^XO^_G~R6TB{yCr`#2>CFg6VV#FR9>ZU4 z8OLr6rDW+r7AOOvi_a-jPelvM5pJ-U1RYB!wt4B~hTO%McZ3@kroQd6e`Ael2e7Y` zKRd$Lgmsr)%s#Af3@_$9IF_%~{EnUes!M*~R?ILHo?yXjqFDHNGuH#|f$zmk%x>p= zfY+%Hg~(u5afe|N_@Eg82oP&AmH0Ut@Yt+YDT8mGziTeJ??-2sQ`hc$98G|Lj5}X!C|-s(%pI6({V{rqbaP1%LEz8F`*zR5z37+x^0R(sz8pj1F0~6 z9mfauzKsb)w$lJX2Jq4AGnh4Bf03`vG(LEaCQO)MdVhS5@53~*e;Y17kE2gc91XpU% z*w{!I*8p=0g(Ac}!tbGTz%ziEBm;e15-r!|mYPXbg3L_!PQ9a1XD+2>)R|ANNKYo) zTI*7D1aTJYlV5@pT|?1K*-FIuJvK=k#AC!PqzOt5n^8xzYe7!Lqb%9t43Kumc@ zYko+DY*W*S8@@45?6g#ta(4`?@i;750nCgTL{) zV+LW~hTe{j4rBh{Z#zeMH~)6;^y$+vr$Zlm*${LCSn}Kfs>rfqgHoHW|<)8qI)!jA~7hmJ-?_lfRHW!`8AA*#dbG(RAtGrXbtl>_r^veIM*y%gV^j+7<-Pt zVG40?p14Nr6A;<~gc<9?Lw1Vdf<_0`WXk-#g5O6@)EK%Gr&3-1Pm}Lq?H$;;Wiaa3X_`t2{xGV!Z*||xMV>1QYCIE)GfDG1$mkWP$Lq_U^ z$t-2F#U$S$;tVqf+YH7&93%TtbQlZb3pj^0e+n>%VA|Qmj9&m?`I^l2!3@L#(})+u zBLV;d8o^GrYrJ!~SEyaHCG*nWSn!=S* zCRG+mcVizAH!>~rq=pms&pgpig!?ClyV$6Uc;qccZhy`LQ zWF6a>v=BWr)+hlAGe~-DZq%5&4f`|}oQJ_@=5xb>-W@$Y*3dvg^x1SX`>~H~iU+h6 z?>BXm_*?}si=fc}0x2wp#nlFO(i3wI(UJfIG=rPPYM*UXdr5YsI-8R3pj-g~IjLE) zLnWnvq2df&w`WVc3!~nUEIuUW;g}EFP91xPe}S0=kN5s zXo3(Cm|WHy1G6I#En+o7MBdRzt@)*8&Ob3@u$D9we2(MgGugI7H=~paOa223goqf6 zd53MsQxw1)vJ)i+&hXDPXNc~TGM=CN%u12wu?C1!PIMfRR_@?LmzVn$R2>RS}n??3xyVPgyerIjYFTdmbhLUT@y6&v~S@)WLRvF(Lm$4`V zynJ%^(*O;I9O6k1V8{Z+{hr%s70;f6<#(RO3lxh$q;vLaGN+l|$ zh`j6RiPnzCE0e%F$5U#%-(|a>se#K)CbCw?IYECJvqbxNMF?g(YA%um8m9p#_BrQK z>9swEDAky$ze%Q@?dTd)*G%b4Ds`;Z;}|%a{6E`=QEPmdG|vD4002ovPDHLkV1ky? Byix!F literal 0 HcmV?d00001 diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/client/__tests__/index.test.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/client/__tests__/index.test.tsx new file mode 100644 index 00000000..894ddf3a --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/client/__tests__/index.test.tsx @@ -0,0 +1,148 @@ +/* + * 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. + */ + +import { expect, describe, test, vi } from 'vitest'; +import { Layout } from '@coze-studio/open-chat/types'; + +import { type CozeChatOptions } from '@/types/client'; + +import { AuthClient } from '../auth'; +import type * as ClientModule from '..'; + +vi.hoisted(() => { + // @ts-expect-error -- 将 IS_OVERSEA 提升到最外层 + global.IS_OVERSEA = false; +}); + +vi.mock('@/components/widget', () => ({ + default: vi.fn(), +})); + +const testBotId = '7313780910216806444'; + +const config = { + config: { + botId: testBotId, + }, + auth: { + type: 'token', + onRefreshToken: () => Promise.resolve('test'), + token: 'Test', + }, + componentProps: { + title: '历史学教授', + }, +}; + +const config2: CozeChatOptions = { + config: { + bot_id: testBotId, + }, + auth: { + // @ts-expect-error -- 测试兼容逻辑 + type: 'token', + onRefreshToken: () => Promise.resolve('test'), + token: 'Test', + }, + componentProps: { + title: '历史学教授', + }, +}; +const config3: CozeChatOptions = { + config: { + bot_id: testBotId, + }, + auth: { + onRefreshToken: () => Promise.resolve('test'), + token: 'Test', + }, + componentProps: { + title: '历史学教授', + }, +}; + +const config4: CozeChatOptions = { + config: { + bot_id: testBotId, + }, + auth: { + // @ts-expect-error -- 测试兼容逻辑 + type: 'token', + onRefreshToken: () => Promise.resolve(''), + token: '', + }, + componentProps: { + title: '历史学教授', + }, +}; + +describe('client', async () => { + const { WebChatClient } = await vi.importActual('..'); + + test('client list', () => { + const client1 = new WebChatClient(config); + const client2 = new WebChatClient({ + ...config, + el: document.createElement('div'), + }); + + client1.destroy(); + + expect(WebChatClient.clients.length).toBe(1); + expect(!WebChatClient.clients.includes(client1)).toBe(true); + + client2.destroy(); + + expect(WebChatClient.clients.length).toBe(0); + expect(!WebChatClient.clients.includes(client2)).toBe(true); + }); + + test('client mount', () => { + const client = new WebChatClient(config2); + const client2 = new WebChatClient({ + ...config2, + el: document.createElement('div'), + }); + + // @ts-expect-error -- ut + expect(!!client.defaultRoot).toBe(true); + + // @ts-expect-error -- ut + expect(!!client2.defaultRoot).toBe(false); + }); + + test('init layout mobile', () => { + vi.mock('react-device-detect', () => ({ isMobileOnly: true })); + const client = new WebChatClient(config2); + + expect(client.options?.ui?.base?.layout).toBe(Layout.MOBILE); + }); + + test('init aut', async () => { + const auth2 = new AuthClient(config2); + + expect(await auth2.initToken()).toBe(true); + expect(auth2.checkOptions()).toBe(true); + + const auth3 = new AuthClient(config3); + expect(await auth3.initToken()).toBe(true); + expect(auth3.checkOptions()).toBe(false); + + const auth4 = new AuthClient(config4); + expect(await auth4.initToken()).toBe(false); + expect(auth4.checkOptions()).toBe(true); + }); +}); diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/client/auth.ts b/frontend/packages/studio/open-platform/chat-app-sdk/src/client/auth.ts new file mode 100644 index 00000000..dad93395 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/client/auth.ts @@ -0,0 +1,74 @@ +/* + * 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. + */ + +import { AuthType } from '@coze-studio/open-chat/types'; + +import { type CozeChatOptions } from '@/types/client'; + +export class AuthClient { + readonly options: CozeChatOptions; + public constructor(options: CozeChatOptions) { + this.options = options; + } + public async initToken() { + try { + if ( + this.options.auth?.type === AuthType.TOKEN && + !this.options.auth?.token + ) { + const token = await this.options.auth?.onRefreshToken?.(''); + this.options.auth.token = token; + if (!token) { + alert( + 'The access token is missing. Please check the configuration information.', + ); + } + return !!token; + } + } catch (_) { + console.error('[WebSdk Error] initToken error'); + alert( + 'The access token is missing. Please check the configuration information.', + ); + + return false; + } + return true; + } + + public checkOptions() { + if (this.options.auth?.type !== AuthType.TOKEN) { + console.error("Non-Token is unsupported; auth's type must be token"); + alert( + "The auth type (unauth) is unsupported yet; auth's type must be token", + ); + return false; + } + if (this.options.auth?.type === AuthType.TOKEN) { + if (!this.options.auth.onRefreshToken) { + console.error('[WebSdk Error] onRefreshToken must be provided'); + alert('onRefreshToken must be provided'); + return false; + } + if (typeof this.options.auth.onRefreshToken !== 'function') { + console.error('[WebSdk Error] onRefreshToken must be a function'); + alert('onRefreshToken must be a function'); + return false; + } + } + return true; + } +} diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/client/index.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/client/index.tsx new file mode 100644 index 00000000..543d0c7b --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/client/index.tsx @@ -0,0 +1,142 @@ +/* + * 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. + */ + +/* eslint-disable complexity */ +import { isMobileOnly } from 'react-device-detect'; + +import { createRoot } from 'react-dom/client'; +import { nanoid } from 'nanoid'; +import { Language, Layout, AuthType } from '@coze-studio/open-chat/types'; + +import { type CozeChatOptions } from '@/types/client'; +import { createGlobalStore, type ClientStore } from '@/store/global'; +import CozeClientWidget from '@/components/widget'; +import '@coze-common/assets/style/index.less'; +import './main.less'; + +import { AuthClient } from './auth'; +const formatOptions = (optionsRaw: CozeChatOptions) => { + const options: CozeChatOptions = optionsRaw; + const layoutDefault = isMobileOnly ? Layout.MOBILE : Layout.PC; + options.config = options.config || {}; + options.config.botId = + options.config.botInfo?.botId || + options.config.botId || + options.config.bot_id || + ''; + options.ui = optionsRaw.ui || {}; + + // 小助手 ui基础配置 + options.ui.base = Object.assign( + { + layout: optionsRaw.componentProps?.layout || layoutDefault, + lang: optionsRaw.componentProps?.lang || Language.EN, + zIndex: optionsRaw.componentProps?.zIndex, + icon: optionsRaw.componentProps?.icon, + }, + optionsRaw.ui?.base || {}, + ); + + // chatBot 配置格式化 + options.ui.chatBot = Object.assign( + { + title: optionsRaw.componentProps?.title, + width: optionsRaw.componentProps?.width, + uploadable: optionsRaw.componentProps?.uploadable ?? true, + }, + optionsRaw.ui?.chatBot || {}, + ); + + options.ui.asstBtn = Object.assign( + { + isNeed: true, + }, + options.ui.asstBtn || {}, + ); + options.ui.header = Object.assign( + { + isShow: true, + isNeedClose: true, + }, + options.ui.header || {}, + ); + + return options; +}; +export class WebChatClient { + static clients: WebChatClient[] = []; + private root: ReturnType | undefined; + private readonly defaultRoot?: HTMLDivElement; + private readonly globalStore: ClientStore; + readonly authClient: AuthClient; + readonly chatClientId = nanoid(); + readonly options: CozeChatOptions; + readonly senderName: string; + + public constructor(options: CozeChatOptions) { + console.info('WebChatClient constructorxxx', options); + this.senderName = `chat-app-sdk-${Date.now()}`; + this.options = formatOptions(options); + this.authClient = new AuthClient(options); + + const { el } = this.options; + + this.globalStore = createGlobalStore(this); + if (!this.authClient.checkOptions()) { + return; + } + let renderEl: HTMLElement; + if (!el) { + this.defaultRoot = document.createElement('div'); + document.body.appendChild(this.defaultRoot); + renderEl = this.defaultRoot; + } else { + renderEl = el; + } + + this.root = createRoot(renderEl); + this.root.render( + , + ); + + WebChatClient.clients.push(this); + } + + public showChatBot() { + this.globalStore.getState().setChatVisible(true); + } + public hideChatBot() { + this.globalStore.getState().setChatVisible(false); + } + public async getToken() { + if (this.options.auth?.type === AuthType.TOKEN) { + return await this.options.auth?.onRefreshToken?.(''); + } + } + + public destroy() { + this.root?.unmount(); + if (this.defaultRoot) { + this.defaultRoot.remove(); + } + + WebChatClient.clients = WebChatClient.clients.filter(c => c !== this); + } +} diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/client/main.less b/frontend/packages/studio/open-platform/chat-app-sdk/src/client/main.less new file mode 100644 index 00000000..1a5bb302 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/client/main.less @@ -0,0 +1,29 @@ +.coze-chat-sdk { + * { + box-sizing: border-box; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + textarea { + padding: 0; + } + + a:focus, + input:focus, + p:focus, + svg:focus, + li:focus, + div:focus, + textarea:focus, + a:focus-visible, + input:focus-visible, + p:focus-visible, + li:focus-visible, + div:focus-visible { + box-shadow: none; + outline: none; + border: none; + } +} diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/components/icons/close.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/icons/close.tsx new file mode 100644 index 00000000..7556c9df --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/icons/close.tsx @@ -0,0 +1,44 @@ +/* + * 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. + */ + +import cls from 'classnames'; + +import styles from './index.module.less'; + +export const Close = ({ + classNames, + onClick, + themeType = 'dark', +}: { + classNames?: string; + onClick: () => void; + themeType?: 'dark' | 'light'; +}) => ( +
+ + + +
+); diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/components/icons/index.module.less b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/icons/index.module.less new file mode 100644 index 00000000..998fb21c --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/icons/index.module.less @@ -0,0 +1,38 @@ + +.close { + display: flex; + align-items: center; + justify-content: center; + height: 24px; + width: 24px; + color: rgba(28,31,35, 80%); + border-radius: 6px; + cursor: pointer; + + &:hover { + background: #EEE; + } + + &.light { + color: #FFF; + + &:hover{ + background: none; + } + } +} + +@keyframes animation-rotate { + 0% { + transform: rotate(0); + } + + 100% { + transform: rotate(1turn); + } +} + +.spin { + animation: animation-rotate .6s linear infinite; + animation-fill-mode: forwards; +} diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/components/icons/spin.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/icons/spin.tsx new file mode 100644 index 00000000..68379637 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/icons/spin.tsx @@ -0,0 +1,72 @@ +/* + * 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. + */ + +import cls from 'classnames'; + +import styles from './index.module.less'; + +export const Spin = ({ classNames }: { classNames?: string }) => ( +
+ +
+); diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/ast-btn.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/ast-btn.tsx new file mode 100644 index 00000000..2dd47d58 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/ast-btn.tsx @@ -0,0 +1,58 @@ +/* + * 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. + */ + +import { type FC } from 'react'; + +import cls from 'classnames'; +import { Layout } from '@coze-studio/open-chat/types'; + +import { getCssVars } from '@/util/style'; +import { type AstBtnProps } from '@/types/chat'; +import { useGlobalStore } from '@/store/context'; +import WidgetPng from '@/assets/widget.png'; + +import styles from './index.module.less'; + +export const AstBtn: FC = ({ position = 'fixed', client }) => { + const { chatVisible, setChatVisible, layout } = useGlobalStore(s => ({ + chatVisible: s.chatVisible, + setChatVisible: s.setChatVisible, + layout: s.layout, + })); + + const { base: baseConf, asstBtn: asstBtnConf } = client?.options?.ui || {}; + const iconUrl = baseConf?.icon; + const zIndex = baseConf?.zIndex; + const zIndexStyle = getCssVars({ zIndex }); + if (chatVisible || !asstBtnConf?.isNeed) { + return null; + } + + return ( +
{ + e.stopPropagation(); + setChatVisible(true); + }} + > + logo +
+ ); +}; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/chat-content.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/chat-content.tsx new file mode 100644 index 00000000..d2a241fd --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/chat-content.tsx @@ -0,0 +1,113 @@ +/* + * 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. + */ + +import { createPortal } from 'react-dom'; +import { useState, type FC, useEffect } from 'react'; + +import { useShallow } from 'zustand/react/shallow'; +import cls from 'classnames'; +import { Layout } from '@coze-studio/open-chat/types'; + +import { getCssVars } from '@/util/style'; +import { type ChatContentProps } from '@/types/chat'; +import { useGlobalStore } from '@/store/context'; + +import { Close } from '../icons/close'; +import { ChatNonIframe } from './chat-non-iframe'; + +import styles from './index.module.less'; + +const ChatSlot: FC< + ChatContentProps & { isNewCreated: boolean } + // eslint-disable-next-line complexity +> = ({ client, isNewCreated }) => { + const { chatVisible, setChatVisible, layout, themeType } = + useGlobalStore( + useShallow(s => ({ + layout: s.layout, + setIframe: s.setIframe, + senderName: s.senderName, + chatVisible: s.chatVisible, + setChatVisible: s.setChatVisible, + themeType: s.themeType, + })), + ); + const { + base: baseConf, + chatBot: chatBotConf, + header: headerConf, + } = client?.options?.ui || {}; + + const zIndex = baseConf?.zIndex; + const zIndexStyle = getCssVars({ zIndex }); + const width = + layout === Layout.MOBILE ? undefined : chatBotConf?.width || 460; + if (!chatVisible) { + // 不显示chat框 + return null; + } + + return ( +
+ {headerConf?.isNeedClose !== false ? ( + { + setChatVisible(false); + }} + classNames={styles.closeBtn} + themeType={themeType === 'bg-theme' ? 'light' : 'dark'} + /> + ) : null} + +
+ ); +}; + +export const ChatContent: FC = ({ client }) => { + const { el } = client?.options?.ui?.chatBot || {}; + const [chatContentEl] = useState(() => { + if (el) { + return el; + } + const elCreated = document.createElement('div'); + document.body.appendChild(elCreated); + return elCreated; + }); + const isNewCreated = chatContentEl !== el; + useEffect( + () => () => { + if (isNewCreated) { + document.body.removeChild(chatContentEl); + } + }, + [el, chatContentEl], + ); + + return createPortal( + , + chatContentEl, + ); +}; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/chat-non-iframe.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/chat-non-iframe.tsx new file mode 100644 index 00000000..dd667094 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/chat-non-iframe.tsx @@ -0,0 +1,77 @@ +/* + * 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. + */ + +import { useCallback, useEffect, useState, type FC } from 'react'; + +import { Language } from '@coze-studio/open-chat/types'; +import { initI18nInstance, I18n } from '@coze-arch/i18n/raw'; +import { I18nProvider } from '@coze-arch/i18n/i18n-provider'; +import { + zhCN, + enUS, + ConfigProvider, + LocaleProvider, +} from '@coze-arch/bot-semi'; + +import { type ChatContentProps } from '@/types/chat'; +import { useGlobalStore } from '@/store'; + +import { NonIframeBot } from './non-iframe-bot'; +import { NonIframeApp } from './non-iframe-app'; + +import styles from './index.module.less'; + +export const ChatNonIframe: FC = ({ client }) => { + const options = client?.options; + const setImagePreview = useGlobalStore(s => s.setImagePreview); + const setIframeLoaded = useGlobalStore(s => s.setIframeLoaded); + const lang = options?.ui?.base?.lang || Language.EN; + const [i18nReady, setI18nReady] = useState(false); + const locale = lang === Language.ZH_CN ? zhCN : enUS; + + const onImageClick = useCallback((extra: { url: string }) => { + setImagePreview(preview => { + preview.url = extra.url; + preview.visible = true; + }); + }, []); + useEffect(() => { + setIframeLoaded(true); + }, []); + + useEffect(() => { + initI18nInstance({ lng: lang }).then(() => setI18nReady(true)); + }, [lang]); + + if (!i18nReady) { + return null; + } + return ( + + + +
+ {options?.config?.type === 'app' ? ( + + ) : ( + + )} +
+
+
+
+ ); +}; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/image-preview.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/image-preview.tsx new file mode 100644 index 00000000..c1bdcd78 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/image-preview.tsx @@ -0,0 +1,85 @@ +/* + * 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. + */ + +import { useEffect, useState } from 'react'; + +import { useShallow } from 'zustand/react/shallow'; +import { ImagePreview as ImagePreviewSemi } from '@coze-arch/bot-semi'; + +import { useGlobalStore } from '@/store'; + +interface PreviewProps { + zIndex: number; + className?: string; +} +export const ImagePreview: React.FC = ({ zIndex, className }) => { + const { imagePreviewUrl, imagePreviewVisible, setImagePreview } = + useGlobalStore( + useShallow(s => ({ + imagePreviewVisible: s.imagePreview.visible, + imagePreviewUrl: s.imagePreview.url, + setImagePreview: s.setImagePreview, + })), + ); + const onVisibleChange = (visible: boolean) => { + setImagePreview(preview => (preview.visible = visible)); + }; + const [imageUrl, setImageUrl] = useState(imagePreviewUrl); + useEffect(() => { + setImageUrl(imagePreviewUrl); + (async () => { + if (imagePreviewUrl?.startsWith('blob:')) { + const base64Url = await revertBlobUrlToBase64(imagePreviewUrl); + if (base64Url) { + setImageUrl(base64Url); + } + } + })(); + }, [imagePreviewUrl]); + return ( + + ); +}; +const revertBlobUrlToBase64 = (blobUrl: string): Promise => + new Promise((resolve, reject) => { + (async () => { + try { + const response = await fetch(blobUrl); + const blob = await response.blob(); + const reader = new FileReader(); + + reader.onloadend = () => { + const base64data = reader.result; + resolve(base64data as string); + }; + + reader.onerror = error => { + console.error('转换过程中出现错误:', error); + resolve(null); + }; + reader.readAsDataURL(blob); + } catch (error) { + console.error('转换过程中出现错误:', error); + resolve(null); + } + })(); + }); diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/index.module.less b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/index.module.less new file mode 100644 index 00000000..6fd80e96 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/index.module.less @@ -0,0 +1,111 @@ + +.coze-ast-btn { + position: fixed; + bottom: 30px; + right: 30px; + display: flex; + justify-content: center; + align-items: center; + height: 56px; + width: 56px; + cursor: pointer; + z-index: var(--coze-z-index-iframe); + + transition: transform 0.3s ease; + + &:hover { + transform: scale(1.16); + } + + &:active { + transform: scale(1.08); + } + + > img { + width: 100%; + height: 100%; + } + + > svg { + width: 100%; + height: 100%; + } + + &.mobile { + bottom: 24px; + right: 24px; + height: 40px; + width: 40px; + + &:active { + transform: scale(1.08); + } + } +} + +.iframe-wrapper { + position: fixed; + z-index: calc(var(--coze-z-index-iframe) - 1); + background-color: #fff; + + &:not(.mobile) { + height: calc(100% - 40px); + min-height: 400px; + max-height: 1200px; + bottom: 20px; + right: 20px; + border-radius: 8px; + box-shadow: 0 6px 8px 0 rgb(29 28 35 / 6%), 0 0 2px 0 rgb(29 28 35 / 18%); + } + + .loading { + height: 100%; + width: 100%; + } + + .close-btn { + position: absolute; + right: 16px; + top: 12px; + width: 32px; + height: 32px; + z-index: 100; + } + + .extra-close { + width: 32px; + height: 32px; + } + + .coze-iframe { + width: 100%; + height: 100%; + background: #fff; + border: none; + border-radius: 8px; + overflow: hidden; + } + + &.mobile { + inset: 0; + + .close-btn { + right: 12px; + top: 24px; + } + + .coze-iframe { + border-radius: 0; + } + } + + &.auto-fix-container { + position: relative; + width: 100%; + height: 100%; + right: 0; + bottom: 0; + min-height: auto; + max-height: auto; + } +} diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/index.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/index.tsx new file mode 100644 index 00000000..2a5abb50 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/index.tsx @@ -0,0 +1,56 @@ +/* + * 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. + */ + +import { type FC } from 'react'; + +import { getCssVars } from '@/util/style'; +import { type CozeWidgetProps, type WidgetAdapterProps } from '@/types/chat'; +import { GlobalStoreProvider } from '@/store/context'; +import { useMessageInteract } from '@/hooks/use-message-interact'; +import { useImagePreview } from '@/hooks/use-image-preview'; + +import { ImagePreview } from './image-preview'; +import { ChatContent } from './chat-content'; +import { AstBtn } from './ast-btn'; + +const IFRAME_INDEX = 2; + +const WidgetAdapter: FC = ({ client, position }) => { + useImagePreview(client); + useMessageInteract(client.chatClientId, client.options); + + const { base: baseConf } = client?.options?.ui || {}; + const zIndex = baseConf?.zIndex; + const zIndexStyle = getCssVars({ zIndex }); + + return ( + <> + + + + + ); +}; + +const CozeClientWidget: FC = props => ( + + + +); + +export default CozeClientWidget; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-app/index.module.less b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-app/index.module.less new file mode 100644 index 00000000..12e08a1f --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-app/index.module.less @@ -0,0 +1,4 @@ +.extra-close { + width: 32px; + height: 32px; +} diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-app/index.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-app/index.tsx new file mode 100644 index 00000000..7be52f90 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-app/index.tsx @@ -0,0 +1,93 @@ +/* + * 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. + */ + +/* eslint-disable complexity */ +import { type FC } from 'react'; + +import { BuilderChat } from '@coze-studio/open-chat'; + +import { type ChatContentProps } from '@/types/chat'; +import { useGlobalStore } from '@/store'; + +import styles from './index.module.less'; + +type IOnImageClick = (extra: { url: string }) => void; + +export const NonIframeApp: FC< + ChatContentProps & { onImageClick: IOnImageClick } +> = ({ client, onImageClick }) => { + const options = client?.options; + const setThemeType = useGlobalStore(s => s.setThemeType); + const isNeedExtra = options?.ui?.header?.isNeedClose ?? true; + const areaUi = { + showInputArea: true, + isDisabled: false, + uploadable: options?.ui?.chatBot?.uploadable, + isNeedClearContext: options?.ui?.chatBot?.isNeedClearContext ?? true, + isNeedClearMessage: false, + isNeedAddNewConversation: + options?.ui?.chatBot?.isNeedAddNewConversation ?? true, + isNeedFunctionCallMessage: + options?.ui?.chatBot?.isNeedFunctionCallMessage ?? true, + isNeedQuote: options?.ui?.chatBot?.isNeedQuote, + feedback: options?.ui?.chatBot?.feedback, + header: { + isShow: true, + title: options?.ui?.chatBot?.title, + icon: options?.ui?.base?.icon, + ...options?.ui?.header, + extra: isNeedExtra ?
: null, + }, + conversations: options?.ui?.conversations, + input: { + isNeedAudio: options?.ui?.chatBot?.isNeedAudio, + }, + footer: options?.ui?.footer, + }; + return ( + + ); +}; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-bot/index.module.less b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-bot/index.module.less new file mode 100644 index 00000000..47eced39 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-bot/index.module.less @@ -0,0 +1,8 @@ +.chat-app-wrapper { + height: 100%; +} + +.extra-close { + width: 32px; + height: 32px; +} diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-bot/index.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-bot/index.tsx new file mode 100644 index 00000000..2122a467 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/components/widget/non-iframe-bot/index.tsx @@ -0,0 +1,68 @@ +/* + * 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. + */ + +import React, { type FC } from 'react'; + +import { WebSdkChat } from '@coze-studio/open-chat'; + +import { getChatConfig } from '@/util/get-chat-config'; +import { type ChatContentProps } from '@/types/chat'; +import { useGlobalStore } from '@/store'; + +import styles from './index.module.less'; + +type IOnImageClick = (extra: { url: string }) => void; + +export const NonIframeBot: FC< + ChatContentProps & { onImageClick: IOnImageClick } +> = props => { + const title = props.client.options.ui?.chatBot?.title; + const icon = props.client.options.ui?.base?.icon; + const headerExtra = props.client.options.ui?.header?.isNeedClose ? ( +
+ ) : null; + const layout = props.client.options.ui?.base?.layout; + const { onImageClick } = props; + const { userInfo } = props.client.options; + const setThemeType = useGlobalStore(s => s.setThemeType); + + const iframeParams = getChatConfig( + props.client.chatClientId, + props.client.options, + ); + if (iframeParams.chatConfig.auth) { + iframeParams.chatConfig.auth.onRefreshToken = + props.client.options.auth?.onRefreshToken; + } + + return ( +
+ +
+ ); +}; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/App.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/App.tsx new file mode 100644 index 00000000..c510583a --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/App.tsx @@ -0,0 +1,43 @@ +/* + * 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. + */ + +import { RouterProvider } from 'react-router-dom'; +import { type FC, useEffect, useState } from 'react'; + +import { initI18nInstance, I18n } from '@coze-arch/i18n/raw'; +import { I18nProvider } from '@coze-arch/i18n/i18n-provider'; + +import { devRouter } from './routes'; + +const DevApp: FC = () => { + const [i18nReady, setI18nReady] = useState(false); + + useEffect(() => { + initI18nInstance().then(() => setI18nReady(true)); + }, []); + + if (!i18nReady) { + return null; + } + + return ( + + + + ); +}; + +export default DevApp; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/index.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/index.tsx new file mode 100644 index 00000000..03485cf1 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/index.tsx @@ -0,0 +1,28 @@ +/* + * 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. + */ + +import './main.less'; + +import { createRoot } from 'react-dom/client'; + +import DevApp from './App'; + +const rootEl = document.createElement('div'); +rootEl.setAttribute('className', 'coze-chat-sdk'); +document.body.append(rootEl); + +const root = createRoot(rootEl); +root.render(); diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/main.less b/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/main.less new file mode 100644 index 00000000..e11e0094 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/main.less @@ -0,0 +1,11 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +html, +body, +#root { + height: 100%; + padding: 0; + margin: 0; +} diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/page/AppWidget.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/page/AppWidget.tsx new file mode 100644 index 00000000..da61115f --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/page/AppWidget.tsx @@ -0,0 +1,49 @@ +/* + * 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. + */ + +import { type FC, useState } from 'react'; + +import { nanoid } from 'nanoid'; +import { OpenApiSource } from '@coze-studio/open-chat/types'; +import { WebSdkChat } from '@coze-studio/open-chat'; + +const uid = nanoid(); + +const botConfig = { + bot_id: process.env.CHAT_APP_INDEX_COZE_BOT_ID || '', + user: uid, + conversation_id: uid, + source: OpenApiSource.WebSdk, +}; + +const TestAppWidget: FC = () => { + const [visible] = useState(false); + // 触发更新 + return ( + <> + {visible ? ( + + ) : null} + + ); +}; + +export default TestAppWidget; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/page/Chat.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/page/Chat.tsx new file mode 100644 index 00000000..9aae0988 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/page/Chat.tsx @@ -0,0 +1,48 @@ +/* + * 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. + */ + +import { type FC } from 'react'; + +import { nanoid } from 'nanoid'; +import { OpenApiSource } from '@coze-studio/open-chat/types'; +import { WebSdkChat } from '@coze-studio/open-chat'; + +const uid = nanoid(); + +const botConfig = { + user: uid, + conversation_id: uid, + bot_id: process.env.CHAT_APP_INDEX_COZE_BOT_ID || '', + source: OpenApiSource.WebSdk, +}; + +const TestChatDemo: FC = () => ( + +); + +export default TestChatDemo; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/page/Client.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/page/Client.tsx new file mode 100644 index 00000000..a5309d6e --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/page/Client.tsx @@ -0,0 +1,119 @@ +/* + * 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. + */ + +import { useEffect } from 'react'; + +import { + AuthType, + ChatType, + Language, + Layout, +} from '@coze-studio/open-chat/types'; + +import { WebChatClient } from '@/client'; + +export const TestClientDemo = () => { + useEffect(() => { + new WebChatClient({ + config: { + type: ChatType.BOT, + appInfo: { + appId: process.env.CHAT_APP_CHATFLOW_COZE_APP_ID || '', + workflowId: process.env.CHAT_APP_CHATFLOW_COZE_WORKFLOW_ID || '', + }, + + botInfo: { + botId: process.env.CHAT_APP_INDEX_COZE_BOT_ID || '', + parameters: { + botId: process.env.CHAT_APP_INDEX_COZE_BOT_ID || '', + botName: '历史学教授', + }, + }, + }, + + auth: { + type: AuthType.TOKEN, + token: process.env.CHAT_APP_COZE_TOKEN || '', + onRefreshToken: () => process.env.CHAT_APP_COZE_TOKEN || '', + }, + componentProps: { + title: '历史学教授', + lang: Language.ZH_CN, + layout: Layout.PC, + }, + extra: { + webChat: { + test: '123123', + }, + }, + ui: { + asstBtn: { + isNeed: true, + }, + chatBot: { + title: '历史学教授33', + uploadable: true, + isNeedAudio: true, + isNeedFunctionCallMessage: true, + isNeedQuote: true, + feedback: { + isNeedFeedback: true, + feedbackPanel: { + title: + '起来不是一个有明确意义的旅游相关问题哦。你可以告诉我关于旅游的具体问题,比如想去的旅游目的地、旅游预算、旅游方', + tags: [ + { + label: '信息不正确', + }, + { + label: '涉及敏感信息', + isNeedDetail: true, + }, + ], + }, + }, + }, + header: { + isShow: true, + isNeedClose: true, + }, + footer: { + isShow: true, + expressionText: ' 由{{name}}提sdd供', + linkvars: { + name: { + text: 'Coze', + link: 'https://www.coze.com', + }, + }, + }, + conversations: { + isNeed: true, + }, + base: { + layout: Layout.PC, + lang: Language.ZH_CN, + }, + }, + userInfo: { + id: '12334', + url: process.env.CHAT_APP_COZE_BOT_USER_URL || '', + nickname: '3qweqweq4we', + }, + }); + }, []); + return
; +}; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/routes/index.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/routes/index.tsx new file mode 100644 index 00000000..5a8e4f69 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/dev-app/routes/index.tsx @@ -0,0 +1,45 @@ +/* + * 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. + */ + +import { createBrowserRouter, Outlet } from 'react-router-dom'; + +import TestAppWidget from '@/dev-app/page/AppWidget'; + +import { TestClientDemo } from '../page/Client'; +import TestChatDemo from '../page/Chat'; + +const Layout = () => ; +export const devRouter: ReturnType = + createBrowserRouter([ + { + path: '/', + element: , + children: [ + { + path: 'chat', + element: , + }, + { + path: 'app_widget', + element: , + }, + { + path: 'client', + element: , + }, + ], + }, + ]); diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/export-ui/index.ts b/frontend/packages/studio/open-platform/chat-app-sdk/src/export-ui/index.ts new file mode 100644 index 00000000..a66349e1 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/export-ui/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +window.CozeWebSDK = window.CozeWebSDK || {}; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/hooks/use-image-preview.ts b/frontend/packages/studio/open-platform/chat-app-sdk/src/hooks/use-image-preview.ts new file mode 100644 index 00000000..ed821b05 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/hooks/use-image-preview.ts @@ -0,0 +1,64 @@ +/* + * 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. + */ + +import { useCallback, useEffect } from 'react'; + +import { + type PostMessage, + PostMessageEvent, +} from '@coze-studio/open-chat/types'; + +import { postMessageUtil } from '@/types/post'; +import { useGlobalStore } from '@/store'; +import { type WebChatClient } from '@/client'; + +export const useImagePreview = (client: WebChatClient) => { + const { setImagePreview } = useGlobalStore(s => ({ + setImagePreview: s.setImagePreview, + })); + const onMessageHandler = useCallback<{ + (event: MessageEvent): void; + }>( + event => { + const msg = event?.data; + + if (msg.chatStoreId !== client.chatClientId) { + return; + } + + switch (msg.event) { + case PostMessageEvent.ImageClick: + // @ts-expect-error -- linter-disable-autofix + if (postMessageUtil.isImageClick(msg)) { + setImagePreview(preview => { + preview.url = msg.payload.url; + preview.visible = true; + }); + } + break; + default: + } + }, + [setImagePreview, client], + ); + + useEffect(() => { + window.addEventListener('message', onMessageHandler); + return () => { + window.removeEventListener('message', onMessageHandler); + }; + }, [onMessageHandler]); +}; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/hooks/use-message-interact.ts b/frontend/packages/studio/open-platform/chat-app-sdk/src/hooks/use-message-interact.ts new file mode 100644 index 00000000..43d4e4ff --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/hooks/use-message-interact.ts @@ -0,0 +1,120 @@ +/* + * 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. + */ + +import { useEffect, useRef } from 'react'; + +import { useShallow } from 'zustand/react/shallow'; +import { + type IframeParams, + IframeMessageEvent, + WebSdkError, +} from '@coze-studio/open-chat/types'; +import { PostMessageChannel } from '@coze-arch/bot-utils/post-message-channel'; + +import { getChatConfig } from '@/util/get-chat-config'; +import { type CozeChatOptions } from '@/types/client'; +import { useGlobalStore } from '@/store/context'; + +export const useMessageInteract = ( + chatClientId: string, + cozeChatOption: CozeChatOptions, +) => { + const refPostChannel = useRef(); + //const [themeType, setThemeType] = useState<'bg-theme' | 'light'>('light'); + const { + iframe: iframeEl, + senderName, + setThemeType, + } = useGlobalStore( + useShallow(s => ({ + iframe: s.iframe, + senderName: s.senderName, + setThemeType: s.setThemeType, + })), + ); + const refProps = useRef<{ + chatClientId: string; + cozeChatOption: CozeChatOptions; + }>({ + chatClientId, + cozeChatOption, + }); + refProps.current = { + chatClientId, + cozeChatOption, + }; + + useEffect(() => { + if (iframeEl?.contentWindow) { + refPostChannel.current = new PostMessageChannel({ + channelPort: iframeEl.contentWindow, + senderName, + }); + refPostChannel.current.onRequest( + IframeMessageEvent.GET_IFRAME_PARAMS, + () => { + const iframeParams = getChatConfig( + refProps.current.chatClientId, + refProps.current.cozeChatOption, + ); + return { + code: 0, + data: iframeParams, + }; + }, + ); + refPostChannel.current.onRequest( + IframeMessageEvent.GET_NEW_TOKEN, + async token => { + let tokenNew; + try { + tokenNew = + await refProps.current.cozeChatOption.auth?.onRefreshToken?.( + token || '', + ); + } catch (e) { + console.error('[WebSdk Error] Get Token Error'); + } + + if (tokenNew) { + return { + code: 0, + data: tokenNew, + }; + } else { + return { + code: WebSdkError.AUTH_TOKEN_GET_FAILED, + message: 'Get Token Error', + }; + } + }, + ); + refPostChannel.current.onRequest( + IframeMessageEvent.THEME_CHANGE, + theme => { + setThemeType(theme as 'bg-theme' | 'light'); + return { + code: 0, + }; + }, + ); + return () => { + refPostChannel.current?.destory(); + refPostChannel.current = undefined; + }; + } + }, [iframeEl, senderName]); +}; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/index.ts b/frontend/packages/studio/open-platform/chat-app-sdk/src/index.ts new file mode 100644 index 00000000..471f2c28 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/index.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +import { WebChatClient } from '@/client'; + +window.CozeWebSDK = window.CozeWebSDK || {}; +window.CozeWebSDK.WebChatClient = WebChatClient; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/store/__tests__/setter.test.ts b/frontend/packages/studio/open-platform/chat-app-sdk/src/store/__tests__/setter.test.ts new file mode 100644 index 00000000..b634792e --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/store/__tests__/setter.test.ts @@ -0,0 +1,107 @@ +/* + * 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. + */ + +import { describe, test, expect, vi } from 'vitest'; +import { AuthType } from '@coze-studio/open-chat/types'; + +import { createGlobalStore } from '@/store/global'; +import { WebChatClient } from '@/client'; +import type * as ClientModule from '@/client'; + +vi.hoisted(() => { + // @ts-expect-error -- 将 IS_OVERSEA 提升到最外层 + global.IS_OVERSEA = false; +}); + +vi.mock('./auth', () => ({ + AuthClient: vi.fn().mockImplementation(() => ({ + initToken: vi.fn().mockResolvedValue(false), + checkOptions: vi.fn().mockReturnValue(true), + })), +})); + +vi.mock('@/components/widget', () => ({ + default: vi.fn(), +})); + +vi.mock('@coze-studio/open-chat', () => ({ + postErrorMessage: vi.fn(), + ChatSdkErrorType: { + OPEN_API_ERROR: 'OPEN_API_ERROR', + }, +})); + +describe('createGlobalStore setter', () => { + console.log('[dev]...', WebChatClient); + test('simple states', async () => { + const store = createGlobalStore( + new WebChatClient({ + config: { + botId: '', + }, + }), + ); + + const { setIframe, setChatVisible, setIframeLoaded, setImagePreview } = + store.getState(); + const iframe = document.createElement('iframe'); + + setIframe(iframe); + await setChatVisible(true); + setIframeLoaded(false); + setImagePreview(preview => { + preview.url = 'xxx'; + preview.visible = true; + }); + store.getState().setThemeType('bg-theme'); + expect(store.getState().themeType).toBe('bg-theme'); + + expect(store.getState()).toEqual( + expect.objectContaining({ + iframe, + chatVisible: true, + iframeLoaded: false, + }), + ); + + expect(store.getState().imagePreview).toMatchObject({ + url: 'xxx', + visible: true, + }); + }); + + test('token invalid', async () => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { WebChatClient: Client } = + await vi.importActual('@/client'); + + const store = createGlobalStore( + new Client({ + config: { + botId: '', + }, + auth: { + type: AuthType.TOKEN, + token: '', + onRefreshToken: () => '', + }, + }), + ); + const { setChatVisible } = store.getState(); + await setChatVisible(true); + expect(store.getState().chatVisible).toBe(false); + }); +}); diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/store/context.tsx b/frontend/packages/studio/open-platform/chat-app-sdk/src/store/context.tsx new file mode 100644 index 00000000..1ecf664c --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/store/context.tsx @@ -0,0 +1,47 @@ +/* + * 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. + */ + +import { type ReactNode, createContext, type FC, useContext } from 'react'; + +import { useStoreWithEqualityFn } from 'zustand/traditional'; +import { shallow } from 'zustand/shallow'; + +import { type ClientStateAction, type ClientStore } from './global'; + +export const GlobalStoreContext = createContext<{ + globalStore: ClientStore; + // @ts-expect-error -- linter-disable-autofix +}>(undefined); + +export const GlobalStoreProvider: FC<{ + children: ReactNode; + globalStore: ClientStore; +}> = ({ children, globalStore }) => ( + + {children} + +); + +export const useGlobalStore: ( + selector: (store: ClientStateAction) => T, +) => T = selector => { + const store = useContext(GlobalStoreContext).globalStore; + return useStoreWithEqualityFn(store, selector, shallow); +}; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/store/global.ts b/frontend/packages/studio/open-platform/chat-app-sdk/src/store/global.ts new file mode 100644 index 00000000..1c6a431a --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/store/global.ts @@ -0,0 +1,125 @@ +/* + * 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. + */ + +import { devtools, subscribeWithSelector } from 'zustand/middleware'; +import { create } from 'zustand'; +import { produce } from 'immer'; +import { type ImagePreview, Layout } from '@coze-studio/open-chat/types'; +import { ChatSdkErrorType, postErrorMessage } from '@coze-studio/open-chat'; + +import { type AuthClient } from '@/client/auth'; +import { type WebChatClient } from '@/client'; + +interface ClientState { + chatVisible: boolean; + iframe?: HTMLIFrameElement; + iframeLoaded: boolean; + imagePreview: ImagePreview; + layout: Layout; + senderName: string; + themeType: 'bg-theme' | 'light'; +} + +interface ClientAction { + setIframe: (el?: HTMLIFrameElement) => void; + setIframeLoaded: (loaded: boolean) => void; + setChatVisible: (visible: boolean) => void; + setImagePreview: (recipe: (preview: ImagePreview) => void) => void; + setThemeType: (themeType: 'bg-theme' | 'light') => void; +} + +export type ClientStateAction = ClientState & ClientAction; + +export const createGlobalStore = (client: WebChatClient) => { + const { options, senderName } = client; + const { layout = Layout.PC } = options?.ui?.base ?? {}; + const authClient = client.authClient as AuthClient; + const defaultState: ClientState = { + chatVisible: false, + iframeLoaded: false, + themeType: 'light', + imagePreview: { + url: '', + visible: false, + }, + layout, + senderName, + }; + + return create()( + devtools( + subscribeWithSelector(set => ({ + ...defaultState, + setThemeType: (themeType: 'bg-theme' | 'light') => { + set({ + themeType, + }); + }, + setIframe: element => { + set({ + iframe: element, + }); + }, + setChatVisible: async visible => { + const chatBot = options?.ui?.chatBot; + if (authClient && !(await authClient.initToken())) { + postErrorMessage({ + type: ChatSdkErrorType.OPEN_API_ERROR, + code: 401, + message: 'invalid token', + }); + return; + } + // 判断是否能够显示、隐藏 + if (visible) { + if ((await chatBot?.onBeforeShow?.()) === false) { + return; + } + } else { + if ((await chatBot?.onBeforeHide?.()) === false) { + return; + } + } + set({ + chatVisible: visible, + }); + // 显示、隐藏后的回调。 + if (visible) { + chatBot?.onShow?.(); + } else { + chatBot?.onHide?.(); + } + }, + setIframeLoaded: loaded => { + set({ + iframeLoaded: loaded, + }); + }, + setImagePreview: recipe => + set( + produce(draft => { + recipe(draft.imagePreview); + }), + ), + })), + { + enabled: IS_DEV_MODE, + name: 'sdkChatApp.global', + }, + ), + ); +}; +export type ClientStore = ReturnType; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/store/index.ts b/frontend/packages/studio/open-platform/chat-app-sdk/src/store/index.ts new file mode 100644 index 00000000..1780db5a --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/store/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { useGlobalStore } from './context'; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/test/setup.ts b/frontend/packages/studio/open-platform/chat-app-sdk/src/test/setup.ts new file mode 100644 index 00000000..95d8c372 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/test/setup.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +vi.mock('@/client', () => ({ + default: vi.fn(), + WebChatClient: vi.fn(), +})); + +vi.mock('@coze-studio/open-chat', () => ({ + default: vi.fn(), +})); + +vi.stubGlobal('IS_DEV_MODE', false); + +beforeEach(() => { + global.alert = info => { + console.log(info); + }; +}); diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/types/chat.ts b/frontend/packages/studio/open-platform/chat-app-sdk/src/types/chat.ts new file mode 100644 index 00000000..8199aef8 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/types/chat.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +import { type ClientStore } from '@/store/global'; +import { type WebChatClient } from '@/client'; + +export interface CozeWidgetProps { + position?: 'static' | 'fixed'; + client: WebChatClient; + globalStore: ClientStore; +} + +export type WidgetAdapterProps = Pick; +export type AstBtnProps = WidgetAdapterProps; +export type ChatIframProps = Pick; +export type ChatContentProps = Pick; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/types/client.ts b/frontend/packages/studio/open-platform/chat-app-sdk/src/types/client.ts new file mode 100644 index 00000000..89f2642a --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/types/client.ts @@ -0,0 +1,52 @@ +/* + * 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. + */ + +import { + type ComponentProps, + type AuthProps, + type UiProps, + type OpenUserInfo, + type ChatType, + type AppInfo, + type BotInfo, +} from '@coze-studio/open-chat/types'; + +export interface CozeChatOptions { + config: { + /** @deprecated 该使用方式已废弃,请使用botId */ + bot_id?: string; + + /** @deprecated 该使用方式已废弃,请使用botId */ + botId?: string; + /** @deprecated 该字段已废弃,请使用auth字段进行配置 */ + sdk_verify_token?: string; + type?: ChatType; + + appInfo?: AppInfo; + botInfo?: BotInfo; + }; + extra?: { + webChat: Record; + }; + auth?: AuthProps; + userInfo?: OpenUserInfo; + ui?: UiProps; + /** @deprecated 该使用方式已废弃,请使用ui属性对ui进行配置 */ + el?: HTMLElement; + + /** @deprecated 该使用方式已废弃,请使用ui属性对ui进行配置 */ + componentProps?: ComponentProps; // 待废弃 +} diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/types/post.ts b/frontend/packages/studio/open-platform/chat-app-sdk/src/types/post.ts new file mode 100644 index 00000000..6633b7d9 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/types/post.ts @@ -0,0 +1,34 @@ +/* + * 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. + */ + +import { + type PostMessage, + PostMessageEvent, +} from '@coze-studio/open-chat/types'; + +export const postMessageUtil = { + isImageClick( + msg: PostMessage>, + ): msg is PostMessage { + return msg.event === PostMessageEvent.ImageClick && !!msg?.payload?.url; + }, +}; + +export interface PostMessageData { + [PostMessageEvent.ImageClick]: { + url: string; + }; +} diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/typings.d.ts b/frontend/packages/studio/open-platform/chat-app-sdk/src/typings.d.ts new file mode 100644 index 00000000..c4a4694a --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/typings.d.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +/// + +interface Window { + CozeWebSDK: { + WebChatClient: unknown; + WebCardRuntime: unknown; + WebMdBoxRuntime: unknown; + }; +} diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/util/get-chat-config.ts b/frontend/packages/studio/open-platform/chat-app-sdk/src/util/get-chat-config.ts new file mode 100644 index 00000000..8a6e13d4 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/util/get-chat-config.ts @@ -0,0 +1,60 @@ +/* + * 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. + */ + +import { nanoid } from 'nanoid'; +import { pick } from 'lodash-es'; +import { ChatType } from '@coze-studio/open-chat/types'; +import { OpenApiSource, type IframeParams } from '@coze-studio/open-chat/types'; + +import { type CozeChatOptions } from '@/types/client'; + +export const getChatConfig = ( + chatClientId: string, + cozeChatOption: CozeChatOptions, +): IframeParams => { + const { config, auth, userInfo, ui, extra } = cozeChatOption; + return { + chatClientId, + chatConfig: { + type: config?.type || ChatType.BOT, + bot_id: (config?.botId ?? config?.bot_id) || '', + appInfo: config?.appInfo, + botInfo: config?.botInfo, + conversation_id: nanoid(), + extra, + ui: { + base: pick(ui?.base || {}, ['icon', 'lang', 'layout']), + chatBot: pick(ui?.chatBot || {}, [ + 'title', + 'uploadable', + 'isNeedClearContext', + 'isNeedClearMessage', + 'isNeedAudio', + 'isNeedFunctionCallMessage', + 'isNeedQuote', + 'isNeedAddNewConversation', + 'feedback', + ]), + footer: ui?.footer, + header: ui?.header, + conversations: ui?.conversations, + }, + auth: pick(auth || {}, ['type', 'token']), + source: OpenApiSource.WebSdk, + }, + userInfo, + }; +}; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/src/util/style.ts b/frontend/packages/studio/open-platform/chat-app-sdk/src/util/style.ts new file mode 100644 index 00000000..68f5b4ab --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/src/util/style.ts @@ -0,0 +1,28 @@ +/* + * 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. + */ + +const clientCssVars = { + '--coze-z-index-iframe': 1000, +}; + +export const getCssVars = ({ zIndex }: { zIndex?: number }) => ({ + ...clientCssVars, + ...(typeof zIndex === 'number' + ? { + '--coze-z-index-iframe': zIndex, + } + : {}), +}); diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/tailwind.config.js b/frontend/packages/studio/open-platform/chat-app-sdk/tailwind.config.js new file mode 100644 index 00000000..5cadaeb4 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/tailwind.config.js @@ -0,0 +1,36 @@ +/* + * 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. + */ + +/** @type {import('tailwindcss').Config} */ +const { genTailwindPlugin } = require('@coze-arch/tailwind-config/util'); +module.exports = { + darkMode: 'class', + presets: [require('@coze-arch/tailwind-config')], + important: '.coze-chat-sdk', + content: [ + './src/**/*.{html,tsx}', + '../../../../../packages/components/coze-design/src/**/*.{js,ts,jsx,tsx}', + './node_modules/@coze-arch/coze-design/dist/**/*.{js,jsx,css}', + '../open-chat/src/**/*.{js,ts,jsx,tsx}', + '../../../common/chat-area/chat-area/src/**/*.{js,ts,jsx,tsx}', + '../../../common/chat-area/chat-uikit/src/**/*.{js,ts,jsx,tsx}', + '../../../common/chat-area/plugin-chat-shortcuts/src/**/*.{js,ts,jsx,tsx}', + ], + corePlugins: { + preflight: false, // 关闭@tailwind base默认样式,避免对现有样式影响:https://code.byted.org/obric/bot-studio-monorepo/merge_requests/2945 + }, + plugins: [genTailwindPlugin(':root', '.dark .coze-chat-sdk-dark')], +}; diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/tsconfig.build.json b/frontend/packages/studio/open-platform/chat-app-sdk/tsconfig.build.json new file mode 100644 index 00000000..42010eb4 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/tsconfig.build.json @@ -0,0 +1,62 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "preserveSymlinks": false, + "skipLibCheck": true, + "outDir": "./dist_ignore", + "types": [], + "tsBuildInfoFile": "dist_ignore/tsconfig.build.tsbuildinfo", + "sourceMap": false + }, + "include": ["src", "src/**/*.json"], + "exclude": ["./src/**/*.test.ts", "src/**/__tests__/**", "src/test/setup.ts"], + "references": [ + { + "path": "../../../arch/bot-env-adapter/tsconfig.build.json" + }, + { + "path": "../../../arch/bot-env/tsconfig.build.json" + }, + { + "path": "../../../arch/bot-typings/tsconfig.build.json" + }, + { + "path": "../../../arch/bot-utils/tsconfig.build.json" + }, + { + "path": "../../../arch/i18n/tsconfig.build.json" + }, + { + "path": "../../../common/assets/tsconfig.build.json" + }, + { + "path": "../../../components/bot-semi/tsconfig.build.json" + }, + { + "path": "../../../../config/eslint-config/tsconfig.build.json" + }, + { + "path": "../../../../config/postcss-config/tsconfig.build.json" + }, + { + "path": "../../../../config/stylelint-config/tsconfig.build.json" + }, + { + "path": "../../../../config/tailwind-config/tsconfig.build.json" + }, + { + "path": "../../../../config/ts-config/tsconfig.build.json" + }, + { + "path": "../../../../config/vitest-config/tsconfig.build.json" + }, + { + "path": "../../../../infra/plugins/pkg-root-webpack-plugin/tsconfig.build.json" + }, + { + "path": "../open-chat/tsconfig.build.json" + } + ] +} diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/tsconfig.json b/frontend/packages/studio/open-platform/chat-app-sdk/tsconfig.json new file mode 100644 index 00000000..551407c2 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/tsconfig.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@coze-arch/ts-config/tsconfig.web.json", + "compilerOptions": { + "rootDir": "./", + "types": ["vitest/globals", "node"], + "paths": { + "@/*": ["./src/*"] + }, + "esModuleInterop": true, + "resolveJsonModule": true + }, + "include": [ + "src/**/*.json", + "src", + "rspack-config", + "stories", + "vitest.config.ts" + ], + "exclude": ["node_modules"] +} diff --git a/frontend/packages/studio/open-platform/chat-app-sdk/vitest.config.ts b/frontend/packages/studio/open-platform/chat-app-sdk/vitest.config.ts new file mode 100644 index 00000000..e9401743 --- /dev/null +++ b/frontend/packages/studio/open-platform/chat-app-sdk/vitest.config.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +import { defineConfig } from '@coze-arch/vitest-config'; + +export default defineConfig({ + dirname: __dirname, + preset: 'web', + test: { + setupFiles: ['src/test/setup.ts'], + }, +}); diff --git a/frontend/packages/studio/open-platform/open-chat/package.json b/frontend/packages/studio/open-platform/open-chat/package.json index 38f7b74e..edba5c79 100644 --- a/frontend/packages/studio/open-platform/open-chat/package.json +++ b/frontend/packages/studio/open-platform/open-chat/package.json @@ -3,26 +3,72 @@ "version": "0.0.1", "description": "Coze Web ChatApp SDK ", "license": "Apache-2.0", - "author": "gaoding.devingao@bytedance.com", + "author": "yangyu.1@bytedance.com", "maintainers": [ - "gaoding.devingao@bytedance.com" + "gaoding.devingao@bytedance.com", + "yangyu.1@bytedance.com" ], "sideEffects": false, "exports": { - ".": "./src/index.ts" + ".": "./src/index.ts", + "./types": "./src/exports/types.ts", + "./envs": "./src/util/env.ts" }, "main": "src/index.ts", "types": "./src/index.ts", + "typesVersions": { + "*": { + "types": [ + "./src/exports/types.ts" + ], + "envs": [ + "./src/util/env.ts" + ] + } + }, "scripts": { "build": "exit 0", "lint": "eslint ./ --cache", "test": "vitest --run --passWithNoTests", - "test:cov": "npm run test -- --coverage" + "test:cov": "vitest run --coverage" }, "dependencies": { + "@coze-arch/bot-api": "workspace:*", + "@coze-arch/bot-semi": "workspace:*", + "@coze-arch/bot-utils": "workspace:*", + "@coze-arch/coze-design": "0.0.6-alpha.346d77", "@coze-arch/i18n": "workspace:*", - "@coze/chat-sdk": "0.1.11-beta.19", - "react": "~18.2.0" + "@coze-arch/idl": "workspace:*", + "@coze-arch/logger": "workspace:*", + "@coze-common/chat-answer-action": "workspace:*", + "@coze-common/chat-area": "workspace:*", + "@coze-common/chat-area-plugin-chat-background": "workspace:*", + "@coze-common/chat-area-plugin-message-grab": "workspace:*", + "@coze-common/chat-area-plugin-reasoning": "workspace:*", + "@coze-common/chat-area-plugins-chat-shortcuts": "workspace:*", + "@coze-common/chat-core": "workspace:*", + "@coze-common/chat-uikit": "workspace:*", + "@coze-common/chat-uikit-shared": "workspace:*", + "@coze-common/chat-workflow-render": "workspace:*", + "@coze-studio/file-kit": "workspace:*", + "@coze-studio/open-env-adapter": "workspace:*", + "@coze-studio/slardar-adapter": "workspace:*", + "@coze/api": "1.3.5", + "@douyinfe/semi-icons": "^2.36.0", + "ahooks": "^3.7.8", + "axios": "^1.4.0", + "classnames": "^2.3.2", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.7", + "eventemitter3": "^5.0.1", + "immer": "^10.0.3", + "lodash-es": "^4.17.21", + "nanoid": "^4.0.2", + "react": "~18.2.0", + "react-device-detect": "2.2.3", + "react-dom": "~18.2.0", + "react-router-dom": "^6.11.1", + "zustand": "^4.4.7" }, "devDependencies": { "@coze-arch/bot-env": "workspace:*", @@ -34,7 +80,6 @@ "@coze-arch/tailwind-config": "workspace:*", "@coze-arch/ts-config": "workspace:*", "@coze-arch/vitest-config": "workspace:*", - "@coze-studio/open-env-adapter": "workspace:*", "@rspack/plugin-react-refresh": "0.6.0", "@testing-library/jest-dom": "^6.1.5", "@testing-library/react": "^14.1.2", diff --git a/frontend/packages/studio/open-platform/open-chat/src/assets/add-new-chat.svg b/frontend/packages/studio/open-platform/open-chat/src/assets/add-new-chat.svg new file mode 100644 index 00000000..5da392e9 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/assets/add-new-chat.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/packages/studio/open-platform/open-chat/src/assets/chatflow-logo.png b/frontend/packages/studio/open-platform/open-chat/src/assets/chatflow-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0eccceec8be0bfda805a902b36a35865ecd67a39 GIT binary patch literal 3215 zcmds4`#aO^AE&mSNqWp-&8b4DBndea8Pg+&*qV@8$zeG+5)U<|*pTE*Na@LDM#w2z zZ8ZzD$MD9-=^ z$lv;mwU}5Efw*sQSo-8=HOJ1?wuo|GYiAF5(bD*h{t(Df#C;eNs4p@u7K zJ?g`;%d{=g(%ND~XmHimu5#%khsn|^dH4BN%PV^q&7yOH3ys=$yUQEhH=nt6W%6zL zW_xbz8y9XxZQas?7qPY!r@p>y-OK*#WtFmzE7i{D@`n%gP^s1SeqWCg*`A}652ADx zKMSdGf{;J5EkO&@3M(^x8Xamf6ic~y0A)|9!?C{i^%dHAw`yV>-TxTXu=9M0kfx=R zU3B^Rcu@1BF zDIevk(?XeEf-ZSXTIM1IL_G-E=T4j}hl@YBCW0qU3Tjv9f zm_aqG+kDL98m}T+(Qku*_=)lMfkzmrBC~7#OAI!MO4=bg?=AQKRo*tw+o4*_pnPlE zVC&`Ysv7|Wkwt(ukO+-+Q!6KL2~5hpK~w~8W#uK^o0JO!4s*}d_j`$48NCOXt4-aW zYU6fA3#u3$;Yg-B=^5KJ=ZSAD1TD!Lu`cdTXdjqK=-#BErD?FRDdMl>EGS3uix#HK zqx^{`&QILZih25ojbEkPNWR2#umM*Bja$^$w>xJD_nXwhq1j;~Zm*Tn#WUW3}A0=k@c2p#$ z>1p)ZGwupC@SOPfb5}vy3e2GNzH*@G@wQ8S!V6#2F)0I_esgu67f2F$igCl>Z$oy9 z{V`QSG6!jA#=f~PU+^73ugzYd>ht<~8-0;@Cs0;TEg*e$N*&WB+MIAGQ`nN<0#?XZ z%GkH3phh(9s=9}H`b#nteX%w7e;4dxJ!sJ#oW=z$fb1-Z+5M^YxtUcoIt z&-^aD-;{hzmwCDuS=P4rEJPkSQx>icdk^JJp*cQ^l%-8jYAY8n0N=r{J(i^PvXslP81bfZ^~cfw*O)o|9LFb}T=*foQk!A#wJG~1#5t?5m@k1S9dB+Ujb4oIv2*+k zd#mVmX~9Vv0I*CVvbdm7pBRLsG~}E+*R$sN%yi?oPx%lYhTlxQzXM!Vk>HT0 z8Dg$Zllr#6Ox$qI#!N!hY4AYyo3a``3WMj7kShG-Klx&Lfz*mqV&!UUM{%n4)oBE| z%;xL!d6@c>UfbkL9uca(YhdOM@TkgvF36NjhH5ztd}v7r;Z<1G2R*BU9Qy;T=lDiQLjg!`?$=daF!_ z&G3857YO>)xbEx&eD7*uBftN4iwTi?r!hC%P;!%=uwFV^W33OOit8;!$LtWub|gRX z)d%5i^@gKcpq3N(XU{{DuxXu&V3aM5QfsG$QNQhCqK!m_!#?eM_7=i}ID((3+j*qt z1O6Qf9iHsN-hd|A?5BgjMl?)Xl{5iRjfMM*9<20(@DRzCC=YYL0H}74#49LG zwP^V1;}-`3Z0mf`!WgnUyXjGO@*VyBCr@lFh$uJ5U&i8|!vcQ_@5Gyo+h{{Y%4X-(Iz8H(TWZKy{FzE8rtGXHfNs!^|M?}5ts7Ru8)j`pI zM6l9)2@#bKOrP#-G%j;1{-^Hw$CgUQqLu*8-}X5S&@3)#K(wBv1%7SKsFbCphM32D zVU^K)EswaXW8}R7xV|txQcZ_d0z2hf#G-TdSsd@Yvp;3})^?5hvU^vQU_9XeLCgW+WJ|Zf68{76Y|ViH literal 0 HcmV?d00001 diff --git a/frontend/packages/studio/open-platform/open-chat/src/assets/coze-logo.png b/frontend/packages/studio/open-platform/open-chat/src/assets/coze-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b4c900920902fc058e19368bc803374fb8912616 GIT binary patch literal 4645 zcmV+=658#FP)d4xn! zqXH2mK%j_GkOBn(c@)}&r$F06gbMFE)(ZojyVxliMd~@GouP8Q1C00Tc3?=;cCBWv556s^@(RpQw-SD-2 zYdj@xz}+>_L?!eJCGPn9gL7{z_vIL3H*DBB%X3<9#`|&^G*K&horGGu@!t8p(GLS+ ziPWN#ehNQ03!0cL20SU({`3Chw%HG)-SM3CLrqerj9HHFKSegle!#?DbZOs}(A1~L zidkMk{KXzHu?qa3KofCb4H9dR)<5hvD=BOuWiTMSLa zi$vREsg!DZ0x`n_?<@&T)CCPqtPr}8-(rgp3O)*Op`oCdNM*RAZEGt)IxS#!kHeph zj3_vC$cK?pA4*dl0)MO;g!dUIRugI2P_XorBz)qmBrG~1fyITkHaRX+L}!l-`|!c8 zBD{~~)oodL`<()Rq3aVc(-7+~-9PR3SOoo6EMe(1z~|3T!xz@IK|2vCB5FWrxr|D( z{-yrUr-H)!;Xx1HdOHsf|6_cud1-j&xe+9I82S$>uFIY~otOb)wGmpkHVv0t+z##S7^9#G z709ZTMmo*bN^3b?pdwou`U-y|eUe@UYgVB&);3F)0SVtFXzzt$AM zH^1Hit3T7qQtDVj1(a0*!L|fZ1E=hk$rY>`=FM~9+u!PhMGIZH_x>R?b_%LYn8b={ zBbJ7LPq%;{{h$k$EJ-4%A{_oQss{A=BG?(T2_)5it;`2ZEEQ9q@U=SmEK2XPrAhep zO^4x7znV75ofbcqe9!J~fNQVHFsUw*>aw(gky>D>mB_AE37QmX2#=C3?dL|yWuQhx z@VR3OUBh(+%%A7NwO41LClmER>WgV0RzWJ|!*y46U~&hIGmX8}&uqh_w-uQa*g+YaQINM1)kW1`MHcVgf7GU#SEH zN3KQJrW^ai?{{&VY%o$U)P-2<^iv1UUz_sR$7cVEoUj8ONxF z<;z>RP1Lq}YK6KGtBk28y}Dt22kTTj^sQ@EI;7*mcoiZ7J%;mB+o7Yw2@RC#sH}0gPZi^(SE~i-XmY7dmwl&;+gLBuiC6_0l!#TIZVL=ZVD;-D zrIk>s7U+iB_MCQV3iA?9-9oZD5UX|TSH9Ga9T2PrqNLVKN-JT!QA4T4Es?wO%oI(e zLOs+$oph{+8LG54QZftzrkm6#gbhSYA#%?9mGhi zj=>im?}o8^B&uozjMcSa8)x^}Bwu{w#;yv|DV?RZ5({Gbv-i~y+-M`cnT+IIah!-> z$6|`mw3pFH5=+nt_pv#Obed>rWMVPP+?{ctfp{zn5!uoED)tzm4qkz7s#ODe9~u(T zQ3WQ=85iOumdBQYanmUcreie%_aK<=i4!ra0tjbW8&yih_^pT42T#rD=F3I_6~uu@Xz|;$*TL&SzDE=K<{5TY$Un?t^{%^00bUCw%3?479b% zX-k5K#KwQYV<2{{8P#bw#nP%(ftXOglBC{r>uz{#Q$HU+xw#*GU>DYXu5(HqrOSJs zfy1+wEl0r?G6tTKuA5(35iDfHFm$VHLIv=}a zi5n!iCql8P;I%h~d2-L@Lf_kw#TnF2mv@-<F8DaKd|H31tQVlKJbr-$R0a913+5-`@Zlo= zfT~-1N}B5=ok@@rs0O7Vm;{tkvT9oY6jdQpNmoaky4FZ8m8C$^mzE#$I{T*;lPIV zJt%3NaO8*&?`|)m@$q3a>vP?up!&1u1sue4-rM29;GoZK;!)t*wP8>btb7Cp%hv|X z^FbHxtKzojz)P>>Sf^coT_>Dq^==;z^ zHIRfGuJ6KWA!pq6sP1xpag-ZNrf-O2LOmTBl3z?dcM-P-4;FBQ6?lYHgR^Y;HNTkX z1YRqE3R?;febjW&C>|z01b*L`+?%Lv)k`$`q52-%l!eD1&+>6LtKd=GryJVVD%hPH z8dk7-PZ93;>oENG_d{@a(B}cUXRn9TD-L{oWip7QqNNh7gp!J5sVs(M7V;EqC?wS! zFG>p{{jtn7Amhc5yEaZx>r@ z&0MvxwiF(AAk!tF-4iS&dF;$4B>P{FjGPt}Hz9)F9TKgj$x{*AO7kT}Ks=bKv$r?Sl)}cA?wR&HA-lBHEj z7pjJ}Qe9q^Sbb-#C8Sl*MI@kzbYtj7VVx)hFIHi>fF{> z^DMpl_j!g`V@M3Ki;&23&uxLvtxe-i4X;s)D)5cPFXD~MshKs&Dhhy|)QgT>Wt8ZJ{ zdD3%-`#tDGDL#P3P3V!ZWU&jg=SY68ptN2Z-dfj|zoyl%qylK@6S13~JOuaLcL2Q& zp-Gf9)2xZKWa^t$V`w<}M?QtQ7&V-94M_)DW|d^GA&290_wEAfChcT%LPM6is9!|Jg0Kwkm2@5C@#reLxLY9hou*(_N3KlXS(T3M8ctaiRfM@tHJ zK8xjzH-_MzzwO0+bkH%mL()?;A*JWpTm%H$=ssI?KXmemRFXit?jQfW3*LBhi1Q6u z(}tjo>Mga6+^uJx9nb~~%o6mN5Szqm<0RMsJpaNl1_=NcoZrLAYiq!4p61={BS`*- zumdH9uyVyLIBj_=Xx~^*E-F_WKmAe&<5rBxNZA_zT2f6&y8l1{UU+#3!_Z+kc}WT` zST`F^KDkvJGW%PlN6o)C9mKW{p){u;?#EAJekY6#APU&wEOd1$oX+ls;}@hLm3ENG zVc7D@5!ksihlJ6}Z%m%!zmpa_aOD+?FjAwSf{F+pXqM)k22of8==nDDq{C4vcip`o zw!AuO9DB;=7O?&#M}-UKrTFG-?;U|R-@>rDw}|zpu>M5EKZj#S{O^Pjrb8|__{dNJ z9(ah>4frP1lsZIoTd^}bqH*xM9XRd1b=y(+_?hi2^=#abWa)+v8Amf&$|j*blb|6| zQLjG#-$B^2bp&q+hJEBZ;fVrC>;0gJWcFhnp7CiVM_8Iv*LVc+z91rYuRu4_p|F8{ zT8Yt&3Lf_{A)<+Dw1oIf#J%_R!TfnioZn9(VS;rhrzD-R&xvuy!X~3_{uD{=>&s(4 zcL0epj=s>5q2l0$;(ae{htRfw&JlH5U=};E)~5}J*4U^1taJp&{2-W%lIO$k{;&rw z`_4RAxG<&3wX>wOD8%+(MO1+?avxx>^iEU(M~*lqdNPJc*w{(O`Q_UYuWq%d<2zxj z)2$-MWx`oDDwOm{z~E7Un{V3<=Y0xiT32OYc6ZXaNn<2R*DwZ?>Si)`Uv?Y-yc__GHme#A=dUF1 z!QRG65TT_OIyb4MG}P7nzag`c-oP2amjl#;UT8R2>61n+ghk3|danUV1xnKS1EiX^ z!AAn|JMncxF}sZ(B7GW~hz|lbZ``iJ5W;%Kojx6S9j7zZrqp|P{O+rnmA@) znq%7|56-`X_SyVC^1y;yaI+bjm@?1;^x|~XCH6STKsa8@C8!cUB8IH7K&n(qc}etd zdIiLiN|0W1EeiCV&_vB3NyY5j+KrLF5bG#<9;}?mP zNFMh*B9+;#CGk0uB2T&xVA!pt*e1Vx-tUevi^XEGSS%Kc#bU9{56+g5d-v|O&(6-) zqbO=SjPhX=;)}=7Knuq5Q=7hfB!z*tR1EJ7OOEJrY?LsTof@GEugiv zwc8Mb?ABd0laP`fgXj-?z1}f16ypSh*5=u>XAc;2epR)5DhhOS5IY^1XqAi+&>{-w z=kUOB*m0-RIWR>?wHMHG24n zaK$J5r%$;HZ4no4oC~!l2R_uip=u6TKQd3kWd?>Sd06;RTrI{SPM zu}&z2Jd5Mt*md1AZ~^D6gbNR`+g1<+9U&T9Q54>W|K+d}!G~aTv-4GznObE6f+zjH zQsQBWE-Qq_=?~2&KL){cSp}e=d|%y%iu3Jpr@we`DnRX5<5PKAT5knB-0f3b>Vf(*35`fh>H_lmYkXnuTAaJk?p6Gqw_nt)25G{^k^(TM;{5%YUmyL$wL4O|SlL~R; zO2r$^rrUuy>taIDS^g!kP{k4;E>WaH!4q{jb;@ta0fA zDp))wxhg7dv$+PPjHr;%L`be;84n@;5^=7nkdCOjw3jo=+?AE47!HSpBqoIj2;(03r#@kNSn&|aD`B>p{;Hbyd-Qq%y!RK4fOMu?x>=tV8$z-;5YqkOh=jA4VP!1M@nU5z9Q_!;!RO_|Ekk zz+0FqLyU2Md;89R>$J4^CO|}85dKGu7NTfbAyN#47dQ-Ka^Q?9J2POCaMmJ6{2soB z)qmZ7A4h)~8G0;4vOu70d@}D*-jifqm)J$Fb=H-Pp|C88fvX!Y%oF(ipg!dOl9gu%q=cW$6uNa|Ba1 z^~7tB_ki>!7Q47VQ`T2!77&PsT=z+>kEKF%?g8$6oAtGs1cbpe^t?z7JCm%>B9q<& zQgJc0u$hACOaeMRJtdoLYklejJtJ$iI+;99Ru@KgU!eS2XDQ1>x{{Y9$5EPUv%79tJRwt!b}yA5Q3O}#Wr=XInW;?Euo5{ zVCv|OnEqx?n){K`wF0U+aoiwn^CG2~>V?GgA4c1K-E~#%C-#;dxJM)lG)n^$1%v@K zBVyy9`jhU`Ph@f+JyAfz;gGarDCfdjA2SH>hy=TrfH!-3*=b=8N$ZK#yl*LB2_C_SBHfjVQndoFbo~a>p2$_wJ^HLV3Q1P~CZNF0XIL@kKPZxSR6q*PGoIaKpTi0lgjOdp<8$|3IaB|M( z0)mU0c^sGX$mk;)q>M_Gwy}D4<@i zM>5lC!_DM4XtdM&G!fzV(%s;VhPv9O*K5!%5YyP6Wo>PZypp44^g$qh!m~-Q#yPl2 zIy{Fj;Cp{&hHqN~_ef)Z$J2>Rfu^Q&co8XT4T8}kG~<`{q%LY9x95X<1P^(X`Sr6B zh8ZhUX$u9j?rLSJ4j}@P1&X5SOQELszE75+AKB6NKf@$Sn@*|$qI2E zYaK*qDIaiOg~mU~%)?fiO~)mk3M}&;bYGAZWj+rCkuv^%*55o+K=2QE za1lRd1j3L9Q8YB}WWq%`FYG4Z0obFS^XB{BKASOspJg5>pfnnh)QuTem0qCp!mg2? z^F|6SlH%#?qG_gqGU~2Yn2j}?Yb0}7HOYBlv)97mYfSswboTlNKa#1Cnn^&A&TCX< zY3EK=%>tem_8y&c-m)H5k#(e%?8eXv;Zw~FVG+*@L$X@dtE8ECrX(oyOtj-Tt&94iK{Z`=T1R?>>)ECYgx33 zM674!IO8@mq*I&cbrNtD;NJ#yo)6g!u_r@i{cl;#fnq4s?l|#?b{@dr_76tbe1WSVsfqJ9wR~tp2v_Z5Ta{^1_Q4HRqeVDX$4^zylgZa4?8jt#WnGN zVo?oGx)}^E(eeyf&4KIu`|FkTY(V$vuZ+0$3uhWYAq{+ML{>QX*L~mjjb71EcUrC1 z0}%T|;@d{2Oa?)Z?3%TfHtU?hi9oS#*8o>$EO>2o#DlIG#B;zj&|Mp^84Ja(iM=Ef zNTvuz*Op9r5S`Za1UUv8gIN`1Egvzu7~EE?(F}rs*k7|uqucF%3ie=|5kcz%vEA29 zlU%!VxUTpc(*Qfe&1RDvLs@PHuH`Q0j#lrHTKD-W+7<{I4u@W|>2~6n-C|_K7G^sp z4{6nbq}nV#V;W%KJQ(z~VxKX&_Xq0tM@2PqK#gFPdUJEruH?`JkK{@#{bce4&Q~d7 zgQ>uAxRxu!pt{!^d|^dI2gTFx4?j;1#-)?GuH0kCxm?&72#7DGY`UDs8;p(&g6P9e zXYEJN^J-tDs{?t(11|3+xUTP60Td!21hTSsS$av6eh(|6Y~;5*n058OfiD#+fWia> zC};y5Lz@FrdmYy}5)lN}0~=W13i z1dpP~o&bFXOx4qDOZ%P)5q}2{XQ9`%XaQj(51Oql_$tn^#93^utTbIH6TD*unOal9 z3iI2yZ+`=Io}7dH^1%0dh4&!~7Z8F~YN$51sq2ugqEx&AG1Wa&*I^aGc%2&OBG-Nc z7&H%umw#s^pw*4f3i#|ViWop!+RhPRt;++vV;}ZmZ?N-GsO0hs=z?~htu7v&! zZ?atQZHTRIZ*OmR7u^~qivV$+NBn~2ygxX*JskE>Jf(#Jr3#3RGPCfg2od3cl;`;m z(f}K?w1tX|#%uCDFLCzHAqE z6$LGiqZ}=dn{dawX1J~sLW~sFJw_V4Dkyb8oWpc0_cxMs*Dp(rd49?~Ezo;(XR5OR zjom{b#48X|t#*sWt^dH%d=LbAv)@pPo7+hrBEQRU%z%CV8<5A3W(9gQ*WeMnGR?M|;ZkhUdogt#`D<~-B*ECfmg$H~%SJk!Ch zA44$KX*A^5p4Y=E!dg|}>t=zjf$2WPDjzMeTMKakC#2`Ohu_Ph)&fddMz=N=4lW_D zbz#&50gV|83HQ7yrFK*Uj1kTllLRydx6RvHu#C+(;fyg+K<`;yUEPUec|VFmBhM+r z#;4=37wP_!Mq;#pt}wDn;Mt%(64iPO-ej|?;Mn(lY&{r19JeR}y2?mQoV(CYh&nZJ zbD@-t<2W9?g(@}{{}yVZauEe|4P1x^Py2cpiY+O5hk9Z$6&&Ug!Z}yUKik~gJU%)) zGWw9-SuDJOZURMx{j4a$K9;lMIHC=f$^~(atkt9eFYNFqpM?is2VtoC&`0y6l>JVp qbH3h6QG}i2R|aVi z>nKM^e z33x3)zR+woH(|>vWr1W#Q*jx#{G0Gkr#4wFKubX3fhYV5s{p8lL5MzM4ygrb0V^vj zm#QK>hoBI_`@YT`Q47!<#-}Z2Z6VP00Doo70$EmUpv}e{5V4wLpo8$DFGoS|f8wm7IOd0VWelBK-T7V{?-Gf0}Rl5glj)#N6-q-L? z<6(v2aPI-czP@2fMt2eK?Cj`h)P>XnG~v#jJ6F`b%>2PvWnZpkyJ! zQ<*I-R0|Lr+dZmAGin~t5E@&IJaB^B0mJ$(Q=r@3(=y2C@!AWJj@^SNEzmsLhC2mJ zphKZ$WCT7S#={)1wE$__JsB9dxTQGHTSlQBy9p>1F}MeeXAyWUK$DzcDS{x}WkhuB z9yqGsXBG@*NzynDzh^{}Bsac|c`ZOfMABZ=AjnB-UDxh0cvM#cJj`9n1BQelCiI!~NUv-=XY_b-UdU;iuP`0vMvm zvf=-jKM1%l>qQYrYZW0xA3__(K7(>J-gT|k>fkJ{j2p-*dk|`j{zvX51!+!`^pH zAuwv2W;1FE!QPg=o^uWwePrwbSF(W?75X9gJXDNs{m=ilws;L7SpYX>2PdYalrO zl2auyn1>p`Rhs2ARrmM!j)DNm*J{oFT+zWaODt7JEyf&hTKqkWSX)~wrL_7G=YF6= z`v@xsY}!Ys+3uMJ4l1ORG=zMJF|@^yZ6NSFT%h7QCAz1!HO-G4iVboku zHKM1h1&mZzw%dWy>2CHl+8gLHU<#C+-=hS?waA~C8e5DCBJu?@yUzcp{$B_ z&)B<8->N5OhVjp8E1~S59vf@B!>|zt!Q7sL2X*BA{_YmjAWg#!iZkut4Z;l_jrQ)d z3W45rOrufRs+&#zgN?^L_*#fvBQTqAH0s?p;qirN^hdJI zI1hn)`A=4{t!sC8_ah++veOkSR{H(@ADIQ5^asKU0{F{ThlrRO;FVS9K6S{)R(q;0 ziZ~ALB?+@a<)`&PP~#g?9S1)X!MS^G+2DC-qJe#si!J+owz9K z22&?MZCY>~zS-$?uwy*gF48ob!6szbfXbP7y%wMoFp+f$LU0MTvm1;kL^?(@w%Ws1 zc+4TMp#Tk*E~u%jOW4jw~?FlnJB6W5GoU7{f@JBmrA z<_>XbG#}*@I1*`#3_)Jm6BeeYe5uxta27n~j}7C+sGR;>8_h>KF&1`G42X)Wm=Wjk zPEn-4mAxJeWL!wur!~d)yK#=fMq#QfM!7X4Qe`R{c=^~@q#X25~h0Tuvk$<(5 zm{Ggk2yyHq;YZ?5r}cu8+JGYq@;u$r96P{Aqok9j>D<&6dd(ew0CBGWob(-6>v&9n zK;7OlJk~MB;jmejK4li#qkte}Q53&V`I#WV&zVwBE7+9a3Fpl)2#PhPKvA$Wi-ec+ zHaJD)(ly5UO!J$YyIRK?8%XCAun2t3X7jAApkqq)+h}xdKiJYhtt%U>pv~rH6H3u46cR0k^ftV0&cNhT_tjO&)Z8R~{ypB+0{0r}-6AC}~1lWI-h^r6=TElfdOV z9uXj+U7Ixzn^To1%_d3U`_YM8;n;yjga3mmut*EBnc{?3y|2e5IsB(_zWHKLv&(h# zYGR>A2H?RqMio-sseYJg2|{>}I?;LZKG7JTt3^!ogml4-2GWlK&1YFBd9+FH-MfDa z<4DtjfZkkez<_;)7TGOP6e)ROMj&Sv%MjVZ8IkvQF~U#HmH6In+YnnN^NP>tq(TS|Qn9u`xYog0pozsVd{d&hS)Z zI?1b48%&56u}1P z*vymZAsx#Dn)mbU4Ssg^TuQlcxfrOBTD z#t#cEkt>RX(YY06V9k+;ZTP{HdyOeEzuLe8My5gwA+A!LmV-h64)bshD+OyAiD_CW z2@e|0Y3ja2S0`qK+NaxnNwV`MM!E9#>`VVfD;fy3Po4`>sSPsj8s7t-O|_m%73~uQ z(Z5t9@jgw>wjw}r9ABZ|&|hthrQ3y1;3*R{0Qq5otr52`B@HA|^A)2YVWHys`g+%9 zBT(Z;w8{_=1k^8XTNUgYPd{Z&hMp*WR!YqmZf&r&er|KTsBr;;fFjxDKW;_qw(qho zdNWzoho1O0HQV?W2HV#)YAtpZyT+@|WL=gYz6{PIrGC-uAX73}H*Fso*Fe;uqpcwz zHqn_yKiR5F!}Qc_qXLAdFGi%*3ZJqHq2paQu~nO@`NC}t?IX0QmTSoTL&>kD#{AojZ4~sAd&_<}ebP@oj3hQ30Zk9pF5+R)T99 z9lHl3{{~N)UQNv|wMU}q5!ANO%_^`klhvE4*+vD3I(A@hFY^BAu50($Y=G&*)a;VG zw6?t$ARW8M(M;A-r)Jlk>KCAE_c)$KU&_?%k_BksZ4$bNc8?UUr)C=!AZpk6m6ef~ zrM*7eJ(8po`{;`$_7P6azB`#|tAx>5SP(QW1Obz_=8GbJX|X$nGOBSd1jm@0m_%&| z>r=ChW+Vndm{M}VCSAI$4TMzz?H;(f%689iC~x;*7y%aZk`M>E)G6nDHw?q4roHnv zr)C?~K#c}(Vj`35^>D-EJWnzA*^3NBw0rvf(Z@{TzM$PR9QM9r5}yTs4|soX6a{-1 zn~%d!W2C%=NqE>{ZPF>t7cI6Bqc#v2a6~CDVw=q-DY&wGURCLext`z2a2Dl0)-Exf z50pkx{Ch?a%rMM(mSv?m!VYm|_q=%V;(hpuH9pD_E6(L3)`F>%{9lD+ry|B4&!8cc7I+b8 zFvB)^Q&saYKjK_oVKW-vSFwd&!8eyp=%Ii|^=^0PmQJt3+U_}y)jrV%hJM6@L2?=U zJ%a#bRHquAvbMLk-;JW?#}Hz{_o4iG$8oMb-K{GIN2_#oC~TLh?|r0gHyRi1c56p{ zg3jbcCNUzcs%$nJt6;-0IQH`-NoE=`puoV_&cZ+6hHKt`{=9n?J6JHy<1;1BwS-eA zKml{WB*JZDE(Q>!L6E2L`c|3;02$^TGc#LPr`ha$E_*SiU4YH?1@jmu8Hw0RE{bd= zejf}*u_ZhV2e+`=?wO0Tl-6FaciTkAc(>^Hr#|gC&57>!`xO!*{=ig!>eGztr22!g z113f>*#z^1YvGZjl&TEu(X2bW!UlNSYBc!Q7Q+;1OG!IdtI)~5lzCwY4$j47Z#bf3 zE>P)FYHT}~B>UfhugPk;HRsJ%5EN@N*9L)j;G$8c4y6eYf^$Z}&+#k_8!=_#v0<>Z z<%cBMRc?Ao0Un%vnVGJ-oy=U}#1AA1#Hr3+7b15xRw=_!3${yPj$>PEYiq0h{_rN7 zQx!ASvDSO{flmT-0CqV`60s46g{|D6(gf!Soj6|EgwKij(UE8GO&ae6XbOy+gEopb z3}~f{^TspI^T(5nNv{^?Fkrm3wN*KXrOyI1O}w(Qaz4w#RnCJqgb+B`1aJDWEdbsB z@Mji=(XJ55P8`QOI;Fccr=NK81iyCE0&8k&YHDg~YHDg~YHDg~YHDg~YAV710m%&( U?fPQiL;wH)07*qoM6N<$f}&yN0ssI2 literal 0 HcmV?d00001 diff --git a/frontend/packages/studio/open-platform/open-chat/src/assets/widget.png b/frontend/packages/studio/open-platform/open-chat/src/assets/widget.png new file mode 100644 index 0000000000000000000000000000000000000000..a26c942879aa619803a6c1e4cf9acbbf28306869 GIT binary patch literal 25830 zcmV)-K!?AHP);AhBWY$dcQmD^)jfUt z_U+}or|PY$Q-^3=#${Z_Wn9K(AW<|f$1GZF#iFrJeK~_A~R-I#w?NGWl)y`p9Yg2>?0Q zlmO5`_bJeChLW3uFySlnsx3{-@RaI3Q+vlBk9&OmLL0^6K)%3_C zj}X=ZBmhUQTYi_%W6sULsVN&MLSp(opR^3bkH)Huc~A_{` zj~E0R`{kUoXV2EWU*~eU=y$QbaN$ClFkyn;ym_+$4xd*8H1B)g`!qBQz`?)>sJ|)a zYDyYN2J)hfRd9?+Pz=I*qa3?ir%fST*LP5+jVbmuWD#$2%dfRS)*O1OqmjC z6_|6-AXxLg1Os@_#6*ZjvuMGB1zw?0 zP+0Q$yob-vKL01@)>KSAN0WE1rz)3uUT!J>Vgrqgv};{ht0})$7kbweRL7cKoBm`C zOg1*tpe5+Qfdeq#0@hLrW|)8kyai?*G#{IQI>nC!1VZ>p{vJf9Q`S=UpvdqDWgbQY z->Z4qX!NoJngERm+uV@+ty{MeFaEM+%V@^z?_A+kHZAkLikp?nEv1nIz@P-0C7|M( zVxjBaVqe>uy}SPO(Ym_2V9%aCMx%hMTz1)I8s-+biE9o*EN6-w3=a3`_Z&+-`*H?H zS%V@Yn3f2TmFW4R-p?h-+?$$hm>U4YjvYI^RjXEc3<6Kp{qdyfQ{HfMMXve^rK)eD zQ3irR2u!cX*zCHeP}uh9y47#rR#{mY2uESgQj`O6{|pLfm@JpCxSa=b`?}vLOJI~G zCcuu0*qUcK6^VJ?9a%$|Ph+Z0v3QF*z`X$*-DijNfv z9p5|B^~z(fu7BTFa1!=%1uIsp2(G>MT9}CvJ2B>$c-zQvN!vfju}w>Q_{t3&Wd#bI z5md_XjP#2vljD7XM+QK!gwuQa>8A}xXliQmSVMTPydT~cp?P>QhPq_S4 z00cyNRew<7H~;rVSHAGlIm>_X_Ku$SF5nnR1$(Y4*o|zS7x)!i&*pd*aY*2~%3}X6 zdq6-&y`~sUOu-%(*|-uK1*>&kHzwl?vDk`SE&GYNtN~Z`21|z!I z^WTTsp1l2kANjp4Amh*;aP`82C2r&KOcEV1QQ9S z($!c0c;1Aj3;%f>496w_$Du>d-M-<)cWvXydx2v$0o=OmlrTVG-VwORyD<+DRYqy+ zn6C==a(EJ8vB3unMSNr1yP+^X+-*Sk!j z9TP=A0>hLyzt{Kb-b>@0;e>@_G;f_a@zR?vxb(??e)6#kZf9>*@xJ@+3v84;07&?- z0DR6#p>e*SZSsybtug8}g&sJCAQ6Ovn-yLW$vQ>OPghr$&o*!FwHK_sqGrPBcTnlv zqH#F^(G+a&KD_n)58Z$1ub7M%LB{v*-)}U=%{Sj{5)*&<%U>4VtYS(HvdRJnjRF}J zpvZ_wll?oLsAqs6xk+}Mfx7d~JAIf+b#-;Q+R5)|oYMGPpB^`rMn-~S_xD@de)GkR zFMV(;;v9{QjRmB;BgTOoD|r9xJJVSaaK{{u_R~q0lMad$Afkk`bSiwQ5xbzbha4V|Y)JV+N66s(sg5-g(-M zJCGSY+;Y->SRZ}#8zgeK0uabaf{z@7cQ9b5udmNPW63v8pE~vGaWIS$fz~*5@E5nf zyyl~u*^DZ1r&VN4ZnOm!C(R~sI=*yTP9!HoW1Lh_IG%~f)8zIT5}Af)2|W(>-+%x8 zerIQAMX^})F1g~#_xTm|f5~5~r*Rog(%#o|@GDP0am6=%-w(un3)2cb!_A}02*^g` z-Q{E>4U7>7MMkgS#vmg8fF6a(v>5~g4FT=|GQD-{*4(5?lX90{_0*UAT>ZOgTt=-F z@*Q7!;?WD?x-B3g-q6rc6cWy6Fi1GrNVs-@kQm&gPeSh0h;j;tKns_^?ub{UkGI|> zAAn#|or8v0zWlEyU$$b+J>!5F{gSJw|HS1h);_lQoUfn3u5$2K%X?iJt99? z%4A9fZ=zrv{=i@#tRH>!(F!Kix!0Wkz1P%DJomdw`KQsil%MG0jw78fzU{fE-m->G zsr>x;^8pSPx)i1wWjT7qf74FRHCN8^s>GJ=+b?xf6zQ8;Yz{s%!rGWHwTQtsX6Isf@#;uKl2ZK4!aDijkIfcU?`q_vG>cIN|zctb|u+Ig=g_k}2hvR@4ds107 z{r_Ba`9p7GK;)X6n=6pOiOgpC9NtHF#H4NfQDu=*j+vp7Vbc^PQomjID*$1(B`Ak< zZxAt%>WeOC4N)}?h_Ni)UAsQ`%oEq#Ic?gsVpCI7p`)Xth)xn?!tIDj?WTk15~=4y z4UmyApUm&1lEKUhpg>>_y#$E$`t|F5aEKYFef70X&6hn)<1*&uP}^@-y!7nbSHqOz zcVD>v`s<4*3+`*{L&E1u$fQdRLjVxa5Hsie z)AGjVOMgn^a!N?!qziv~_IdxgoXuu`^5n_hefQm$Lu?A4xy2Ifnw(rY$)6hXj(tc^ zs7sWrl$&ykk6{h72}yk@KM6oASn{>gXUw?qXEg41J*7mmyY}FoRkyzU^2awL6K)!5-v9)w&Wh9H7ef-)qaNaZpaakoV6MSaud;gT7ie6@#Y#`l-oIJ@!W+NU z)zyXZ7-2_TqMqg-XhnM=PkzoL4| z$7o!}#gVG&X&-Bz@v$@5ipZg@z^cG_8dw>^FH~}BvXUM1@yH2%xr8m3C{QLNtwc-k zu1XLoRsH%YOV0fCFO|y8rc+4pE~T#sw-XsK&T)$Lu&2I_VquTex)=Ut#i2t#J^+u@ zk|j&bKqY7lBo9iiTt^^&xDhv3dEuoGy{&e_oPVS-B(4UC_i0LI zK@-)~C~BVU(VWwAR9oXwbF)u1wO*pFw;U{*zjy4+Q&+d99XkqU4e0cL@M9Gfp>(#d z{@sl)|Jftlspc`}`;tp8DPrPFOt+Sr{WVH6&x)7PG01U)qQsB)3(&EcWG^It?bxxS z@{BY8zxgv~|MpM3F{UMwporg9O%2gym((x_Drm{#N&|xALo0DYo!s2cz&OxSpw+8; z=;aN01DsGPD;m?lU^8mty3bv^XU|;+n2+Y?&6`)a;DQTGH9{0ZaH^h{EP`;Xpg897 z6e+h?qT-t!U!90pGs3C0wbf(0Fc$=YKW+N!KRz}Ap)D)W1_;-!sHHO(SJ9F)D{Tg3 zc$~X!H#5b>7q(BIo1?jNa&-RkY6HG)JM#3vgGUTN4ju{|4l>37!e-RGg&+NRTieP{ z<4?9YaF+lDNVm3=Sw^N)Diq+>mKyv|(lLt8E<+M+V@N4HO(=oSU{I*`&F*k5ZJW^f=7*Di; zs~zw!3mh4e))e7dG_w8ME`eRIf4PtT{Xg63;6XR3RHgh{$lw=)1;(JgPwpTgXS<(YSSt8o8{``0XxiG^{L5E7 z`LVG82=ON2-{dAmfAUB5^p_u-OlPr~O){nEX@m{t8WgmM<=yUN+8 zRPvno+mDXL?^ot-&&oz7VNH>X!r)W4a^ zQludTW;t!PN1yw2Go6244dra(%lV;s6qz|Lr*O<9*{{;zh!*86DKMk~;hxhIP&y`? zy&hcSRhQS&CqB|dO_RyogPj_?Y^9>I=}+gJ@$tC_4<5{+bSlR5!!iMbM=IZVw+5Z;A=GvmKjJ+L%}P`|#l+{o8-E)AKL%@nyJu z;qhM4$&(=HdEz&Zo&P2_pL!6~+RH8W8XNlZFrwe%Kou?I69rpJC?M-rvj60LQg> zu{QCdb%y4#*JCu79}{xsru3orH5x9Yb&&*Ms{Am)_fvhznP>m|WpLnN8n?A;*Lv3T zsoVic5OD^%VS}S-i{mn%w5OrL)1N2tuiZ6tW0mt0&$@BHJ2 z*j|Tul1tkDE*QdwSpo#+2aXdIl(k>k-%5hRy&mB^zSV5dcGteHCN!lqW!c|lA@xnm zK7x)HnY5VB4dX5V40O`q7H<1=IB*pC9_ZWyf+EA?6m26=;tu%WZQ8bNo5JwYLx&Dk zEMENA^KupSZ=y1jC^F5B=F*F*=+FLSQq<#Oo%5Xr=-B(=zW!jKEMPbyAdHTu?B098 zb1G$j+tDfsjX@6Y0WcaE?A3gtqTDr=oA_SM#pj&+&1L9(+3k;N^d9CdL3%kf2GTzS z7%pFf=%Wl>9hCWG*XI1P-}MK>bVAuf0HUR(#dw^ipZ2D*)e->%V)A4~zt4?Fz#v|x z1R#`<-V_)p8w6Pz!2Ml!yVSAfGwQEFNV(=!tT9}Y?mPGL*4yf7)-2zWZ%Fw8qp|sd zx4`YcfB*g*O090(xKTcGC%UIp<&UG@Xvy5Cbi|q{G4wAQCjrxmql(_lnKKcWK=Pk| z&TIbe^zzdZ5-kqcQ={k;AD+xH2xVQZF(6dJbENt;>~t?bls0?Pv`g(vc;(}Fk6usX zKg95SIn-l*MD&68HquwWevl5CGE2wgogWoaQQ7>vlcwJE_j|Yh=m63_PCM;1J$35T z!pfB^@v%{Z+L<>I;$(1_1jkX$Cpoc8u%VU&5O9ahn>P<-)s2>DZf?#sPrK?8Dl@^Y znFq(9c*h?#(A24Z#3RtNiVM__OeQsOZ?1+G5Lwro^?M1vp;Tl_$r4B16-}F(IO^?{B`}~Z&R+I!mvaO6VW}XC0{hvdr^d!c&qld3%i)wdXC^3I zcJ6)|sFPuUmP?o_;aF;FYI6LYTy4$V8>mbqY(!x^A_$6f%S{tPBI`jHa`n|$`|vpJ+qch$rdYJ-lXG*GjlWA}A~Fsd4dIfDs_C*z zYD06w3Ct;#C!DvKKM>2))rcIj^@=tidB#nOKwAWosWhUC@Hr@ON zYc~1)U9O&ECyjPMh-3nT9AKz88ZRj;Z{%lCjxJh$YxQR=`S_dB4$(P--^%oG5ES!H zrG3Q1RR*Ie1`2G5*9r3Q!w-X4D=c9Dq3Lv58M$G@F%df8sP#oPG-XPTe8}gy+LUTdr*4Bl39!Cgj%pzv-?$n@xzvzzv$z0{9NrtR8C?#3^tuEtcfy9J>dVQ}6L|cTm;02l5hF3^O z4pYhr;zbG|tpu{nC+o9_%_td6B$KRw#DHAG{e=`>)N`6P{kpO+om_HFcJv$y47fmu z2&oJoEfbTeltdaX+F^k~4lwL}FJ4q-hSwrqqsbkEG5TfRy!T#jtrqZH4~c?s&&qfU z9OLFhSs7SUqz4bVsDhTj$*~O*gY6lIA z5^H;WxoQhY!t^6Y1Ex|}@GO=utD&&URPwu{U8<_4UJNrHZFbI_Iq*h-_qyIwx1wNF z0VI8%1SrJ5veD3f2m-kVqsKN(C)N_E3XKd|-_K3FkjhB3HJ#2}Sm~r)JEjwrx7m~> z{W*q80VM1gF20z9c>#DuU^_-C3t`XawY);QQLeqc-L%mK3l^B!vS8f+6j{6?Ev4cV ziIidq*ug%->@wK5XU`tvb%KBR;w!&*36;TcQ!$++dcE+BN}AkU5t~gB8CCtjFm}PA zlK0Nrb+ta5P?a{d2S=3$DiFw9e&Kg6=8oD-`U2AlUa0fVJI~C|?v~mlha#lU)6aZT z$(o#z>4XS2N?_i3=bb(x*gCHE%%;=av#1OtPNECzv!2UlLga~2{YiCtJr*SGL5s~P zl^Fhd?$TNt%qy?KNYyoS&SsNdVd59@LIEJKaKMR$Y`c=qh01ivQp`kU_83B;Wc#r3s;bJ&fP}x-tXZSx z&6{U(p{`e_PhFh=H& z?%1)zBwXPTxMgeV7M)FHAUd(7O-cNS6gdRjm9cI@B|CsP+0p#vQDj~Y!8CFsDh-p) z2FLXfyhD8`nESA7z$;LB#?=&A?K~QJ=Tz%Z&OWooM0^iE_@EgFiyy!~L=M^Y<<(S8 zEeq30M0&m1Gb<9kVz~$Cj#Y4vQ4KvEYs)Y`6F#KLlYPen<$Pz9OJ#NQ5>&b8McS%j zPn2G#*)yhe#0$J}8IsIlrQ#gRo;+BQF5t6-R$r24d!9QZY2QQRgIdKVzKXU?3d7cN{#yfz1LaKwOO`m8sV7oQ-qB_=f`y2Mk> zW-1}dRhP{(GP)ZZvq-iC))C2dYc-Z@R>Ht~z{s_GF(!)AMXyD^K92PM1$CX_XwlxsTSvvit=c38RbJi&Z= z{PD+4RB+m~X<~nw^bXIfEDJsn+r-I4zf;iJlqp%AfIORsf0Y^Ss;Fw139^m0iVokF z*2LtbT82QlS+lMfP&f%ua&UAcNH*%k;)MJ5?K3}uJqOuT&PNypC=?t=2 zP7&N8lMvcT@zJ~MQ$bY9B~4kH)KRJB@@%|Z z?M&{t&FD~@l4R-$a2P?u1BJG&N~CQhBHGR9qhN3-_=BlTrC%?o%HdTHzsNI@7*2n4sXU&NbCsTi z5a2Lcy?V9zh$Gu>nHwq@yfY`W>EgHDcAJ@s6(@qAIs$uPrNjefD^i{+7fs=aV+KUk zI*7)@6(TA*3a?yEO0;IQQM1i}Xl!iM>_Lq_hZTf&E&;rLX&#nnBx-KL=0ctb@0V^k%_ zXP9*h9X>!|)5v4Hk@}~3MCOx_ZJYpWMj2{6A)0Nb^1|G9R3;MLvW^3H{pOfB0YsQd z7j--VV|j~;s;W!f*(CoeudV|c0Hg4b#L96Bvk%@>sYp)bq`O%YDrdh)PJJfNg5vOK-0v+AZdj zM6*RxKz8ojDVrEqL1ih9rtrLEiF%R2fJ4A=q)St4TR;a62Gr70q_*~eUxKDWJ_Jm; zh|3+q3guzNM45&->AEW`fu^92j*ckwfHn=Hjo@_JE!!_4kSKQDx>K*h1q%etoH^4( z2jPvyZcV#(<>~AHw3Ghm9qZ`bA9#iS_V#VGYfqjZ zyf&JotcG$rRn~nwEpqq)Iany48>*OIMT3}}dkh6Yb!8OTv58vT=`sf-sXThVMpo@-S0*BV)ZN0etx(A6E#dq22=R^Hn} z`}gPB5z#{rJ={tk{lq5zOdCxy+@Qeui^cGIpuF*$$I5LI0X&Cfd*XmdXo~U?=Dp~^ z`@Ky#k646LBwVwRrckLtxX=>tK7ms}=FOX@+uGVRR1k6|)@dHC8xdjm6b}2GuyZ&ZXGc#3MVftD1o79#OzZIjPYWu(gQ{*MHm1+M*a;$45SN8vV|1HAmA6 z9FrzgaVIfxqE8=u&(zT69Ef=ld$xsuhc0PN2M!cz{VRQR&wWSe;33qZ4IlE!00%S$ z%qPzadpo#fTUuHOX-b%c9&rlmmNnrAOhzAmPpe2%sN~#slJ{g>vTQn;3ggI2$Kf!a zSQYWowHHAWItilrEiGGGNq_u@4TOPJzV()Pj$d6s5dtJGp6t16=bXpde8UM(lw*`q z#I#_Ki~Dz|MKsm69ewm~-`T_FKoDvyt2qDZn>*>u#kF+)YirpR=?xkbQ9Q@#B#Z_P z#64>#YPa_lg?`|T=Fc$A^5n@irj-8=et4MHyofP99>x9b$cYa6I?<-te1gf(8nrWM zZf-W8?ccvYVB+2HJK}v6qd{w@48L0d4WtAEjIa$Agf)JMU9!4Z=pHdn;c^COh-K$g z(0l%DlF<&)#EpK^$L`q~dW?I~rzDRsC#9GV9EZrXyE#lADT|#|^;pcYV;ANO%vYX# z>aZc#6&$!V3j+WS7c39`vh|o;!s+D5ex%ze)$chSkjyRa+t09`(wsBz=EC!kCcMS#GP%c$>n0deccej4EcU>BGC$10JJcBn zFmhB%-9S@-BDj*)6mFKRBubgTF?+0$Ps3bXyC6saBWf%|4+~{96j3$Dr9S=_v*^Pgm`Sf$ zR!5g!GLin^_omP{|KW7@8hXd%j&;wST&qm2k@fuCm{3H*2_|?Yg~J_34jB2qgNNDE zWWopBk!$9|@Hzz>H*RE*R%^>Cw9O1pWP9|>>T{9;wakJl12Z5Nc>#RD2M;{9=ZwXF zPQx#8;BDsgoXJSliA7siXy^is`Ua1pzB+ozL?>i2$vJ#}a`p9om{4f1QR~ZM%E1O{$q8;x^7e1mhXo@t(^Tj;gWUlpRC)K zg4k6%sll^^+mFCB+|by*^M@P7CCgz1lfcTH7-P20RKiRJ%&^pS@q(;kXwj#LY70Xb z;fS{GLbe-!7e9FT@UtDHi^FU}>F9s+fG7Ep-(0d(qiq)`fRJ$^5~Ufn*2%T`=olLi z%$Hklne2FCl;Iax!@qy|#S@LTC~!Oh6s(6AM4#a;Iu@^xiJlk0-?Yi6!nu&rQM6NC z0|1pE+m-Qh+-8%?{(Wl^#ry2DRZb8uumC99_fgc^{H@vHGig%JcDC{^hZhnYqv-57 z^h^gistKLB9TQ~CCnN;Qu>@6lD=15crWbadAVhdJF0o=0r{NZ6Uq2iuO z-b_F}9I$c$fyRM33yZQX;0VHrL*kJ40|FT|J%l zeWH>+htUq$NNeXnoBPx9E{c@wpzM1wiIWH{iY6HW^Ql;9-$bD`qhTshXeMb6-suGk z7NEn|Lb7FMqkiU-Q?$VJtCnPI$&`g1He<#NjHSSYEdh>)roa#P^&Q$&Q90?gGz=m^ zA4T!YKiHZ#0Pu?Fopng0@ytc(;W%HLs&X-~_og|Lw-#S)pfgbuMHeD{eq@G^g z(oJ1Q0$RGX)@TYeh)(|dH&5s3rz^WmZqh~PSJDmF)zI{5KGoLY``PZ>P!np_4#>m} zXz#v&e*Due+Pt-3I)}?Hs^Z_Rq56r6?PGOZ+@X}XK-Fm=5IG%;AhXCjS1&*AwhVbkv(D7 zZbaLxGbZ0QsU8U8u`Om0$97q8{(t~*F`tq)Wi>*8&ff~ zZ!K)X7w3m2Q|wdM5ddPBqcSfm%`J`v7;+pm2He9F+nk#<1e-el_pUDb-VeHL zuTHe??uF=|fFJk!Tf z*?`{l&N}|Sj=0N-ifdM;(>c&?Bo*Fh0}BY*hSmxPTcM}c78oJ8Us`{JOfNP3cren{ zy?rCJ1ak?rRkT?+igbN_y&>8wR;)mZN5C;S;eZh!65c1VsaTyv7B5UF5E5)(a>*qi zEgIqR?(S}~CkfgPKC_WVM8cNd+E%p5cHGbkp}ie0+AJo8x}f9zMZ4J1aiQ6z`;qnI z5m)P=VdiY(M8nv#r%0=Q(L>d2C$HvXtC-Q(av*QQ>u=ur04aY04U1TEhRGXsftNhn@;<%$ zoioGq0tCx)VR`{PLSaBHnUdhUX$tOVN@e)iPs}z)L+b4d=&{Fo>5hLnY)l~2m;ltk z=}CFJags+jTw6_Z=jLeYlrU*)-~Iw?hax@wY#+V+NyiVJDv!+wQg6Kewl6}vQ zXI;md;s~4gT^zV$-%wX?b8~Z^&8Yn3$&-uH(KFdLpm7U|M_IC|QFM{vC!B)Urf?L} z#%<*fGqx4M5sYNY7dkdpR5UK9VGwa~h7HxjNC<8;z4X9CEp*+A$$dh#4zBULMi*o3TsO6??^)tGozPHtRMfZJL-ka z62!w;T?G(3e(iPgMnkNDq6x^NH{Ze%IoI8nMs9qqH@j0&7B%>7lXC zThj{K6so>xRlDg4108XZ4&US1zX!!!+WoL)HshEDd%CkhULqJd4Lcl^l&nmNOB8a)}1Oh^MEu~-n< zZGfbvDg+74LcAuN!44Jv#X673FIxN=w#hV7wr zCaN`~SS|C59FrLEiYg{xb-dK__v&uEW!uk(k5`zhq|n!mzTNVRAkQzaV73v|TQI+d z&OdJwuj^v(lg;ErgTjJFggl{~Nu&$^mXvQd3VfoyuNfJ~z31M&Mf%=%JE^TTh{&e_ zu<(nbw)@EeY)ons$MAP1H+l4xFHWL)b1OJdM?+>h$?#QZ2)Pc?0^+zxns_<_(5B5t z=*5=~(=*Stv!{)WCg-Qi&odW+VkC$*_{7iep8IvTy;rNd z{RyTTqIU*>A&xO~MvmV7o{99>uXi+@jO`o6rJE`fj6*!sIdoFV}TbN^O3lqA}c5GX1JyyA%` zof3;;i^B#33d%dUWGWHFN<7-JWDO>-u#ZfONqS zsMyaR#QU)g(`3n#3Im99&Z&&z8bb+)$Sp0NC+!ex`>Z-Ui?nTfH?3LAKzJqug6WcA z%9>GCeh3WZSs?g!dt}_Okk0mXkD)DsKiG~t0Lcdb1)t!~U2oX1LECEMVjV@!91#^i znB=7LPC0%vo)yV~%-;RFy1E<(?s6R2h6z=}7g59JRo#>+7frkP%Aeg!!!9u^4%7ca zb1FT`9!+#f=Q+)&sBsbRL+kFB@I=|MB=KQ09Px= zA$Dx%c*~{&wYS;ykFFr}n!&_l{&3pt9L?nb-`VF>7!Cn*Ph8DIeO+!$LdxYxzSUYb zi+=rh8$G|~uo?CWt?c=p1we>S7Yvxw$1_88!~L-gKQLZ+?l0c5XXpQH;`S@16P7-X zP2|~OR6vXZK|5GHcDztpSx`SsA!ZZ~M1=zg0(dxtHH8_i3suCvUm3hnWPm_Z6k1!K zZSU(l_(Emn7jI}%1vn&4Kt}|dx4@EsfHn(PyD8hJ~bgn zOP5ZJtjlB76s5>`DKJUC6dLx_sX1zH&e6HAsZ1m@%5iaQB|DCCEY~#9chbk(R${hs zJ^K&z(bvAd&6u8^kBnf9WJ_xUmEa4W5{F1+jnha-U;fbZyLbF>7jp8rO$N*%iiluC zOQ7O7TQy|!pp2lymtqBJi8uw;DKm=FIZ#L;2a`w0oYzJ6_rePWTdm0JLg(R)ziyg5 z+@K&z7bjHKaW84jhHRKW?6#Q^6+K*FO5q>?Xmm^<*`0ipodr~QMVem>-f)Y^86Q2+dEDPV*3V0w(t?8n6!7f#mo9}nmTb998Jn$a28VJKg35I0DQ6HjBHJ>f z(emZXy{Dgk+GHD|h=El}#V+W6evTVny8XV3ulVuXht~_$?`q{biQM?FQ63?|Hcd_i zp*dxb3nG8Kil)Ui?pa?YfZ@tOf4jg)e|I2&BtIW~LrVX4)^=9LYu52${*ujWDkjU> zknti_IBwOLC&ZbhGS=ec)q2{yYvpRP@d+W>=(uOjoEc!q1ACpc%(a40!(DgXg-vNN z*q{{=ooaE1)TWP(G~C*}0~pFW>emodk(9 zYQq;7qQZ`(H<9o#=m!r8NMUS8JMPE~Z&Whvq5pRh$2i-=f3N+2JAF+9U29^Y>{_-qG$)y8jv!u z;!$-1)(M^JMzj-yh~!I9HEt1u?c2ATW5D21C=Ebhn~i-HWR_Q6`uualKN{~?C8_SQ zN;IG#PGpM)Dg`=DcW^-v1U9&(qjk|lbOZDKD5#YJ#!yRtu!h&*0-_+-7z8Ph#P=4* zA;J_GiI0Tm4B36syo4jJtbT7tJR(GX00Q#%%}Sf&7otts2dhsJg&+V#F- zo!}JdZ8uR$Y%!Qm80U&iL_`KbvMr~Gz_IRyPjR@gaD+yam;iR%GJ{l_MM2huIEN}; zY$MBuOFo~@0kXg-#2Uw>U8gw4i2x&uN0|2$IO7~rxk>qgbA72X5bnJt$D~-GmI&VJ^Tqa~uXBnRRT zn`Nj^JMA=6`Ua}X0t0BxLtC`8JlD~Ac+>qfN+fbjk+mcFIkX%s#0@2H2gyjDPu}Ta zT_D(iR(Na>W{ukH+kC;zRf@-pr-}FR03(xFTdgE%-GxFjNzY^j_l0Y7&3$23tK6Hb z>GF~VTZpwGuK(bnLRe@{yjI~8q|qWrj%>aEz}~xefnf8W$gnV>0eD7XIE;Hws+T$enF#5 zRMgiglL8WtB#R@sXz;R@?eK3jt{~oDaC>7XTU5-Dh_vbxBHn302_VD-5W#sD!R<@(}#%-j1C@C&$KeYJ8kS z(+Ps1)JSZVERCZ{y2MT7l9j)u+5tq|TG(E)WQm!V*aE0nS65eH=AXyK;W{^M_~#9M zeJyKf)Je!GUftY72M^}$SPZAfltz>ShiNQ*$#Dd(`4sokI$=hofsxhgXGz-ZaSvt@Vk&OPAVE&ObrdCtsxLZeALh#cwM^1%K*_wIz*gf=H_ z4<6CUlP6<9oau&OI>B>l%kH~;LJUK4{x)*7Oqz&W0AW)01|>oe3;~hhA1|^OstD4} zUMLfvPt!?T#a`gKE~l+)3a1 zmo2n)Teodop&1gTeh1mW$kG5hgM*|^Cf8jWlIj?=`}XzGSO4KvTF2K^u-B27L05h8 z_4hg87L8}js>pLiW&jr@;t^z1I%rhLhV@_mF93qVfL(#xpWzcQn~g|HsOz=4CW0M3M)K+_;db+DF&CuAXkVHd)jxk!Pf1u1QkxfHyb2dW3%QzyV&H ziq`ao*H5KeZklR_y87;z4PVkaoVlr0$XdOlW!^8CklchZwjx0wrE za7HcNaNQJM*F|X~h96M>^p(J#Uxt$2|KLG-_>p!#zKVPmj){m%n4f%9KubLO&=p_g z=h)4jr6cV8>1F_6+(fU8oj_imBmly5N|YEwp_~|nS@#=cr z^PU}eWI(~-sI~)-P~4zy+RR(#z4n4TMuo;OpwN<0i~3OoZVv%h0iRYi4%MS3V^B)4X{Z( zr6@ye9Tze7efIA*n(82A5?eyjF@JsygMJE~0dQCkQi3>Z2N*#JhzEb!%GX=PIFJ+% zH(+=2<+-Qd_ecA7-@OIdu#yqWycXH8ecUGd5Ww5Hb7#PTyTF?JCX$V5>+pRQjr;HK zjy2m^Kk6K4|BR3&70EsRK~s|x;9wKV=O{NoQGs-F1_c07W0$(iuYTZz6C0NN7M)}X z^KvXOD6pgU`nc`nsbP|$+kSV38MPO=A04mNtDC#nq}Xr3VA37@oQ;lq(YbE8eiGeu z!_+YSyCMWePJrQ4R8=I@7z0lO5T|eGuZX}W0w)FC~R`B&P7!p~rijF6nc&MeT zYs&+_df&0MqNY0+0B<+b?c4 zN@>J@biou&o;9D4V=)YeuYWe4G##pF_nNf8c@t*k22Xa4eZ))u)K z7^d`2s4a4y>Nl~8fX}hkqL!CVFG=v9Amjl6$$^$W`sTmvTv(4pUz*89*h6kmiUI*{~;95c_)mnGd~Z@9v*%v!HNPFv=pE4j2m&kKlgSmvbM3Fn>A_KyZY?Quy6EwIC<;1X=OVa4l|HkruL z7)R9w#?YngpZl3AT7 zHbelD2?(=qfa_2$LzlH%fEgMCNmd}+97Xg{T9eHvpG_ul&q7;NS#41Z5`N{i55B*l z;hbA()JXVD%tKS!ami5hbYvceq-`BGVCjLhEt(*ucdDuibn{Kkbm94vY`IS5ln@hbQ*x(ZIIH6-kSxkI~&3Yl%S+KURGkYHR0qgD90kxcS0s7~Qq?^-G&CzVb(Rj0Q7GM9isJ z;_a}fwT*^U3Ga)TDIqWd1_l{ZDo@v5)5z}LDbX~-1{C;;1&6!=iCKvI*LYOzj&OWO zTxaR0_w1uzKi+QLr4aNL2A)DIEOTMdXecO&L-~Bp`BC$<`VzVe7Oyha6kG@npiS2LC zlGU7PtcrW^2OnyqFMeecy|S?@6<>%G3b8LrDT$)xL3oUlI<>waw^yq{TLYjx^?UPIdkTy7hZV5XWLIfTYz-)wlSmJNH@%=%dh_Vdnb-6 zGwMXe_QFxQ9qS6!g=m?7dKI1j+DUW)n^YmeCC5Z_&k}i5sjIt4yLO|Xv<(>gVfDS( zeJUi`qtRsY425*GzjXKGkKAwvZ+6>2T{p}oUc>zg)5)Y1A@2?yG6*)C^u}zGJSXAA zLIvd918It*K1E%kB00pSewgzhZh?~hZj_rFKlnu@yih3L&!0*9M^$6v(&h^;`_Z>X zlNoiQk_-+e%_*#v1xh7*cNd;fOEWlD0770V>j^FjqihMPN2P`S|aApriH0gD{)e90#8;pkQw&qSk#iH8lkgY?Q;q zh)@d%2*41ysT^q+Y$-rM23wvt^4`hc64jG3II!Um%tQPFHr@L5>x1y-iYPGxGUW5h zXMpr#@{HEjwXJKO`Ov3dbKyV#(`X3l4TXr=5f%jtdnjeSIwlTc%1RcE+4R!-9$vZ) zNk~tO7{&*r_s@mJ0^rlt;W{oi6Tgzt!~;%L5TslC{9pX#k&g8ojwDp4#Q-9Bp88Zc z!V{z#u?ge##CM#UPEjVT%duT&(LjKJ3>FkB<%Nmr-`=}-)sB_}zyAJ|saL;~#)wGcjAIyK^X$+6G@;@03z;T zH30c9S7|-;%=e#o^w#fkTR}=eH-n63`YP+_rp;l*1&~6`RE= z29l$Vr-&q#_7S;hMGz=~dlsH3L=!Q|%aSEaOz~xmwU{(%QXYO~73RQW!C)k)!`@#$ zaLu=Sd-lIbV?^S#Z|m)`UM;D=8s|^tB2Sg)hu$P5o-0-19P32VRqPQPYkCbPw?KT52OvQ@&Itb`7I6S0(ur@H-gZEYNO;jRb5SjuWDOQVygf}&vGx_2x@BYSE zBHr1OjE7{!mSPhu{g_W)1-{`H5bNik{otqe@A<`c{Bs|zJt*g$K!?H`Q(an3LA*H5C!RhHvj}^ z)v8s5n_zW^A%n$Pvt}9A#Kaqf37!S0NAv*bej|Kg}Ck27qd9O4|8p`@b( z;l`5SU2k0f33`qk4w~+a_F_xRXQKonr1H;^uEmDPS#y;y}>t*w**8-$>+vfj&g)4y$?(DmAjcm&zVHDu<@nT1`ucESA$Zfc5~p*_j8zqW!n zIRy#i)KSlJELs9`OrTI1Iq0bXiMcpjC=iS~p3oL{0zLo(H&nD`4WTKF*5C)x2hvSp zPtmjG)qj6w&#smKhsI@0%idipzp-u0cQ%T}fEX5ckVxa`VG`ZP4c~xa!GZ-Qonz6W zMVNQOc$@HjYc_>he-4+B4dv7^8xix#uH_;kdk(oK)(c?J3^NLZh&6_0)Wf%I%qZkW zfjmJ);-T;{1`vFnUZe=GUHyTd@7Zc%v{) z7_WVJqgq;8kSA5aq`QiHiAp}E+9BTXN>!bI@wac7G3%BO(zrNsv`$YA;+Vl77sf+| z&gBIwVD;1Q1t1WMfThsGog}twoVK^nW zkr_N+J?Q!87Pi2 z=qBO_j{$BFxlahdAel>uHv%-QCVTnDp-Rnzf=3DuZQGXr+^}x#XFmhi9gRy_3f>(Y zD4RF^yKsmea0t9-fC2BCJxwsBivkD~M~2CSg5DTS2MvKBoseoP5uea{Xd*brr70jT z>DH-QQC3qZ=i-f?=9>aQbZ2L0z^+;~ZQ3-2o5ul6yfNP~u_*<&gJNJv?mJSsRqnf-fyz8fju3bmRvMSGklx;XFa7#gZ~Vxfou=+9G=vGx zS!+PNO?i$*z?8z2aTq*giX{UO7)d8%uB~VX4YI>@y2s;b%90Z{pVAj%*O4ZZ5qjdP zg$l%t+G15^4R;$%Ka63B`MHUW$PI1PJ8F_d1A$Tn@3_)TN=v*PNLL@jRIZjS? z#R;2GLl!O4sj@iEe%WWdQP@Trh+Uh~o7^+_%sD{3J@!cDm{h?dmE)xnS+%@W*`=C$ zk4g+uLdNYymw)$`DO0X~%QyiaT>?UVVE>~(c;<;eyqheUhIU}i(8uq#CvWW~xZ{z& z!S5Xz4w&-<^}+(oeS*0q5d5Vu9XtXB*0yAg&LY!80gNFHq3VoLdMa73Q;MmX{3y)0 z3Rf*Ef#7pg17Y=v#6h@f0SZ(cG%4r&3@aHBRm?Z4_)xO|FjJ>qJ9GI(cQDVWn?mDq z(xq5Da^Qt$Kk(VTyYJrx{=kAHz8*U+qStzx`fSEDVpWCd)2A0OT^!75N%VuK2@V>Z zQ@~(TIg%Nb#~HOANQT7w1koji`nf93oiIb@(8JBcgu*_|&y7G0^5yZcz%j74X&_Lu zk_KSB7s!#>zyHCV|NF=rKibjy!hJL@Cso?p*4+EVuWtTG%l?OVfCC~3%i6#s_6Z>1 zVYKEkxMUuHU=RetAzVO!P)qG5>&=Wt*V#6v)Bu^=$16jwDKb0(aY0%g6;_J^d-fVI zL7&kWaMyD00!`r|s}>rAzvuI3NIS0*a|&7mYZP_HK0Zg~@(b>`X6p3oZX5SbjjX`C zwE5M4{no~HU&AClX8Z%vb`XeTlL;M}#3Rg@8kw68RsG{J<$Djx>ZJaHN467oLg5K%)gHruUU=FGIblh=@Qj(?4+?#Mh z@@7DikSL^oV2n2i7akJ#8kh$;Iv?T~7?+DoN#+$uMaOPaDiIS=;yVBiyKQ&!@{NT{ zKKjV~1@C_UIM)~+0e9HE>0j@7`Nglizzy3JJmz;GBjqH?(?qD1ow*bX(!g!TE4+{y%SNY&!p@aSzqdhz{~yt%shu zckK(GzK68~s%;d&9ZuGob~D3Ce)9cR3{>B?96!Kxexz&KGU#H976WtQ{?pF-&)8Wq8kSB?BC;D=on_ z_`!JK7uc5KYRInLwr!h|%7Y-_0EOils9RgX$BNc4+JXU5VM#c&NDhBDH?Nq!`0T%W z^Efz;O?rCvu6t?qUw&)fz6bV_ErBZB0dqxI?I4;#wlO>)KWEMylePiV7wfrm=VDe} zotkOLa0vN5r*wY5an@=OC2CGoMhp}sQbMAtAQ*=s+5#dP1LPaWu&PB)lqrJ1BwX#= zw@+dj&=>$og$0KIL%dSZGF8^3!jhXd?Ye1;&tc6manV&YE+qwE*tX@)AHMp^KftSG zW+8=Xgo$|IPDSGfk+$9?Gi5K<7(zOG_H0baV9X`Fd(63xNHvoC$Qgq}*25%~I3zs? zl0~RR#snxR)(%-?FcR8AW?@CnJiq~sfg%X}uoYZ)FsFoPz?|afrzx(+cB+YjW7}#C z0|ad65J6(Vu*QICWtM3(ZbbI2160%k{xBKF}o1ID>Ky=_<}no0>3G-h;&YqT#`PH;sYll zBLWJD?q5)?A2`x;UtkRn705S$fxi*UU|?XXVnf0up21-7EZ0ChBgeo2FbuZ(vZ3E3QZPpvlnKA3eH=U~9Dk0EoUij2~U57VfR(Uf=wQ}lo*=;E}VSs?K z_LyEvyi0h;Al)-&%m`XrTg?zsbYyV3;&)PBrf7ca^yQEfsdf_nXe4CBKp~P9`L^UK zVLM9kX33Wlb4qvy_dZH$O5vOU2lqahsnw*1put;hk>g$s`34oJg3hPF^kX?o#w#rM zFq#J7dd+LUbm64Qmt1fvXb%9Px9{NA9b3QmKO0~E`it-iQRq#I06?L3fL6e^m`Ral zz=HAi_$~?zG&eUFQ5s(WA=&|XOc=8d4Z&7GKmK5@Cs}ziG2;+q%xMZ&T;ylCY!i`h z{3o@fY%GJ^>gGru!}yEs+qWC70gZvi4N$;!%Vf{9u3Lbkg1=|L0L_8@AmjYEZ?%dn z{*-CgPMgi|E7!q)Wj<&VWyz zd)7#SA^Lqf2{J$o!!6vb(q1V%P^myD5%2{I7MOSjt7mW7vSmu5;5au(xLr&`he^dR z2ATvODUV;Um{s9~(IF8RfS7IUpEm8NrbVvJ3 z>t5aPPtSC9Z8^;Mj^G&@zR5uXDk#(nn9&w72jf8kBDi52u7H_HEpISEIJ_bYh-hX~ z)cCT&S4r70m*AR9!XH$sF0snUc0FZ4;YtRHN{qA3BH{2>q|GS+gY8%a*RAWJg681w zdrZQ822unLUYnE=vfw~9bPJH2Xb~JErWHBU8*#M!tonv?PH&jB?EI?A$@4}VNW?6H zHfV2OyJho+e_W3#5v)eQb2gd*aRxi`F@R=p%p^2^jE^>zzBy)8#9R=FIbZ+~#=c;R zXnYT886eY=;fmjv8HEskNRw$II$93Xh;fHfGURC<$0BZTpyO&_{RW@|5Kylc80K>X zfNVi$Io_1lV>=cBAq0sq)YYViuq#kAcmf+q1t?$&7#E|BbD$yk{BnK19&ihbk9;y%MpLxu^f({KlpW=GE5Cn^#>sb8b~-^W2dFN(g|KZQWhF zw;egWd22`e^INuV{*NuVSER!rRfa+j5`cifm}&GdktpO3#!P}X;QNi%DopA#ngIVytE}_9 z>RQjMto6OB+EU;t6gpet?ZXH1`L>oq{zzAE?}3(lU+0n5)~B|&wfts(XXmS(6gGNy z0pLwTg~?+ZZNct`d1Oo@3Oz_>pMV9&BL%~fXz+!ApPMNrz$7|gK$vk4xGo9A*8JyBsjW3PLJa81k(7;Hq0Vv+|UO0I5CS%o)P9aw_E|`OY=4j!+cKw!E0!eBJ(bj zw8zg<0}O16B3@)BYtPaSAktzgS%*lX;zq{g?g?u^9mCXHBO#*#6e2Fa$l9jV*bH~h zJ^W{TCllAe-;#=sZ4hx(55cws{;;EV8v;b50SEv9?|?fO4X+aNm2gh$W+id2B2~kk zBO*OYE^Q{2vJOr*Aj~z;D3gJ-h5T)gi?oLOeR!#`mi#G*c&S*$oA?I6!7o=?7p`ax zv$o&!xq$#EI0gxa0tf>X41$80g|riHfWG}1V@^Sffau%51rWY%+~(RuD_9Ld)*MTK z0elFWPU>XwI@19Cuz-mECJPFAlTrW zNzIUGw0M+|qQTD*MA}@7ov+&DE!p>wNY!u%bLMb@_?4erb15xDHXL=RL}icMON`6a zaq+7}A{hj!$WY@T;_!=OK@=5go_jarH9^XO^_G~R6TB{yCr`#2>CFg6VV#FR9>ZU4 z8OLr6rDW+r7AOOvi_a-jPelvM5pJ-U1RYB!wt4B~hTO%McZ3@kroQd6e`Ael2e7Y` zKRd$Lgmsr)%s#Af3@_$9IF_%~{EnUes!M*~R?ILHo?yXjqFDHNGuH#|f$zmk%x>p= zfY+%Hg~(u5afe|N_@Eg82oP&AmH0Ut@Yt+YDT8mGziTeJ??-2sQ`hc$98G|Lj5}X!C|-s(%pI6({V{rqbaP1%LEz8F`*zR5z37+x^0R(sz8pj1F0~6 z9mfauzKsb)w$lJX2Jq4AGnh4Bf03`vG(LEaCQO)MdVhS5@53~*e;Y17kE2gc91XpU% z*w{!I*8p=0g(Ac}!tbGTz%ziEBm;e15-r!|mYPXbg3L_!PQ9a1XD+2>)R|ANNKYo) zTI*7D1aTJYlV5@pT|?1K*-FIuJvK=k#AC!PqzOt5n^8xzYe7!Lqb%9t43Kumc@ zYko+DY*W*S8@@45?6g#ta(4`?@i;750nCgTL{) zV+LW~hTe{j4rBh{Z#zeMH~)6;^y$+vr$Zlm*${LCSn}Kfs>rfqgHoHW|<)8qI)!jA~7hmJ-?_lfRHW!`8AA*#dbG(RAtGrXbtl>_r^veIM*y%gV^j+7<-Pt zVG40?p14Nr6A;<~gc<9?Lw1Vdf<_0`WXk-#g5O6@)EK%Gr&3-1Pm}Lq?H$;;Wiaa3X_`t2{xGV!Z*||xMV>1QYCIE)GfDG1$mkWP$Lq_U^ z$t-2F#U$S$;tVqf+YH7&93%TtbQlZb3pj^0e+n>%VA|Qmj9&m?`I^l2!3@L#(})+u zBLV;d8o^GrYrJ!~SEyaHCG*nWSn!=S* zCRG+mcVizAH!>~rq=pms&pgpig!?ClyV$6Uc;qccZhy`LQ zWF6a>v=BWr)+hlAGe~-DZq%5&4f`|}oQJ_@=5xb>-W@$Y*3dvg^x1SX`>~H~iU+h6 z?>BXm_*?}si=fc}0x2wp#nlFO(i3wI(UJfIG=rPPYM*UXdr5YsI-8R3pj-g~IjLE) zLnWnvq2df&w`WVc3!~nUEIuUW;g}EFP91xPe}S0=kN5s zXo3(Cm|WHy1G6I#En+o7MBdRzt@)*8&Ob3@u$D9we2(MgGugI7H=~paOa223goqf6 zd53MsQxw1)vJ)i+&hXDPXNc~TGM=CN%u12wu?C1!PIMfRR_@?LmzVn$R2>RS}n??3xyVPgyerIjYFTdmbhLUT@y6&v~S@)WLRvF(Lm$4`V zynJ%^(*O;I9O6k1V8{Z+{hr%s70;f6<#(RO3lxh$q;vLaGN+l|$ zh`j6RiPnzCE0e%F$5U#%-(|a>se#K)CbCw?IYECJvqbxNMF?g(YA%um8m9p#_BrQK z>9swEDAky$ze%Q@?dTd)*G%b4Ds`;Z;}|%a{6E`=QEPmdG|vD4002ovPDHLkV1ky? Byix!F literal 0 HcmV?d00001 diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/audit-panel/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/audit-panel/index.module.less new file mode 100644 index 00000000..dfc678ab --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/audit-panel/index.module.less @@ -0,0 +1,23 @@ +.container { + padding: 20px; + + .label-value { + display: flex; + width: 100%; + font-size: 14px; + margin-bottom: 10px; + + .label { + width: 150px; + } + + .value { + flex: 1; + } + + .img { + max-width: 200px; + height: auto; + } + } +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/audit-panel/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/audit-panel/index.tsx new file mode 100644 index 00000000..b7801ddc --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/audit-panel/index.tsx @@ -0,0 +1,57 @@ +/* + * 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. + */ + +import { type FC, type PropsWithChildren } from 'react'; + +import { type IBuilderChatProps } from '../../type'; + +import styles from './index.module.less'; +const LabelValue: FC> = ({ + label, + children, +}) => ( +
+
{label}:
+
{children}
+
+); + +export const AuditPanel: FC = props => ( +
+ {props?.project?.name} + + + + + {props?.project?.onBoarding?.prologue} + + + {(props?.project?.onBoarding?.suggestions || []).map((item, index) => ( +
{item}
+ ))} +
+ {props?.userInfo?.nickname} + + + + + {props?.areaUi?.input?.placeholder}{' '} + + + {props?.areaUi?.input?.defaultText}{' '} + +
+); diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/background/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/background/index.module.less new file mode 100644 index 00000000..692a84e3 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/background/index.module.less @@ -0,0 +1,53 @@ +.bg-image { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; + overflow: hidden; + box-sizing: border-box; + + .mask { + position: absolute; + top: 0; + left: 0; + background-color: rgba(0, 0, 0, 12%); + width: 100%; + height: 100%; + z-index: 3; + + &::after { + content: ''; + background: linear-gradient( + 180deg, + rgba(99, 99, 99, 40%), + rgba(99, 99, 99, 0%) + ); + height: 216px; + position: relative; + display: block; + width: 100%; + z-index: 10; + } + } + + .img-container { + height: 100%; + width: 100%; + position: relative; + transform: translateX(-50%); + left: 50%; + z-index: 2; + overflow: hidden; + } + + .img { + position: relative; + left: 50%; + width: 100%; + height: 100%; + transform: translateX(-50%); + object-fit: cover; + } +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/background/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/background/index.tsx new file mode 100644 index 00000000..e9306a4c --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/components/background/index.tsx @@ -0,0 +1,49 @@ +/* + * 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. + */ + +import { type FC, useRef } from 'react'; + +import styles from './index.module.less'; + +export const Background: FC<{ + bgInfo?: { + imgUrl: string; + themeColor: string; // 背景颜色 + }; +}> = props => { + const targetRef = useRef(null); + const { bgInfo } = props; + + if (!bgInfo || !bgInfo?.imgUrl) { + return null; + } + const { themeColor = 'transparent', imgUrl } = bgInfo; + + return ( +
+
+
+ {imgUrl ? : null} +
+
+ ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/context/builder-chat-context.tsx b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/context/builder-chat-context.tsx new file mode 100644 index 00000000..ba1aa1ec --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/context/builder-chat-context.tsx @@ -0,0 +1,74 @@ +/* + * 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. + */ + +import { + createContext, + type FC, + type PropsWithChildren, + useContext, +} from 'react'; + +import { useUpdateEffect } from 'ahooks'; + +import { type IBuilderChatProps } from '../type'; +import { combineAppDataWithProps } from '../services/get-bot-info'; +import { type InitData } from '../data-type'; + +interface BuilderChatContextValue { + appDataFromOnLine?: InitData | null; + appDataCombineWithProps?: InitData | null; +} +type BuilderChatContextProps = BuilderChatContextValue & { + setAppDataFromOnLine?: (appDataFromOnLint: InitData | null) => void; + setAppDataCombineWithProps?: ( + appDataCombineWithProps: InitData | null, + ) => void; +}; +const BuilderChatContext = createContext({ + appDataFromOnLine: null, + appDataCombineWithProps: null, +}); + +export const BuilderChatProvider: FC< + PropsWithChildren +> = ({ children, ...props }) => ( + +); + +export const useGetAppDataFromOnLine = () => { + const { appDataFromOnLine } = useContext(BuilderChatContext); + return appDataFromOnLine; +}; + +export const useGetAppDataCombineWithProps = () => { + const { appDataCombineWithProps } = useContext(BuilderChatContext); + return appDataCombineWithProps; +}; + +export const useSetAppDataFromOnLine = () => { + const { setAppDataFromOnLine } = useContext(BuilderChatContext); + return setAppDataFromOnLine; +}; +export const useUpdateAppDataCombineWithProps = (props: IBuilderChatProps) => { + const { appDataFromOnLine, setAppDataCombineWithProps } = + useContext(BuilderChatContext); + useUpdateEffect(() => { + if (appDataFromOnLine) { + const formatAPPInfo = combineAppDataWithProps(appDataFromOnLine, props); + setAppDataCombineWithProps?.(formatAPPInfo); + } + }, [appDataFromOnLine, props]); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/coze-chat.tsx b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/coze-chat.tsx new file mode 100644 index 00000000..61e33a1a --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/coze-chat.tsx @@ -0,0 +1,341 @@ +/* + * 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. + */ + +import { + useEffect, + useImperativeHandle, + useMemo, + type FC, + forwardRef, + type Ref, + useRef, + type PropsWithChildren, + useState, + memo, +} from 'react'; + +import cls from 'classnames'; +import { type InputController } from '@coze-common/chat-uikit-shared'; +import { ContentType } from '@coze-common/chat-core/message/types'; +import { + useSendTextMessage, + useSendNormalizedMessage, +} from '@coze-common/chat-area/hooks/messages/use-send-message'; +import { useClearContext } from '@coze-common/chat-area/hooks/messages/use-clear-context'; +import { useInitStatus } from '@coze-common/chat-area/hooks/context/use-init-status'; +import { I18n } from '@coze-arch/i18n'; + +import type { StudioChatProviderProps } from '@/types/props'; +import { Layout } from '@/types/client'; +import { useGetTheme } from '@/components/studio-open-chat/hooks/use-get-theme'; +import { + OpenChatProvider, + StudioChatArea, +} from '@/components/studio-open-chat'; +import { Loading } from '@/components/loading'; +import { ChatHeader } from '@/components/header'; +import ChatFooter from '@/components/footer'; +import ErrorFallback, { + type InitErrorFallback, +} from '@/components/error-fallback'; +import { ErrorBoundary } from '@/components/error-boundary'; + +import { + type IBuilderChatProps, + type MessageType, + type BuilderChatRef, +} from './type'; +import { getBuilderEventCallbackPlugin } from './plugins/event-callback'; +import { useRequestInit } from './hooks/use-request-init'; +import { useOnboardingUpdate } from './hooks/use-on-boarding-update'; +import { useInitChat } from './hooks/use-init-chat'; +import { useCoreManager } from './hooks/use-core-manager'; +import { useBotAndUserUpdate } from './hooks/use-bot-user-update'; +import { type InitData } from './data-type'; +import { + BuilderChatProvider, + useGetAppDataCombineWithProps, + useUpdateAppDataCombineWithProps, +} from './context/builder-chat-context'; +import { Background } from './components/background'; +import { AuditPanel } from './components/audit-panel'; + +import styles from './index.module.less'; + +export { type BuilderChatRef }; + +export const BuilderChatContent = forwardRef( + ( + { + uiBuilderProps, + chatProps, + }: { + chatProps: StudioChatProviderProps; + uiBuilderProps: IBuilderChatProps; + }, + ref: Ref, + ) => { + const refHasInitController = useRef(false); + const refInputController = useRef(); + const { areaUi } = uiBuilderProps; + const handleClearContext = useClearContext(); + const sendMessage = useSendNormalizedMessage(); + const sendTextMessage = useSendTextMessage(); + useOnboardingUpdate(); + useBotAndUserUpdate(); + useImperativeHandle( + ref, + () => ({ + sendMessage: (message: MessageType) => { + if (message.type === ContentType.Text) { + sendTextMessage({ text: message.text, mentionList: [] }, 'other'); + } else if (message.type === ContentType.Image) { + sendMessage( + { + payload: { + contentType: ContentType.Image, + contentObj: { + image_list: [message.value], + }, + mention_list: [], + }, + }, + 'other', + ); + } else if (message.type === 'file') { + sendMessage( + { + payload: { + contentType: ContentType.File, + contentObj: { + file_list: [message.value], + }, + mention_list: [], + }, + }, + 'other', + ); + } + }, + clearContext: () => { + handleClearContext?.(); + }, + }), + [handleClearContext, sendTextMessage, sendMessage], + ); + useEffect(() => { + refInputController.current?.setInputText?.( + areaUi?.input?.defaultText || '', + ); + }, [areaUi?.input?.defaultText]); + const renderChatInputTopSlot = areaUi?.input?.renderChatInputTopSlot + ? () => areaUi?.input?.renderChatInputTopSlot?.(false) + : undefined; + const isMobile = uiBuilderProps.project?.layout === Layout.MOBILE; + const theme = useGetTheme(); + const { header } = uiBuilderProps.areaUi || {}; + + return ( + + } + isMiniScreen={areaUi?.uiTheme === 'chatFlow' ? true : false} + inputNativeCallbacks={{ + getController: inputControllerIn => { + refInputController.current = inputControllerIn; + if (!refHasInitController.current) { + refInputController.current?.setInputText?.( + areaUi?.input?.defaultText || '', + ); + refHasInitController.current = true; + } + }, + }} + /> + ); + }, +); + +const getErrorCallbackComp = + (props: IBuilderChatProps & { refresh: () => void }): FC => + ({ error, onBeforeRetry }) => ( + <> + + {props.areaUi?.input?.renderChatInputTopSlot?.(true)} + + ); +const BuilderChatWrap: FC> = ({ + children, + ...props +}) => { + const initStatus = useInitStatus(); + const theme = useGetTheme(); + const { footer } = props.areaUi || {}; + const isMobile = props.project?.layout === Layout.MOBILE; + const appInfoResult = useGetAppDataCombineWithProps(); + + const footerConfig = { + ...(footer || { + expressionText: '', + }), + }; + + if (props.project?.mode !== 'websdk') { + if (!footerConfig.expressionText) { + footerConfig.expressionText = I18n.t('chat_GenAI_tips'); + } + } + if (initStatus !== 'initSuccess') { + return props?.areaUi?.renderLoading?.() || ; + } + return ( +
+
+ + {children} +
+ +
+ ); +}; +const BuilderChatContainer = memo( + forwardRef((props: IBuilderChatProps, ref: Ref) => { + const { chatProps, hasReady, error, refresh } = useInitChat(props); + const openRequestInit = useRequestInit(props); + + const builderEventCallbackPlugin = getBuilderEventCallbackPlugin({ + eventCallbacks: props.eventCallbacks, + }); + const appInfoResult = useGetAppDataCombineWithProps(); + useUpdateAppDataCombineWithProps(props); + const plugins = [builderEventCallbackPlugin]; + + const requestManagerOptions = useCoreManager(props); + + const userInfo = useMemo( + () => ({ + url: '', + nickname: '', + ...(props.userInfo || {}), + id: props?.userInfo?.id || chatProps?.userInfo?.id || '', + }), + [props?.userInfo, chatProps], + ); + const ErrorFallbackComp = getErrorCallbackComp({ ...props, refresh }); + if (props?.project?.mode === 'audit') { + return ; + } + if (error) { + return ; + } + if (!chatProps || !hasReady) { + return props?.areaUi?.renderLoading?.() || ; + } + const isCustomBackground = !!appInfoResult?.customBgInfo?.imgUrl || false; + console.log( + '[result] isCustomBackground:', + isCustomBackground, + appInfoResult?.customBgInfo, + ); + + return ( + + + + + + ); + }), +); +export const BuilderChatWeb = forwardRef( + (props: IBuilderChatProps, ref: Ref) => { + const [appDataFromOnLine, setAppDataFromOnLine] = useState( + null, + ); + const [appDataCombineWithProps, setAppDataCombineWithProps] = + useState(null); + + return ( + + + + + + ); + }, +); diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/data-type.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/data-type.ts new file mode 100644 index 00000000..2a6217dc --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/data-type.ts @@ -0,0 +1,116 @@ +/* + * 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. + */ + +import { type MixInitResponse } from '@coze-common/chat-area'; + +import { type EInputMode } from '@/types/props'; + +export interface ProjectInfoResp { + data: { + icon_url: string; + name: string; + }; +} + +export interface SuggestPromoteInfo { + suggestReplyMode?: number; + customizedSuggestPrompt?: string; +} +export interface BackgroundImageResp { + theme_color?: string; + gradient_position: { + left?: number; + right?: number; + }; + canvas_position: { + width?: number; + height?: number; + left?: number; + top?: number; + }; + image_url?: string; + origin_image_url?: string; +} +export interface WorkflowInfoResp { + role?: { + avatar?: { + image_uri?: string; + image_url?: string; + }; + description?: string; + name?: string; + background_image_info?: { + web_background_image?: BackgroundImageResp; + mobile_background_image: BackgroundImageResp; + }; + id: string; + connector_id: string; + suggest_reply_info?: { + suggest_reply_mode?: number; + customized_suggest_prompt?: string; + }; + audio_config?: { + is_text_to_voice_enable?: boolean; + voice_config_map?: Record< + string, + { + voice_id: string; + name: string; + } + >; + }; + workflow_id: string; + onboarding_info: { + prologue: string; + display_all_suggestions: boolean; + suggested_questions: string[]; + }; + user_input_config?: { + default_input_mode: number; + }; + }; +} + +export type InitData = Pick< + MixInitResponse, + 'prologue' | 'onboardingSuggestions' | 'backgroundInfo' +> & { + prologue: string; + onboardingSuggestions: { + id: string; + content: string; + }[]; + botInfo: { + url: string; + nickname: string; + id: string; + }; + displayAllSuggest?: boolean; + suggestPromoteInfo?: SuggestPromoteInfo; + defaultInputMode?: EInputMode; + customBgInfo?: { + imgUrl: string; + themeColor: string; // 背景颜色 + }; +}; + +export interface CozeApiFullFilledRes { + status: string; + reason: { + code: number; + }; + data: unknown; +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/helper/get-connector-id.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/helper/get-connector-id.ts new file mode 100644 index 00000000..3e9e1fa4 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/helper/get-connector-id.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +import { webSdkDefaultConnectorId, chatflowDraftConnectorId } from '@/util'; + +import { type IBuilderChatProps } from '../type'; + +export const getConnectorId = (props: IBuilderChatProps) => { + const { project } = props; + const { mode, connectorId } = project || {}; + if (!connectorId) { + if (mode === 'websdk') { + return webSdkDefaultConnectorId; + } else if (mode === 'draft') { + return chatflowDraftConnectorId; + } + } + return connectorId; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-bot-user-update.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-bot-user-update.ts new file mode 100644 index 00000000..baeb1a26 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-bot-user-update.ts @@ -0,0 +1,46 @@ +/* + * 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. + */ + +import { useUpdateEffect } from 'ahooks'; +import { useBotInfo, useChatArea } from '@coze-common/chat-area'; + +import { useGetAppDataCombineWithProps } from '../context/builder-chat-context'; + +// conversationId、sectionId 重新修改 +export const useBotAndUserUpdate = () => { + const { updateBotInfo } = useBotInfo(); + const { recordBotInfo } = useChatArea(); + const appInfoResult = useGetAppDataCombineWithProps(); + useUpdateEffect(() => { + const id = appInfoResult?.botInfo?.id || ''; + recordBotInfo({ + name: appInfoResult?.botInfo?.nickname || '', + avatar: appInfoResult?.botInfo?.url || '', + }); + updateBotInfo(() => ({ + [id]: { + url: appInfoResult?.botInfo?.url || '', + nickname: appInfoResult?.botInfo?.nickname || '', + id: appInfoResult?.botInfo?.id || '', + allowMention: false, + }, + })); + }, [ + appInfoResult?.botInfo?.nickname, + appInfoResult?.botInfo?.url, + appInfoResult?.botInfo?.id, + ]); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-core-manager.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-core-manager.ts new file mode 100644 index 00000000..a5e12e6a --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-core-manager.ts @@ -0,0 +1,131 @@ +/* + * 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. + */ + +import { useMemo, useRef } from 'react'; + +import { + RequestScene, + type RequestManagerOptions, +} from '@coze-common/chat-core'; + +import { openApiHostByRegionWithToken } from '@/util/env'; + +import { type IBuilderChatProps } from '../type'; +import { getConnectorId } from '../helper/get-connector-id'; +import { useGetAppDataCombineWithProps } from '../context/builder-chat-context'; + +export const useCoreManager = ( + props: IBuilderChatProps, +): RequestManagerOptions => { + const refProps = useRef(props); + const appData = useGetAppDataCombineWithProps(); + const refAppData = useRef(appData); + refProps.current = props; + refAppData.current = appData; + return useMemo( + () => ({ + scenes: { + [RequestScene.SendMessage]: { + hooks: { + onBeforeSendMessage: [ + requestConfig => { + const { body } = requestConfig; + + const bodyDataOld = JSON.parse(body); + const bodyData: Record = {}; + bodyData.additional_messages = + bodyDataOld.additional_messages || []; + bodyData.connector_id = bodyDataOld.connector_id; + bodyData.workflow_id = refProps?.current?.workflow?.id; + bodyData.parameters = refProps?.current?.workflow?.parameters; + bodyData.version = + refProps?.current?.project?.version || undefined; + bodyData.execute_mode = + refProps?.current?.project?.mode === 'draft' + ? 'DEBUG' + : undefined; + bodyData.app_id = + refProps?.current?.project?.type === 'app' + ? refProps?.current?.project?.id + : undefined; + bodyData.bot_id = + refProps?.current?.project?.type === 'bot' + ? refProps?.current?.project?.id + : undefined; + bodyData.conversation_id = new URL( + requestConfig.url, + ).searchParams.get('conversation_id'); + bodyData.connector_id = getConnectorId(refProps?.current); + bodyData.ext = { + _caller: refProps?.current?.project?.caller, + user_id: bodyDataOld.user_id, + }; + bodyData.suggest_reply_info = refAppData.current + ?.suggestPromoteInfo + ? { + suggest_reply_mode: + refAppData.current?.suggestPromoteInfo + ?.suggestReplyMode, + customized_suggest_prompt: + refAppData.current?.suggestPromoteInfo + ?.customizedSuggestPrompt, + } + : undefined; + requestConfig.body = JSON.stringify(bodyData); + requestConfig.url = `${openApiHostByRegionWithToken}/v1/workflows/chat`; + requestConfig.headers.push( + ...Object.entries(refProps.current?.workflow?.header || {}), + ); + + return { + ...requestConfig, + }; + }, + ], + }, + }, + [RequestScene.ClearHistory]: { + hooks: { + onBeforeRequest: [ + requestConfig => { + if (props?.project?.type === 'bot') { + requestConfig.data = { + connector_id: getConnectorId(props), + }; + } else { + requestConfig.data = { + app_id: refProps?.current.project?.id, + conversation_name: + refProps?.current?.project?.conversationName, + get_or_create: false, + workflow_id: refProps?.current?.workflow?.id, + draft_mode: refProps?.current?.project?.mode === 'draft', + connector_id: getConnectorId(props), + }; + } + + return { + ...requestConfig, + }; + }, + ], + }, + }, + }, + }), + [], + ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-init-chat.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-init-chat.ts new file mode 100644 index 00000000..daec9368 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-init-chat.ts @@ -0,0 +1,159 @@ +/* + * 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. + */ + +import { useEffect, useState } from 'react'; + +import { nanoid } from 'nanoid'; +import { useUpdateEffect } from 'ahooks'; +import { patPermissionApi } from '@coze-arch/bot-api'; + +import { type StudioChatProviderProps } from '@/types/props'; +import { OpenApiSource } from '@/types/open'; +import { AuthType } from '@/types/client'; + +import { type IBuilderChatProps } from '../type'; +import { getConnectorId } from '../helper/get-connector-id'; + +const getToken = async () => { + try { + const res = await patPermissionApi.ImpersonateCozeUser({}); + return res.data?.access_token ?? ''; + } catch (_err) { + return ''; + } +}; +const checkParam = (props: IBuilderChatProps) => { + let error: Error | undefined; + if (props?.project?.type === 'bot') { + if (props?.project?.mode !== 'draft') { + error = new Error('mode must be draft when project type is bot'); + } + } else { + if (props?.auth?.type !== 'internal') { + if (!props?.auth?.token) { + error = new Error('token is required when auth type is not internal'); + } + } + } + return error; +}; + +// botId 、 token等修改 +export const useInitChat = ( + props: IBuilderChatProps, +): { + chatProps?: StudioChatProviderProps; + hasReady: boolean; + error: Error | null; + refresh: () => void; +} => { + const [hasReady, setHasReady] = useState(false); + const [error, setError] = useState(null); + const [chatProps, setChatProps] = useState(); + const { project: projectInfo, userInfo } = props; + + useEffect(() => { + if (error || hasReady) { + return; + } + const errorTemp = checkParam(props); + if (errorTemp) { + setError(errorTemp); + return; + } + (async () => { + let token = props.auth?.token; + let refreshToken = props.auth?.refreshToken; + if (props.auth?.type === 'internal') { + token = await getToken(); + refreshToken = getToken; + } + + if (token) { + setChatProps({ + chatConfig: { + bot_id: projectInfo.id, + auth: { + type: AuthType.TOKEN, + token, + onRefreshToken: refreshToken, + connectorId: getConnectorId(props), + }, + ui: { + base: { + layout: projectInfo.layout, + }, + chatBot: { + uploadable: props.areaUi.uploadable, + isNeedClearContext: props.areaUi.isNeedClearContext, + isNeedClearMessage: props.areaUi.isNeedClearMessage, + isNeedAddNewConversation: + props.areaUi.isNeedAddNewConversation ?? false, // 默认false + isNeedAudio: props.areaUi.input?.isNeedAudio ?? !IS_OVERSEA, + isNeedQuote: props.areaUi.isNeedQuote ?? false, // 默认false + isNeedFunctionCallMessage: + props.areaUi.isNeedFunctionCallMessage, + feedback: props.areaUi.feedback, + }, + }, + conversation_id: '', // 无用,可先为空 + source: OpenApiSource.ChatFlow, + }, + layout: projectInfo.layout, + userInfo: { + id: nanoid(), + url: '', + nickname: '', + ...(userInfo || {}), + }, + }); + setHasReady(true); + } else { + setError(new Error('token is empty')); + } + })(); + }, [error, hasReady]); + useUpdateEffect(() => { + setHasReady(false); + setError(null); + }, [ + projectInfo?.id, + projectInfo?.type, + projectInfo?.conversationName, + projectInfo?.conversationId, + projectInfo?.mode, + projectInfo?.conversationId, + props?.workflow?.id, + ]); + + if (chatProps) { + chatProps.chatConfig.ui = chatProps.chatConfig.ui || {}; + chatProps.chatConfig.ui.chatBot = chatProps.chatConfig.ui.chatBot || {}; + chatProps.chatConfig.ui.chatBot.isNeedClearMessage = + props?.areaUi?.isNeedClearMessage; + chatProps.chatConfig.ui.chatBot.uploadable = props.areaUi?.uploadable; + chatProps.chatConfig.ui.chatBot.feedback = props.areaUi?.feedback; + } + return { + hasReady, + chatProps, + error, + refresh: () => { + setError(null); + setHasReady(false); + }, + }; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-on-boarding-update.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-on-boarding-update.ts new file mode 100644 index 00000000..9c9b6750 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-on-boarding-update.ts @@ -0,0 +1,33 @@ +/* + * 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. + */ + +import { useUpdateEffect } from 'ahooks'; +import { useChatArea } from '@coze-common/chat-area'; + +import { useGetAppDataCombineWithProps } from '../context/builder-chat-context'; + +// conversationId、sectionId 重新修改 +export const useOnboardingUpdate = () => { + const { partialUpdateOnboardingData } = useChatArea(); + const appInfoResult = useGetAppDataCombineWithProps(); + + useUpdateEffect(() => { + partialUpdateOnboardingData( + appInfoResult?.prologue, + appInfoResult?.onboardingSuggestions, + ); + }, [appInfoResult?.prologue, appInfoResult?.onboardingSuggestions]); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-request-init.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-request-init.ts new file mode 100644 index 00000000..33860ae1 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/hooks/use-request-init.ts @@ -0,0 +1,56 @@ +/* + * 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. + */ + +import { useCallback, useRef } from 'react'; + +import { type CozeAPI } from '@coze/api'; + +import { type OpenRequestInit } from '@/types/props'; + +import { type IBuilderChatProps } from '../type'; +import { combineAppDataWithProps, getBotInfo } from '../services/get-bot-info'; +import { createOrGetConversation } from '../services/create-conversation'; +import { useSetAppDataFromOnLine } from '../context/builder-chat-context'; + +// conversationId、sectionId 重新修改 +export const useRequestInit = (props: IBuilderChatProps) => { + const refProps = useRef(props); + refProps.current = props; + const setAppDataFromOnLine = useSetAppDataFromOnLine(); + + const openRequestInit = useCallback( + async (apiSdk?: CozeAPI): Promise => { + const getBotInfoPrm = getBotInfo(apiSdk || undefined, refProps.current); + const createOrGetConversationPrm = createOrGetConversation( + apiSdk || undefined, + refProps.current, + ); + const botInfo = await getBotInfoPrm; + const conversationInfo = await createOrGetConversationPrm; + + setAppDataFromOnLine?.(botInfo || null); + const formatAPPInfo = combineAppDataWithProps(botInfo, refProps.current); + return { + ...formatAPPInfo, + ...conversationInfo, + isCustomBackground: !!formatAPPInfo.customBgInfo?.imgUrl, + isBuilderChat: true, + }; + }, + [], + ); + return openRequestInit; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/index.module.less new file mode 100644 index 00000000..de40b4db --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/index.module.less @@ -0,0 +1,97 @@ +/* stylelint-disable selector-class-pattern */ + +.content { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; + + :global { + .semi-upload-hidden-input, + .semi-upload-hidden-input-replace { + display: none; + } + + + :is(.-translate-x-1\/2) { + --tw-translate-x: -50%; + } + + :is(.-translate-y-1\/2) { + --tw-translate-y: -50%; + } + } + + .area { + flex: 1; + overflow: hidden; + + .loading-wrap { + height: 100%; + width: 100%; + } + + &.chat-flow-area { + margin: 0 -12px; + + .loading-wrap { + margin: 0 12px; + } + } + } + + .scroll-view { + padding-left: 0; + padding-right: 0; + } + + :global(.chat-uikit-card-content) { + min-width: auto; + } + + .footer { + background: none; + position: absolute; + width: 100%; + bottom: 2px; + + .footer-text { + color: rgba(48, 59, 94, 47%); + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; + } + } + + &.bg-theme { + .footer { + .footer-text { + color: rgba(255, 255, 255, 60%); + } + } + } + + &.mobile { + .scroll-view { + padding: 0 12px; + } + + .footer { + position: relative; + } + + .chat-ui-builder { + .core-area { + width: 100%; + } + } + } +} + + +.chat-slot { + padding: 0 30px; +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/index.tsx new file mode 100644 index 00000000..2b6953e3 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/index.tsx @@ -0,0 +1,26 @@ +/* + * 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. + */ + +//import { TaroBuilderChat } from './taro-chat'; +import { BuilderChatWeb } from './coze-chat'; +export type { + BuilderChatRef, + IWorkflow, + IProject, + IBuilderChatProps, +} from './type'; + +export const BuilderChat = BuilderChatWeb; diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/index.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/index.ts new file mode 100644 index 00000000..05d72572 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/index.ts @@ -0,0 +1,40 @@ +/* + * 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. + */ + +import { type PluginRegistryEntry } from '@coze-common/chat-area'; + +import { type PluginBizContext } from './types/biz-context'; +import { BizPlugin } from './plugin'; + +export type UIBuilderEventCallbackPlugin = + PluginRegistryEntry; +export const getBuilderEventCallbackPlugin = ( + props: PluginBizContext, +): PluginRegistryEntry => { + const uiBuilderEventCallbackPlugin: UIBuilderEventCallbackPlugin = { + /** + * 贯穿插件生命周期、组件的上下文 + */ + createPluginBizContext() { + return { ...props }; + }, + /** + * 插件本体 + */ + Plugin: BizPlugin, + }; + return uiBuilderEventCallbackPlugin as PluginRegistryEntry; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/plugin.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/plugin.ts new file mode 100644 index 00000000..901f9039 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/plugin.ts @@ -0,0 +1,47 @@ +/* + * 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. + */ + +import { + PluginMode, + PluginName, + WriteableChatAreaPlugin, + createWriteableLifeCycleServices, +} from '@coze-common/chat-area'; + +import { type PluginBizContext } from './types/biz-context'; +import { bizLifeCycleServiceGenerator } from './services/life-cycle'; + +export class BizPlugin extends WriteableChatAreaPlugin { + /** + * 插件类型 + * PluginMode.Readonly = 只读模式 + * PluginMode.Writeable = 可写模式 + */ + public pluginMode = PluginMode.Writeable; + /** + * 插件名称 + * 请点 PluginName 里面去定义 + */ + public pluginName = PluginName.UIBuilderEventcallbackPlugin; + + /** + * 生命周期服务 + */ + public lifeCycleServices = createWriteableLifeCycleServices( + this, + bizLifeCycleServiceGenerator, + ); +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/app.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/app.ts new file mode 100644 index 00000000..93c230ff --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/app.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +import { type WriteableAppLifeCycleServiceGenerator } from '@coze-common/chat-area'; + +import { type PluginBizContext } from '../../types/biz-context'; + +export const appLifeCycleServiceGenerator: WriteableAppLifeCycleServiceGenerator< + PluginBizContext +> = plugin => ({}); diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/command.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/command.ts new file mode 100644 index 00000000..46f84fb6 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/command.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +import { type WriteableCommandLifeCycleServiceGenerator } from '@coze-common/chat-area'; + +import { type PluginBizContext } from '../../types/biz-context'; + +export const commandLifeCycleServiceGenerator: WriteableCommandLifeCycleServiceGenerator< + PluginBizContext +> = plugin => ({}); diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/index.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/index.ts new file mode 100644 index 00000000..634fe9f4 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/index.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +import { type WriteableLifeCycleServiceGenerator } from '@coze-common/chat-area'; + +import { type PluginBizContext } from '../../types/biz-context'; +import { renderLifeCycleServiceGenerator } from './render'; +import { messageLifeCycleServiceGenerator } from './message'; +import { commandLifeCycleServiceGenerator } from './command'; +import { appLifeCycleServiceGenerator } from './app'; + +export const bizLifeCycleServiceGenerator: WriteableLifeCycleServiceGenerator< + PluginBizContext +> = plugin => ({ + appLifeCycleService: appLifeCycleServiceGenerator(plugin), + messageLifeCycleService: messageLifeCycleServiceGenerator(plugin), + commandLifeCycleService: commandLifeCycleServiceGenerator(plugin), + renderLifeCycleService: renderLifeCycleServiceGenerator(plugin), +}); diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/message.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/message.ts new file mode 100644 index 00000000..ef799848 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/message.ts @@ -0,0 +1,67 @@ +/* + * 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. + */ + +import { type WriteableMessageLifeCycleServiceGenerator } from '@coze-common/chat-area'; + +import { type PluginBizContext } from '../../types/biz-context'; + +export const messageLifeCycleServiceGenerator: WriteableMessageLifeCycleServiceGenerator< + PluginBizContext +> = plugin => { + let hasInit = false; + let lastMessageId = ''; + let nowReceivingReplyId = ''; + return { + onBeforeMessageGroupListUpdate: ctx => { + const { messages } = + plugin.chatAreaPluginContext.readonlyAPI.message.getMessagesStoreInstantValues(); + const latestMessage = messages?.[0]; + if (!hasInit || lastMessageId === latestMessage?.message_id) { + hasInit = true; + return ctx; + } + plugin.pluginBizContext.eventCallbacks?.onMessageChanged?.(); + lastMessageId = latestMessage?.message_id; + + if ( + (latestMessage?.type === 'answer' && + latestMessage?.is_finish === true) || + !lastMessageId + ) { + plugin.pluginBizContext?.eventCallbacks?.onMessageReceivedFinish?.(); + } + return ctx; + }, + onAfterSendMessage: ctx => { + const chatflowExecuteId: string = + // @ts-expect-error -- linter-disable-autofix, 新添加参数,接口未支持 + ctx?.message?.extra_info?.chatflow_execute_id || ''; + if (chatflowExecuteId) { + plugin.pluginBizContext?.eventCallbacks?.onGetChatFlowExecuteId?.( + chatflowExecuteId, + ); + } + plugin.pluginBizContext?.eventCallbacks?.onMessageSended?.(); + }, + onBeforeReceiveMessage: ctx => { + if (nowReceivingReplyId === ctx.message.reply_id) { + return; + } + nowReceivingReplyId = ctx.message.reply_id; + plugin.pluginBizContext?.eventCallbacks?.onMessageReceivedStart?.(); + }, + }; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/render.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/render.ts new file mode 100644 index 00000000..63a9370d --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/services/life-cycle/render.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +import { type WriteableRenderLifeCycleServiceGenerator } from '@coze-common/chat-area'; + +import { type PluginBizContext } from '../../types/biz-context'; + +export const renderLifeCycleServiceGenerator: WriteableRenderLifeCycleServiceGenerator< + PluginBizContext +> = plugin => ({}); diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/types/biz-context.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/types/biz-context.ts new file mode 100644 index 00000000..1b34d575 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/types/biz-context.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +import type { IEventCallbacks } from '../../../type'; +export interface PluginBizContext { + eventCallbacks?: IEventCallbacks; +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/typings.d.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/typings.d.ts new file mode 100644 index 00000000..4ae6594a --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/plugins/event-callback/typings.d.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +/// diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/services/create-conversation.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/services/create-conversation.ts new file mode 100644 index 00000000..e544ad2d --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/services/create-conversation.ts @@ -0,0 +1,99 @@ +/* + * 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. + */ + +import i18n from '@coze-arch/i18n/intl'; +import { type CozeAPI } from '@coze/api'; + +import { type IBuilderChatProps } from '../type'; +import { getConnectorId } from '../helper/get-connector-id'; +export const createOrGetConversation = async ( + apiSdk: CozeAPI | undefined, + props: IBuilderChatProps, +) => { + let conversationId = ''; + let sectionId = ''; + + try { + if (props?.project?.type === 'bot') { + const res = await apiSdk?.conversations.create( + { + // @ts-expect-error -- linter-disable-autofix + connector_id: getConnectorId(props), + }, + { + headers: { + 'Accept-Language': i18n.language === 'zh-CN' ? 'zh' : 'en', + }, + }, + ); + conversationId = res?.id || ''; + // @ts-expect-error -- linter-disable-autofix + sectionId = res.last_section_id; + } else { + if (IS_OPEN_SOURCE) { + const res = (await apiSdk?.post( + '/v1/workflow/conversation/create', + { + app_id: props.project?.id, + conversation_name: props?.project?.conversationName, + get_or_create: true, + draft_mode: props?.project?.mode === 'draft', + workflow_id: props?.workflow?.id, + connector_id: getConnectorId(props), + }, + false, + { + headers: { + 'Accept-Language': i18n.language === 'zh-CN' ? 'zh' : 'en', + }, + }, + )) as { + data: { + id: string; + last_section_id: string; + }; + }; + conversationId = res?.data?.id || ''; + sectionId = res?.data?.last_section_id || ''; + } else { + const res = await apiSdk?.conversations.create( + { + // @ts-expect-error -- linter-disable-autofix + app_id: props.project?.id, + conversation_name: props?.project?.conversationName, + get_or_create: true, + draft_mode: props?.project?.mode === 'draft', + workflow_id: props?.workflow?.id, + connector_id: getConnectorId(props), + }, + { + headers: { + 'Accept-Language': i18n.language === 'zh-CN' ? 'zh' : 'en', + }, + }, + ); + conversationId = res?.id || ''; + sectionId = res?.last_section_id || ''; + } + } + return { conversationId, sectionId }; + } catch (error) { + throw { + code: -1002, + message: '', + }; + } +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/services/get-bot-info.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/services/get-bot-info.ts new file mode 100644 index 00000000..7943030f --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/services/get-bot-info.ts @@ -0,0 +1,188 @@ +/* + * 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. + */ + +import { cloneDeep } from 'lodash-es'; +import i18n from '@coze-arch/i18n/intl'; +import { type CozeAPI } from '@coze/api'; + +import { EInputMode } from '@/types/props'; +import ChatFlowUserIcon from '@/assets/chatflow-logo.png'; + +import { type IBuilderChatProps } from '../type'; +import { getConnectorId } from '../helper/get-connector-id'; +import { + type ProjectInfoResp, + type WorkflowInfoResp, + type InitData, + type CozeApiFullFilledRes, +} from '../data-type'; +const getFormatAppData = ( + appData?: ProjectInfoResp['data'], + workflowData?: WorkflowInfoResp['role'], + props?: IBuilderChatProps, +): InitData => { + const appInfoResult: InitData = { + prologue: workflowData?.onboarding_info?.prologue || '', + onboardingSuggestions: + workflowData?.onboarding_info?.suggested_questions?.map( + (item, index) => ({ + id: index.toString(), + content: item, + }), + ) || [], + displayAllSuggest: workflowData?.onboarding_info?.display_all_suggestions, + botInfo: { + url: workflowData?.avatar?.image_url || appData?.icon_url || '', + nickname: + workflowData?.name || appData?.name || props?.project.defaultName || '', + id: props?.project?.id || '', + }, + suggestPromoteInfo: { + suggestReplyMode: workflowData?.suggest_reply_info?.suggest_reply_mode, + customizedSuggestPrompt: + workflowData?.suggest_reply_info?.customized_suggest_prompt, + }, + backgroundInfo: workflowData?.background_image_info, + defaultInputMode: + workflowData?.user_input_config?.default_input_mode === 2 + ? EInputMode.Voice + : EInputMode.Text, + }; + + // 内部插件中用的是origin_image_url,但是这里origin_image_url 会过期,因此使用image_url重写 + if (appInfoResult.backgroundInfo?.web_background_image) { + appInfoResult.backgroundInfo.web_background_image.origin_image_url = + appInfoResult.backgroundInfo.web_background_image.image_url; + } + if (appInfoResult.backgroundInfo?.mobile_background_image) { + appInfoResult.backgroundInfo.mobile_background_image.origin_image_url = + appInfoResult.backgroundInfo.mobile_background_image.image_url; + } + + return appInfoResult; +}; + +export const combineAppDataWithProps = ( + appInfoResultRaw: InitData, + props?: IBuilderChatProps, +): InitData => { + const appInfoResult = cloneDeep(appInfoResultRaw); + + if (props?.project?.id) { + appInfoResult.botInfo.id = props?.project?.id; + } + if (props?.project?.name) { + appInfoResult.botInfo.nickname = props?.project?.name; + } + if (props?.project?.iconUrl) { + appInfoResult.botInfo.url = props?.project?.iconUrl; + } + + if (props?.project?.onBoarding?.prologue) { + appInfoResult.prologue = props?.project?.onBoarding?.prologue; + } + if (props?.project?.onBoarding?.suggestions?.length) { + appInfoResult.onboardingSuggestions = + props?.project?.onBoarding?.suggestions.map((item, index) => ({ + id: index.toString(), + content: item, + })) || []; + } + if (props?.project?.onBoarding?.displayAllSuggest) { + appInfoResult.displayAllSuggest = + props?.project?.onBoarding?.displayAllSuggest; + } + + if (!appInfoResult.displayAllSuggest) { + appInfoResult.onboardingSuggestions = + appInfoResult.onboardingSuggestions.slice(0, 3); + } + + if (props?.project?.suggestPromoteInfo?.suggestReplyMode) { + appInfoResult.suggestPromoteInfo = { + suggestReplyMode: props?.project?.suggestPromoteInfo?.suggestReplyMode, + customizedSuggestPrompt: + props?.project?.suggestPromoteInfo?.customizedSuggestPrompt, + }; + } + + if (props?.areaUi?.bgInfo?.imgUrl) { + // 去掉backgroundInfo, 使用本地写的背景组件,不再使用插件进行背景显示。 + appInfoResult.customBgInfo = props?.areaUi?.bgInfo; + } else { + appInfoResult.customBgInfo = undefined; + } + return appInfoResult; +}; + +export const getBotInfo = async ( + apiSdk: CozeAPI | undefined, + props: IBuilderChatProps, +) => { + const connectorId = getConnectorId(props); + const isWebSdk = props?.project?.mode === 'websdk'; + const workflowId = props?.workflow?.id; + const isDebugParam = props?.project?.mode === 'draft' ? 'true' : ''; + const callerParam = props?.project?.caller || ''; + const lang = i18n.language; + console.log('i18n.language', lang); + const [appRes, workflowRes] = await Promise.allSettled([ + isWebSdk + ? apiSdk?.get( + `/v1/apps/${props?.project?.id}?version=${props?.project?.version || ''}&connector_id=${connectorId}`, + {}, + false, + { + headers: { + 'Accept-Language': i18n.language === 'zh-CN' ? 'zh' : 'en', + }, + }, + ) + : null, + + workflowId + ? apiSdk?.get( + `/v1/workflows/${workflowId}?${[ + `connector_id=${connectorId}`, + `is_debug=${isDebugParam}`, + `caller=${callerParam}`, + ].join('&')}`, + {}, + false, + { + headers: { + 'Accept-Language': i18n.language === 'zh-CN' ? 'zh' : 'en', + }, + }, + ) + : null, + ]); + const appData = + appRes?.status === 'fulfilled' ? appRes?.value?.data : undefined; + const workflowData = + workflowRes?.status === 'fulfilled' + ? workflowRes?.value?.data?.role + : undefined; + if (isWebSdk && !appData) { + throw { code: (appRes as CozeApiFullFilledRes)?.reason?.code, message: '' }; + } + const appInfo = getFormatAppData(appData, workflowData, props); + if (props?.workflow?.id && !appInfo.botInfo.url) { + appInfo.botInfo.url = ChatFlowUserIcon; + } + + return appInfo; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/type.ts b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/type.ts new file mode 100644 index 00000000..e0dedc37 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/builder-chat/type.ts @@ -0,0 +1,138 @@ +/* + * 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. + */ + +import type React from 'react'; + +import { type ImageModel, type FileModel } from '@coze-common/chat-core'; +import { type ContentType } from '@coze-common/chat-area'; + +import { type OpenUserInfo } from '@/types/user'; +import { type DebugProps } from '@/types/props'; +import { + type Layout, + type FooterConfig, + type HeaderConfig, + type FeedbackConfig, + type ConversationsConfig, +} from '@/types/client'; +import { type OnImageClick } from '@/types/'; + +export interface IWorkflow { + id?: string; + parameters?: Record; + header?: Record; +} +export interface SuggestPromoteInfo { + customizedSuggestPrompt?: string; + + suggestReplyMode?: number; +} +export interface IProject { + id: string; + type: 'app' | 'bot'; + mode: 'draft' | 'release' | 'websdk' | 'audit'; // 草稿模式 | 发布模式 | webSdk发布 + caller?: 'UI_BUILDER' | 'CANVAS'; + connectorId?: string; + conversationName?: string; // project的话,必须填写 + conversationId?: string; // type 为bot的话,必须填写 + sectionId?: string; // type 为bot的话,必须填写 + name?: string; + desc?: string; + defaultName?: string; + defaultIconUrl?: string; + iconUrl?: string; + layout?: Layout; + version?: string; + onBoarding?: { + prologue?: string; + displayAllSuggest?: boolean; + suggestions?: string[]; + }; + suggestPromoteInfo?: SuggestPromoteInfo; +} +export interface IEventCallbacks { + onMessageChanged?: () => void; + onMessageSended?: () => void; + onMessageReceivedStart?: () => void; + onMessageReceivedFinish?: () => void; + onImageClick?: OnImageClick; + onGetChatFlowExecuteId?: (id: string) => void; + onThemeChange?: (theme: 'bg-theme' | 'light') => void; +} +export interface IBuilderChatProps { + workflow: IWorkflow; + project: IProject; + spaceId?: string; + eventCallbacks?: IEventCallbacks; + userInfo?: OpenUserInfo; + + areaUi: { + isDisabled?: boolean; // 默认 false + uploadable?: boolean; // 默认 true + isNeedClearContext?: boolean; // 是否显示 clearContext按钮 + isNeedClearMessage?: boolean; // 是否显示 clearMessage按钮 + isNeedAddNewConversation?: boolean; //是否显示新增会话 + isNeedFunctionCallMessage?: boolean; + isNeedQuote?: boolean; + feedback?: FeedbackConfig; + input?: { + placeholder?: string; + renderChatInputTopSlot?: (isChatError?: boolean) => React.ReactNode; + isShow?: boolean; //默认 true + defaultText?: string; + isNeedAudio?: boolean; // 是否需要语音输入,默认是false + isNeedTaskMessage?: boolean; + }; + header?: HeaderConfig & { + title?: string; + icon?: string; + }; // 默认是 + footer?: FooterConfig; + conversations?: ConversationsConfig; + uiTheme?: 'uiBuilder' | 'chatFlow'; // uiBuilder 的主题 + renderLoading?: () => React.ReactNode; + bgInfo?: { + imgUrl: string; + themeColor: string; // 背景颜色 + }; + }; + auth?: { + type: 'external' | 'internal'; // 内部: cookie换token, 外部: internal + token?: string; + refreshToken?: () => Promise | string; + }; + style?: React.CSSProperties; + debug?: DebugProps; +} + +export type MessageType = + | { + type: ContentType.Text; + text: string; + } + | { + type: ContentType.Image; + value: ImageModel; + } + | { + type: ContentType.File; + value: FileModel; + }; + +export interface BuilderChatRef { + sendMessage: (message: MessageType) => void; + clearContext: () => void; +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/web-sdk/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/chat/web-sdk/index.module.less new file mode 100644 index 00000000..159e7791 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/web-sdk/index.module.less @@ -0,0 +1,19 @@ +.coze-chat-app { + box-sizing: border-box; + display: flex; + flex-direction: column; + + * { + box-sizing: border-box; + } + + &.bordered { + border-radius: 8px; + box-shadow: 0 6px 8px 0 rgb(29 28 35 / 6%), 0 0 2px 0 rgb(29 28 35 / 18%); + } + + .content { + flex: 1 1 auto; + min-height: 0; + } +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/chat/web-sdk/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/chat/web-sdk/index.tsx new file mode 100644 index 00000000..43b108a7 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/chat/web-sdk/index.tsx @@ -0,0 +1,152 @@ +/* + * 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. + */ + +import { type FC } from 'react'; + +import cs from 'classnames'; +import { useInitStatus } from '@coze-common/chat-area'; + +import { webSdkDefaultConnectorId } from '@/util'; +import { + type StudioChatProviderProps, + type WebSdkChatProps, +} from '@/types/props'; +import { OpenApiSource } from '@/types/open'; +import { ChatType, Layout } from '@/types/client'; +import { useGetTheme } from '@/components/studio-open-chat/hooks/use-get-theme'; +import { + OpenChatProvider, + StudioChatArea, +} from '@/components/studio-open-chat'; +import { Loading } from '@/components/loading'; +import { ChatHeader } from '@/components/header'; +import ChatFooter from '@/components/footer'; +import ErrorFallback from '@/components/error-fallback'; +import { ErrorBoundary } from '@/components/error-boundary'; + +import styles from './index.module.less'; + +export const WebSdkChat: FC = ({ + useInIframe = true, + ...props +}) => { + const { chatConfig } = props ?? {}; + if (!chatConfig?.bot_id) { + return null; + } + if (chatConfig.auth) { + chatConfig.auth.connectorId = + chatConfig.auth.connectorId || webSdkDefaultConnectorId; + } + return ; +}; + +const CozeChat: FC = props => { + const { + layout, + className, + useInIframe = false, + chatConfig, + userInfo, + style, + onImageClick, + onThemeChange, + } = props; + if (!chatConfig.ui) { + chatConfig.ui = {}; + } + if (!chatConfig.ui.chatBot) { + chatConfig.ui.chatBot = {}; + } + if (chatConfig.auth?.type === 'token') { + chatConfig.ui.chatBot.isNeedClearMessage = false; + // chatConfig.ui.chatBot.isNeedAddNewConversation 不需要设置,按照用户的需要设置。 + chatConfig.ui.chatBot.isNeedAddNewConversation = + chatConfig.ui.chatBot.isNeedAddNewConversation ?? true; + chatConfig.ui.chatBot.isNeedClearContext = + chatConfig.ui.chatBot.isNeedClearContext ?? true; + } else { + // 老版本的代码做兼容 + chatConfig.ui.chatBot.isNeedClearMessage = true; + chatConfig.ui.chatBot.isNeedAddNewConversation = false; + chatConfig.ui.chatBot.isNeedClearContext = false; + } + + const chatProps: StudioChatProviderProps = { + chatConfig: { + ...chatConfig, + source: OpenApiSource.WebSdk, + }, + layout, + userInfo, + initErrorFallbackFC: ErrorFallback, + onImageClick, + }; + + return ( + +
+ + + +
+
+ ); +}; + +const WebSdkChatArea: FC<{ + chatProps: StudioChatProviderProps; + webSdkProps: WebSdkChatProps; +}> = ({ chatProps, webSdkProps }) => { + const { layout, title, headerExtra, icon, chatConfig } = webSdkProps; + const initStatus = useInitStatus(); + const isMobile = layout === Layout.MOBILE; + const { header: headerConf, conversations } = chatConfig?.ui || {}; + const theme = useGetTheme(); + const isShowConversations = + conversations?.isNeed && chatConfig.type !== ChatType.APP; + + if (initStatus !== 'initSuccess') { + return ; + } + return ( +
+ + } + /> + +
+ ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/builder-chat/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/builder-chat/index.tsx deleted file mode 100644 index dec23109..00000000 --- a/frontend/packages/studio/open-platform/open-chat/src/components/builder-chat/index.tsx +++ /dev/null @@ -1,170 +0,0 @@ -/* - * 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. - */ - -import '@coze/chat-sdk/webCss'; -import { forwardRef, useMemo, type Ref, useImperativeHandle } from 'react'; - -import { - openApiHostByRegionWithToken, - openApiCdnUrlByRegion, -} from '@coze-studio/open-env-adapter'; -import { I18n } from '@coze-arch/i18n'; -import ChatSdk from '@coze/chat-sdk/webJs'; -import { - type RawMessage, - type IChatFlowProps, - type Language, -} from '@coze/chat-sdk'; - -import { type IBuilderChatProps, type BuilderChatRef } from '@/types'; - -const { - ChatFlowFramework, - ChatSlot, - useSendMessage, - ChatType, - RawMessageType, -} = ChatSdk; - -export { ChatType, RawMessageType }; -export const ChatContent = forwardRef( - (_props: {}, ref: Ref) => { - const { sendMessage } = useSendMessage(); - useImperativeHandle( - ref, - () => ({ - sendMessage: (message: RawMessage) => { - sendMessage(message); - }, - }), - [sendMessage], - ); - return ; - }, -); - -export const BuilderChat = forwardRef( - (props: IBuilderChatProps, ref: Ref) => { - const { workflow } = props; - const eventCallbacks: IChatFlowProps['eventCallbacks'] = useMemo( - () => ({ - onImageClick: props.eventCallbacks?.onImageClick, - onGetChatFlowExecuteId: props.eventCallbacks?.onGetChatFlowExecuteId, - onThemeChange: props.eventCallbacks?.onThemeChange, - onInitSuccess: props.eventCallbacks?.onInitSuccess, - message: { - afterMessageReceivedFinish: - props.eventCallbacks?.afterMessageReceivedFinish, - }, - }), - [props.eventCallbacks], - ); - const { userInfo } = props; - const { auth } = props; - const areaUi: IChatFlowProps['areaUi'] = useMemo( - // eslint-disable-next-line complexity - () => ({ - layout: props.project?.layout, - isDisabled: props.areaUi?.isDisabled, - input: { - isNeed: props.areaUi?.input?.isShow, - isNeedAudio: props.areaUi?.input?.isNeedAudio ?? !IS_OVERSEA, - placholder: props.areaUi?.input?.placeholder, - isNeedTaskMessage: props.areaUi?.input?.isNeedTaskMessage, - defaultText: props.areaUi?.input?.defaultText, - renderChatInputTopSlot: props.areaUi?.input?.renderChatInputTopSlot, - }, - clearContext: - props.project?.mode === 'websdk' - ? { - isNeed: true, - position: 'inputLeft', - } - : { - isNeed: false, - }, - clearMessage: - props.project?.mode === 'websdk' - ? { - isNeed: true, - position: 'headerRight', - } - : { - isNeed: - props.areaUi?.isNeedClearMessage !== undefined - ? props.areaUi?.isNeedClearMessage - : true, - position: 'inputLeft', - }, - uploadBtn: { - isNeed: props.areaUi?.uploadable, - }, - uiTheme: props.areaUi?.uiTheme, - renderLoading: props.areaUi?.renderLoading, - header: { - isNeed: props.areaUi?.header?.isShow || false, - icon: props.project?.iconUrl, - title: props.project?.name, - renderRightSlot: () => <>{props.areaUi?.header?.extra || null}, - }, - footer: props.areaUi?.footer, - }), - [props.areaUi, props.project], - ); - const setting: IChatFlowProps['setting'] = useMemo( - () => ({ - apiBaseUrl: openApiHostByRegionWithToken, - cdnBaseUrlPath: openApiCdnUrlByRegion, - language: I18n.language as Language, - logLevel: IS_BOE ? 'debug' : 'release', - ...(props.setting || {}), - }), - [], - ); - const project: IChatFlowProps['project'] = useMemo( - () => ({ - id: props.project?.id || '', - type: props.project?.type, - mode: props.project?.mode as 'release', - caller: props.project?.caller, - defaultName: props.project?.defaultName, - defaultIconUrl: props.project?.defaultIconUrl, - connectorId: props.project?.connectorId, - conversationName: props.project?.conversationName, - name: props.project?.name, - iconUrl: props.project?.iconUrl, - OnBoarding: props.project?.onBoarding, - }), - [props.project], - ); - return ( - <> - - - - - ); - }, -); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/index.ts b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/index.ts new file mode 100644 index 00000000..4b609b4d --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/index.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + +export { PcConversationItem } from './pc'; +export { MobileConversationItem } from './mobile'; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/mobile/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/mobile/index.module.less new file mode 100644 index 00000000..15b7163c --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/mobile/index.module.less @@ -0,0 +1,66 @@ +.conversation-item { + display: flex; + flex-direction: column; +} + +.conversation-item-time { + padding: 8px 8px 0; + color: var(--coz-fg-secondary); + font-size: 14px; + font-weight: 400; + line-height: 20px; + margin-top: 4px; +} + +.conversation-item-content { + display: flex; + height: 40px; + padding: 4px 8px; + align-items: center; + align-self: stretch; + border-radius: var(--mini, 5px); /* 兼容 iOS/Safari/微信 */ + user-select: none; +} + +.conversation-item-content-active { + background: var(--coz-mg-secondary-hovered); +} + +.conversation-item-mask { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + // background: var(--coz-mg-mask); + // 半透明白色背景 + background: rgba(255, 255, 255, 25%); + // 毛玻璃效果 + backdrop-filter: blur(2.5px) saturate(1.2); + z-index: calc(var(--chat-z-index-input) + 2); +} + +.conversation-item-content-touched { + animation: jelly 0.7s cubic-bezier(0.4, 0, 0.2, 1); + will-change: transform; + border: 1px solid var(--coz-stroke-primary); +} + +.conversation-item-content-operate-visible { + z-index: calc(var(--chat-z-index-input) + 3); + background: var(--coz-bg-max); + border: 1px solid var(--coz-stroke-primary); + box-shadow: 0 0 8px 1px rgba(0, 0, 0, 20%); +} + +@keyframes jelly { + 0% { transform: scale(1); } + + 70% { + transform: scale(1.08); /* 慢慢放大到1.08 */ + } + + 100% { + transform: scale(1); + } +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/mobile/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/mobile/index.tsx new file mode 100644 index 00000000..2c0e2da0 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/mobile/index.tsx @@ -0,0 +1,165 @@ +/* + * 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. + */ + +import { useCallback, useRef, useState } from 'react'; + +import cls from 'classnames'; +import { I18n } from '@coze-arch/i18n'; +import { Typography } from '@coze-arch/coze-design'; +import { type Conversation } from '@coze/api'; + +import { + conversationSortMap, + type SortedConversationItem, +} from '@/types/conversations'; + +import { MobileConversationOperate } from './operate'; + +import s from './index.module.less'; + +export const MobileConversationItem = ({ + isActive, + item, + shouldDisplayTime, + onConversationChange, + onRename, + onDelete, +}: { + isActive: boolean; + item: SortedConversationItem; + shouldDisplayTime: boolean; + onConversationChange: (conversation: Conversation) => void; + onRename: (conversation: Conversation) => void; + onDelete: (conversation: Conversation) => void; +}) => { + const [visible, setVisible] = useState(false); + const [isTouched, setIsTouched] = useState(false); + const longPressTimerRef = useRef(null); + const longClickTimerRef = useRef(null); + // 长按检测时间(毫秒) + const LONG_PRESS_DURATION = 500; + const LONG_CLICK_DURATION = 300; + + const closeOperate = () => { + setVisible(false); + setIsTouched(false); + }; + + const handleTouchStart = useCallback((e: React.TouchEvent) => { + longPressTimerRef.current = setTimeout(() => { + setVisible(true); + }, LONG_PRESS_DURATION); + longClickTimerRef.current = setTimeout(() => { + setIsTouched(true); + }, LONG_CLICK_DURATION); + }, []); + + const handleTouchEnd = useCallback((e: React.TouchEvent) => { + if (longClickTimerRef.current) { + clearTimeout(longClickTimerRef.current); + longClickTimerRef.current = null; + } + if (longPressTimerRef.current) { + clearTimeout(longPressTimerRef.current); + longPressTimerRef.current = null; + } + }, []); + + const handleTouchMove = useCallback(() => { + if (longClickTimerRef.current) { + clearTimeout(longClickTimerRef.current); + longClickTimerRef.current = null; + } + // 如果用户移动手指,取消长按 + if (longPressTimerRef.current) { + clearTimeout(longPressTimerRef.current); + closeOperate(); + longPressTimerRef.current = null; + } + }, []); + + return ( +
+ {shouldDisplayTime ? ( +
+ {conversationSortMap.get(item.sort)} +
+ ) : null} + { + onRename(item); + closeOperate(); + }} + onDelete={() => { + onDelete(item); + closeOperate(); + }} + visible={visible} + > +
{ + if (visible) { + return; + } + onConversationChange(item); + }} + onTouchStart={handleTouchStart} + onTouchEnd={handleTouchEnd} + onTouchMove={handleTouchMove} + onContextMenu={e => { + e.preventDefault(); + }} + > + + {item.name || + I18n.t('web_sdk_conversation_default_name', {}, '新创建的会话')} + +
+
+ {visible ? ( +
{ + e.stopPropagation(); + e.preventDefault(); + closeOperate(); + }} + className={s['conversation-item-mask']} + >
+ ) : null} +
+ ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/mobile/operate/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/mobile/operate/index.tsx new file mode 100644 index 00000000..7c028662 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/mobile/operate/index.tsx @@ -0,0 +1,67 @@ +/* + * 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. + */ + +import { I18n } from '@coze-arch/i18n'; +import { IconCozEdit, IconCozTrashCan } from '@coze-arch/coze-design/icons'; +import { Menu } from '@coze-arch/coze-design'; + +export const MobileConversationOperate = ({ + onRename, + onDelete, + visible, + children, +}: { + onRename: () => void; + onDelete: () => void; + visible: boolean; + children: React.ReactNode; +}) => ( + <> + + { + e.stopPropagation(); + e.preventDefault(); + onRename(); + }} + icon={} + > + {I18n.t('workflow_detail_node_rename', {}, '重命名')} + + { + e.stopPropagation(); + e.preventDefault(); + onDelete(); + }} + icon={} + > + + {I18n.t('web_sdk_delete', {}, '删除')} + + + + } + > + {children} + + +); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/pc/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/pc/index.module.less new file mode 100644 index 00000000..e64eb879 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/pc/index.module.less @@ -0,0 +1,39 @@ +.conversation-item { + display: flex; + flex-direction: column; +} + +.conversation-item-time { + padding: 8px 8px 0; + color: var(--coz-fg-secondary); + font-size: 14px; + font-weight: 400; + line-height: 20px; + margin-top: 4px; +} + +.conversation-item-content { + display: flex; + height: 40px; + padding: 4px 8px; + align-items: center; + align-self: stretch; + border-radius: var(--mini, 5px); + cursor: pointer; + + .conversation-operate { + display: none; + } + + &:hover { + background: var(--coz-mg-secondary-hovered); + + .conversation-operate { + display: block; + } + } +} + +.conversation-item-content-active { + background: var(--coz-mg-secondary-hovered); +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/pc/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/pc/index.tsx new file mode 100644 index 00000000..364dc3ff --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/pc/index.tsx @@ -0,0 +1,113 @@ +/* + * 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. + */ + +import { useState } from 'react'; + +import cls from 'classnames'; +import { I18n } from '@coze-arch/i18n'; +import { IconCozMore } from '@coze-arch/coze-design/icons'; +import { IconButton, Typography } from '@coze-arch/coze-design'; +import { type Conversation } from '@coze/api'; + +import { + conversationSortMap, + type SortedConversationItem, +} from '@/types/conversations'; + +import { Operate } from './operate'; + +import s from './index.module.less'; + +export const PcConversationItem = ({ + isActive, + item, + shouldDisplayTime, + onConversationChange, + onRename, + onDelete, +}: { + isActive: boolean; + item: SortedConversationItem; + shouldDisplayTime: boolean; + onConversationChange: (conversation: Conversation) => void; + onRename: (conversation: Conversation) => void; + onDelete: (conversation: Conversation) => void; +}) => { + const [visible, setVisible] = useState(false); + + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + setVisible(true); + }; + + return ( +
+ {shouldDisplayTime ? ( +
+ {conversationSortMap.get(item.sort)} +
+ ) : null} +
onConversationChange(item)} + > + + {item.name || + I18n.t('web_sdk_conversation_default_name', {}, '新创建的会话')} + + { + onRename(item); + setVisible(false); + }} + onDelete={() => { + onDelete(item); + setVisible(false); + }} + visible={visible} + setVisible={setVisible} + > + } + color="secondary" + /> + +
+
+ ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/pc/operate/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/pc/operate/index.tsx new file mode 100644 index 00000000..540998ca --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-item/pc/operate/index.tsx @@ -0,0 +1,68 @@ +/* + * 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. + */ + +import { I18n } from '@coze-arch/i18n'; +import { IconCozEdit, IconCozTrashCan } from '@coze-arch/coze-design/icons'; +import { Menu } from '@coze-arch/coze-design'; + +export const Operate = ({ + children, + onRename, + onDelete, + visible, + setVisible, +}: { + children: React.ReactNode; + onRename: () => void; + onDelete: () => void; + visible: boolean; + setVisible: (visible: boolean) => void; +}) => ( + setVisible(false)} + render={ + + { + e.stopPropagation(); + e.preventDefault(); + onRename(); + }} + icon={} + > + {I18n.t('workflow_detail_node_rename', {}, '重命名')} + + { + e.stopPropagation(); + e.preventDefault(); + onDelete(); + }} + icon={} + > + + {I18n.t('web_sdk_delete', {}, '删除')} + + + + } + > + {children} + +); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-list/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-list/index.module.less new file mode 100644 index 00000000..6a156723 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-list/index.module.less @@ -0,0 +1,40 @@ +.conversations { + display: flex; + flex-direction: column; + min-width: 320px; + height: 100%; +} + +.conversations-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px; +} + +.conversations-header-title { + color: var(--coz-fg-primary); + font-size: 16px; + font-weight: 600; + line-height: 22px; +} + +.conversations-create-button { + margin: 0 16px; + width: calc(100% - 32px); +} + +.conversations-list { + display: flex; + flex-direction: column; + gap: 4px; + overflow-y: auto; + flex: 1; + margin-bottom: 10px; +} + +.conversations-list-group { + display: flex; + flex-direction: column; + padding: 0 8px; +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-list/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-list/index.tsx new file mode 100644 index 00000000..dd67e06d --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/conversation-list/index.tsx @@ -0,0 +1,296 @@ +/* + * 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. + */ + +import { + useMemo, + useRef, + useState, + forwardRef, + useImperativeHandle, + useEffect, +} from 'react'; + +import { useShallow } from 'zustand/react/shallow'; +import { I18n } from '@coze-arch/i18n'; +import { IconCozPlus, IconCozSideNav } from '@coze-arch/coze-design/icons'; +import { Button, IconButton, Spin, Toast } from '@coze-arch/coze-design'; +import { type Conversation } from '@coze/api'; + +import { + type ConversationSort, + type SortedConversationItem, +} from '@/types/conversations'; +import { Layout } from '@/types/client'; + +import { + PcConversationItem, + MobileConversationItem, +} from '../conversation-item'; +import { type ChatState } from '../../studio-open-chat/store/store'; +import { useChatAppProps, useChatAppStore } from '../../studio-open-chat/store'; + +import s from './index.module.less'; + +export interface ConversationListSiderRef { + getConversationInfo: () => + | { + conversationId: string; + sectionId?: string; + } + | undefined; + handleCreateConversation: () => Promise; + handleDeleteConversation: (conversation: Conversation) => Promise; +} + +export const ConversationList = forwardRef< + ConversationListSiderRef, + { + onRename: (conversation: Conversation) => void; + onDelete: (conversation: Conversation) => void; + loading: boolean; + groupedConversations: Map; + conversations: Conversation[]; + hasMore: boolean; + loadMore: () => Promise; + } +>( + ( + { + onRename, + onDelete, + loading, + groupedConversations, + conversations, + hasMore, + loadMore, + }, + ref, + ) => { + const { + currentConversationInfo, + updateCurrentConversationInfo, + cozeApi, + updateConversations, + } = useChatAppStore( + useShallow(state => ({ + currentConversationInfo: state.currentConversationInfo, + updateCurrentConversationInfo: state.updateCurrentConversationInfo, + cozeApi: state.cozeApi, + updateConversations: state.updateConversations, + })), + ); + + const conversationRef = useRef(); + const [addLoading, setAddLoading] = useState(false); + const { + layout, + chatConfig: { bot_id: botId, auth: { connectorId } = {} }, + } = useChatAppProps(); + + const isMobile = useMemo(() => layout === Layout.MOBILE, [layout]); + + const listContainerRef = useRef(null); + const loadMoreRef = useRef(null); + + const handleCreateConversation = async () => { + if (!currentConversationInfo) { + return; + } + try { + const res = await cozeApi?.conversations.create({ + bot_id: botId, + // @ts-expect-error: 有这个属性,但是 openapi 没有暴露 + connector_id: connectorId, + }); + if (res?.id) { + conversationRef.current = { + ...currentConversationInfo, + id: res.id, + last_section_id: res.last_section_id, + }; + updateConversations([res], 'add'); + updateCurrentConversationInfo({ + ...currentConversationInfo, + ...res, + name: '', + }); + Toast.info({ + content: I18n.t('web_sdk_create_conversation', {}, '已创建新会话'), + showClose: false, + }); + } + } catch (error) { + console.error(error); + } + }; + + const handleConversationChange = (conversation: Conversation) => { + if ( + !currentConversationInfo || + conversation.id === currentConversationInfo?.id + ) { + return; + } + const c = { + ...currentConversationInfo, + ...conversation, + id: conversation.id, + sectionId: conversation.last_section_id, + }; + conversationRef.current = c; + updateCurrentConversationInfo(c); + }; + + const handleDeleteConversation = async (conversation: Conversation) => { + try { + const res = (await cozeApi?.delete( + `/v1/conversations/${conversation.id}`, + )) as { + code: number; + }; + if (res.code !== 0) { + return false; + } + await updateConversations([conversation], 'remove'); + return true; + } catch (error) { + console.error(error); + return false; + } + }; + + useImperativeHandle(ref, () => ({ + getConversationInfo: () => { + if (conversationRef.current) { + return { + conversationId: conversationRef.current.id, + sectionId: conversationRef.current.last_section_id, + }; + } else if (conversations.length > 0) { + return { + conversationId: conversations[0].id, + sectionId: conversations[0].last_section_id, + }; + } + return undefined; + }, + handleCreateConversation, + handleDeleteConversation, + })); + + useEffect(() => { + const observer = new IntersectionObserver( + entries => { + const [entry] = entries; + if (entry.isIntersecting && hasMore && !loading) { + loadMore(); + } + }, + { + root: listContainerRef.current, + rootMargin: '100px', + threshold: 0.01, + }, + ); + + if (loadMoreRef.current) { + observer.observe(loadMoreRef.current); + } + + return () => { + if (loadMoreRef.current) { + observer.unobserve(loadMoreRef.current); + } + }; + }, [hasMore, loading, loadMore]); + + return ( +
+
+ + {I18n.t('web_sdk_conversation_history', {}, '会话历史')} + + { + if (currentConversationInfo) { + updateCurrentConversationInfo({ + ...currentConversationInfo, + conversationListVisible: false, + }); + } + }} + icon={} + /> +
+ +
+ {Array.from(groupedConversations.entries()).map( + ([sort, conversationList]) => ( +
+ {conversationList.map((conversation, index) => + isMobile ? ( + + ) : ( + + ), + )} +
+ ), + )} +
+ {loading ? ( + + ) : null} +
+
+ ); + }, +); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/index.module.less new file mode 100644 index 00000000..0c942145 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/index.module.less @@ -0,0 +1,17 @@ +.conversations-container { + display: flex; + height: 100%; + width: 100%; +} + +.conversations-side-sheet { + z-index: calc(var(--chat-z-index-input) + 1); + padding: 0; +} + +.conversations-list-delete-modal-text { + color: var(--coz-fg-secondary); + font-size: 14px; + font-weight: 400; + line-height: 20px; +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/index.tsx new file mode 100644 index 00000000..2adb7295 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/conversation-list-sider/index.tsx @@ -0,0 +1,256 @@ +/* + * 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. + */ + +/* eslint-disable complexity */ +import { + Fragment, + type ReactNode, + useState, + forwardRef, + useRef, + useImperativeHandle, +} from 'react'; + +import { useShallow } from 'zustand/react/shallow'; +import { I18n } from '@coze-arch/i18n'; +import { IconCozSideNav } from '@coze-arch/coze-design/icons'; +import { Input, Modal, SideSheet } from '@coze-arch/coze-design'; +import { type Conversation } from '@coze/api'; + +import { useChatAppStore } from '../studio-open-chat/store'; +import { + useConversationList, + useGroupedConversations, +} from '../studio-open-chat/hooks/use-conversation-list'; +import { + ConversationList, + type ConversationListSiderRef, +} from './conversation-list'; + +import s from './index.module.less'; + +export const ConversationListSider = forwardRef< + Pick, + { + children: ReactNode; + } +>(({ children }, ref) => { + const conversationListRef = useRef(null); + const { currentConversationInfo, updateConversations, cozeApi } = + useChatAppStore( + useShallow(state => ({ + currentConversationInfo: state.currentConversationInfo, + updateConversations: state.updateConversations, + cozeApi: state.cozeApi, + })), + ); + + const { loading, conversations, hasMore, loadMore } = useConversationList(); + + const groupedConversations = useGroupedConversations(conversations); + + const [isModalLoading, setIsModalLoading] = useState(false); + const [modalInfo, setModalInfo] = useState<{ + visible: boolean; + type: 'rename' | 'delete'; + conversation: Conversation; + } | null>(null); + + const handleOpenRenameModal = (conversation: Conversation) => { + setModalInfo({ + visible: true, + type: 'rename', + conversation, + }); + }; + + const handleOpenDeleteModal = (conversation: Conversation) => { + setModalInfo({ + visible: true, + type: 'delete', + conversation, + }); + }; + + const handleUpdateConversationName = async (conversation: Conversation) => { + try { + const res = (await cozeApi?.put(`/v1/conversations/${conversation.id}`, { + name: conversation.name, + })) as { + data: Conversation; + code: number; + }; + if (res.code !== 0) { + return; + } + await updateConversations([res.data], 'update'); + } catch (error) { + console.error(error); + } + }; + + useImperativeHandle(ref, () => ({ + getConversationInfo: () => { + if (conversationListRef.current) { + return conversationListRef.current.getConversationInfo(); + } + return undefined; + }, + })); + + const handleModalOk = async () => { + if (!modalInfo) { + return; + } + setIsModalLoading(true); + try { + if (modalInfo?.type === 'rename') { + await handleUpdateConversationName(modalInfo.conversation); + } else { + if (modalInfo.conversation.id === currentConversationInfo?.id) { + if ( + await conversationListRef.current?.handleDeleteConversation( + modalInfo.conversation, + ) + ) { + await conversationListRef.current?.handleCreateConversation(); + } + } else { + await conversationListRef.current?.handleDeleteConversation( + modalInfo.conversation, + ); + } + } + setIsModalLoading(false); + setModalInfo(null); + } catch (error) { + console.error(error); + setIsModalLoading(false); + } + }; + + return ( +
+ {currentConversationInfo?.conversationListVisible && + currentConversationInfo?.isLargeWidth ? ( + + ) : null} + + {children} + + } + closable={false} + closeOnEsc={false} + maskClosable={false} + getPopupContainer={() => + document.querySelector('.coze-chat-sdk') as HTMLElement + } + placement="left" + width={320} + className={s['conversations-side-sheet']} + headerStyle={{ + display: 'none', + }} + bodyStyle={{ + padding: 0, + height: '100%', + }} + > + + + + document.querySelector('.coze-chat-sdk') as HTMLElement + } + okButtonProps={{ + loading: isModalLoading, + }} + visible={modalInfo?.visible} + onCancel={() => setModalInfo(null)} + title={ + modalInfo?.type === 'delete' + ? I18n.t('web_sdk_delete_conversation', {}, '删除会话') + : I18n.t('web_sdk_rename_conversation', {}, '重命名会话') + } + onOk={handleModalOk} + okText={ + modalInfo?.type === 'delete' + ? I18n.t('web_sdk_delete', {}, '删除') + : I18n.t('web_sdk_confirm', {}, '确定') + } + cancelText={I18n.t('web_sdk_cancel', {}, '取消')} + okButtonColor={modalInfo?.type === 'delete' ? 'red' : 'brand'} + closable={false} + maskClosable={false} + style={{ + maxWidth: '80%', + }} + > + {modalInfo?.type === 'rename' ? ( + + setModalInfo({ + ...modalInfo, + conversation: { + ...modalInfo.conversation, + name: value, + }, + }) + } + /> + ) : ( + + {I18n.t( + 'web_sdk_conversation_delete_content', + {}, + '删除后,会话将无法恢复,确认要删除吗?', + )} + + )} + +
+ ); +}); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/error-boundary/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/error-boundary/index.tsx new file mode 100644 index 00000000..e5b2e94c --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/error-boundary/index.tsx @@ -0,0 +1,42 @@ +/* + * 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. + */ + +import React, { type FC, type PropsWithChildren } from 'react'; + +import { ErrorBoundary as FlowErrorBoundary } from '@coze-arch/logger'; +import { I18n } from '@coze-arch/i18n'; +import { Typography } from '@coze-arch/bot-semi'; + +import { studioOpenClientReporter } from '@/helper'; + +const { Title, Text } = Typography; + +const FallbackComponent: FC = () => ( +
+ {I18n.t('404_title')} + {I18n.t('404_content')} +
+); + +export const ErrorBoundary: FC = ({ children }) => ( + + {children} + +); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/error-fallback/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/components/error-fallback/index.module.less new file mode 100644 index 00000000..cf8b0b4e --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/error-fallback/index.module.less @@ -0,0 +1,32 @@ +.wrapper { + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex: 1; +} + +.icon { + width: 48px; + height: 48px; +} + +.message { + margin-top: 10px; + line-height: 22px; + font-size: 16px; + font-weight: 500; + color: #060709cc; +} + +.extra { + margin-top: 4px; + line-height: 16px; + font-size: 12px; + color: #0607094d; +} + +.btn { + margin-top: 28px; +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/error-fallback/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/error-fallback/index.tsx new file mode 100644 index 00000000..9855753e --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/error-fallback/index.tsx @@ -0,0 +1,99 @@ +/* + * 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. + */ + +import { type FC } from 'react'; + +import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n'; +import { Button } from '@coze-arch/coze-design'; + +import { + getServerError, + type SDKInitError, + ServerErrorCode, + specCodeList, +} from '@/util/error'; +import ErrorUnbindPng from '@/assets/error-unbind.png'; +import ErrorDefaultPng from '@/assets/error-default.png'; + +import styles from './index.module.less'; + +export interface InitErrorFallback { + /** + * null 表示未获取到报错信息 + */ + error: SDKInitError | null; + onBeforeRetry?: () => void; + refresh?: () => void; +} + +const ErrorFallback: FC = ({ + error, + onBeforeRetry, + refresh, +}) => { + let msg = I18n.t('overview_bi_assistant_system_error'); + if (error) { + msg = error.msg; + const wrapError = getServerError(error); + + if (wrapError) { + msg = wrapError.msg; + } + } + const defaultError = I18n.t('web_sdk_retry_notification'); + + const hideExtra = !!error?.code && specCodeList.includes(error.code); + + return ( +
+ +
{msg}
+ {!hideExtra && ( +
+ {I18n.t( + `web_sdk_api_error_${error?.code}` as I18nKeysNoOptionsType, + {}, + defaultError, + )} +
+ )} + +
+ ); +}; + +export default ErrorFallback; + +export const getErrorIcon = (error: SDKInitError | null) => { + switch (error?.code) { + case ServerErrorCode.BotUnbind: + return ErrorUnbindPng; + default: + return ErrorDefaultPng; + } +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/footer/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/components/footer/index.module.less new file mode 100644 index 00000000..09861d30 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/footer/index.module.less @@ -0,0 +1,34 @@ +.footer { + flex: 0 0 auto; + display: flex; + align-items: center; + justify-content: center; + height: 32px; + font-size: 12px; + font-weight: 400; + + background: none; + position: absolute; + width: 100%; + bottom: 2px; + z-index: 50; + + > .text { + color: rgba(48, 59, 94, 47%); + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; + + > .link { + color: #4d53e8; + text-decoration: none; + } + } + + &.bg-theme { + > .text { + color: rgba(255, 255, 255, 60%); + } + } +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/footer/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/footer/index.tsx new file mode 100644 index 00000000..6c671bfd --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/footer/index.tsx @@ -0,0 +1,106 @@ +/* + * 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. + */ + +import { type FC, Fragment } from 'react'; + +import cls from 'classnames'; +import { cozeOfficialHost } from '@coze-studio/open-env-adapter'; +import { I18n } from '@coze-arch/i18n'; + +import { type FooterConfig } from '@/types/client'; + +import styles from './index.module.less'; + +const getDefaultText = () => + I18n.t('web_sdk_official_banner', { + docs_link: ( + + {I18n.t('web_sdk_official_banner_link')} + + ), + }); + +const getTextByExpress = ( + expressionText: string, + linkvars?: Record< + string, + { + text: string; + link: string; + } + >, +) => { + const arrLinks: React.ReactNode[] = []; + const splitLinkTag = '{{{link}}}'; + const textWithLinkTags = expressionText.replace( + /\{\{\s*(\w+)\s*\}\}/g, + (_, key) => { + const { link, text: linkText } = linkvars?.[key] || {}; + if (link && linkText) { + arrLinks.push( + + {linkText} + , + ); + return splitLinkTag; + } else { + arrLinks.push(linkText || ''); + } + return splitLinkTag; + }, + ); + return textWithLinkTags.split(splitLinkTag).map((item, index) => ( + + {item} + {arrLinks[index]} + + )); +}; + +const ChatFooter: FC< + FooterConfig & { + footerClassName?: string; + textClassName?: string; + theme?: 'bg-theme' | 'light'; + } +> = ({ + isShow = true, + expressionText, + linkvars, + footerClassName, + textClassName, + theme, +}) => + isShow ? ( +
+ + {expressionText + ? getTextByExpress(expressionText, linkvars) + : getDefaultText()} + +
+ ) : null; + +export default ChatFooter; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/header/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/components/header/index.module.less new file mode 100644 index 00000000..879f8885 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/header/index.module.less @@ -0,0 +1,5 @@ +.float-open-conversations-btn { + position: absolute; + top: 20px; + left: 0; +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/header/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/header/index.tsx new file mode 100644 index 00000000..489565bd --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/header/index.tsx @@ -0,0 +1,77 @@ +/* + * 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. + */ + +import { type FC } from 'react'; + +import { useShallow } from 'zustand/react/shallow'; +import { I18n } from '@coze-arch/i18n'; +import { IconCozArrowRightFill } from '@coze-arch/coze-design/icons'; +import { IconButton, Tooltip } from '@coze-arch/coze-design'; + +import { useChatAppStore } from '../studio-open-chat/store'; +import { type ChatHeaderProps } from './type'; +import ChatHeaderPC from './pc'; +import ChatHeaderMobile from './mobile'; + +import styles from './index.module.less'; + +const FloatBtn = () => { + const { updateCurrentConversationInfo, currentConversationInfo } = + useChatAppStore( + useShallow(s => ({ + updateCurrentConversationInfo: s.updateCurrentConversationInfo, + currentConversationInfo: s.currentConversationInfo, + })), + ); + return currentConversationInfo?.conversationListVisible ? null : ( + + } + onClick={() => { + if (!currentConversationInfo) { + return; + } + updateCurrentConversationInfo({ + ...currentConversationInfo, + conversationListVisible: true, + }); + }} + > + + ); +}; + +export const ChatHeader: FC = ({ + isMobile, + ...props +}) => { + const { isShowConversations, isShowHeader } = props; + if (!isShowHeader) { + return isShowConversations ? : null; + } + return isMobile ? ( + + ) : ( + + ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/header/mobile/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/components/header/mobile/index.module.less new file mode 100644 index 00000000..707f588b --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/header/mobile/index.module.less @@ -0,0 +1,43 @@ +/* stylelint-disable declaration-no-important */ +.header { + flex: 0 0 auto; + display: flex; + align-items: center; + height: 72px; + padding: 24px 16px 16px; + + .conversation-list-btn { + margin-right: 8px; + } + + .avatar { + width: 28px; + height: 28px; + margin-right: 15px; + } + + .title { + flex: 1 1 auto; + font-size: 20px; + margin-right: 12px; + font-weight: 700; + color: #000000d9; + } + + .icon-btn { + width: 32px; + height: 32px; + } + + &.bg-theme { + .icon-btn, + .title { + color: #fff; + } + + .icon-btn:disabled, + .icon-btn:hover { + color: rgba(255, 255, 255, 80%) !important; + } + } +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/header/mobile/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/header/mobile/index.tsx new file mode 100644 index 00000000..f728d3d4 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/header/mobile/index.tsx @@ -0,0 +1,101 @@ +/* + * 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. + */ + +import { useShallow } from 'zustand/react/shallow'; +import cls from 'classnames'; +import { IconCozSideNav } from '@coze-arch/coze-design/icons'; +import { Button, IconButton } from '@coze-arch/coze-design'; +import { Typography } from '@coze-arch/bot-semi'; + +import { useChatAppStore } from '@/components/studio-open-chat/store'; +import { + useChatChatButtonInfo, + useChatOpInfo, +} from '@/components/studio-open-chat/hooks/use-chat-op-info'; +import CozeLogoPng from '@/assets/coze-logo.png'; + +import { type ChatHeaderProps } from '../type'; + +import styles from './index.module.less'; + +const ChatHeaderMobile = ({ + iconUrl = CozeLogoPng, + title = 'Coze Bot', + extra, + theme, + isShowConversations, +}: ChatHeaderProps) => { + const { headerTopLeftOps } = useChatOpInfo(); + const buttonList = useChatChatButtonInfo(headerTopLeftOps); + + const { updateCurrentConversationInfo, currentConversationInfo } = + useChatAppStore( + useShallow(s => ({ + updateCurrentConversationInfo: s.updateCurrentConversationInfo, + currentConversationInfo: s.currentConversationInfo, + })), + ); + + return ( +
+ {currentConversationInfo?.conversationListVisible || + !isShowConversations ? null : ( + } + className={styles['conversation-list-btn']} + onClick={() => { + if (!currentConversationInfo) { + return; + } + updateCurrentConversationInfo({ + ...currentConversationInfo, + conversationListVisible: true, + }); + }} + /> + )} + avatar + + {title} + + {buttonList?.map(item => ( +
+ ); +}; + +export default ChatHeaderMobile; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/header/pc/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/components/header/pc/index.module.less new file mode 100644 index 00000000..28f3ebb6 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/header/pc/index.module.less @@ -0,0 +1,44 @@ +/* stylelint-disable declaration-no-important */ +.header { + flex: 0 0 auto; + display: flex; + align-items: center; + height: 56px; + padding: 0 16px; + border-bottom: 1px solid #1d1c2314; + + .conversation-list-btn { + margin-right: 8px; + } + + .avatar { + width: 24px; + height: 24px; + margin-right: 8px; + } + + .title { + flex: 1 1 auto; + margin-right: 12px; + font-size: 18px; + font-weight: 600; + color: #1c1d23; + } + + .icon-btn { + width: 32px; + height: 32px; + } + + &.bg-theme { + .icon-btn, + .title { + color: #fff; + } + + .icon-btn:disabled, + .icon-btn:hover { + color: rgba(255, 255, 255, 80%)!important; + } + } +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/header/pc/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/header/pc/index.tsx new file mode 100644 index 00000000..590815b0 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/header/pc/index.tsx @@ -0,0 +1,131 @@ +/* + * 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. + */ + +import { useShallow } from 'zustand/react/shallow'; +import cls from 'classnames'; +import { IconCozSideNav } from '@coze-arch/coze-design/icons'; +import { Button, IconButton } from '@coze-arch/coze-design'; +import { Typography } from '@coze-arch/bot-semi'; + +import { useChatAppStore } from '@/components/studio-open-chat/store'; +import { + useChatChatButtonInfo, + useChatOpInfo, +} from '@/components/studio-open-chat/hooks/use-chat-op-info'; +import CozeLogoPng from '@/assets/coze-logo.png'; + +import { type ChatHeaderProps } from '../type'; + +import styles from './index.module.less'; + +const ChatHeader = ({ + iconUrl = CozeLogoPng, + title = 'Coze Bot', + extra, + theme, + isShowConversations, +}: ChatHeaderProps) => { + const { headerTopLeftOps } = useChatOpInfo(); + const buttonList = useChatChatButtonInfo(headerTopLeftOps); + const { updateCurrentConversationInfo, currentConversationInfo } = + useChatAppStore( + useShallow(s => ({ + updateCurrentConversationInfo: s.updateCurrentConversationInfo, + currentConversationInfo: s.currentConversationInfo, + })), + ); + // 清空上下文已存在,且需要删除聊天记录按钮 + return ( +
+ {currentConversationInfo?.conversationListVisible || + !isShowConversations ? null : ( + } + className={styles['conversation-list-btn']} + onClick={() => { + if (!currentConversationInfo) { + return; + } + updateCurrentConversationInfo({ + ...currentConversationInfo, + conversationListVisible: true, + }); + }} + /> + )} + avatar + + {title} + + {buttonList?.map( + item => ( +
+ ); +}; + +export default ChatHeader; diff --git a/frontend/packages/studio/open-platform/open-chat/src/types/base.ts b/frontend/packages/studio/open-platform/open-chat/src/components/header/type.ts similarity index 62% rename from frontend/packages/studio/open-platform/open-chat/src/types/base.ts rename to frontend/packages/studio/open-platform/open-chat/src/components/header/type.ts index bfc15c79..712b4a32 100644 --- a/frontend/packages/studio/open-platform/open-chat/src/types/base.ts +++ b/frontend/packages/studio/open-platform/open-chat/src/components/header/type.ts @@ -16,17 +16,15 @@ import { type ReactNode } from 'react'; -export enum Layout { - PC = 'pc', - MOBILE = 'mobile', -} - -export interface HeaderConfig { - isShow?: boolean; //Whether to display headers, the default is true - isNeedClose?: boolean; //Whether you need the close button, the default is true. - extra?: ReactNode | false; // For standing, default none -} - -export interface DebugProps { - cozeApiRequestHeader?: Record; +export interface ChatHeaderProps { + title: string; + iconUrl?: string; + extra?: ReactNode | false; + theme?: 'bg-theme' | 'light'; + isNeedTitle?: boolean; + isNeedLogo?: boolean; + isNeedClearMessage?: boolean; + isFixTop?: boolean; + isShowConversations?: boolean; + isShowHeader?: boolean; } diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/icon/add-new-conversation.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/icon/add-new-conversation.tsx new file mode 100644 index 00000000..797f8f0a --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/icon/add-new-conversation.tsx @@ -0,0 +1,46 @@ +/* + * 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. + */ + +import { type FC } from 'react'; + +import Icon from '@douyinfe/semi-icons'; +export const IconAddNewConversation: FC<{ + className?: string; + width?: string; + height?: string; +}> = ({ width, height, className }) => ( + + + + } + className={className} + style={{ + width, + height, + }} + /> +); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/loading/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/components/loading/index.module.less new file mode 100644 index 00000000..a65be097 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/loading/index.module.less @@ -0,0 +1,8 @@ + +.loading { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/loading/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/loading/index.tsx new file mode 100644 index 00000000..d71878b5 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/loading/index.tsx @@ -0,0 +1,25 @@ +/* + * 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. + */ + +import { Spin } from '@coze-arch/coze-design'; + +import styles from './index.module.less'; + +export const Loading = () => ( +
+ +
+); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/area/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/area/index.module.less new file mode 100644 index 00000000..d3d22c0a --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/area/index.module.less @@ -0,0 +1,27 @@ +.chat-input { + padding: 10px 0; +} + +.area { + flex: 1 1 auto; + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + min-height: 0; + + * { + box-sizing: border-box; + } + + :global(.chat-uikit-on-boarding-pc){ + & > div { + padding-right: 14px; + padding-left: 14px; + } + } + + .safe-area { + height: 24px; + } +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/area/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/area/index.tsx new file mode 100644 index 00000000..5212c614 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/area/index.tsx @@ -0,0 +1,130 @@ +/* + * 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. + */ + +import React, { type FC, useEffect, useMemo, useRef } from 'react'; + +import cs from 'classnames'; +import { ChatFlowRender } from '@coze-common/chat-workflow-render'; +import { + ChatArea, + useInitStatus, + type ComponentTypesMap, +} from '@coze-common/chat-area'; +import { I18n } from '@coze-arch/i18n/intl'; + +import { type StudioChatAreaProps } from '@/types/props'; +import { Layout } from '@/types/client'; + +import { useChatAppProps } from '../store'; +import { ShortcutBar } from '../components/shortcut-bar'; +import { ChatInputLeftSlot } from '../components/chat-input-let-slot'; + +import styles from './index.module.less'; + +// eslint-disable-next-line complexity +export const StudioChatArea: FC = ({ + coreAreaClassName, + className, + showInputArea = true, + inputPlaceholder, + inputNativeCallbacks, + messageGroupListClassName, + renderChatInputTopSlot, + isShowClearContextDivider, + headerNode, + messageMaxWidth, + isMiniScreen, + enableMultimodalUpload = false, +}) => { + const initStatus = useInitStatus(); + const { layout, onInitStateChange, chatConfig } = useChatAppProps(); + const refContainer = useRef(null); + const { readonly } = useChatAppProps(); + + const chatAreaComponentTypes: Partial = useMemo( + () => ({ + chatInputIntegration: { + renderChatInputTopSlot: controller => ( + <> + {renderChatInputTopSlot?.()} + + + ), + }, + contentBox: ChatFlowRender, + }), + [renderChatInputTopSlot], + ); + useEffect(() => { + switch (initStatus) { + case 'initSuccess': + case 'initFail': + onInitStateChange?.(initStatus); + break; + default: + } + }, [initStatus, onInitStateChange]); + + if (initStatus !== 'initSuccess') { + return null; + } + const uploadable = chatConfig?.ui?.chatBot?.uploadable ?? true; + const enableLegacyUploadFlag = !enableMultimodalUpload && uploadable; + const enableMultimodalUploadFlag = enableMultimodalUpload && uploadable; + return ( +
+ , + }} + componentTypes={chatAreaComponentTypes} + readonly={readonly} + uiKitChatInputButtonConfig={{ + isClearContextButtonVisible: false, + isClearHistoryButtonVisible: false, + }} + isMiniScreen={isMiniScreen} + headerNode={headerNode} + /> +
+ ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/audio-unfocus-text/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/audio-unfocus-text/index.module.less new file mode 100644 index 00000000..7dd7aa62 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/audio-unfocus-text/index.module.less @@ -0,0 +1,3 @@ +.unfocus-text { + color: rgba(48, 68, 112, 38%); +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/audio-unfocus-text/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/audio-unfocus-text/index.tsx new file mode 100644 index 00000000..ca91681b --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/audio-unfocus-text/index.tsx @@ -0,0 +1,24 @@ +/* + * 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. + */ + +import { I18n } from '@coze-arch/i18n'; + +import styles from './index.module.less'; +export const AudioUnfocusText = () => ( +
+ {I18n.t('chat_voice_input_need_focus')} +
+); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/chat-input-let-slot/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/chat-input-let-slot/index.module.less new file mode 100644 index 00000000..3aee7e7b --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/chat-input-let-slot/index.module.less @@ -0,0 +1,37 @@ +/* stylelint-disable declaration-no-important */ +.container { + padding: 4px; + border-radius: 4px; + + @apply coz-bg-max; + + .button { + justify-content: flex-start; + + width: 100%; + padding-right: 8px; + padding-left: 8px; + + font-weight: 400; + text-align: left; + + border-radius: 4px; + + @apply coz-fg-primary; + + &:global(.coz-btn-secondary) { + @apply coz-fg-primary; + } + } +} + +.popover { + border-radius: 4px; +} + +.chat-input-left-slot { + .disabled { + background-color: rgba(249, 249, 249, 80%)!important; + color: rgba(55, 67 , 106, 38%)!important; + } +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/chat-input-let-slot/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/chat-input-let-slot/index.tsx new file mode 100644 index 00000000..7e96dae0 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/chat-input-let-slot/index.tsx @@ -0,0 +1,125 @@ +/* + * 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. + */ + +import { type FC, useEffect, useState } from 'react'; + +import cls from 'classnames'; +import { OutlinedIconButton, UIKitTooltip } from '@coze-common/chat-uikit'; +import { IconCozMore } from '@coze-arch/coze-design/icons'; +import { Button, Popover, Space } from '@coze-arch/coze-design'; + +import { Layout } from '@/types/client'; + +import { useChatAppProps } from '../../store'; +import { useIsShowBackground } from '../../hooks/use-is-show-background'; +import { + type ButtonProps, + useChatChatButtonInfo, + useChatOpInfo, +} from '../../hooks/use-chat-op-info'; + +import styles from './index.module.less'; + +const MoreBtn: FC<{ + buttonList: ButtonProps[]; +}> = ({ buttonList }) => { + const showBackground = useIsShowBackground(); + const [visible, setVisible] = useState(false); + const { readonly } = useChatAppProps(); + useEffect(() => { + document.addEventListener('click', () => { + setVisible(false); + }); + return () => { + setVisible(false); + }; + }, []); + return ( + + {buttonList?.map((item, index) => ( + + ))} + + } + trigger="custom" + visible={visible} + position="topLeft" + style={{ + borderRadius: '4px', + }} + > + } + size="default" + onClick={e => { + e.stopPropagation(); + setVisible(visibleTemp => !visibleTemp); + }} + className={cls('mr-12px', '!rounded-full')} + /> + + ); +}; + +export const ChatInputLeftSlot = () => { + const { chatInputLeftOps } = useChatOpInfo(); + const showBackground = useIsShowBackground(); + const { chatConfig } = useChatAppProps(); + const isMobile = chatConfig.ui?.base?.layout === Layout.MOBILE; + const buttonClass = showBackground ? '!coz-fg-images-white' : ''; + + const buttonList = useChatChatButtonInfo(chatInputLeftOps); + if (chatInputLeftOps.length === 0) { + return null; + } + + return ( +
+ {buttonList.length > 1 ? : null} + {buttonList.length === 1 ? ( + + { + buttonList[0].onClick?.(); + }} + className={cls('mr-12px', '!rounded-full', buttonClass, { + [styles.disabled]: buttonList[0].disabled, + })} + /> + + ) : null} +
+ ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/shortcut-bar/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/shortcut-bar/index.tsx new file mode 100644 index 00000000..5ad00bd1 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/components/shortcut-bar/index.tsx @@ -0,0 +1,108 @@ +/* + * 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. + */ + +import { useRef } from 'react'; + +import classNames from 'classnames'; +import { + ShortcutBar as ChatAreaShortcutBar, + getUIModeByBizScene, + type ShortCutCommand, +} from '@coze-common/chat-area-plugins-chat-shortcuts'; +import { type ChatInputIntegrationController } from '@coze-common/chat-area'; +import { ToolType } from '@coze-arch/idl/playground_api'; + +import { useChatAppStore } from '../../store'; +import { useIsShowBackground } from '../../hooks/use-is-show-background'; + +export interface ShortcutBarRenderProps { + controller: ChatInputIntegrationController; + onShortcutActive?: (shortcut: ShortCutCommand | undefined) => void; +} + +export const ShortcutBar = ({ + controller, + onShortcutActive, +}: ShortcutBarRenderProps) => { + const activeShortcutRef = useRef(undefined); + + const showBackground = useIsShowBackground(); + const shortcuts = useChatAppStore(store => store.shortcuts); + const defaultId = shortcuts.at(0)?.command_id; + + if (!shortcuts?.length) { + return null; + } + return ( + { + activeShortcutRef.current = shortcutInfo; + // 开启template快捷指令时,隐藏输入框&快捷指令bar + const chatInputSlotVisible = !isTemplateShortcutActive; + controller.setChatInputSlotVisible(chatInputSlotVisible); + onShortcutActive?.(shortcutInfo); + }} + onBeforeSendTemplateShortcut={({ message, options, shortcut }) => { + const parameters = {}; + Object.entries( + ( + options?.extendFiled?.toolList as Array<{ + plugin_id: string; + plugin_api_name: string; + parameters: Record< + string, + { + value: string; + resource_type: 'uri' | ''; + } + >; + }> + )?.[0]?.parameters || {}, + ).map(item => { + const [key, value] = item; + parameters[key] = value.value; + }); + + const optionsNew = options || {}; + + if (!optionsNew.extendFiled) { + optionsNew.extendFiled = {}; + } + if ( + shortcut.tool_type === ToolType.ToolTypePlugin || + shortcut.tool_type === ToolType.ToolTypeWorkFlow + ) { + optionsNew.extendFiled.shortcut_command = { + command_id: shortcut.command_id, + parameters, + }; + } + + return { + message, + options, + }; + }} + /> + ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/__test__/user-info.test.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/__test__/user-info.test.tsx new file mode 100644 index 00000000..9742ae26 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/__test__/user-info.test.tsx @@ -0,0 +1,61 @@ +/* + * 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. + */ + +import { describe, test, expect, vi } from 'vitest'; +import { renderHook } from '@testing-library/react'; + +import { type StudioChatProviderProps } from '@/types/props'; +import { OpenApiSource } from '@/types/open'; + +import { useUserInfo } from '../use-user-info'; +import { ChatPropsProvider } from '../../store/context'; + +vi.hoisted(() => { + // @ts-expect-error -- 将 IS_OVERSEA 提升到最外层 + global.IS_OVERSEA = false; +}); + +vi.mock('@/components/conversation-list-sider', () => ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + ConversationListSider: () =>
, +})); + +describe('user-info', () => { + const testProps: StudioChatProviderProps = { + chatConfig: { + bot_id: 'test', + source: OpenApiSource.WebSdk, + conversation_id: 'test', + }, + userInfo: { + id: 'test-id', + nickname: 'test-nickname', + url: 'test-url', + }, + }; + + test('test props first', () => { + const { result: userInfo } = renderHook(useUserInfo, { + wrapper: props => ( + + {props.children} + + ), + }); + + expect(userInfo.current?.nickname).toBe('test-nickname'); + }); +}); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/index.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/index.ts new file mode 100644 index 00000000..bfe5c41a --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/index.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + +export { useUserInfo } from './use-user-info'; +export { useError } from './use-error'; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-chat-op-info.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-chat-op-info.tsx new file mode 100644 index 00000000..d754cc58 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-chat-op-info.tsx @@ -0,0 +1,124 @@ +/* + * 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. + */ + +import { useMemo } from 'react'; + +import { + useBuiltinButtonStatus, + useChatAreaController, + useClearContext, +} from '@coze-common/chat-area'; +import { I18n } from '@coze-arch/i18n'; +import { IconCozBroom, IconCozChatPlus } from '@coze-arch/coze-design/icons'; + +import { IconAddNewConversation } from '@/components/icon/add-new-conversation'; + +import { useChatAppProps } from '../store'; + +export type ChatOp = 'clearContext' | 'clearMessage' | 'addNewConversation'; +export interface ButtonProps { + icon?: React.ReactNode; + text?: string | React.ReactNode; + disabled?: boolean; + onClick?: (event?: React.MouseEvent) => void; +} +export const useChatOpInfo = () => { + const { chatConfig } = useChatAppProps(); + const chatInputLeftOps: ChatOp[] = []; + const headerTopLeftOps: ChatOp[] = []; + console.log('useChatOpInfo:', chatConfig); + if (chatConfig?.ui?.header?.isShow) { + if (chatConfig?.ui?.chatBot?.isNeedClearContext) { + chatInputLeftOps.push('clearContext'); + + if (chatConfig?.ui?.chatBot?.isNeedClearMessage) { + headerTopLeftOps.push('clearMessage'); + } else if (chatConfig?.ui?.chatBot?.isNeedAddNewConversation) { + headerTopLeftOps.push('addNewConversation'); + } + } else { + // 在实际使用中, clearMessage 和 addNewConversation 不会同时出现,两个功能是重复的,只是为了区分按钮的样式 + if (chatConfig?.ui?.chatBot?.isNeedClearMessage) { + chatInputLeftOps.push('clearMessage'); + } else if (chatConfig?.ui?.chatBot?.isNeedAddNewConversation) { + chatInputLeftOps.push('addNewConversation'); + } + } + } else { + if (chatConfig?.ui?.chatBot?.isNeedClearContext) { + chatInputLeftOps.push('clearContext'); + } + // 在实际使用中, clearMessage 和 addNewConversation 不会同时出现,两个功能是重复的,只是为了区分按钮的样式 + if (chatConfig?.ui?.chatBot?.isNeedClearMessage) { + chatInputLeftOps.push('clearMessage'); + } else if (chatConfig?.ui?.chatBot?.isNeedAddNewConversation) { + chatInputLeftOps.push('addNewConversation'); + } + } + return { + chatInputLeftOps, + headerTopLeftOps, + }; +}; +export const useChatChatButtonInfo = (opList: ChatOp[]) => { + const { isClearHistoryButtonDisabled, isClearContextButtonDisabled } = + useBuiltinButtonStatus({}); + const { readonly } = useChatAppProps(); + const { clearHistory } = useChatAreaController(); + const clearContext = useClearContext(); + const buttonList = useMemo( + () => + opList.map(item => { + if (item === 'addNewConversation') { + return { + icon: , + text: I18n.t('web_sdk_add_new_conversation'), + disabled: isClearHistoryButtonDisabled || readonly, + onClick: () => { + clearHistory?.(); + }, + }; + } else if (item === 'clearContext') { + return { + icon: , + text: I18n.t('store_start_new_chat'), + disabled: isClearContextButtonDisabled || readonly, + onClick: () => { + clearContext(); + }, + }; + } else { + return { + icon: , + text: I18n.t('coze_home_delete_btn'), + disabled: isClearHistoryButtonDisabled || readonly, + onClick: () => { + clearHistory?.(); + }, + }; + } + }), + [ + opList, + isClearContextButtonDisabled, + isClearHistoryButtonDisabled, + readonly, + clearHistory, + clearContext, + ], + ); + return buttonList; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-conversation-list.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-conversation-list.ts new file mode 100644 index 00000000..c2dfdc49 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-conversation-list.ts @@ -0,0 +1,196 @@ +/* + * 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. + */ + +import { useEffect, useMemo } from 'react'; + +import { useShallow } from 'zustand/react/shallow'; +import dayjs from 'dayjs'; +import { type Conversation, type ListConversationReq } from '@coze/api'; + +import { + ConversationSort, + type SortedConversationItem, +} from '@/types/conversations'; + +import { type ChatState } from '../store/store'; +import { useChatAppProps, useChatAppStore } from '../store'; +import { usePaginationRequest } from './use-pagination-request'; + +// 扩展ListConversationReq类型以满足PaginationParams约束 +type ExtendedListConversationReq = ListConversationReq & { + sort_field: 'created_at' | 'updated_at'; + [key: string]: unknown; +}; + +interface UseConversationListParams { + pageSize?: number; + initialPageNum?: number; + order?: ExtendedListConversationReq['sort_field']; +} + +interface UseConversationListReturn { + conversations: Conversation[]; + loading: boolean; + hasMore: boolean; + loadMore: () => Promise; +} + +export const useConversationList = ( + conversationListParams?: UseConversationListParams, +): UseConversationListReturn => { + const { + pageSize = 20, + initialPageNum = 1, + order = 'updated_at', + } = conversationListParams ?? {}; + const { + chatConfig: { bot_id: botId, auth: { connectorId } = {} }, + } = useChatAppProps(); + + const { + cozeApiSdk, + currentConversationInfo, + updateCurrentConversationInfo, + conversations, + updateConversations, + } = useChatAppStore( + useShallow(state => ({ + cozeApiSdk: state.cozeApi, + conversations: state.conversations, + updateCurrentConversationInfo: state.updateCurrentConversationInfo, + currentConversationInfo: state.currentConversationInfo, + updateConversations: state.updateConversations, + })), + ); + + const { data, hasMore, loadMore, loading } = usePaginationRequest< + Conversation, + ExtendedListConversationReq + >({ + requestFn: async params => { + if (!cozeApiSdk || !botId) { + return { data: [], has_more: false }; + } + try { + const result = await cozeApiSdk.conversations.list(params); + return { + data: result.conversations, + has_more: result.has_more, + }; + } catch (e) { + console.error(e); + return { data: [], has_more: false }; + } + }, + requestParams: { + bot_id: botId, + connector_id: connectorId, + sort_field: order, + }, + pageSize, + initialPageNum, + autoLoad: !!cozeApiSdk && !!botId, + }); + + useEffect(() => { + if (data) { + updateConversations(data, 'replace'); + } + }, [data]); + + useEffect(() => { + if (!currentConversationInfo && data.length > 0) { + const chatContainer = document.querySelector('.coze-chat-sdk'); + let info: ChatState['currentConversationInfo'] = { + ...data[0], + conversationListVisible: false, + isLargeWidth: false, + }; + if (chatContainer && (chatContainer as HTMLElement).offsetWidth >= 780) { + info = { + ...info, + conversationListVisible: true, + isLargeWidth: true, + }; + } + updateCurrentConversationInfo(info); + } + }, [currentConversationInfo, data]); + + return { + conversations, + loading, + hasMore, + loadMore, + }; +}; + +export const useGroupedConversations = (conversations: Conversation[]) => { + const sortedConversations: SortedConversationItem[] = useMemo(() => { + const today = new Date(); + const oneDay = 24 * 60 * 60 * 1000; + const thirtyDays = 30 * oneDay; + + const newConversationList = conversations + .map(item => { + const dateString = item.updated_at || item.created_at || 0; + const date = dayjs.unix(Number(dateString)).toDate(); + const diff = today.getTime() - date.getTime(); + + if (today.toLocaleDateString() === date.toLocaleDateString()) { + return { + ...item, + sort: ConversationSort.Today, + }; + } else if (diff < thirtyDays) { + return { + ...item, + sort: ConversationSort.In30days, + }; + } else { + return { + ...item, + sort: ConversationSort.Others, + }; + } + }) + .sort((a, b) => { + if (a.sort !== b.sort) { + return a.sort - b.sort; + } else { + return ( + dayjs.unix(Number(b.updated_at || b.created_at || 0)).valueOf() - + dayjs.unix(Number(a.updated_at || a.created_at || 0)).valueOf() + ); + } + }); + + return newConversationList; + }, [conversations]); + + const groupedConversations = useMemo(() => { + const groups = new Map(); + sortedConversations.forEach(conversation => { + if (!groups.has(conversation.sort)) { + groups.set(conversation.sort, []); + } + groups.get(conversation.sort)?.push(conversation); + }); + return groups; + }, [sortedConversations]); + + return groupedConversations; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-error.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-error.ts new file mode 100644 index 00000000..a69bff0f --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-error.ts @@ -0,0 +1,54 @@ +/* + * 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. + */ + +import { useState } from 'react'; + +import { Toast } from '@coze-arch/bot-semi'; + +import { type SDKInitError } from '@/util/error'; +import { catchParse } from '@/util'; + +type ErrorState = boolean | SDKInitError; +export type SetInitError = (error: ErrorState) => void; +export const useError = () => { + const [initError, setError] = useState(false); + + return { + initError, + setInitError: (error: ErrorState) => { + if (!error) { + setError(error); + } else { + if (initError && typeof initError !== 'boolean') { + return; + } + setError(error); + } + }, + onMessageSendFail: (_params, _from, error) => { + if (error instanceof Error) { + const res = catchParse<{ code?: number; msg?: string }>( + error.message, + {}, + ); + + if (res?.code && res?.msg) { + Toast.error(res.msg); + } + } + }, + }; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-get-theme.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-get-theme.ts new file mode 100644 index 00000000..ddd62ffb --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-get-theme.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +import { useIsShowBackground } from './use-is-show-background'; + +export const useGetTheme = () => { + const isShowBackground = useIsShowBackground(); + return isShowBackground ? 'bg-theme' : 'light'; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-is-show-background.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-is-show-background.ts new file mode 100644 index 00000000..3f770922 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-is-show-background.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +import { useChatAppProps, useChatAppStore } from '../store'; + +export const useIsShowBackground = () => { + const backgroundInfo = useChatAppStore(s => s.backgroundInfo); + const { isCustomBackground } = useChatAppProps(); + + // 自定义背景图,或者背景图有数据,则有背景状态 + return ( + isCustomBackground || + !!backgroundInfo?.mobile_background_image?.origin_image_url || + false + ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-pagination-request.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-pagination-request.ts new file mode 100644 index 00000000..41cc5711 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-pagination-request.ts @@ -0,0 +1,148 @@ +/* + * 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. + */ + +import { useState, useCallback, useEffect } from 'react'; + +import { useRequest } from 'ahooks'; + +interface PaginationParams { + page_num?: number; + page_size?: number; + [key: string]: unknown; +} + +interface PaginationResponse { + data: T[]; + has_more?: boolean; + total?: number; + [key: string]: unknown; +} + +interface UsePaginationRequestParams { + requestFn: (params: P) => Promise>; + requestParams: Omit; + pageSize?: number; + initialPageNum?: number; + autoLoad?: boolean; + dataKey?: string; + hasMoreKey?: string; +} + +interface UsePaginationRequestReturn { + data: T[]; + loading: boolean; + error: Error | undefined; + hasMore: boolean; + currentPage: number; + loadMore: () => Promise; + refresh: () => Promise; + setPageNum: (pageNum: number) => void; + reset: () => Promise; +} + +export const usePaginationRequest = ({ + requestFn, + requestParams, + pageSize = 20, + initialPageNum = 1, + autoLoad = true, +}: UsePaginationRequestParams): UsePaginationRequestReturn => { + const [currentPage, setCurrentPage] = useState(initialPageNum); + const [allData, setAllData] = useState([]); + const [hasMore, setHasMore] = useState(true); + + const { loading, error, run } = useRequest( + async (pageNum?: number) => { + const targetPage = pageNum ?? currentPage; + const params = { + ...requestParams, + page_size: pageSize, + page_num: targetPage, + }; + + const res = await requestFn(params as P); + return res; + }, + { + manual: true, + onSuccess: (res, [pageNum]) => { + const targetPage = pageNum ?? currentPage; + const responseData = res.data; + const responseHasMore = !!res.has_more; + + if (targetPage === 1) { + // 如果是第一页,直接替换数据 + setAllData(responseData); + } else { + // 如果是加载更多,追加数据 + setAllData(prev => [...prev, ...responseData]); + } + + setHasMore(responseHasMore); + setCurrentPage(targetPage); + }, + onError: err => { + console.error('分页请求失败:', err); + }, + }, + ); + + const loadMore = useCallback(async () => { + if (!loading && hasMore) { + await run(currentPage + 1); + } + }, [loading, hasMore, currentPage, run]); + + const refresh = useCallback(async () => { + setAllData([]); + setCurrentPage(1); + await run(1); + }, [run]); + + const setPageNum = useCallback( + async (pageNum: number) => { + if (pageNum >= 1) { + await run(pageNum); + } + }, + [run], + ); + + const reset = useCallback(async () => { + setAllData([]); + setCurrentPage(initialPageNum); + await setHasMore(false); + }, [initialPageNum]); + + // 组件挂载时自动加载第一页 + useEffect(() => { + if (autoLoad) { + run(initialPageNum); + } + }, [autoLoad, initialPageNum, run]); + + return { + data: allData, + loading, + error, + hasMore, + currentPage, + loadMore, + refresh, + setPageNum, + reset, + }; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-update-conversation-name-by-message.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-update-conversation-name-by-message.ts new file mode 100644 index 00000000..2c595b38 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-update-conversation-name-by-message.ts @@ -0,0 +1,52 @@ +/* + * 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. + */ + +import { useEffect, useRef } from 'react'; + +import { useShallow } from 'zustand/react/shallow'; +import { isEqual } from 'lodash-es'; +import { useChatAreaStoreSet } from '@coze-common/chat-area'; + +import { useChatAppStore } from '../store'; + +export const useUpdateConversationNameByMessage = () => { + const currentConversationNameRef = useRef(); + const { updateCurrentConversationNameByMessage, currentConversationInfo } = + useChatAppStore( + useShallow(s => ({ + updateCurrentConversationNameByMessage: + s.updateCurrentConversationNameByMessage, + currentConversationInfo: s.currentConversationInfo, + })), + ); + + const { useMessagesStore } = useChatAreaStoreSet(); + + const messages = useMessagesStore(s => s.messages, isEqual); + + useEffect(() => { + currentConversationNameRef.current = currentConversationInfo?.name; + }, [currentConversationInfo]); + + useEffect(() => { + const message = messages[messages.length - 1]; + const name = message?.content.slice(0, 100); + if (message && !currentConversationNameRef.current) { + updateCurrentConversationNameByMessage(name); + currentConversationNameRef.current = name; + } + }, [messages]); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-user-info.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-user-info.ts new file mode 100644 index 00000000..18052103 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/hooks/use-user-info.ts @@ -0,0 +1,46 @@ +/* + * 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. + */ + +import { useMemo } from 'react'; + +import { nanoid } from 'nanoid'; +import { type UserSenderInfo } from '@coze-common/chat-area'; + +import { useChatAppStore } from '../store'; +export const useUserInfo = () => { + const userInfo = useChatAppStore(s => s.userInfo); + + return useMemo(() => { + const openUserInfo = userInfo; + if (!openUserInfo) { + return { + id: nanoid(), + nickname: '', + url: '', + userUniqueName: '', + userLabel: null, + }; + } + + const areaUserInfo: UserSenderInfo = { + ...openUserInfo, + userUniqueName: '', + userLabel: null, + }; + + return areaUserInfo; + }, [userInfo]); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/index.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/index.ts new file mode 100644 index 00000000..18fdf862 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/index.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + +export { OpenChatProvider } from './provider'; +export { StudioChatArea } from './area'; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/index.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/index.ts new file mode 100644 index 00000000..181e08b3 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/index.ts @@ -0,0 +1,38 @@ +/* + * 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. + */ + +import { type PluginRegistryEntry } from '@coze-common/chat-area'; + +import { type PluginBizContext } from './types/biz-context'; +import { BizPlugin } from './plugin'; + +export type ChatCommonPlugin = PluginRegistryEntry; +export const getChatCommonPlugin = (props: PluginBizContext) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const WebsdkChatCommonPlugin: ChatCommonPlugin = { + /** + * 贯穿插件生命周期、组件的上下文 + */ + createPluginBizContext() { + return { ...props }; + }, + /** + * 插件本体 + */ + Plugin: BizPlugin, + }; + return WebsdkChatCommonPlugin; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/plugin.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/plugin.ts new file mode 100644 index 00000000..ef04d817 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/plugin.ts @@ -0,0 +1,47 @@ +/* + * 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. + */ + +import { + PluginMode, + PluginName, + WriteableChatAreaPlugin, + createWriteableLifeCycleServices, +} from '@coze-common/chat-area'; + +import { type PluginBizContext } from './types/biz-context'; +import { bizLifeCycleServiceGenerator } from './services/life-cycle'; + +export class BizPlugin extends WriteableChatAreaPlugin { + /** + * 插件类型 + * PluginMode.Readonly = 只读模式 + * PluginMode.Writeable = 可写模式 + */ + public pluginMode = PluginMode.Writeable; + /** + * 插件名称 + * 请点 PluginName 里面去定义 + */ + public pluginName = PluginName.WebsdkChatCommonPlugin; + + /** + * 生命周期服务 + */ + public lifeCycleServices = createWriteableLifeCycleServices( + this, + bizLifeCycleServiceGenerator, + ); +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/app.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/app.ts new file mode 100644 index 00000000..5c4696b5 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/app.ts @@ -0,0 +1,27 @@ +/* + * 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. + */ + +import { type WriteableAppLifeCycleServiceGenerator } from '@coze-common/chat-area'; + +import { type PluginBizContext } from '../../types/biz-context'; + +export const appLifeCycleServiceGenerator: WriteableAppLifeCycleServiceGenerator< + PluginBizContext +> = plugin => ({ + onInitialError: () => { + plugin.pluginBizContext.onInitialError(); + }, +}); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/command.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/command.ts new file mode 100644 index 00000000..dddb4a7c --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/command.ts @@ -0,0 +1,30 @@ +/* + * 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. + */ + +import { type WriteableCommandLifeCycleServiceGenerator } from '@coze-common/chat-area'; + +import { type PluginBizContext } from '../../types/biz-context'; + +export const commandLifeCycleServiceGenerator: WriteableCommandLifeCycleServiceGenerator< + PluginBizContext +> = plugin => ({ + /*onImageClick: async ctx => { + const url = ctx.url; + plugin.pluginBizContext.onImageClick?.({ + url + }); + }*/ +}); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/index.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/index.ts new file mode 100644 index 00000000..634fe9f4 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/index.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +import { type WriteableLifeCycleServiceGenerator } from '@coze-common/chat-area'; + +import { type PluginBizContext } from '../../types/biz-context'; +import { renderLifeCycleServiceGenerator } from './render'; +import { messageLifeCycleServiceGenerator } from './message'; +import { commandLifeCycleServiceGenerator } from './command'; +import { appLifeCycleServiceGenerator } from './app'; + +export const bizLifeCycleServiceGenerator: WriteableLifeCycleServiceGenerator< + PluginBizContext +> = plugin => ({ + appLifeCycleService: appLifeCycleServiceGenerator(plugin), + messageLifeCycleService: messageLifeCycleServiceGenerator(plugin), + commandLifeCycleService: commandLifeCycleServiceGenerator(plugin), + renderLifeCycleService: renderLifeCycleServiceGenerator(plugin), +}); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/message.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/message.ts new file mode 100644 index 00000000..8c64ebbe --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/message.ts @@ -0,0 +1,52 @@ +/* + * 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. + */ + +import { type WriteableMessageLifeCycleServiceGenerator } from '@coze-common/chat-area'; +import { Toast } from '@coze-arch/bot-semi'; + +import { catchParse } from '@/util'; + +import { type PluginBizContext } from '../../types/biz-context'; + +export const messageLifeCycleServiceGenerator: WriteableMessageLifeCycleServiceGenerator< + PluginBizContext +> = plugin => ({ + onSendMessageError: ctx => { + const { error } = ctx; + if (error instanceof Error) { + const res = catchParse<{ code?: number; msg?: string }>( + error.message, + {}, + ); + if (res?.code && res?.msg) { + Toast.error(res.msg); + } + } + }, + onBeforeSendMessage: ctx => { + const { options = {} } = ctx; + const optionNew = Object.assign({}, options, { + extendFiled: { + ...options?.extendFiled, + extra: { + ...(options?.extendFiled?.extra || {}), + ...(plugin.pluginBizContext.extraBody || {}), + }, + }, + }); + return { ...ctx, options: optionNew }; + }, +}); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/render.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/render.ts new file mode 100644 index 00000000..63a9370d --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/services/life-cycle/render.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +import { type WriteableRenderLifeCycleServiceGenerator } from '@coze-common/chat-area'; + +import { type PluginBizContext } from '../../types/biz-context'; + +export const renderLifeCycleServiceGenerator: WriteableRenderLifeCycleServiceGenerator< + PluginBizContext +> = plugin => ({}); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/types/biz-context.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/types/biz-context.ts new file mode 100644 index 00000000..6763800e --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/types/biz-context.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +import { type ChatAreaEventCallback } from '@coze-common/chat-area'; +export interface PluginBizContext { + onInitialError: () => void; + onImageClick?: ChatAreaEventCallback['onImageClick']; + extraBody?: Record; +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/typings.d.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/typings.d.ts new file mode 100644 index 00000000..4ae6594a --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/plugin/typings.d.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +/// diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/index.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/index.ts new file mode 100644 index 00000000..3b38949b --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/index.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +export { useSendMessageAdapter } from './use-send-message'; +export { useClearMessageContextAdapter } from './use-clear-message-context'; +export { useClearHistoryAdapter } from './use-clear-history'; +export { useMessageList, useGetMessageListByPairs } from './use-message-list'; +export { + useCommonOnAfterResponseHooks, + useCommonOnBeforeRequestHooks, + useCommonErrorResponseHooks, +} from './use-common-hooks'; + +export { + messageConverterToCoze, + MessageParser, + messageConverterToSdk, +} from './message'; +export { useBreakMessage } from './use-break-message'; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/index.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/index.ts new file mode 100644 index 00000000..9877d6dd --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/index.ts @@ -0,0 +1,19 @@ +/* + * 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. + */ + +export { messageConverterToCoze } from './message-convert-to-coze'; +export { messageConverterToSdk } from './message-convert-to-sdk'; +export { MessageParser } from './message-parser'; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/message-convert-to-coze.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/message-convert-to-coze.ts new file mode 100644 index 00000000..cd2c9a04 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/message-convert-to-coze.ts @@ -0,0 +1,197 @@ +/* + * 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. + */ + +import { nanoid } from 'nanoid'; +import { getFileInfo, FileTypeEnum } from '@coze-studio/file-kit/logic'; +import { ContentType } from '@coze-common/chat-core'; +import { + type ChatV3Message, + type ContentType as SdkContentType, + type ListMessageData, +} from '@coze/api'; + +import { catchParse } from '@/util'; + +interface ObjectStringItem { + type: 'text' | 'image' | 'file'; + text?: string; + file_id?: string; + file_url?: string; +} +const microSeconds = 1000; +// 消息转换成 Coze的消息,主要用于消息接收后,在页面显示。 +class MessageConverseToCoze { + public convertMessageListResponse(res: ListMessageData, botId = '') { + const { + data: messageList = [], + has_more: hasMore, + first_id: firstId, + last_id: lastId, + } = res; + const messageListForCoze = + messageList + .map(item => this.convertMessage(item, botId)) + .filter(item => !!item.message_id) || []; + console.log('messageListForCoze', messageListForCoze); + return { + code: 0, + message_list: messageListForCoze, + hasmore: hasMore, + cursor: lastId, + next_cursor: firstId, + }; + } + public convertMessage(message: ChatV3Message, botId = '') { + const { content_type, content } = + this.convertContent(message.content_type, message.content as string) || + {}; + const isQuestion = message.type === ('question' as ChatV3Message['type']); + const replyId = message.chat_id || `--custom-replyId--${nanoid()}`; + const messageId = isQuestion + ? replyId + : message.id || `--custom-messageId-${nanoid()}`; // 无messageId,输出一个默认的 + + const senderId = isQuestion ? '' : message.bot_id || botId; + if (!content_type || !messageId || !replyId) { + return {}; + } + let pluginName = ''; + if (message.type === 'function_call') { + const contentObj = catchParse<{ plugin: string }>( + message.content as string, + ); + pluginName = contentObj?.plugin || ''; + } + + return { + // @ts-expect-error -- linter-disable-autofix, 新添加参数,sdk中还未支持到 + reasoning_content: message.reasoning_content, + content, + content_time: (message.created_at || 0) * microSeconds, + content_type, + message_id: messageId, + reply_id: replyId, + role: message.role, + + // @ts-expect-error -- linter-disable-autofix, 新添加参数,sdk中还未支持到 + section_id: message.section_id, + sender_id: senderId, // todo 用户id添加 + source: 0, //... + status: '', + extra_info: { + local_message_id: '', + plugin: pluginName, + coze_api_message_id: message.id, + coze_api_chat_id: message.chat_id, + }, + type: message.type, + }; + } + public convertContent(contentType: SdkContentType, content: string) { + switch (contentType) { + case 'object_string': { + return { + content_type: ContentType.Mix, + content: this.convertMixContent(content), + }; + } + case 'card': { + return { + content_type: ContentType.Card, + content, + }; + } + case 'text': { + return { + content_type: ContentType.Text, + content, + }; + } + default: { + return; + } + } + } + private convertMixContent(content: string) { + const contentObj = catchParse(content); + if (!contentObj) { + return; + } + const itemList = contentObj + ?.map(item => { + switch (item.type) { + case 'text': { + return { + type: ContentType.Text, + text: item.text || '', + // @ts-expect-error -- linter-disable-autofix + is_refer: item.is_refer || undefined, + }; + } + case 'image': { + return { + type: ContentType.Image, + image: { + key: item?.file_id || '', + image_ori: { + height: undefined, + width: undefined, + url: item?.file_url, + }, + image_thumb: { + height: undefined, + width: undefined, + url: item?.file_url, + }, + }, + // @ts-expect-error -- linter-disable-autofix + is_refer: item.is_refer || undefined, + }; + } + case 'file': { + const { fileType = FileTypeEnum.DEFAULT_UNKNOWN } = + // @ts-expect-error -- linter-disable-autofix, 新添加参数,sdk中还未支持到 + getFileInfo(new File([], item?.name)) || {}; + return { + type: ContentType.File, + file: { + file_key: item.file_id || '', + + // @ts-expect-error -- linter-disable-autofix, 新添加参数,sdk中还未支持到 + file_name: item?.name, + // @ts-expect-error -- linter-disable-autofix, 新添加参数,sdk中还未支持到 + file_size: item?.size, + file_type: fileType, + file_url: item?.file_url, + }, + // @ts-expect-error -- linter-disable-autofix + is_refer: item.is_refer || undefined, + }; + } + default: { + return; + } + } + }) + .filter(item => !!item); + const contentResult = { + item_list: itemList.filter(item => !item.is_refer), + refer_items: itemList.filter(item => item.is_refer), + }; + return JSON.stringify(contentResult); + } +} +export const messageConverterToCoze = new MessageConverseToCoze(); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/message-convert-to-sdk.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/message-convert-to-sdk.ts new file mode 100644 index 00000000..d5936cbb --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/message-convert-to-sdk.ts @@ -0,0 +1,269 @@ +/* + * 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. + */ + +import { ContentType, type MessageContent } from '@coze-common/chat-core'; +import { type ShortCutCommand } from '@coze-common/chat-area-plugins-chat-shortcuts'; +import { type MixInitResponse, type ChatMessage } from '@coze-common/chat-area'; +import { + type EnterMessage, + type RoleType, + type ContentType as CozeApiContentType, + type ObjectStringItem, +} from '@coze/api'; + +import { catchParse } from '@/util'; +import { type OpenUserInfo } from '@/types/user'; + +class MessageConverterToSdk { + public convertRequestBody({ + body, + userInfo, + connectorId, + parameters, + shortcuts, + }: { + body: string; + userInfo?: OpenUserInfo; + connectorId?: string; + parameters?: Record; + shortcuts?: ShortCutCommand[]; + }): string { + const messageBody: Record = catchParse(body) || {}; + const contentType = messageBody.content_type as ContentType; + const content = messageBody.query as string; + const shortcutCommand = messageBody.shortcut_command as string; + return JSON.stringify({ + bot_id: messageBody.bot_id, + user_id: userInfo?.id, + stream: true, + connector_id: connectorId, + additional_messages: [this.convertRequestMessage(contentType, content)], + parameters, + shortcut_command: this.convertShortcuts(shortcuts || [], shortcutCommand), + enable_card: true, + }); + } + // 替换 chat请求中的 message部分 + private convertRequestMessage(contentType: ContentType, content: string) { + return { + role: 'user', + ...this.convertContent(contentType, content), + }; + } + private convertContent( + contentType: ContentType, + content: string, + isNeedFileUrl = false, + ) { + switch (contentType) { + case ContentType.Text: + return { + content_type: 'text', + content, + }; + case ContentType.Card: + return { + content_type: 'card', + content, + }; + case ContentType.Image: + case ContentType.File: + case ContentType.Mix: { + return this.convertMixContent(content, isNeedFileUrl); + } + default: { + throw new Error('Error: unknown content Type'); + } + } + } + private convertMixContent(content: string, isNeedFileUrl = false) { + const contentObj = catchParse(content) as MessageContent & + MessageContent & + MessageContent; + if (!contentObj) { + return; + } + let mixObjectList: MessageContent['item_list'] = [ + ...(contentObj?.item_list || []), + ]; + const mixReferObjectList: MessageContent['item_list'] = [ + // @ts-expect-error -- linter-disable-autofix + ...(contentObj?.refer_items || []), + ]; + mixObjectList = mixObjectList.concat( + (contentObj?.image_list || []).map(item => ({ + type: ContentType.Image, + image: item, + })), + ); + mixObjectList = mixObjectList.concat( + (contentObj?.file_list || []).map(item => ({ + type: ContentType.File, + file: item, + })), + ); + mixObjectList = mixObjectList.concat( + (mixReferObjectList || []).map(item => ({ + ...item, + is_refer: true, + })), + ); + + return { + content_type: 'object_string', + content: JSON.stringify( + mixObjectList + .map(item => { + switch (item.type) { + case ContentType.Text: + return { + type: 'text', + text: item.text, + // @ts-expect-error -- linter-disable-autofix + is_refer: item.is_refer || undefined, + }; + case ContentType.Image: { + return { + type: 'image', + file_id: item.image.key, + file_url: + isNeedFileUrl || !item.image.key + ? item.image.image_ori?.url + : undefined, + // @ts-expect-error -- linter-disable-autofix + is_refer: item.is_refer || undefined, + }; + } + case ContentType.File: { + return { + type: 'file', + file_id: item.file.file_key || !item.file.file_key, + file_url: isNeedFileUrl ? item.file?.file_url : undefined, + // @ts-expect-error -- linter-disable-autofix + is_refer: item.is_refer || undefined, + }; + } + default: { + return null; + } + } + }) + .filter(item => !!item), + ), + }; + } + public convertMessageListResponse( + messageList: MixInitResponse['messageList'], + ) { + return messageList + ?.reverse() + .map(item => { + const cozeMessage = this.convertMessage(item); + //(alias) type CozeApiContentType = "text" | "card" | "object_string" + + if (cozeMessage?.content_type === 'object_string') { + const contentObj = catchParse( + cozeMessage.content as unknown as string, + ) as ObjectStringItem[]; + const contentTemp = contentObj?.map(item2 => { + if (item2.type === 'image' || item2.type === 'file') { + return { + type: 'text', + text: item2.file_url, + }; + } + return item2; + }); + if (contentTemp?.length === 1 && contentTemp[0]?.type === 'text') { + cozeMessage.content_type = 'text' as CozeApiContentType; + cozeMessage.content = contentTemp[0].text; + } else if (contentTemp?.length > 0) { + cozeMessage.content = JSON.stringify(contentTemp); + } else { + return null; + } + } + return cozeMessage; + }) + .filter(item => !!item); + } + private convertMessage(message: ChatMessage): EnterMessage | null { + if ( + message.type && + ['ack', 'answer', 'question'].includes(message.type) && + message.role && + ['user', 'assistant'].includes(message.role) && + message.content_type && + ['card', 'image', 'text', 'object_string', 'file'].includes( + message.content_type, + ) && + message.content + ) { + // @ts-expect-error -- linter-disable-autofix + const sdkMessage: EnterMessage = { + role: message.role as RoleType, + ...this.convertContent( + message.content_type as ContentType, + message.content || '', + true, + ), + }; + return sdkMessage; + } + return null; + } + private convertShortcuts( + shortcuts: ShortCutCommand[], + commandStr: + | string + | { + command_id: string; + parameters: Record; + }, + ) { + let command; + if (typeof commandStr === 'string') { + command = catchParse(commandStr); + } else if (typeof commandStr === 'object') { + command = commandStr; + } else { + return commandStr; + } + const currentShortcut = shortcuts.find( + item => item.command_id === command.command_id, + ); + + if (currentShortcut?.components_list) { + const toolParameterMap = new Map( + currentShortcut.components_list + .filter(c => c.parameter && c.name) + .map(c => [c.parameter, c.name]), + ); + + Object.keys(command.parameters).forEach(key => { + const compName = toolParameterMap.get(key); + const val = command.parameters[key]; + if (compName) { + delete command.parameters[key]; + command.parameters[compName] = val; + } + }); + } + + return command; + } +} +export const messageConverterToSdk = new MessageConverterToSdk(); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/message-parser.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/message-parser.ts new file mode 100644 index 00000000..8b670c38 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/message/message-parser.ts @@ -0,0 +1,251 @@ +/* + * 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. + */ + +import { nanoid } from 'nanoid'; +import { + ContentType, + type RequestManagerOptions, + type ParsedEvent, +} from '@coze-common/chat-core'; +import { I18n } from '@coze-arch/i18n'; +import { Toast } from '@coze-arch/coze-design'; +import { safeJSONParse } from '@coze-arch/bot-utils'; +import { type CreateChatData, type ChatV3Message } from '@coze/api'; + +import { catchParse } from '@/util'; +import { type OpenUserInfo } from '@/types/user'; + +import { messageConverterToCoze } from './message-convert-to-coze'; + +type MessageParserFunc = ReturnType< + Required['hooks']>['onGetMessageStreamParser'] +>; +// 消息解析,主要用于从服务端获取到消息后,解析成coze能适配的数据结构 +enum ChunkEvent { + ERROR = 'error', + DONE = 'done', + MESSAGE_DELTA = 'conversation.message.delta', + MESSAGE_COMPLETED = 'conversation.message.completed', + // 其他消息暂时不处理,中间过程消息。 + CHAT_COMPLETED = 'conversation.chat.completed', + CHAT_CREATED = 'conversation.chat.created', + CHAT_FAILED = 'conversation.chat.failed', +} + +export class MessageParser { + private seqNo = 0; //标识消息的序号 + private indexNo = 0; //标识类型的序号 + private indexNoMap: Record = {}; + private conversationId = ''; + private localMessageId = ''; + private sendMessageContent = ''; + private sendMessageContentType = ''; + private botId = ''; + private sectionId = ''; + private botVersion = ''; + private userInfo?: OpenUserInfo; + constructor({ + requestMessageRawBody, + userInfo, + sectionId, + }: { + requestMessageRawBody: Record; + userInfo?: OpenUserInfo; + sectionId?: string; + }) { + this.conversationId = requestMessageRawBody.conversation_id as string; + this.localMessageId = requestMessageRawBody.local_message_id as string; + this.sendMessageContent = requestMessageRawBody.query as string; + this.sendMessageContentType = requestMessageRawBody.content_type as string; + this.botId = requestMessageRawBody.bot_id as string; + this.botVersion = requestMessageRawBody.bot_version as string; + this.userInfo = userInfo; + this.sectionId = sectionId || ''; + } + public parse( + parseEvent: Partial, + { terminate }: { terminate: () => void }, + ): ParsedEvent | undefined { + const { data, event } = parseEvent; + switch (event) { + case ChunkEvent.CHAT_CREATED: { + return this.createAckMessage(data as string) as unknown as ParsedEvent; + } + case ChunkEvent.MESSAGE_DELTA: { + const message = this.createMessage(data as string); + if (!message) { + return; + } + return message as unknown as ParsedEvent; + } + case ChunkEvent.MESSAGE_COMPLETED: { + return this.createMessage( + data as string, + true, + ) as unknown as ParsedEvent; + } + case ChunkEvent.CHAT_COMPLETED: + case ChunkEvent.DONE: { + terminate(); + return; + } + // 对话过程中出现异常,例如:token 消耗完了 + case ChunkEvent.CHAT_FAILED: { + const messageError = safeJSONParse(data) as CreateChatData; + const errorMsg = messageError.last_error?.msg || I18n.t('sendFailed'); + + Toast.error(errorMsg); + throw new Error('Chat stream error'); + } + case ChunkEvent.ERROR: { + const messageError = safeJSONParse(data) as { + code: number; + msg: string; + }; + const errorMsg = messageError?.msg || I18n.t('sendFailed'); + Toast.error(errorMsg); + throw new Error('Chat stream error'); + } + default: + return; + } + } + + private createMessage(data: string, isComplete = false) { + const dataValue = catchParse(data); + if (!dataValue) { + return; + } + const messageType = dataValue?.type || ''; + dataValue.chat_id = + !dataValue.chat_id || dataValue.chat_id === '0' ? '' : dataValue.chat_id; + dataValue.id = !dataValue.id || dataValue.id === '0' ? '' : dataValue.id; + + const message = messageConverterToCoze.convertMessage( + dataValue, + this.botId, + ); + if (!message) { + return; + } + if ( + isComplete && + message.content_type === ContentType.Text && + message.type === 'answer' + ) { + message.content = ''; + } + message.section_id = message.section_id || this.sectionId; + return { + event: 'message', + data: { + conversation_id: this.conversationId, + index: this.getIndexNo(messageType), + is_finish: isComplete, + seq_id: this.getSeqNo(), + message: { ...message, sender: this.botId }, + }, + }; + } + private createAckMessage(data: string) { + const messageType = 'ack'; + const dataValue = catchParse( + data, + ); + if (!dataValue) { + return; + } + const chatId = dataValue?.id === '0' || !dataValue?.id ? '' : dataValue?.id; + const replyId = chatId || `--custom-replyId--${nanoid()}`; + // @ts-expect-error -- linter-disable-autofix, 新添加参数,sdk中还未支持到 + const messageId = dataValue.inserted_additional_messages?.lastItem?.id; + + return { + event: 'message', + data: { + conversation_id: this.conversationId, + index: this.getIndexNo(messageType), + is_finish: true, + message: { + content: this.sendMessageContent, + content_time: (dataValue?.created_at || 0) * 1000, + content_type: this.sendMessageContentType, + extra_info: { + local_message_id: this.localMessageId, + chatflow_execute_id: dataValue?.execute_id, + coze_api_message_id: messageId, + coze_api_chat_id: chatId, + }, + message_id: replyId, + reply_id: replyId, + role: 'user', + // @ts-expect-error -- linter-disable-autofix, 新添加参数,sdk中还未支持到 + section_id: dataValue?.section_id || this.sectionId, //todo 添加代码 + sender_id: this.userInfo?.id, + source: 0, //... + status: '', + type: messageType, + }, + seq_id: this.getSeqNo(), + }, + }; + } + + private getSeqNo() { + return this.seqNo++; + } + private getIndexNo(messageType: string) { + if (!this.indexNoMap[messageType]) { + this.indexNoMap[messageType] = this.indexNo++; + } + return this.indexNoMap[messageType]; + } + + static getMessageParser({ + requestMessageRawBody, + userInfo, + sectionId, + }: { + requestMessageRawBody: Record; + userInfo?: OpenUserInfo; + sectionId?: string; + }): MessageParserFunc { + let parser: MessageParser | undefined = new MessageParser({ + requestMessageRawBody, + userInfo, + sectionId, + }); + const destroy = () => { + parser = undefined; + }; + return (parseEvent, method) => { + const { terminate } = method; + const { type, event } = parseEvent as ParsedEvent & { type: string }; + if (type === 'event') { + // + const result = parser?.parse(parseEvent as ParsedEvent, { terminate }); + if ( + [ChunkEvent.DONE, ChunkEvent.ERROR, ChunkEvent.CHAT_FAILED].includes( + event as ChunkEvent, + ) + ) { + destroy(); + } + return result; + } + }; + } +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-break-message.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-break-message.ts new file mode 100644 index 00000000..6d297a39 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-break-message.ts @@ -0,0 +1,40 @@ +/* + * 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. + */ + +import { useMemo } from 'react'; + +import { type SceneConfig } from '@coze-common/chat-core'; + +export const useBreakMessage = (): SceneConfig => + useMemo(() => { + const config = { + url: '/v3/chat/cancel', + method: 'POST', + hooks: { + onBeforeRequest: [ + requestConfig => { + const conversationId = requestConfig.data.conversation_id; + const chatId = requestConfig.data.query_message_id; + return { + ...requestConfig, + data: { conversation_id: conversationId, chat_id: chatId }, + }; + }, + ], + }, + }; + return config; + }, []); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-clear-history.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-clear-history.ts new file mode 100644 index 00000000..32a884d3 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-clear-history.ts @@ -0,0 +1,73 @@ +/* + * 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. + */ + +import { useMemo, useRef } from 'react'; + +import { type SceneConfig } from '@coze-common/chat-core'; + +import { OpenApiSource } from '@/types/open'; +import { useChatAppProps } from '@/components/studio-open-chat/store'; + +import { type ChatProviderFunc } from '../type'; +export const useClearHistoryAdapter = ({ + refChatFunc, +}: { + refChatFunc?: React.MutableRefObject; +}): SceneConfig => { + const { chatConfig } = useChatAppProps(); + const refConnectorId = useRef(''); + refConnectorId.current = chatConfig?.auth?.connectorId || ''; + + return useMemo(() => { + const onAfterResponse = [ + response => { + const { data: resCreateConversation } = response; + const { code, data: conversationData } = resCreateConversation; + const { id: conversationId, last_section_id: sectionId } = + conversationData || {}; + refChatFunc?.current?.setConversationId(conversationId, sectionId); + return { + ...response, + data: { + code, + new_section_id: sectionId, + }, + }; + }, + ]; + const config = { + url: + IS_OPEN_SOURCE && chatConfig.source === OpenApiSource.ChatFlow + ? '/v1/workflow/conversation/create' + : '/v1/conversation/create', + method: 'POST', + hooks: { + onBeforeRequest: [ + requestConfig => { + const botId = requestConfig.data.bot_id; + return { + ...requestConfig, + data: { bot_id: botId, connector_id: refConnectorId.current }, + }; + }, + ], + onErrorResponse: onAfterResponse, + onAfterResponse, + }, + }; + return config; + }, []); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-clear-message-context.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-clear-message-context.ts new file mode 100644 index 00000000..b80d007b --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-clear-message-context.ts @@ -0,0 +1,54 @@ +/* + * 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. + */ + +import { useMemo } from 'react'; + +import { type SceneConfig } from '@coze-common/chat-core'; + +export const useClearMessageContextAdapter = (): SceneConfig => + useMemo(() => { + const onAfterResponse = [ + response => { + const { data } = response; + const { code, data: res } = data; + return { + ...response, + data: { + code, + new_section_id: res.id, + }, + }; + }, + ]; + return { + url: '/v1/conversations/:conversation_id/clear', + hooks: { + onBeforeRequest: [ + requestConfig => { + const conversationId = requestConfig.data.conversation_id; + const url = `/v1/conversations/${conversationId}/clear`; + return { + ...requestConfig, + url, + data: { conversation_id: conversationId }, + }; + }, + ], + onErrorResponse: onAfterResponse, + onAfterResponse, + }, + }; + }, []); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-common-hooks.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-common-hooks.ts new file mode 100644 index 00000000..af4ff467 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-common-hooks.ts @@ -0,0 +1,93 @@ +/* + * 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. + */ + +import { useMemo } from 'react'; + +import axios from 'axios'; +import { type RequestManagerOptions } from '@coze-common/chat-core'; + +import { isAuthError } from '@/util/error'; +import { useChatAppProps } from '@/components/studio-open-chat/store'; + +export const useCommonOnBeforeRequestHooks = + (): Required['hooks']['onBeforeRequest'] => { + const { debug } = useChatAppProps(); + return [ + // 去除无用的头部 + requestConfig => { + requestConfig.headers.delete('x-requested-with'); + Object.keys(debug?.cozeApiRequestHeader || {}).forEach(key => { + requestConfig.headers.set( + key, + debug?.cozeApiRequestHeader?.[key] || '', + ); + }); + return requestConfig; + }, + ]; + }; +const handleCommonError = async ( + response, + refreshToken?: () => Promise, +) => { + const { code } = response?.response?.data || {}; + let responseOut = response; + if (isAuthError(code)) { + const token = await refreshToken?.(); + if (token) { + const config = { ...response.config }; + config.headers = { ...config.headers }; + config.headers.Authorization = `Bearer ${token}`; + responseOut = await axios.request(config); + } + } + + return responseOut; +}; + +export const useCommonErrorResponseHooks = ( + refreshToken?: () => Promise, +): Required['hooks']['onErrorResponse'] => + useMemo( + () => [async response => handleCommonError(response, refreshToken)], + [refreshToken], + ); +export const useCommonOnAfterResponseHooks = ( + refreshToken?: () => Promise, +): Required['hooks']['onAfterResponse'] => + useMemo( + () => [ + // 用户登录权限判断 + async response => handleCommonError(response, refreshToken), + // 用户Url恢复 + response => { + if ( + response.config.url && + /^\/v1\/conversations\/[^\\]+\/clear$/.test(response.config.url) + ) { + return { + ...response, + config: { + ...response.config, + url: '/v1/conversations/:conversation_id/clear', + }, + }; + } + return response; + }, + ], + [refreshToken], + ); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-message-list.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-message-list.ts new file mode 100644 index 00000000..a02ce801 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-message-list.ts @@ -0,0 +1,128 @@ +/* + * 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. + */ + +import { useCallback, useMemo } from 'react'; + +import { type SceneConfig } from '@coze-common/chat-core'; +import { type MixInitResponse } from '@coze-common/chat-area'; + +import { useChatCozeSdk } from '../context'; +import { useChatAppProps } from '../../../store'; +import { messageConverterToCoze } from './message'; + +type ChatMessageList = MixInitResponse['messageList']; +export const useMessageList = (): SceneConfig => { + const getMessageListByPairs = useGetMessageListByPairs(); + const { chatConfig } = useChatAppProps(); + const { bot_id: botId } = chatConfig || {}; + const { refMessageListLeft } = useChatCozeSdk(); + return useMemo(() => { + const onAfterResponse = [ + response => { + const { data } = response; + const conversationId = response.config?.params?.conversation_id; + const lastMessageList = + (refMessageListLeft?.current?.[conversationId] as ChatMessageList) || + []; + const lastAnswerChatId = + lastMessageList[lastMessageList.length - 1]?.reply_id; + if (lastAnswerChatId) { + if ( + data.data?.[0].type === 'question' && + !data.message_list?.[0].chatId + ) { + data.data[0].chat_id = lastAnswerChatId; + } + } + const dataForCoze = messageConverterToCoze.convertMessageListResponse( + data, + botId, + ); + + return { + ...response, + data: { + ...dataForCoze, + message_list: getMessageListByPairs( + conversationId, + dataForCoze.message_list, + ), + }, + }; + }, + ]; + return { + url: '/v1/conversation/message/list', + hooks: { + onBeforeRequest: [ + requestConfig => { + const conversationId = requestConfig.data.conversation_id; + const data = { + after_id: requestConfig.data.cursor, + limit: requestConfig.data.count, + }; + + return { + ...requestConfig, + data, + params: { + conversation_id: conversationId, + }, + }; + }, + ], + onErrorResponse: onAfterResponse, + onAfterResponse, + }, + }; + }, [botId]); +}; + +// 接口返回的数据,并能保证 问题、回答 成对返回,因此需要将多返回的 回答 保存下来,等下次请求数据中的第一条数据是同一个 对话的时候,拼接上去。 +export const useGetMessageListByPairs = () => { + const { refMessageListLeft } = useChatCozeSdk(); + return useCallback( + (conversationId: string, messageList: ChatMessageList = []) => { + const messageListLeft: ChatMessageList = []; // 需要留下来的 + const messageListResponse: ChatMessageList = []; // 需要返回给前端的 + for (let i = 0; i < messageList.length; i++) { + if (messageList[i].type !== 'question') { + messageListLeft.push(messageList[i]); + } else { + messageListResponse.push(...messageListLeft); + messageListLeft.splice(0, messageListLeft.length); + messageListResponse.push(messageList[i]); + } + } + const lastMessageList = + (refMessageListLeft?.current?.[conversationId] as ChatMessageList) || + []; + // 将上次遗留的数据,拼接上去 + if (lastMessageList.length) { + if (lastMessageList[0]?.reply_id === messageListResponse[0]?.reply_id) { + messageListResponse.unshift(...lastMessageList); + } + } + + // 重置本次遗留的数据 + if (refMessageListLeft?.current) { + refMessageListLeft.current[conversationId] = messageListLeft; + } + return messageListResponse; + }, + [], + ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-send-message.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-send-message.ts new file mode 100644 index 00000000..81e5ff09 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/api-adapter/use-send-message.ts @@ -0,0 +1,87 @@ +/* + * 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. + */ + +import { useRef } from 'react'; + +import { useShallow } from 'zustand/react/shallow'; +import { type SceneConfig } from '@coze-common/chat-core'; +import { type UserSenderInfo } from '@coze-common/chat-area'; +import i18n from '@coze-arch/i18n/intl'; + +import { openApiHostByRegionWithToken } from '@/util/env'; +import { catchParse } from '@/util'; +import { + useChatAppProps, + useChatAppStore, +} from '@/components/studio-open-chat/store'; + +import { type ChatProviderFunc } from '../type'; +import { messageConverterToSdk, MessageParser } from './message'; + +export const useSendMessageAdapter = ( + userInfo?: UserSenderInfo, + refChatFunc?: React.MutableRefObject, +): SceneConfig => { + const { debug, chatConfig } = useChatAppProps(); + const { shortcuts } = useChatAppStore( + useShallow(state => ({ + shortcuts: state.shortcuts, + })), + ); + + const refChatConfig = useRef(chatConfig); + const refConnectorId = useRef(''); + const shortcutsRef = useRef(shortcuts); + shortcutsRef.current = shortcuts; + refConnectorId.current = chatConfig?.auth?.connectorId || ''; + refChatConfig.current = chatConfig; + return { + url: '/v3/chat', + hooks: { + onBeforeSendMessage: [ + requestConfig => { + const messageBody: Record = + catchParse(requestConfig.body) || {}; + const url = `${openApiHostByRegionWithToken}/v3/chat?conversation_id=${messageBody.conversation_id}`; + const body = messageConverterToSdk.convertRequestBody({ + body: requestConfig.body, + userInfo, + connectorId: refConnectorId.current, + parameters: refChatConfig.current.botInfo?.parameters, + shortcuts: shortcutsRef.current, + }); + Object.keys(debug?.cozeApiRequestHeader || {}).forEach(key => { + requestConfig.headers.push([ + key, + debug?.cozeApiRequestHeader?.[key] || '', + ]); + }); + requestConfig.headers.push([ + 'Accept-Language', + i18n.language === 'zh-CN' ? 'zh' : 'en', + ]); + return { ...requestConfig, body, url }; + }, + ], + onGetMessageStreamParser: requestMessageRawBody => + MessageParser.getMessageParser({ + requestMessageRawBody, + userInfo, + sectionId: refChatFunc?.current?.getSectionId(), + }), + }, + }; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/chat-provider.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/chat-provider.tsx new file mode 100644 index 00000000..2ddee848 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/chat-provider.tsx @@ -0,0 +1,287 @@ +/* + * 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. + */ + +import React, { + type FC, + useMemo, + type PropsWithChildren, + forwardRef, + useImperativeHandle, + useRef, + useEffect, +} from 'react'; + +import { useShallow } from 'zustand/react/shallow'; +import copy from 'copy-to-clipboard'; +import { useMemoizedFn } from 'ahooks'; +import { isFile } from '@coze-common/chat-uikit'; +import { Scene } from '@coze-common/chat-core'; +import { ReasoningPluginRegistry } from '@coze-common/chat-area-plugin-reasoning'; +import { + type GrabPublicMethod, + useCreateGrabPlugin, +} from '@coze-common/chat-area-plugin-message-grab'; +import { + allIgnorableMessageTypes, + ChatAreaProvider, + useChatAreaStoreSet, + type PluginRegistryEntry, + useRegenerateMessageByUserMessageId, + usePluginPublicMethods, + PluginName, +} from '@coze-common/chat-area'; +import { I18n } from '@coze-arch/i18n'; +import { UIToast } from '@coze-arch/bot-semi'; +import { type Conversation } from '@coze/api'; + +import { type SDKInitError, isAuthError } from '@/util/error'; +import { ChatType } from '@/types/client'; +import { studioOpenClientReporter, createSDKUploadPluginClass } from '@/helper'; + +import { useChatAppProps, useChatAppStore } from '../../store'; +import { useUpdateConversationNameByMessage } from '../../hooks/use-update-conversation-name-by-message'; +import { useIsShowBackground } from '../../hooks/use-is-show-background'; +import { useUserInfo } from '../../hooks'; +import { useUploadFileApi } from './use-upload-file-api'; +import { useRequestInit } from './use-request-init'; +import { useCoreOverrideConfig } from './use-core-override-config'; +import { type ChatProviderFunc } from './type'; +import { getCozeSdkPlugin } from './plugin'; +import { useBgBackgroundPlugin } from './hooks/use-bg-background-plugin'; +import { ChatCozeSdkProvider, useChatCozeSdk } from './context'; +const ChatProviderFuncComp = forwardRef< + ChatProviderFunc | undefined, + PropsWithChildren +>(({ children }, ref) => { + const { chatConfig } = useChatAppProps(); + const { + updateCurrentConversationInfo, + currentConversationInfo, + updateConversations, + } = useChatAppStore( + useShallow(s => ({ + updateCurrentConversationInfo: s.updateCurrentConversationInfo, + currentConversationInfo: s.currentConversationInfo, + updateConversations: s.updateConversations, + })), + ); + const { useGlobalInitStore, useSectionIdStore } = useChatAreaStoreSet(); + const setConversationIdInArea = useGlobalInitStore( + state => state.setConversationId, + ); + const conversationId = useGlobalInitStore(state => state.conversationId); + const sectionId = useSectionIdStore(state => state.latestSectionId); + const chatCore = useGlobalInitStore(state => state.chatCore); + const setConversationId = useMemoizedFn( + (conversationIdNew: string, sectionIdNew: string) => { + const isConversations = + chatConfig.ui?.conversations?.isNeed && + chatConfig.type !== ChatType.APP; + if (isConversations && currentConversationInfo) { + const timestamp = Math.floor(Date.now() / 1000); + const newConversation: Conversation = { + id: conversationIdNew, + last_section_id: sectionIdNew, + updated_at: timestamp, + created_at: timestamp, + meta_data: {}, + }; + updateCurrentConversationInfo({ + ...currentConversationInfo, + ...newConversation, + }); + updateConversations([newConversation], 'add'); + } else { + setConversationIdInArea(conversationIdNew); + chatCore?.updateConversationId(conversationIdNew); + } + }, + ); + const getConversationId = useMemoizedFn(() => conversationId || ''); + const getSectionId = useMemoizedFn(() => sectionId || ''); + + useUpdateConversationNameByMessage(); + + const regenerateMessageByUserMessageId = + useRegenerateMessageByUserMessageId(); + useImperativeHandle( + ref, + () => ({ + regenerateMessageByUserMessageId, + setConversationId, + getConversationId, + getSectionId, + }), + [ + regenerateMessageByUserMessageId, + setConversationId, + getConversationId, + getSectionId, + ], + ); + + const grabPluginPublicMethods = usePluginPublicMethods( + PluginName.MessageGrab, + ); + useEffect(() => { + if (!grabPluginPublicMethods) { + return; + } + + grabPluginPublicMethods.updateEnableGrab(true); + }, []); + return <>{children}; +}); + +const ChatProviderImpl: FC<{ + children: React.ReactNode; + plugins?: PluginRegistryEntry[]; +}> = ({ children, plugins }) => { + const refLastIsError = useRef(false); + const { refreshToken } = useChatCozeSdk(); + const { initError, setInitError } = useChatAppStore( + useShallow(s => ({ + initError: s.initError, + setInitError: s.setInitError, + })), + ); + const refChatFunc = useRef(); + const { + chatConfig, + onImageClick, + initErrorFallbackFC: ErrorFallback, + } = useChatAppProps(); + const userInfo = useUserInfo(); + const requestToInit = useRequestInit(); + const createChatCoreOverrideConfig = useCoreOverrideConfig({ + refChatFunc, + }); + const { GrabPlugin } = useCreateGrabPlugin({ + scene: 'store', + }); + const uploadFileApi = useUploadFileApi(); + const isShowBackground = useIsShowBackground(); + const { ChatBackgroundPlugin } = useBgBackgroundPlugin(); + // plugin初始化 + const cozeSdkChatPlugin = getCozeSdkPlugin({ + refreshToken, + regenerateMessageByUserMessageId: id => { + refChatFunc.current?.regenerateMessageByUserMessageId?.(id); + }, + }); + + const SDKUploadPlugin = useMemo( + () => + createSDKUploadPluginClass({ + botId: chatConfig.bot_id || '', + source: chatConfig.source, + uploadFile: uploadFileApi, + }), + [chatConfig, uploadFileApi], + ); + useEffect(() => { + if (!initError) { + return; + } + if (refLastIsError.current) { + return; + } + (async () => { + try { + if (isAuthError((initError as SDKInitError).code)) { + await refreshToken?.(); + setInitError(false); + } + } catch (err) { + console.error(err); + } + refLastIsError.current = true; + })(); + }, [initError]); + + if (initError) { + if (ErrorFallback) { + return ( + { + if (isAuthError((initError as SDKInitError).code)) { + await refreshToken?.(); + setInitError(false); + } else { + location.reload(); + } + }} + /> + ); + } + + return null; + } + + return ( + { + refLastIsError.current = false; + }, + onCopyUpload: ({ message: msg, extra: { fileIndex } }) => { + if (isFile(msg.content_obj)) { + copy(msg.content_obj.file_list[fileIndex ?? 0]?.file_key ?? ''); + UIToast.success({ + content: I18n.t('copy_success') ?? 'Copy Successfully', + }); + } + }, + }} + configs={{ + showFunctionCallDetail: false, + uploadPlugin: SDKUploadPlugin, + ignoreMessageConfigList: allIgnorableMessageTypes, + isShowFunctionCallBox: + chatConfig?.ui?.chatBot?.isNeedFunctionCallMessage ?? true, + }} + pluginRegistryList={[ + cozeSdkChatPlugin, + ReasoningPluginRegistry, + ChatBackgroundPlugin, + GrabPlugin, + ...(plugins || []), + ]} + showBackground={isShowBackground} + enableDragUpload={false} + > + {children} + + ); +}; + +export const ChatProvider: FC<{ + children: React.ReactNode; + plugins?: PluginRegistryEntry[]; +}> = ({ children, plugins }) => ( + + {children} + +); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/context.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/context.tsx new file mode 100644 index 00000000..6d5dc7b5 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/context.tsx @@ -0,0 +1,67 @@ +/* + * 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. + */ + +import React, { + useContext, + createContext, + type ReactNode, + useRef, + type FC, +} from 'react'; + +import { type TokenManager } from '@coze-common/chat-core'; +import { type CozeAPI } from '@coze/api'; + +import { useApiClient } from './use-api-client'; + +interface ContextValue { + cozeApiSdk: CozeAPI; + tokenManager: TokenManager; + refreshToken?: () => Promise; + refMessageListLeft?: React.RefObject>; +} +// @ts-expect-error: 先不检查 +export const ChatCozeSdkContext = createContext({}); +export const ChatCozeSdkProvider: FC<{ children: ReactNode }> = ({ + children, +}) => { + const refMessageListLeft = useRef>({}); + const { tokenManagerClient, cozeApiClient, refreshToken } = useApiClient(); + + return ( + + {children} + + ); +}; + +export const useChatCozeSdk = (): ContextValue => { + const { cozeApiSdk, tokenManager, refMessageListLeft, refreshToken } = + useContext(ChatCozeSdkContext); + return { + cozeApiSdk, + tokenManager, + refMessageListLeft, + refreshToken, + }; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/helper/convert-shortcut-data.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/helper/convert-shortcut-data.ts new file mode 100644 index 00000000..286798e4 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/helper/convert-shortcut-data.ts @@ -0,0 +1,150 @@ +/* + * 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. + */ + +import { type ShortCutCommand } from '@coze-common/chat-area-plugins-chat-shortcuts'; +import { SendType, ToolType, InputType } from '@coze-arch/idl/playground_api'; + +const refInputTypeMap = { + text: InputType.TextInput, + select: InputType.Select, + file: InputType.MixUpload, + image: InputType.UploadImage, + audio: InputType.UploadAudio, + doc: InputType.UploadDoc, + table: InputType.UploadTable, + code: InputType.CODE, + zip: InputType.ARCHIVE, + ppt: InputType.PPT, + video: InputType.VIDEO, + txt: InputType.TXT, +}; +export interface ShortcutCommandInOpenApi { + id: string; + name: string; + command: string; + description: string; + query_template: string; + icon_url: string; + components?: Array<{ + name: string; + description: string; + type: 'text' | 'file' | 'select'; + tool_parameter: string; + is_hide: boolean; + options: Array; + default_value: string; + }>; + tool?: { + name: string; + type: 'plugin' | 'workflow'; + plugin_id: string; + plugin_api_name: string; + workflow_id: string; + params?: Array<{ + name: string; + is_required: boolean; + description: string; + type: string; + default_value: string; + is_refer_component: boolean; + }>; + }; + send_type: 'panel' | 'query'; + card_schema: string; +} + +export const convertShortcutData = ( + shortcutCommands?: ShortcutCommandInOpenApi[], + botInfo?: { + name?: string; + iconUrl?: string; + id?: string; + }, +): ShortCutCommand[] => + //@ts-expect-error: 不知道为什么报错 + shortcutCommands?.map(item => { + const sendType = + item.send_type && + (item.send_type === 'panel' + ? SendType.SendTypePanel + : SendType.SendTypeQuery); + + let componentsList; + let toolType; + if (sendType !== SendType.SendTypeQuery) { + componentsList = + item.components?.map(componentItem => ({ + name: componentItem.name, + description: componentItem.description, + parameter: componentItem.tool_parameter, + hide: componentItem.is_hide, + options: + (componentItem.type === 'select' && componentItem.options) || [], + + input_type: refInputTypeMap[componentItem.type], + upload_options: + (componentItem.type === 'file' && + componentItem.options?.map(option => refInputTypeMap[option])) || + [], + + default_value: { + value: componentItem.default_value || '', + type: 0, + }, + })) || []; + if (item?.tool?.type && ['plugin', 'workflow'].includes(item.tool.type)) { + toolType = + item.tool.type === 'plugin' + ? ToolType.ToolTypePlugin + : ToolType.ToolTypeWorkFlow; + } + } + + return { + object_id: botInfo?.id || '', + command_name: item.name, + shortcut_command: item.command, + description: item.description, + send_type: sendType, + tool_type: toolType, + work_flow_id: item?.tool?.workflow_id || '', + plugin_id: item?.tool?.plugin_id || '', + plugin_api_name: item?.tool?.name || '', + template_query: item.query_template, + components_list: componentsList, + card_schema: item.card_schema, + command_id: item.id, + tool_info: { + tool_name: item.tool?.name, + tool_params_list: + item.tool?.params?.map(param => ({ + name: param.name, + required: param.is_required, + desc: param.description, + type: param.type, + default_value: param.default_value, + refer_component: param.is_refer_component, + })) || [], + }, + shortcut_icon: { + url: item.icon_url, + }, + bot_info: { + icon_url: botInfo?.iconUrl || '', + name: botInfo?.name || '', + }, + }; + }) || []; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/helper/coze-api-custom.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/helper/coze-api-custom.ts new file mode 100644 index 00000000..bb75c210 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/helper/coze-api-custom.ts @@ -0,0 +1,102 @@ +/* + * 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. + */ + +import axios, { type AxiosInstance, type AxiosResponse } from 'axios'; +import { CozeAPI, type ClientOptions } from '@coze/api'; + +import { isAuthError } from '@/util/error'; + +type OnRefreshToken = (oldToken?: string) => Promise | string; + +export class CozeApiCustom extends CozeAPI { + private onRefreshToken?: OnRefreshToken; + private refreshTokenPromise?: Promise | string; + constructor({ + onRefreshToken, + axiosOptions, + ...config + }: ClientOptions & { + onRefreshToken?: OnRefreshToken; + }) { + super({ + ...config, + axiosOptions: { + ...(axiosOptions || {}), + timeout: 10 * 60 * 1000, + validateStatus: () => true, + }, + }); + this.onRefreshToken = onRefreshToken; + this.axiosInstance = axios.create(); + this.useAuthError(); + } + setRefreshToken(onRefreshToken?: OnRefreshToken) { + this.onRefreshToken = onRefreshToken; + } + useAuthError() { + const authInterceptor = async response => { + const { code } = response?.data || {}; + if (isAuthError(code || response.status)) { + // 由于 鉴权问题导致的失败,进行一次重新发送数据。 + const oldToken = this.getTokenFromHeaderAuth( + String(response.config.headers.getAuthorization() || ''), + ); + const token = await this.refreshToken(oldToken); + if (token) { + const config = { ...response.config }; + config.headers = { ...response.config.headers }; + response.config.headers.Authorization = `Bearer ${token}`; + return await axios.request(response.config); + } + } + return response; + }; + this.useResponseInterceptors(authInterceptor, authInterceptor); + } + getTokenFromHeaderAuth(authorization: string) { + return authorization.replace(/^\s*Bearer\s*/, '').replace(/\s+$/, ''); + } + async refreshToken(oldToken: string): Promise { + if (this.refreshTokenPromise) { + return this.refreshTokenPromise; + } + if (oldToken !== this.token) { + // 同时并发的接口已经获取过token,直接返回 + return this.token as string; + } + this.refreshTokenPromise = this.onRefreshToken?.(this.token); + const token = await this.refreshTokenPromise; + this.refreshTokenPromise = undefined; + this.token = token || ''; + return this.token; + } + useResponseInterceptors( + responseInterceptor: + | ((response: AxiosResponse) => AxiosResponse | Promise) + | undefined, + rejectResponseInterceptor: + | ((response: AxiosResponse) => AxiosResponse | Promise) + | undefined, + ) { + this.getAxiosInstance().interceptors.response.use( + responseInterceptor, + rejectResponseInterceptor, + ); + } + getAxiosInstance() { + return this.axiosInstance as AxiosInstance; + } +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/hooks/use-bg-background-plugin.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/hooks/use-bg-background-plugin.ts new file mode 100644 index 00000000..0b8fa9c1 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/hooks/use-bg-background-plugin.ts @@ -0,0 +1,51 @@ +/* + * 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. + */ + +import { useEffect, useMemo } from 'react'; + +import { + createChatBackgroundPlugin, + ChatBackgroundEventName, +} from '@coze-common/chat-area-plugin-chat-background'; + +import { + useChatAppProps, + useChatAppStore, +} from '@/components/studio-open-chat/store'; +export const useBgBackgroundPlugin = () => { + const { ChatBackgroundPlugin, chatBackgroundEvent } = useMemo( + () => createChatBackgroundPlugin(), + [], + ); + const { isCustomBackground } = useChatAppProps(); + + const backgroundInfo = useChatAppStore(s => s.backgroundInfo); + const backgroundInfoToShow = isCustomBackground ? undefined : backgroundInfo; + useEffect(() => { + // 监听用户设置背景图,将更新的背景图信息传入插件 + chatBackgroundEvent.emit( + ChatBackgroundEventName.OnBackgroundChange, + backgroundInfoToShow || { + mobile_background_image: {}, + web_background_image: {}, + }, + ); + }, [backgroundInfoToShow]); + + return { + ChatBackgroundPlugin, + }; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/components/delete-message/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/components/delete-message/index.tsx new file mode 100644 index 00000000..06f6f188 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/components/delete-message/index.tsx @@ -0,0 +1,85 @@ +/* + * 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. + */ + +import { type ComponentProps, type PropsWithChildren } from 'react'; + +import classNames from 'classnames'; +import { + useChatAreaStoreSet, + useIsDeleteMessageLock, + useMessageBoxContext, +} from '@coze-common/chat-area'; +import { I18n } from '@coze-arch/i18n'; +import { IconCozTrashCan } from '@coze-arch/coze-design/icons'; +import { IconButton, Tooltip } from '@coze-arch/coze-design'; + +import { Layout } from '@/types/client'; +import { useChatAppProps } from '@/components/studio-open-chat/store'; + +import { useDeleteMessage } from '../../hooks/use-delete-message'; +import { useCurMessageInfo } from '../../hooks/use-cur-message-info'; + +type DeleteMessageProps = Omit< + ComponentProps, + 'icon' | 'iconSize' | 'onClick' +>; + +export const DeleteMessage: React.FC> = ({ + className, + ...props +}) => { + const storeSet = useChatAreaStoreSet(); + const { useGlobalInitStore } = storeSet; + const { groupId } = useMessageBoxContext(); + const isDeleteMessageLock = useIsDeleteMessageLock(groupId); + const { chatConfig } = useChatAppProps(); + const deleteMessage = useDeleteMessage(); + const conversationId = useGlobalInitStore(state => state.conversationId); + const { messageId, cozeApiMessageId, isShowDelete } = useCurMessageInfo(); + const trigger = + chatConfig.ui?.base?.layout === Layout.MOBILE ? 'custom' : 'hover'; + + if (!cozeApiMessageId || !isShowDelete || !messageId) { + // cozeApiMessageId 不存在的话,说明该条数据有问题,在openApi数据中不存在该条数据,是手工创造的 + return null; + } + + return ( + + + } + onClick={() => { + // 通过 groupId 索引即可 + + deleteMessage(conversationId || '', messageId); + }} + color="secondary" + {...props} + /> + + ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-footer/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-footer/index.tsx new file mode 100644 index 00000000..b41a0630 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-footer/index.tsx @@ -0,0 +1,96 @@ +/* + * 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. + */ + +import { useEffect } from 'react'; + +import classNames from 'classnames'; +import { + type ComponentTypesMap, + useMessageBoxContext, + useChatAreaStoreSet, +} from '@coze-common/chat-area'; +import { + ActionBarContainer, + CopyTextMessage, + QuoteMessage, +} from '@coze-common/chat-answer-action'; + +import { useChatAppProps } from '@/components/studio-open-chat/store'; +import { useIsShowBackground } from '@/components/studio-open-chat/hooks/use-is-show-background'; + +import { useCurMessageInfo } from '../../hooks/use-cur-message-info'; +import { DeleteMessage } from '../../components/delete-message'; +import { useMessageFooterInfo } from './use-message-footer-info'; + +const ChatMessageFooterContent = () => { + const { lastMessageText } = useMessageFooterInfo(); + const showBackground = useIsShowBackground(); + const { isShowDelete, isNeedQuote } = useCurMessageInfo(); + const buttonClass = showBackground ? '!coz-fg-images-white' : ''; + const isShowMessageFooter = !!lastMessageText || isShowDelete; + if (!isShowMessageFooter) { + return null; + } + return ( + + ), + isShowDelete && ( + + ), + !!lastMessageText && isNeedQuote && ( + + ), + ]} + rightContent={[]} + /> + ); +}; + +export const UIKitMessageBoxFooter: ComponentTypesMap['messageActionBarFooter'] = + ({ refreshContainerWidth }) => { + const { meta } = useMessageBoxContext(); + const { message } = useMessageBoxContext(); + const { useWaitingStore } = useChatAreaStoreSet(); + const { readonly } = useChatAppProps(); + + const waiting = useWaitingStore(state => !!state.waiting); + const isAnswerMessage = message.type === 'answer'; + const isLastGroupMessage = + meta.isGroupLastMessage && meta.isFromLatestGroup; + + useEffect(() => { + refreshContainerWidth(); + }, []); + + if (!isLastGroupMessage || !isAnswerMessage || waiting || readonly) { + /* + * 以下情况不展示footer: + * 非最后一个message + * 非回答message + * 进行中的message + */ + return null; + } + return ; + }; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-footer/use-message-footer-info.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-footer/use-message-footer-info.ts new file mode 100644 index 00000000..0dfaf211 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-footer/use-message-footer-info.ts @@ -0,0 +1,53 @@ +/* + * 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. + */ + +import { useMemo } from 'react'; + +import { isEqual } from 'lodash-es'; +import { + useMessageBoxContext, + useChatAreaStoreSet, + getIsTextMessage, +} from '@coze-common/chat-area'; + +export const useMessageFooterInfo = () => { + const { groupId } = useMessageBoxContext(); + const { useMessagesStore } = useChatAreaStoreSet(); + + const messageGroupList = useMessagesStore(s => s.messageGroupList, isEqual); + const messages = useMessagesStore(s => s.messages, isEqual); + const findMessage = useMessagesStore(s => s.findMessage, isEqual); + + const lastMessageText = useMemo(() => { + const messageGroup = messageGroupList.find( + group => group.groupId === groupId, + ); + return messageGroup?.memberSet.llmAnswerMessageIdList + .map(item => { + const messageItem = findMessage(item); + if (getIsTextMessage(messageItem)) { + return messageItem.content; + } + return ''; + }) + .filter(item => !!item) + .reverse() + .join('\n'); + }, [messageGroupList, messages, findMessage, groupId]); + return { + lastMessageText, + }; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-hover-slot/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-hover-slot/index.tsx new file mode 100644 index 00000000..22e2e978 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-hover-slot/index.tsx @@ -0,0 +1,129 @@ +/* + * 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. + */ + +import { useMemo, useRef } from 'react'; + +import cls from 'classnames'; +import { useSize } from 'ahooks'; +import { useMessageBoxContext } from '@coze-common/chat-area'; +import { + ActionBarHoverContainer, + CopyTextMessage, + QuoteMessage, +} from '@coze-common/chat-answer-action'; + +import { useChatAppProps } from '@/components/studio-open-chat/store'; + +import { useCurMessageInfo } from '../../hooks/use-cur-message-info'; +import { DeleteMessage } from '../../components/delete-message'; +import { useMessageHoverInfo } from './use-message-hover-info'; + +// 如果message不是最后一个message group的最后一条answer消息,那么在hover时展示answer actions +export const UIKitMessageBoxHoverSlot: React.FC = () => { + const { meta } = useMessageBoxContext(); + const { message } = useMessageBoxContext(); + const { readonly } = useChatAppProps(); + + const isLastGroupMessage = + meta.isGroupLastMessage && + meta.isFromLatestGroup && + message?.type === 'answer'; + + if (isLastGroupMessage || readonly) { + /* + * 以下情况不展示footer: + * 非最后一个message + * 非回答message + * 进行中的message + */ + return null; + } + return ; +}; + +const UIKitMessageBoxHoverSlotContent = () => { + const popoverContainerRef = useRef(null); + const actionBarRef = useRef(null); + const { message } = useMessageBoxContext(); + const { showHoverText, isMultiMessage } = useMessageHoverInfo(); + const { isShowDelete, isNeedQuote } = useCurMessageInfo(); + const isShowHoverContainer = isShowDelete || !!showHoverText; + + const isShowQuote = + message.type === 'answer' && !!showHoverText && isNeedQuote; + const { width: actionBarSize } = useSize(actionBarRef) || {}; + const actionBarLeft = useMemo(() => { + let containerSize = actionBarRef.current?.closest( + '.coze-chat-hover-message-wrapper', + )?.clientWidth; + + if (containerSize && isMultiMessage) { + containerSize += 44; + return actionBarSize && containerSize && actionBarSize > containerSize + ? actionBarSize - containerSize + : 0; + } + return 0; + }, [actionBarSize]); + if (!isShowHoverContainer) { + return null; + } + + console.log('actionBarSize:', { + actionBarSize, + actionBarLeft, + }); + const wrapperClass = 'flex justify-center items-center'; + // 如果message不是最后一个message group 的消息,那么在hover时展示answer actions + return ( + <> +
+ + {showHoverText ? ( + + ) : null} + {isShowDelete ? : null} + {isShowQuote ? : null} + +
+ {/** 多popover嵌套,会导致定位不准确,因此用一个空白的站位来定位popover */} + +
+ + ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-hover-slot/use-message-hover-info.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-hover-slot/use-message-hover-info.ts new file mode 100644 index 00000000..325fe31c --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box-hover-slot/use-message-hover-info.ts @@ -0,0 +1,56 @@ +/* + * 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. + */ + +import { type MixMessageContent } from '@coze-common/chat-core/message/types'; +import { + useMessageBoxContext, + getIsTextMessage, + ContentType, +} from '@coze-common/chat-area'; + +import { catchParse } from '@/util'; + +export const useMessageHoverInfo = () => { + const { meta, message } = useMessageBoxContext(); + const isNeedHoverAnswer = + message.type === 'answer' && + (!meta.isFromLatestGroup || !meta.isGroupLastAnswerMessage); + + let showHoverText: string | undefined; + let isMultiMessage = false; + if (message.type === 'question' || message.type === 'ack') { + // question 会存在mix的数据结构,需获取text的值 + if (getIsTextMessage(message)) { + showHoverText = message.content; + } else if (message?.content_type === ContentType.Mix) { + const contentObj = catchParse(message.content); + showHoverText = contentObj?.item_list + .map(item => (item.type === 'text' && item.text) || '') + .filter(item => !!item) + .join('\n'); + + isMultiMessage = (contentObj?.item_list?.length || 0) > 1; + } + } else if (isNeedHoverAnswer) { + if (getIsTextMessage(message)) { + showHoverText = message.content; + } + } + return { + showHoverText, + isMultiMessage, + }; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box/index.module.less b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box/index.module.less new file mode 100644 index 00000000..f4fd99ab --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box/index.module.less @@ -0,0 +1,5 @@ +.message-box-wrapper { + :global(.coz-fg-images-user-name) { + color: var(--coze-fg-image-user-name); + } +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box/index.tsx new file mode 100644 index 00000000..a986e33e --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-box/index.tsx @@ -0,0 +1,37 @@ +/* + * 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. + */ + +import cls from 'classnames'; +import { MessageBox as UIKitMessageBox } from '@coze-common/chat-uikit'; +import { type CustomComponent } from '@coze-common/chat-area'; + +import styles from './index.module.less'; +// coze-chat-message-wrapper coze-chat-hover-message-wrapper 用于element获取,不可删除 +export const UIKitMessageBoxPlugin: CustomComponent['UIKitMessageBoxPlugin'] = + ({ messageType, classname, ...props }) => ( + + ); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-group-footer/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-group-footer/index.tsx new file mode 100644 index 00000000..221888b8 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-group-footer/index.tsx @@ -0,0 +1,30 @@ +/* + * 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. + */ + +import { type CustomComponent } from '@coze-common/chat-area'; + +import { useMessageGroupFooterInfo } from './use-message-group-footer-info'; +export const UIKitMessageGroupFooterPlugin: CustomComponent['MessageGroupFooter'] = + ({ messageGroup }) => { + const { isLatest } = messageGroup; + const { isShowFeedbackInLastGroup, cozeApiMessageId } = + useMessageGroupFooterInfo(messageGroup); + + if (!isLatest || !isShowFeedbackInLastGroup || !cozeApiMessageId) { + return null; + } + return
; + }; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-group-footer/use-message-group-footer-info.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-group-footer/use-message-group-footer-info.ts new file mode 100644 index 00000000..af771872 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/custom-components/message-group-footer/use-message-group-footer-info.ts @@ -0,0 +1,49 @@ +/* + * 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. + */ + +import { useMemo } from 'react'; + +import { + type MessageGroup, + useChatAreaStoreSet, +} from '@coze-common/chat-area'; + +import { useChatAppStore } from '@/components/studio-open-chat/store'; +export const useMessageGroupFooterInfo = (messageGroup: MessageGroup) => { + const feedbackInfo = useChatAppStore(s => s.feedbackInfo); + const { useMessagesStore } = useChatAreaStoreSet(); + const { findMessage } = useMessagesStore.getState(); + + const lastGroupFeedbackInfo = useChatAppStore(s => s.lastGroupFeedbackInfo); + const { messageId } = lastGroupFeedbackInfo; + const messageInfo = findMessage(messageId || ''); + // @ts-expect-error -- linter-disable-autofix, 新添加参数,接口未支持 + const cozeApiMessageId = messageInfo?.extra_info?.coze_api_message_id; + + const isShowFeedbackInLastGroup = useMemo(() => { + if ( + lastGroupFeedbackInfo.isShowCustomPanel && + messageId && + feedbackInfo[cozeApiMessageId] === 'thumbDown' + ) { + // 当前message已经是点踩了,同时需要展示自定义面板 + + return messageGroup.memberSet.llmAnswerMessageIdList.includes(messageId); + } + return false; + }, [feedbackInfo, lastGroupFeedbackInfo, messageGroup]); + return { isShowFeedbackInLastGroup, cozeApiMessageId }; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/hooks/use-cur-message-info.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/hooks/use-cur-message-info.ts new file mode 100644 index 00000000..450f618a --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/hooks/use-cur-message-info.ts @@ -0,0 +1,36 @@ +/* + * 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. + */ + +import { useMessageBoxContext } from '@coze-common/chat-area'; + +import { useChatAppProps } from '@/components/studio-open-chat/store'; + +export const useCurMessageInfo = () => { + const { message } = useMessageBoxContext(); + const { chatConfig } = useChatAppProps(); + + // @ts-expect-error -- linter-disable-autofix, 新添加参数,接口未支持 + const cozeApiMessageId = message.extra_info.coze_api_message_id; + // @ts-expect-error -- linter-disable-autofix, 新添加参数,接口未支持 + const cozeApiChatId = message.extra_info.coze_api_chatId_id; + return { + messageId: message.message_id, + cozeApiMessageId, + cozeApiChatId, + isShowDelete: false, // 暂时下掉删除按钮 + isNeedQuote: chatConfig.ui?.chatBot?.isNeedQuote ?? false, + }; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/hooks/use-delete-message.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/hooks/use-delete-message.ts new file mode 100644 index 00000000..4765da99 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/hooks/use-delete-message.ts @@ -0,0 +1,77 @@ +/* + * 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. + */ + +import { useChatActionLockService } from '@coze-common/chat-area/context/chat-action-lock'; +import { useChatAreaStoreSet } from '@coze-common/chat-area'; +import { I18n } from '@coze-arch/i18n'; +import { Toast } from '@coze-arch/coze-design'; + +import { useChatCozeSdk } from '../../context'; + +export const useDeleteMessage = () => { + const storeSet = useChatAreaStoreSet(); + const chatActionLockService = useChatActionLockService(); + const { cozeApiSdk } = useChatCozeSdk(); + + return async (conversationId: string, messageId: string) => { + if (!messageId || !conversationId) { + return; + } + + const { useMessagesStore, useSuggestionsStore } = storeSet; + const { findMessage, isLastMessageGroup } = useMessagesStore.getState(); + const { clearSuggestions } = useSuggestionsStore.getState(); + + const messageInfo = findMessage(messageId); + const groupId = messageInfo?.reply_id; + // @ts-expect-error -- linter-disable-autofix, 新添加参数,接口未支持 + const cozeApiMessageId = messageInfo?.extra_info?.coze_api_message_id; + if (!messageInfo || !groupId) { + throw new Error(`message not found, id: ${messageId}`); + } + + if ( + chatActionLockService.answerAction.getIsLock( + groupId, + 'deleteMessageGroup', + ) + ) { + return; + } + + chatActionLockService.answerAction.lock(groupId, 'deleteMessageGroup'); + + const isLast = isLastMessageGroup(groupId); + + const { deleteMessageById } = useMessagesStore.getState(); + + try { + await cozeApiSdk?.conversations.messages.delete( + conversationId, + cozeApiMessageId, + ); + deleteMessageById(messageId); + if (isLast) { + clearSuggestions(); + } + } catch (e) { + console.error(e); + Toast.error(I18n.t('Delete_failed')); + } finally { + chatActionLockService.answerAction.unlock(groupId, 'deleteMessageGroup'); + } + }; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/hooks/use-submit-feedback-api.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/hooks/use-submit-feedback-api.ts new file mode 100644 index 00000000..b81d2f9f --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/hooks/use-submit-feedback-api.ts @@ -0,0 +1,60 @@ +/* + * 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. + */ + +import { useChatAreaStoreSet } from '@coze-common/chat-area'; + +import { type FeedbackTag } from '@/types/client'; +import { useChatAppStore } from '@/components/studio-open-chat/store'; + +import { useChatCozeSdk } from '../../context'; +export interface FeedbackSubmitBody { + feedbackType: 'thumbDown' | 'thumbUp' | 'default'; + feedbackText?: string; + feedbackTagList?: FeedbackTag[]; +} +const paramMap = { + thumbDown: 'unlike', + thumbUp: 'like', + default: 'default', +}; +export const useSubmitFeedbackApi = () => { + const { useGlobalInitStore } = useChatAreaStoreSet(); + + const { cozeApiSdk } = useChatCozeSdk(); + const conversationId = useGlobalInitStore(state => state.conversationId); + const updateFeedbackInfo = useChatAppStore(s => s.updateFeedbackInfo); + + return (cozeApiMessageId: string, body: FeedbackSubmitBody) => { + updateFeedbackInfo(cozeApiMessageId, body.feedbackType); + const feedBackType = paramMap[body.feedbackType] || 'default'; + if (feedBackType === 'default') { + return cozeApiSdk?.makeRequest( + `/v1/conversations/${conversationId}/messages/${cozeApiMessageId}/feedback`, + 'DELETE', + ); + } else { + return cozeApiSdk?.makeRequest( + `/v1/conversations/${conversationId}/messages/${cozeApiMessageId}/feedback`, + 'POST', + { + feedback_type: feedBackType, + comment: body.feedbackText, + reason_types: body.feedbackTagList?.map(item => item.label), + }, + ); + } + }; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/index.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/index.ts new file mode 100644 index 00000000..c2c1fe22 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/index.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ + +import { type PluginRegistryEntry } from '@coze-common/chat-area'; + +import { type PluginBizContext } from './types/biz-context'; +import { BizPlugin } from './plugin'; + +export const getCozeSdkPlugin = (props: PluginBizContext) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const WebsdkChatExtraBodyPlugin: PluginRegistryEntry = { + /** + * 贯穿插件生命周期、组件的上下文 + */ + createPluginBizContext() { + return { ...props }; + }, + /** + * 插件本体 + */ + Plugin: BizPlugin, + }; + return WebsdkChatExtraBodyPlugin; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/plugin.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/plugin.ts new file mode 100644 index 00000000..9ad26a7f --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/plugin.ts @@ -0,0 +1,57 @@ +/* + * 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. + */ + +import { + PluginMode, + PluginName, + WriteableChatAreaPlugin, + createWriteableLifeCycleServices, + createCustomComponents, +} from '@coze-common/chat-area'; + +import { type PluginBizContext } from './types/biz-context'; +import { bizLifeCycleServiceGenerator } from './services/life-cycle'; +import { UIKitMessageGroupFooterPlugin } from './custom-components/message-group-footer'; +import { UIKitMessageBoxHoverSlot } from './custom-components/message-box-hover-slot'; +import { UIKitMessageBoxFooter } from './custom-components/message-box-footer'; +import { UIKitMessageBoxPlugin } from './custom-components/message-box'; +export class BizPlugin extends WriteableChatAreaPlugin { + /** + * 插件类型 + * PluginMode.Readonly = 只读模式 + * PluginMode.Writeable = 可写模式 + */ + public pluginMode = PluginMode.Writeable; + /** + * 插件名称 + * 请点 PluginName 里面去定义 + */ + public pluginName = PluginName.WebsdkChatCozeSdkPlugin; + + /** + * 生命周期服务 + */ + public lifeCycleServices = createWriteableLifeCycleServices( + this, + bizLifeCycleServiceGenerator, + ); + public customComponents = createCustomComponents({ + UIKitMessageBoxPlugin, + MessageGroupFooter: UIKitMessageGroupFooterPlugin, + MessageBoxFooter: UIKitMessageBoxFooter, + MessageBoxHoverSlot: UIKitMessageBoxHoverSlot, + }); +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/app.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/app.ts new file mode 100644 index 00000000..93c230ff --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/app.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +import { type WriteableAppLifeCycleServiceGenerator } from '@coze-common/chat-area'; + +import { type PluginBizContext } from '../../types/biz-context'; + +export const appLifeCycleServiceGenerator: WriteableAppLifeCycleServiceGenerator< + PluginBizContext +> = plugin => ({}); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/command.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/command.ts new file mode 100644 index 00000000..46f84fb6 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/command.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +import { type WriteableCommandLifeCycleServiceGenerator } from '@coze-common/chat-area'; + +import { type PluginBizContext } from '../../types/biz-context'; + +export const commandLifeCycleServiceGenerator: WriteableCommandLifeCycleServiceGenerator< + PluginBizContext +> = plugin => ({}); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/index.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/index.ts new file mode 100644 index 00000000..634fe9f4 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/index.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +import { type WriteableLifeCycleServiceGenerator } from '@coze-common/chat-area'; + +import { type PluginBizContext } from '../../types/biz-context'; +import { renderLifeCycleServiceGenerator } from './render'; +import { messageLifeCycleServiceGenerator } from './message'; +import { commandLifeCycleServiceGenerator } from './command'; +import { appLifeCycleServiceGenerator } from './app'; + +export const bizLifeCycleServiceGenerator: WriteableLifeCycleServiceGenerator< + PluginBizContext +> = plugin => ({ + appLifeCycleService: appLifeCycleServiceGenerator(plugin), + messageLifeCycleService: messageLifeCycleServiceGenerator(plugin), + commandLifeCycleService: commandLifeCycleServiceGenerator(plugin), + renderLifeCycleService: renderLifeCycleServiceGenerator(plugin), +}); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/message.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/message.ts new file mode 100644 index 00000000..8ccb7dce --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/message.ts @@ -0,0 +1,42 @@ +/* + * 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. + */ + +import { type ChatCoreError } from '@coze-common/chat-core'; +import { type WriteableMessageLifeCycleServiceGenerator } from '@coze-common/chat-area'; + +import { isAuthError } from '@/util/error'; + +import { type PluginBizContext } from '../../types/biz-context'; +export const messageLifeCycleServiceGenerator: WriteableMessageLifeCycleServiceGenerator< + PluginBizContext +> = plugin => { + let lastRetryId = ''; + return { + onSendMessageError: async ctx => { + const error = ctx.error as ChatCoreError; + + if (isAuthError(error?.ext?.code || 0) || error?.stack?.includes('401')) { + await plugin.pluginBizContext?.refreshToken?.(); + const newRetryId = + ctx.message.message_id || ctx.message?.extra_info?.local_message_id; + if (newRetryId !== lastRetryId) { + lastRetryId = newRetryId; + plugin.pluginBizContext?.regenerateMessageByUserMessageId(newRetryId); + } + } + }, + }; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/render.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/render.ts new file mode 100644 index 00000000..63a9370d --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/services/life-cycle/render.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +import { type WriteableRenderLifeCycleServiceGenerator } from '@coze-common/chat-area'; + +import { type PluginBizContext } from '../../types/biz-context'; + +export const renderLifeCycleServiceGenerator: WriteableRenderLifeCycleServiceGenerator< + PluginBizContext +> = plugin => ({}); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/types/biz-context.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/types/biz-context.ts new file mode 100644 index 00000000..09dca14b --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/types/biz-context.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export interface PluginBizContext { + refreshToken?: () => Promise; + regenerateMessageByUserMessageId: (messageId: string) => void; +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/typings.d.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/typings.d.ts new file mode 100644 index 00000000..4ae6594a --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/plugin/typings.d.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +/// diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/type.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/type.ts new file mode 100644 index 00000000..3ca81a7f --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/type.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +export interface ChatProviderFunc { + regenerateMessageByUserMessageId: (id: string) => void; + setConversationId: (id: string, sectionId: string) => void; + getConversationId: () => string; + getSectionId: () => string; +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-api-client.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-api-client.ts new file mode 100644 index 00000000..8a69b37b --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-api-client.ts @@ -0,0 +1,98 @@ +/* + * 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. + */ + +import { useEffect, useMemo } from 'react'; + +import { useShallow } from 'zustand/react/shallow'; +import { useMemoizedFn } from 'ahooks'; +import { TokenManager } from '@coze-common/chat-core'; + +import { openApiHostByRegionWithToken } from '@/util/env'; + +import { useChatAppStore, useChatAppProps } from '../../store'; +import { CozeApiCustom } from './helper/coze-api-custom'; +export const useApiClient = () => { + const { debug } = useChatAppProps(); + + const { + token = '', + refreshToken: refreshTokenRaw, + setToken, + setCozeApi, + } = useChatAppStore( + useShallow(s => ({ + token: s.token, + setToken: s.setToken, + refreshToken: s.refreshToken, + setCozeApi: s.setCozeApi, + })), + ); + + const tokenManagerClient = useMemo( + () => + new TokenManager({ + apiKey: token, + }), + [], + ); + + // coze Api相关的 实例 + const cozeApiClient = useMemo( + () => + new CozeApiCustom({ + token, + allowPersonalAccessTokenInBrowser: true, + baseURL: openApiHostByRegionWithToken, + axiosOptions: { + headers: { + ...(debug?.cozeApiRequestHeader || {}), + }, + }, + }), + [], + ); + // 语音播放相关的token实例 + // 更新各client实例的token, + const updateToken = useMemoizedFn(newToken => { + tokenManagerClient?.updateApiKey(newToken); + cozeApiClient.token = newToken; + }); + // 刷新token的逻辑 + const refreshToken = useMemoizedFn(async () => { + const newToken = await refreshTokenRaw?.(); + if (newToken) { + updateToken(newToken); + setToken(newToken); + } + return newToken; + }); + + cozeApiClient.setRefreshToken(refreshToken); + + useEffect(() => { + updateToken(token); + }, [token]); + + useEffect(() => { + setCozeApi(cozeApiClient); + }, [cozeApiClient]); + + return { + tokenManagerClient, + cozeApiClient, + refreshToken, + }; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-core-manager.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-core-manager.ts new file mode 100644 index 00000000..cbe89597 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-core-manager.ts @@ -0,0 +1,95 @@ +/* + * 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. + */ + +import { useMemo } from 'react'; + +import { mergeWith, isArray } from 'lodash-es'; +import { + RequestScene, + type RequestManagerOptions, +} from '@coze-common/chat-core'; + +import { openApiHostByRegionWithToken } from '@/util/env'; + +import { useChatAppProps } from '../../store'; +import { useUserInfo } from '../../hooks'; +import { type ChatProviderFunc } from './type'; +import { useChatCozeSdk } from './context'; +import { + useClearMessageContextAdapter, + useSendMessageAdapter, + useClearHistoryAdapter, + useCommonOnAfterResponseHooks, + useCommonOnBeforeRequestHooks, + useCommonErrorResponseHooks, + useMessageList, + useBreakMessage, +} from './api-adapter'; + +export const useCoreManager = ({ + refChatFunc, +}: { + refChatFunc?: React.MutableRefObject; +}): RequestManagerOptions => { + const userInfo = useUserInfo(); + const { refreshToken } = useChatCozeSdk(); + const clearMessageContextAdapter = useClearMessageContextAdapter(); + const sendMessageAdapter = useSendMessageAdapter( + userInfo || undefined, + refChatFunc, + ); + const clearHistoryAdapter = useClearHistoryAdapter({ refChatFunc }); + const commonOnBeforeRequestHooks = useCommonOnBeforeRequestHooks(); + const commonOnAfterResponseHooks = + useCommonOnAfterResponseHooks(refreshToken); + const commonErrorResponseHooks = useCommonErrorResponseHooks(refreshToken); + const messageListAdapter = useMessageList(); + const { requestManagerOptions } = useChatAppProps(); + const breakMessageAdapter = useBreakMessage(); + return useMemo( + () => + mergeWith( + { + baseURL: openApiHostByRegionWithToken, + scenes: { + [RequestScene.SendMessage]: sendMessageAdapter, + [RequestScene.ClearMessageContext]: clearMessageContextAdapter, + [RequestScene.ClearHistory]: clearHistoryAdapter, + [RequestScene.GetMessage]: messageListAdapter, + [RequestScene.BreakMessage]: breakMessageAdapter, + }, + hooks: { + onBeforeRequest: commonOnBeforeRequestHooks, + onAfterResponse: commonOnAfterResponseHooks, + onErrorResponse: commonErrorResponseHooks, + }, + }, + requestManagerOptions, + (objValue, srcValue) => { + if (isArray(objValue)) { + return objValue.concat(srcValue); + } + }, + ), + [ + sendMessageAdapter, + clearMessageContextAdapter, + clearHistoryAdapter, + commonOnBeforeRequestHooks, + commonOnAfterResponseHooks, + ], + ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-core-override-config.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-core-override-config.ts new file mode 100644 index 00000000..0ee23e6b --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-core-override-config.ts @@ -0,0 +1,45 @@ +/* + * 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. + */ + +import { useMemo } from 'react'; + +import { type CreateChatCoreOverrideConfig } from '@coze-common/chat-area'; + +import { useChatAppProps } from '../../store'; +import { useCoreManager } from './use-core-manager'; +import { type ChatProviderFunc } from './type'; +import { useChatCozeSdk } from './context'; + +export const useCoreOverrideConfig = ({ + refChatFunc, +}: { + refChatFunc?: React.MutableRefObject; +}) => { + const { chatConfig } = useChatAppProps(); + const { tokenManager } = useChatCozeSdk(); + const requestManagerOptions = useCoreManager({ refChatFunc }); + return useMemo(() => { + const config: CreateChatCoreOverrideConfig = { + env: 'thirdPart', + biz: 'third_part', + deployVersion: 'inhouse', + requestManagerOptions, + tokenManager, + }; + + return config; + }, [chatConfig]); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-request-init.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-request-init.ts new file mode 100644 index 00000000..6de9ff0a --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-request-init.ts @@ -0,0 +1,380 @@ +/* + * 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. + */ + +import { useCallback } from 'react'; + +import { type ShortCutCommand } from '@coze-common/chat-area-plugins-chat-shortcuts'; +import { type MixInitResponse } from '@coze-common/chat-area'; +import i18n from '@coze-arch/i18n/intl'; +import { type BotInfo, type CozeAPI } from '@coze/api'; + +import { type OpenRequestInit, EInputMode } from '@/types/props'; + +import { useChatAppProps, useChatAppStore } from '../../store'; +import { useUserInfo } from '../../hooks'; +import { + convertShortcutData, + type ShortcutCommandInOpenApi, +} from './helper/convert-shortcut-data'; +import { useChatCozeSdk } from './context'; +import { + messageConverterToCoze, + messageConverterToSdk, + useGetMessageListByPairs, +} from './api-adapter'; +const messageGetLimit = 10; +interface GetRequestInfoProps { + botId: string; + cozeApiSdk: CozeAPI; +} + +type BotInfoResp = BotInfo & { + default_user_input_type?: string; + media_config?: { + is_voice_call_closed?: boolean; + }; +}; +const getBotOnlineInfo = async ({ + botId, + cozeApiSdk, +}: GetRequestInfoProps & { connectorId: string }): Promise< + Pick< + MixInitResponse, + | 'prologue' + | 'onboardingSuggestions' + | 'botVersion' + | 'botInfoMap' + | 'backgroundInfo' + > & { + defaultInputMode?: EInputMode; + shortcuts?: ShortCutCommand[]; + isCustomBackground?: boolean; + voiceCallClose?: boolean; + } +> => { + const botRes = await cozeApiSdk.get< + undefined, + { + code: number; + data: BotInfoResp; + } + >(`/v1/bots/${botId}`, undefined, false, { + headers: { + 'Accept-Language': i18n.language === 'zh-CN' ? 'zh' : 'en', + }, + }); + const botInfo = botRes?.data; + const backgroundInfo: MixInitResponse['backgroundInfo'] = + botInfo.background_image_info || {}; + + // 做兜底处理 + if (backgroundInfo?.web_background_image?.image_url) { + backgroundInfo.web_background_image.origin_image_url = + backgroundInfo.web_background_image.origin_image_url || + backgroundInfo.web_background_image.image_url; + } + if (backgroundInfo?.mobile_background_image?.image_url) { + backgroundInfo.mobile_background_image.origin_image_url = + backgroundInfo.mobile_background_image.origin_image_url || + backgroundInfo.mobile_background_image.image_url; + } + return { + prologue: botInfo.onboarding_info.prologue, + onboardingSuggestions: + botInfo.onboarding_info?.suggested_questions?.map((question, index) => ({ + id: `${index}`, + content: question, + })) || [], + botVersion: botInfo.version, + botInfoMap: { + [botInfo.bot_id]: { + url: botInfo.icon_url, + nickname: botInfo.name, + id: botInfo.bot_id, + allowMention: false, + }, + }, + backgroundInfo, + defaultInputMode: + botInfo.default_user_input_type === 'voice' + ? EInputMode.Voice + : botInfo.default_user_input_type === 'call' + ? EInputMode.VoiceCall + : EInputMode.Text, + shortcuts: + convertShortcutData( + botInfo.shortcut_commands as unknown as ShortcutCommandInOpenApi[], + { + id: botId, + name: botInfo.name, + iconUrl: botInfo.icon_url, + }, + ) || [], + voiceCallClose: botInfo.media_config?.is_voice_call_closed ?? true, + }; +}; +const getConversationInfo = async ({ + botId, + cozeApiSdk, + conversationId: conversationIdIn, + sectionId: sectionIdIn, + connectorId, + defaultHistoryMessage, + onDefaultHistoryClear, +}: GetRequestInfoProps & { + conversationId?: string; + sectionId?: string; + connectorId: string; + defaultHistoryMessage?: MixInitResponse['messageList']; + onDefaultHistoryClear?: () => void; +}): Promise< + Pick< + MixInitResponse, + | 'conversationId' + | 'messageList' + | 'hasMore' + | 'cursor' + | 'next_cursor' + | 'lastSectionId' + > +> => { + let conversationId: string = conversationIdIn || ''; + let sectionId: string = sectionIdIn || ''; + if (!conversationId) { + const { data: conversationRes } = (await cozeApiSdk.get( + '/v1/conversations', + { + bot_id: botId, + connector_id: connectorId, + page_num: 1, + page_size: 1, + }, + )) as { + data: { + conversations: { + id: string; + last_section_id: string; + }[]; + }; + }; + let { id: conversationIdNew, last_section_id: sectionIdNew } = + conversationRes?.conversations?.[0] || {}; + if (!conversationIdNew) { + const historyMessage = messageConverterToSdk.convertMessageListResponse( + defaultHistoryMessage, + ); + + try { + const { id: conversationIdCreated, last_section_id: sectionIdCreated } = + await cozeApiSdk.conversations.create( + { + bot_id: botId, + messages: historyMessage, + // @ts-expect-error: connector_id is not in the type + connector_id: connectorId, + }, + { + headers: { + 'Accept-Language': i18n.language === 'zh-CN' ? 'zh' : 'en', + }, + }, + ); + conversationIdNew = conversationIdCreated; + sectionIdNew = sectionIdCreated || ''; + + onDefaultHistoryClear?.(); + } catch (err) { + ///historyMessage 可能导致失败,兜底一下 + const { id: conversationIdCreated, last_section_id: sectionIdCreated } = + await cozeApiSdk.conversations.create( + { + bot_id: botId, + // @ts-expect-error: connector_id is not in the type + connector_id: connectorId, + }, + { + headers: { + 'Accept-Language': i18n.language === 'zh-CN' ? 'zh' : 'en', + }, + }, + ); + conversationIdNew = conversationIdCreated; + sectionIdNew = sectionIdCreated || ''; + } + } + conversationId = conversationIdNew; + sectionId = sectionIdNew; + } + + const resMessage = await cozeApiSdk.conversations.messages.list( + conversationId, + { + limit: messageGetLimit, + }, + ); + const { + message_list: messageList, + hasmore: hasMore, + cursor, + next_cursor: nextCursor, + } = messageConverterToCoze.convertMessageListResponse(resMessage, botId); + + return { + lastSectionId: sectionId, + conversationId, + messageList, + hasMore, + cursor, + next_cursor: nextCursor, + }; +}; +const getCustomInitInfo = async ({ + cozeApiSdk, + openRequestInit, +}: { + cozeApiSdk: CozeAPI; + openRequestInit?: + | OpenRequestInit + | { + (cozeApi?: CozeAPI): Promise | OpenRequestInit; + }; +}) => { + let result: OpenRequestInit; + if (openRequestInit) { + if (typeof openRequestInit === 'function') { + result = await openRequestInit(cozeApiSdk); + } else { + result = openRequestInit; + } + if (result.isBuilderChat) { + return { + botOnlineInfo: { + prologue: result.prologue, + onboardingSuggestions: result.onboardingSuggestions, + botInfoMap: { + [result.botInfo.id]: result.botInfo, + }, + backgroundInfo: result.backgroundInfo, + defaultInputMode: result.defaultInputMode, + shortcuts: [], + isCustomBackground: result.isCustomBackground, + }, + conversationId: result.conversationId, + sectionId: result.sectionId, + }; + } + return { + conversationId: result.conversationId, + sectionId: result.sectionId, + }; + } + return null; +}; + +export const useRequestInit = () => { + const { + chatConfig, + openRequestInit, + defaultHistoryMessage = [], + onDefaultHistoryClear, + } = useChatAppProps(); + + const { cozeApiSdk } = useChatCozeSdk(); + const setInitError = useChatAppStore(s => s.setInitError); + const setDefaultInputMode = useChatAppStore(s => s.setDefaultInputMode); + const updateShortcuts = useChatAppStore(s => s.updateShortcuts); + const setIsStartBotVoiceCall = useChatAppStore(s => s.setIsStartBotVoiceCall); + const updateBackgroundInfo = useChatAppStore(s => s.updateBackgroundInfo); + + const getMessageListByPairs = useGetMessageListByPairs(); + + const connectorId = chatConfig?.auth?.connectorId || ''; + const { bot_id: botId = '' } = chatConfig; + const userInfo = useUserInfo(); + const requestToInit = useCallback< + () => Promise + // @ts-expect-error -- linter-disable-autofix + >(async () => { + if (!cozeApiSdk) { + return {}; + } + try { + const { conversationId, sectionId, botOnlineInfo } = + (await getCustomInitInfo({ + cozeApiSdk, + openRequestInit, + })) || {}; + + const [requestDataBotInfo, requestDataConversationInfo] = + await Promise.all([ + botOnlineInfo || getBotOnlineInfo({ botId, cozeApiSdk, connectorId }), + getConversationInfo({ + botId, + cozeApiSdk, + conversationId, + sectionId, + connectorId, + onDefaultHistoryClear, + defaultHistoryMessage, + }), + ]); + const prologue = (requestDataBotInfo.prologue || '').replaceAll( + '{{user_name}}', + userInfo?.nickname || '', + ); + setIsStartBotVoiceCall( + requestDataBotInfo.defaultInputMode === EInputMode.VoiceCall, + ); + setDefaultInputMode( + requestDataBotInfo.defaultInputMode || EInputMode.Text, + ); + updateShortcuts(requestDataBotInfo.shortcuts || []); + console.log( + 'requestDataBotInfo.shortcuts:', + requestDataBotInfo.shortcuts, + ); + + updateBackgroundInfo(requestDataBotInfo.backgroundInfo); + if (requestDataBotInfo.isCustomBackground) { + requestDataBotInfo.backgroundInfo = undefined; + } + console.log('[result]2:', requestDataBotInfo); + return { + ...requestDataBotInfo, + ...requestDataConversationInfo, + prologue, + messageList: getMessageListByPairs( + requestDataConversationInfo.conversationId || '', + requestDataConversationInfo.messageList, + ), + userInfoMap: userInfo?.id + ? { + [userInfo?.id]: userInfo, + } + : undefined, + }; + } catch (e) { + console.error('useRequestInit error', e); + const { code } = (e as { code: number; message: string }) || {}; + if (code) { + setInitError({ code, msg: '' }); + } else { + setInitError({ code: -1, msg: '' }); + } + } + }, [botId, cozeApiSdk, defaultHistoryMessage, onDefaultHistoryClear]); + return requestToInit; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-upload-file-api.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-upload-file-api.ts new file mode 100644 index 00000000..54578e92 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/coz-sdk/use-upload-file-api.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ + +import { useCallback } from 'react'; + +import { type UploadFileApi } from '@/helper'; + +import { useChatCozeSdk } from './context'; +export const useUploadFileApi = (): UploadFileApi => { + const { cozeApiSdk } = useChatCozeSdk(); + return useCallback( + async ({ file }) => { + const result = await cozeApiSdk?.files.upload({ + file, + }); + + return { + uri: result?.id || '', + url: '', + }; + }, + [cozeApiSdk], + ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/index.tsx new file mode 100644 index 00000000..b46e747f --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/provider/index.tsx @@ -0,0 +1,68 @@ +/* + * 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. + */ + +import React, { useEffect, type FC } from 'react'; + +import { type PluginRegistryEntry } from '@coze-common/chat-area'; + +import { type StudioChatProviderProps } from '@/types/props'; + +import { ChatPropsProvider } from '../store/context'; +import { useChatAppProps, useChatAppStore } from '../store'; +import { getChatCommonPlugin } from '../plugin'; +import { useGetTheme } from '../hooks/use-get-theme'; +import { ChatProvider as ChatProviderCozeSdk } from './coz-sdk/chat-provider'; + +const ChatProvider: FC<{ + children: React.ReactNode; + plugins?: PluginRegistryEntry[]; +}> = ({ children, plugins }) => { + const { chatConfig, onImageClick, onThemeChange } = useChatAppProps(); + const setInitError = useChatAppStore(s => s.setInitError); + // plugin初始化 + const commonChatPlugin = getChatCommonPlugin({ + onImageClick, + onInitialError: () => { + setInitError(true); + }, + extraBody: chatConfig.extra?.webChat, + }); + const theme = useGetTheme(); + useEffect(() => { + onThemeChange?.(theme); + }, [theme]); + return ( + , + ...(plugins || []), + ]} + > + {children} + + ); +}; + +export const OpenChatProvider: FC< + StudioChatProviderProps & { + children: React.ReactNode; + plugins?: PluginRegistryEntry[]; + } +> = ({ children, plugins, ...props }) => ( + + {children} + +); diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/store/context.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/store/context.tsx new file mode 100644 index 00000000..f1de8e41 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/store/context.tsx @@ -0,0 +1,157 @@ +/* + * 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. + */ + +import { + createContext, + type FC, + type ReactNode, + useContext, + useState, + useMemo, + useRef, +} from 'react'; + +import { useStoreWithEqualityFn } from 'zustand/traditional'; +import { shallow } from 'zustand/shallow'; +import { nanoid } from 'nanoid'; +import { merge } from 'lodash-es'; +import { useUpdateEffect } from 'ahooks'; + +import { ChatSdkError, SDKErrorCode } from '@/util/error'; +import { + type CozeChatConfig, + type StudioChatProviderProps, +} from '@/types/props'; +import { OpenApiSource } from '@/types/open'; +import { ChatType, Layout } from '@/types/client'; +import { type ConversationListSiderRef } from '@/components/conversation-list-sider/conversation-list'; +import { ConversationListSider } from '@/components/conversation-list-sider'; + +import { createChatStore, type ChatStore, type ChatStateAction } from './store'; + +export const ChatPropsContext = createContext<{ + appProps?: StudioChatProviderProps; + store?: ChatStore; +}>({}); +interface CProps extends StudioChatProviderProps { + chatConfig: CozeChatConfig; +} +export const ChatPropsProvider: FC<{ + children: ReactNode; + appProps: StudioChatProviderProps; +}> = ({ children, appProps }) => { + const defaultChatConfig = useMemo( + () => ({ + conversation_id: nanoid(), + source: OpenApiSource.WebSdk, + }), + [], + ); + + const conversationRef = useRef< + Pick + >({ + getConversationInfo: () => undefined, + }); + + { + /* app 模式暂时不支持,先从 UI 阶段去掉这个适配 */ + } + const isShowConversations = + appProps.chatConfig.ui?.conversations?.isNeed && + appProps.chatConfig.type !== ChatType.APP; + + const appPropsState = useMemo(() => { + // 这里是当有会话列表这个功能时,需要通过注入 openRequestInit 的方式注入会话列表中选择了的会话 id 和 选取 id + const openRequestInit: StudioChatProviderProps['openRequestInit'] = + async cozeApi => { + let res; + if (typeof appProps.openRequestInit === 'function') { + res = await appProps.openRequestInit(cozeApi); + } else if (appProps.openRequestInit) { + res = appProps.openRequestInit; + } + return { + ...res, + ...conversationRef.current?.getConversationInfo(), // 这里是因为 ChatType.APP 模式下的 ChatSDK 会注入一些 botInfo 信息 + }; + }; + const state = { + ...appProps, + openRequestInit, + chatConfig: merge({}, defaultChatConfig, appProps.chatConfig), + }; + state.chatConfig.conversation_id = + state.chatConfig.conversation_id || defaultChatConfig.conversation_id; + return state; + }, [appProps]); + + const [store] = useState(() => + createChatStore(appProps.chatConfig, appProps.userInfo), + ); + useUpdateEffect(() => { + if (appProps.userInfo) { + store.getState().setUserInfo(appProps.userInfo); + } + }, [appProps.userInfo]); + return ( + + {isShowConversations ? ( + + {children} + + ) : ( + children + )} + + ); +}; + +export const useChatAppProps = (): CProps => { + const { appProps } = useContext(ChatPropsContext); + + const { + chatConfig = { + conversation_id: nanoid(), + source: OpenApiSource.WebSdk, + bot_id: '', + }, + layout = Layout.PC, + enableReplacePrologueNicknameWithVar = false, + } = appProps ?? {}; + + return { + ...appProps, + chatConfig, + layout, + enableReplacePrologueNicknameWithVar, + }; +}; + +export const useChatAppStore: ( + selector: (store: ChatStateAction) => T, +) => T = selector => { + const { store } = useContext(ChatPropsContext); + if (!store) { + throw ChatSdkError.create(SDKErrorCode.StoreProvider); + } + return useStoreWithEqualityFn(store, selector, shallow); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/store/index.tsx b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/store/index.tsx new file mode 100644 index 00000000..7446cd92 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/store/index.tsx @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { useChatAppProps, useChatAppStore } from './context'; diff --git a/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/store/store.ts b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/store/store.ts new file mode 100644 index 00000000..b1265aa7 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/components/studio-open-chat/store/store.ts @@ -0,0 +1,354 @@ +/* + * 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. + */ + +/* eslint-disable max-lines-per-function */ +import { devtools, subscribeWithSelector } from 'zustand/middleware'; +import { create } from 'zustand'; +import { nanoid } from 'nanoid'; +import { produce } from 'immer'; +import { type Conversation, type CozeAPI } from '@coze/api'; +import { type ShortCutCommand } from '@coze-common/chat-area-plugins-chat-shortcuts'; +import { type MixInitResponse } from '@coze-common/chat-area'; + +import { type SDKInitError } from '@/util/error'; +import { getStorageKey, LocalStorageKey, storageUtil } from '@/util'; +import { type OpenUserInfo } from '@/types/user'; +import { EInputMode, type CozeChatConfig } from '@/types/props'; +import { AuthType } from '@/types/client'; + +type ErrorState = boolean | SDKInitError; +/** store 相关 */ +export interface ChatState { + userInfo?: OpenUserInfo; + token?: string; + isNeedToken?: boolean; + isNeedStopRespond?: boolean; + initError?: ErrorState; + voiceCallClose?: boolean; + defaultInputMode?: EInputMode; + isStartBotVoiceCall?: boolean; + backgroundInfo: MixInitResponse['backgroundInfo']; + feedbackInfo: { + [itemId: string]: 'thumbUp' | 'thumbDown' | 'default'; + }; + lastGroupFeedbackInfo: { + messageId?: string; + isShowCustomPanel: boolean; + }; + shortcuts: ShortCutCommand[]; + /** + * @description 用来表示被选中的当前会话 id 、上下文的 id 和是否显示会话列表 + */ + currentConversationInfo?: Conversation & { + conversationListVisible: boolean; + isLargeWidth: boolean; + }; + conversations: Conversation[]; + cozeApi?: CozeAPI; + hasRefusedCallByUser?: boolean; +} + +export interface ChatAction { + setUserId: (userId: string) => void; + setToken: (token: string) => void; + refreshToken: () => Promise; + setUserInfo: (userInfo: OpenUserInfo) => void; + setInitError: (error: ErrorState) => void; + setDefaultInputMode: (defaultInputMode: EInputMode) => void; + setVoiceCallClose: (voiceCallClose: boolean) => void; + setIsStartBotVoiceCall: (isStartBotVoiceCall?: boolean) => void; + setHasRefusedCallByUser: (hasRefusedCallByUser: boolean) => void; + updateFeedbackInfo: ( + itemId: string, + feedbackType: 'thumbUp' | 'thumbDown' | 'default', + ) => void; + updateLastGroupFeedbackInfo: ( + messageId: string, + isShowCustomPanel: boolean, + ) => void; + updateShortcuts: (shortcuts: ShortCutCommand[]) => void; + updateBackgroundInfo: (info: MixInitResponse['backgroundInfo']) => void; + /** + * @description 这里只是为给发送消息那里一个方法,用户根据第一条 query 来更新会话名称 + * @param currentConversationInfo 当配置了 isNeedConversationAdd 后的当前会话会话信息,包括会话列表是否展开 + */ + updateCurrentConversationInfo: ( + currentConversationInfo: ChatState['currentConversationInfo'], + ) => void; + /** + * @description 这里只是为给发送消息那里一个方法,用户根据第一条 query 来更新会话名称 + * @param name 会话名称 + */ + updateCurrentConversationNameByMessage: (name: string) => Promise; + updateConversations: ( + conversations: Conversation[], + operate: 'replace' | 'add' | 'remove' | 'update', + ) => void; + setCozeApi: (cozeApi: CozeAPI) => void; +} +export type ChatStateAction = ChatState & ChatAction; + +const getDefaultUserInfo = (userInfo?: OpenUserInfo) => { + if (userInfo?.id) { + return userInfo; + } + const userIdKey = getStorageKey(LocalStorageKey.UID); + return { + id: storageUtil.getItem(userIdKey, '') || nanoid(), + nickname: '', + url: '', + }; +}; + +export const createChatStore = ( + chatConfig: CozeChatConfig, + userInfo?: OpenUserInfo, +) => { + const isNeedToken = chatConfig?.auth?.type === AuthType.TOKEN; + const token = isNeedToken ? chatConfig?.auth?.token : ''; + return create()( + devtools( + subscribeWithSelector((set, get) => ({ + userInfo: getDefaultUserInfo(userInfo), + token, + isNeedToken, + voiceCallClose: true, + isStartBotVoiceCall: false, + isNeedStopRespond: true, + initError: false, + voiceInfoList: [], + lastGroupFeedbackInfo: { + cozeApiMessageId: '', + isShowCustomPanel: false, + }, + backgroundInfo: {}, + shortcuts: [], + defaultInputMode: EInputMode.Text, + feedbackInfo: {}, + currentConversationInfo: undefined, + conversations: [], + cozeApi: undefined, + hasRefusedCallByUser: false, + setHasRefusedCallByUser: (hasRefusedCallByUser: boolean) => { + set( + produce(s => { + s.hasRefusedCallByUser = hasRefusedCallByUser; + }), + ); + }, + refreshToken: async () => { + const tokenOld = get().token || ''; + const newToken = await chatConfig?.auth?.onRefreshToken?.(tokenOld); + if (newToken) { + set( + produce(s => { + s.token = newToken; + }), + ); + } + return newToken || ''; + }, + setUserInfo: userInfoIn => { + set( + produce(s => { + s.userInfo = getDefaultUserInfo(userInfoIn); + }), + ); + }, + setUserId: (userId: string) => { + set( + produce(s => { + s.userInfo = { + id: userId, + nickname: '', + url: '', + }; + }), + ); + }, + setToken: (tokenNew: string) => { + set( + produce(s => { + s.token = tokenNew; + }), + ); + }, + setInitError: (error: ErrorState) => { + const { initError } = get(); + if (error && initError && typeof initError !== 'boolean') { + return; + } + set( + produce(s => { + s.initError = error; + }), + ); + }, + setDefaultInputMode: (defaultInputMode: EInputMode) => { + set( + produce(s => { + s.defaultInputMode = defaultInputMode; + }), + ); + }, + updateLastGroupFeedbackInfo: ( + messageId: string, + isShowCustomPanel: boolean, + ) => { + set( + produce(s => { + s.lastGroupFeedbackInfo = { + messageId, + isShowCustomPanel, + }; + }), + ); + }, + updateFeedbackInfo: ( + itemId: string, + feedbackType: 'thumbUp' | 'thumbDown' | 'default', + ) => { + set( + produce(s => { + s.feedbackInfo[itemId] = feedbackType; + }), + ); + }, + updateShortcuts(shortcuts: ShortCutCommand[]) { + set( + produce(s => { + s.shortcuts = shortcuts; + }), + ); + }, + updateBackgroundInfo(info: MixInitResponse['backgroundInfo']) { + set( + produce(s => { + s.backgroundInfo = info; + }), + ); + }, + setVoiceCallClose(voiceCallClose: boolean) { + set( + produce(s => { + s.voiceCallClose = voiceCallClose; + }), + ); + }, + setIsStartBotVoiceCall(isStartBotVoiceCall?: boolean) { + set( + produce(s => { + s.isStartBotVoiceCall = isStartBotVoiceCall; + }), + ); + }, + updateCurrentConversationInfo: currentConversationInfo => { + set( + produce(s => { + if (!currentConversationInfo) { + s.currentConversationInfo = currentConversationInfo; + } else { + s.currentConversationInfo = { + ...s.currentConversationInfo, + ...currentConversationInfo, + }; + } + }), + ); + }, + updateCurrentConversationNameByMessage: async (name: string) => { + const { + currentConversationInfo, + updateCurrentConversationInfo, + updateConversations, + cozeApi, + } = get(); + if (!currentConversationInfo || currentConversationInfo.name) { + return Promise.resolve(false); + } + try { + const res = (await cozeApi?.put( + `/v1/conversations/${currentConversationInfo.id}`, + { + name, + }, + )) as { + data: Conversation; + code: number; + }; + if (res.code !== 0) { + return Promise.resolve(false); + } + updateCurrentConversationInfo({ + ...currentConversationInfo, + name, + }); + await updateConversations([res.data], 'update'); + return true; + } catch (error) { + console.error(error); + return Promise.resolve(false); + } + }, + updateConversations: (conversations, operate) => { + set( + produce(s => { + if (operate === 'replace') { + s.conversations = conversations; + } else if (operate === 'add') { + s.conversations = [...s.conversations, ...conversations]; + } else if (operate === 'remove') { + conversations.forEach(conversation => { + const index = s.conversations.findIndex( + c => c.id === conversation.id, + ); + if (index !== -1) { + s.conversations.splice(index, 1); + } + }); + } else if (operate === 'update') { + conversations.forEach(conversation => { + const index = s.conversations.findIndex( + c => c.id === conversation.id, + ); + if (index !== -1) { + s.conversations[index] = { + ...s.conversations[index], + ...conversation, + }; + } + }); + } + }), + ); + }, + setCozeApi: (cozeApi: CozeAPI) => { + set( + produce(s => { + s.cozeApi = cozeApi; + }), + ); + }, + })), + { + enabled: IS_DEV_MODE, + name: 'CozeChatApp.global', + }, + ), + ); +}; + +export type ChatStore = ReturnType; diff --git a/frontend/packages/studio/open-platform/open-chat/src/exports/types.ts b/frontend/packages/studio/open-platform/open-chat/src/exports/types.ts new file mode 100644 index 00000000..056cbae8 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/exports/types.ts @@ -0,0 +1,38 @@ +/* + * 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. + */ + +export { SDKErrorCode, ChatSdkError } from '../util/error'; +export { + Layout, + type CozeChatConfig, + type IframeParams, + type ComponentProps, + type AuthProps, + type UiProps, + WebSdkError, + IframeMessageEvent, + AuthType, + ChatType, + type AppInfo, + type BotInfo, +} from '../types/client'; +export { PostMessageEvent, type PostMessage } from '../types/post'; +export { Language } from '../types/i18n'; + +export { type ImagePreview, type OnImageClick } from '../types'; +export { OpenApiSource } from '../types/open'; +export { type OpenUserInfo } from '../types/user'; +export type { ContentType } from '@coze-common/chat-core'; diff --git a/frontend/packages/studio/open-platform/open-chat/src/helper/clear-local-message-history.ts b/frontend/packages/studio/open-platform/open-chat/src/helper/clear-local-message-history.ts new file mode 100644 index 00000000..f3106362 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/helper/clear-local-message-history.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +import { storageUtil } from '@/util'; + +import { getLocalMessageHistoryKey } from './get-local-message-history-key'; +export const clearLocalMessageHistory = (botId: string) => { + const chatHistoryStorageKey = getLocalMessageHistoryKey(botId); + storageUtil.setItem(chatHistoryStorageKey, []); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/helper/coze-api-upload.ts b/frontend/packages/studio/open-platform/open-chat/src/helper/coze-api-upload.ts new file mode 100644 index 00000000..667f018c --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/helper/coze-api-upload.ts @@ -0,0 +1,124 @@ +/* + * 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. + */ + +import { EventEmitter } from 'eventemitter3'; +import { + type UploadPluginInterface, + type FileType, +} from '@coze-common/chat-core'; +import { FileStatus } from '@coze-common/chat-area'; + +import { type OpenApiSource } from '@/types/open'; +import { type EventPayloadMap } from '@/types/core'; + +export interface UploadFileData { + uri: string; + url: string; +} + +export type UploadFileApi = (props: { + file: File; + botId: string; + source: OpenApiSource; + token?: string; + onProgress?: (percent: number) => void; +}) => Promise; + +export const createSDKUploadPluginClass = ({ + botId, + source, + token, + uploadFile, +}: { + botId: string; + source: OpenApiSource; + token?: string; + uploadFile: UploadFileApi; +}) => + class SDKUploadPlugin implements UploadPluginInterface { + file: File; + fileType: FileType; + eventBus = new EventEmitter(); + /** + * 目前用不到 只是为了对齐 core&area 类型 + */ + // @ts-expect-error -- linter-disable-autofix + userId: string; + // @ts-expect-error -- linter-disable-autofix + abortController: AbortController; + + constructor(props: { file: File; type: FileType; userId: string }) { + this.file = props.file; + this.fileType = props.type; + + this.upload() + .then(meta => { + this.eventBus.emit('complete', { + percent: 100, + status: FileStatus.Success, + uploadResult: { + Uri: meta.uri, + Url: meta.url, + }, + type: 'success', + }); + }) + .catch(err => { + this.eventBus.emit('error'); + }); + } + + start() { + return; + } + + on( + eventName: T, + callback: (info: EventPayloadMap[T]) => void, + ) { + this.eventBus.on(eventName, callback); + } + + pause() { + return; + } + + cancel() { + return; + } + + async upload() { + this.eventBus.emit('ready', { + percent: 0, + status: FileStatus.Uploading, + }); + + const result = await uploadFile({ + source, + botId, + file: this.file, + token, + onProgress: percent => { + this.eventBus.emit('progress', { + percent, + status: FileStatus.Uploading, + }); + }, + }); + + return result; + } + }; diff --git a/frontend/packages/studio/open-platform/open-chat/src/helper/get-local-message-history-key.ts b/frontend/packages/studio/open-platform/open-chat/src/helper/get-local-message-history-key.ts new file mode 100644 index 00000000..1db4519e --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/helper/get-local-message-history-key.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +import { getStorageKey, LocalStorageKey } from '@/util'; + +export const getLocalMessageHistoryKey = (botId?: string) => + getStorageKey(LocalStorageKey.ChatHistory, botId); diff --git a/frontend/packages/studio/open-platform/open-chat/src/helper/get-local-message-history.ts b/frontend/packages/studio/open-platform/open-chat/src/helper/get-local-message-history.ts new file mode 100644 index 00000000..c0879c9e --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/helper/get-local-message-history.ts @@ -0,0 +1,28 @@ +/* + * 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. + */ + +import { type MixInitResponse } from '@coze-common/chat-area'; + +import { storageUtil } from '@/util'; + +import { getLocalMessageHistoryKey } from './get-local-message-history-key'; + +export const getLocalMessageHistory = (botId: string) => { + const chatHistoryStorageKey = getLocalMessageHistoryKey(botId); + const messageList: MixInitResponse['messageList'] = + storageUtil.getItem(chatHistoryStorageKey, []) || []; + return messageList; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/helper/index.ts b/frontend/packages/studio/open-platform/open-chat/src/helper/index.ts new file mode 100644 index 00000000..1fbc374b --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/helper/index.ts @@ -0,0 +1,26 @@ +/* + * 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. + */ + +export { clearLocalMessageHistory } from './clear-local-message-history'; +export { getLocalMessageHistory } from './get-local-message-history'; +export { getLocalMessageHistoryKey } from './get-local-message-history-key'; +export { studioOpenClientReporter } from './studio-open-client-reporter'; +export { + type UploadFileApi, + type UploadFileData, + createSDKUploadPluginClass, +} from './coze-api-upload'; +export { isShowFeedback } from './is-show-feedback'; diff --git a/frontend/packages/studio/open-platform/open-chat/src/helper/is-show-feedback.ts b/frontend/packages/studio/open-platform/open-chat/src/helper/is-show-feedback.ts new file mode 100644 index 00000000..e9bfd4bf --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/helper/is-show-feedback.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +import { type FeedbackConfig } from '@/types/client'; + +export const isShowFeedback = (feedback?: FeedbackConfig) => + feedback && + feedback?.isNeedFeedback && + feedback.feedbackPanel?.tags?.filter(item => !!item.label).length; diff --git a/frontend/packages/studio/open-platform/open-chat/src/helper/studio-open-client-reporter.ts b/frontend/packages/studio/open-platform/open-chat/src/helper/studio-open-client-reporter.ts new file mode 100644 index 00000000..1760cdce --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/helper/studio-open-client-reporter.ts @@ -0,0 +1,78 @@ +/* + * 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. + */ + +import { createMinimalBrowserClient } from '@coze-studio/slardar-adapter'; +import { getSlardarEnv } from '@coze-common/chat-core'; +import { Reporter } from '@coze-arch/logger'; + +import { ChatSdkError, SDKErrorCode } from '@/util/error'; +import { eventMeta } from '@/util/env'; + +const slardarInstance = createMinimalBrowserClient(); +slardarInstance.init({ + bid: 'bot_studio_sdk', + /** + * 与core上报到同一处 bid_env,打通数据 + */ + env: getSlardarEnv({ + env: 'thirdPart', + deployVersion: 'release', + }), +}); +slardarInstance.start(); + +const eventPrefix = 'open_sdk_custom_event_'; +const errorPrefix = 'open_sdk_custom_error_'; + +const enum OpenReportEvent { + IframeLoaded = 'IframeLoaded', + ClientInit = 'ClientInit', +} + +class OpenReporter extends Reporter { + openSdkEvent(eventName: OpenReportEvent, meta: Record) { + this.event({ + eventName: eventPrefix + eventName, + meta: { + ...(meta ?? {}), + ...eventMeta, + }, + }); + } + + openSdkError(error: Error | unknown, code = SDKErrorCode.Base) { + if (!(error instanceof Error)) { + this.errorEvent({ + eventName: errorPrefix + SDKErrorCode.NotError, + error: ChatSdkError.create(SDKErrorCode.NotError), + meta: { + error, + }, + }); + return; + } + + this.errorEvent({ + eventName: errorPrefix + code, + error, + meta: eventMeta, + }); + } +} + +export const studioOpenClientReporter = new OpenReporter(); + +studioOpenClientReporter.init(slardarInstance); diff --git a/frontend/packages/studio/open-platform/open-chat/src/index.ts b/frontend/packages/studio/open-platform/open-chat/src/index.ts index c263dedb..0b7458ca 100644 --- a/frontend/packages/studio/open-platform/open-chat/src/index.ts +++ b/frontend/packages/studio/open-platform/open-chat/src/index.ts @@ -14,19 +14,23 @@ * limitations under the License. */ +// 应用的chat组件导出 export { BuilderChat, - ChatType, - RawMessageType, -} from './components/builder-chat'; + type BuilderChatRef, + type IProject, + type IWorkflow, + type IBuilderChatProps, +} from './chat/builder-chat'; -export { Layout } from './types'; -export type { - IWorkflow, - IProject, - IEventCallbacks, - IBuilderChatProps, - BuilderChatRef, - DebugProps, - HeaderConfig, -} from './types'; +// WebSdk中 bot的chat组件导出 +export { WebSdkChat } from './chat/web-sdk'; + +// 错误相关的方法和枚举值 +export { + isAuthError, + OpenApiError, + postErrorMessage, + ChatSdkErrorType, + type ChatSDKErrorData, +} from './util/error'; diff --git a/frontend/packages/studio/open-platform/open-chat/src/types/builder-chat.ts b/frontend/packages/studio/open-platform/open-chat/src/types/builder-chat.ts deleted file mode 100644 index d46c6993..00000000 --- a/frontend/packages/studio/open-platform/open-chat/src/types/builder-chat.ts +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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. - */ - -import type React from 'react'; - -import { - type ChatFrameworkProps, - type IMessageCallback, - type RawMessage, - type IChatFlowProps, - type FooterConfig, - type IOnImageClickEvent, -} from '@coze/chat-sdk'; - -import { type Layout, type DebugProps, type HeaderConfig } from './base'; -export interface IWorkflow { - id?: string; - parameters?: Record; - header?: Record; -} -export interface IProject { - id: string; - type: 'app' | 'bot'; - mode: 'draft' | 'release' | 'websdk' | 'audit'; // Draft Mode | Publish Mode | webSdk Publish - caller?: 'UI_BUILDER' | 'CANVAS'; - connectorId?: string; - conversationName?: string; // Project must be filled in - conversationId?: string; // If the type is bot, it must be filled in - sectionId?: string; // If the type is bot, it must be filled in - name?: string; - defaultName?: string; - defaultIconUrl?: string; - iconUrl?: string; - layout?: Layout; - version?: string; - onBoarding?: { - prologue: string; - suggestions: string[]; - }; -} -export interface IEventCallbacks { - onMessageChanged?: () => void; - onMessageSended?: () => void; - onMessageReceivedStart?: () => void; - onMessageRecievedFinish?: () => void; - onImageClick?: IOnImageClickEvent; - onGetChatFlowExecuteId?: (id: string) => void; - onThemeChange?: (theme: 'bg-theme' | 'light') => void; - afterMessageReceivedFinish?: IMessageCallback['afterMessageReceivedFinish']; - onInitSuccess?: () => void; -} -export interface IBuilderChatProps { - workflow: IWorkflow; - project: IProject; - eventCallbacks?: IEventCallbacks; - userInfo: IChatFlowProps['userInfo']; - areaUi: { - isDisabled?: boolean; // Default false - uploadable?: boolean; // Default true - isNeedClearContext?: boolean; // Whether to display the clearContext button - isNeedClearMessage?: boolean; // Whether to display the clearMessage button - - //isShowHeader?: boolean;//default false - //isShowFooter?: boolean;//default false - input?: { - placeholder?: string; - renderChatInputTopSlot?: (isChatError?: boolean) => React.ReactNode; - isShow?: boolean; //Default true - defaultText?: string; - isNeedAudio?: boolean; // Whether voice input is required, the default is false - isNeedTaskMessage?: boolean; - }; - header?: HeaderConfig; // Default is - footer?: FooterConfig; - uiTheme?: 'uiBuilder' | 'chatFlow'; // Theme for uiBuilder - renderLoading?: () => React.ReactNode; - }; - auth?: { - type: 'external' | 'internal'; // Internal: cookie for token, external: internal - token?: string; - refreshToken?: () => Promise | string; - }; - style?: React.CSSProperties; - debug?: DebugProps; - setting?: Partial; -} - -export interface BuilderChatRef { - sendMessage: (message: RawMessage) => void; -} diff --git a/frontend/packages/studio/open-platform/open-chat/src/types/client.ts b/frontend/packages/studio/open-platform/open-chat/src/types/client.ts new file mode 100644 index 00000000..34507663 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/types/client.ts @@ -0,0 +1,207 @@ +/* + * 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. + */ + +import { type ReactNode } from 'react'; + +import { type OpenApiSource } from '@/types/open'; +import type { Language } from '@/types/i18n'; + +import { type OpenUserInfo } from './user'; +export enum Layout { + PC = 'pc', + MOBILE = 'mobile', +} + +export interface FeedbackTag { + label: string; + isNeedDetail?: boolean; +} + +export interface FeedbackConfig { + isNeedFeedback?: boolean; // 默认是false + feedbackPanel?: { + title?: string; + placeholder?: string; + tags?: FeedbackTag[]; + }; +} +interface BaseUiProps { + icon?: string; // 助手的图标url,用于小助手按钮显示,以及页面logo显示 + layout?: Layout; + lang?: Language; + zIndex?: number; +} +export interface HeaderConfig { + isShow?: boolean; //是否显示header, 默认是true + isNeedClose?: boolean; //是否需要关闭按钮, 默认是true + extra?: ReactNode | false; // 用于站位的,默认无 +} + +export interface ConversationsConfig { + isNeed?: boolean; // 默认值 false +} +interface ChatBotUiProps { + title?: string; + uploadable?: boolean; + isNeedClearContext?: boolean; // 是否需要清除上下文,默认 为true + isNeedClearMessage?: boolean; //是否需要删除消息,默认是true + isNeedAddNewConversation?: boolean; // 是否需要添加会话按钮 + isNeedAudio?: boolean; // 是否需要音色。默认是true + isNeedFunctionCallMessage?: boolean; //默认是true + isNeedQuote?: boolean; // 默认是 false + isNeedConversationAdd?: boolean; // 是否需要会话添加,同时有会话列表的功能,默认是 false + feedback?: FeedbackConfig; // + // 仅影响chat框的外部框架,不影响内部显示的属性 + width?: number; + el?: HTMLElement; + onHide?: () => void; // 当chat聊天框隐藏时,触发该事件 + onShow?: () => void; // 当chat聊天框显示时,触发该事件 + onBeforeShow?: () => Promise | boolean; // 显示聊天框前调用,如果用户返回了 false,则不显示聊天框 + onBeforeHide?: () => Promise | boolean; // 隐藏聊天框前调用,如果用户返回了 false,则不隐藏聊天框 +} +export enum ChatType { + BOT = 'bot', + APP = 'app', +} +export interface AppInfo { + appId: string; + workflowId: string; + conversationName?: string; + parameters?: Record; + version?: string; +} +export interface BotInfo { + botId?: string; + parameters?: Record; +} +// +export interface FooterConfig { + isShow?: boolean; //是否显示 + expressionText?: string; // 例如 由{{name}}提供。 + linkvars?: Record< + string, + { + text: string; + link: string; + } + >; +} +export interface CozeChatConfig { + type?: ChatType; // 默认是bot + bot_id?: string; + appInfo?: AppInfo; + botInfo?: BotInfo; + source: OpenApiSource; + extra?: { + webChat: Record; + }; + auth?: AuthProps; + ui?: { + base?: Pick; + chatBot?: Pick< + ChatBotUiProps, + | 'title' + | 'uploadable' + | 'isNeedClearContext' + | 'isNeedClearMessage' + | 'isNeedAddNewConversation' + | 'isNeedAudio' + | 'isNeedFunctionCallMessage' + | 'isNeedQuote' + | 'feedback' + >; + footer?: FooterConfig; + header?: HeaderConfig; + conversations?: ConversationsConfig; + }; + // open SDk生成的,不能外部传入 + conversation_id: string; +} + +interface ChatComponentProps { + layout: Layout; + lang: Language; + title: string; + icon: string; + zIndex: number; + uploadable: boolean; + width: number; +} + +/** @deprecated 后续会弃用 */ +export type ComponentProps = Partial; + +/** 鉴权相关类型 */ +export enum AuthType { + UNAUTH = 'unauth', // 无需鉴权 + TOKEN = 'token', // 通过函数获取token +} + +export interface AuthProps { + type?: AuthType; + /* + * type == refresh_token, 用户需传入token、refreshToken参数 + */ + token?: string; // 用户可主动传入token + /* + * type == TOKEN_BY_FUNC, token过期或者无token时,会触发该事件,需要用户传入新的token + */ + onRefreshToken?: (token?: string) => Promise | string; + connectorId?: string; +} + +export interface UiProps { + base?: BaseUiProps; + asstBtn?: { + isNeed?: boolean; // 默认值是true + }; + chatBot?: ChatBotUiProps; + footer?: FooterConfig; + header?: HeaderConfig; + conversations?: ConversationsConfig; +} + +export enum IframeMessageEvent { + GET_IFRAME_PARAMS = 'GET_IFRAME_PARAMS', + GET_NEW_TOKEN = 'GET_NEW_TOKEN', + THEME_CHANGE = 'THEME_CHANGE', +} + +// 双方通信传递的数据结构 +export interface IframeParams { + // 通信标识 + // iframe回传父页面 的事件前缀 => 用于父级页面的 多实例 区分 message 来源 + chatClientId: string; + chatConfig: CozeChatConfig; + userInfo?: OpenUserInfo; +} + +export enum WebSdkError { + // 超时 + TIMEOUT = -1, + // 未知错误 + UNKNOWN = -2, + // 禁止操作 + FORBIDDEN = -3, + + // 未知错误 + SUCCESS = 0, + + // 鉴权失败 + AUTH_FAILED = 100001, + // token拉取失败 + AUTH_TOKEN_GET_FAILED = 100002, +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/types/conversations.ts b/frontend/packages/studio/open-platform/open-chat/src/types/conversations.ts new file mode 100644 index 00000000..0904a7e1 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/types/conversations.ts @@ -0,0 +1,45 @@ +/* + * 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. + */ + +import { I18n } from '@coze-arch/i18n'; +import { type Conversation as CozeConversation } from '@coze/api'; + +declare module '@coze/api' { + interface Conversation { + name?: string; + updated_at: number; + } +} + +export enum ConversationSort { // 数字越小,越靠前 + Today = 0, + In30days = 1, + Others = 999, +} + +export const conversationSortMap = new Map([ + [ConversationSort.Today, I18n.t('profile_history_today', {}, '今天')], + [ + ConversationSort.In30days, + I18n.t('log_pay_wall_date_filter_30_days', {}, '过去30天'), + ], + [ConversationSort.Others, I18n.t('web_sdk_past', {}, '过往')], +]); + +export interface SortedConversationItem extends CozeConversation { + sort: ConversationSort; + name?: string; +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/types/core.ts b/frontend/packages/studio/open-platform/open-chat/src/types/core.ts new file mode 100644 index 00000000..c28967ac --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/types/core.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +import { type EventPayloadMaps as BaseEventPayloadMap } from '@coze-common/chat-core'; +import type { MixInitResponse } from '@coze-common/chat-area'; + +export type EventPayloadMap = BaseEventPayloadMap & { + ready: boolean; +}; + +// @ts-expect-error -- linter-disable-autofix +export type ChatMessage = MixInitResponse['messageList'][number]; diff --git a/frontend/packages/studio/open-platform/open-chat/src/types/i18n.ts b/frontend/packages/studio/open-platform/open-chat/src/types/i18n.ts new file mode 100644 index 00000000..78c10450 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/types/i18n.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export enum Language { + EN = 'en', + ZH_CN = 'zh-CN', +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/types/index.ts b/frontend/packages/studio/open-platform/open-chat/src/types/index.ts index 5ac558d7..a155e584 100644 --- a/frontend/packages/studio/open-platform/open-chat/src/types/index.ts +++ b/frontend/packages/studio/open-platform/open-chat/src/types/index.ts @@ -14,11 +14,24 @@ * limitations under the License. */ -export { Layout, type HeaderConfig, type DebugProps } from './base'; -export type { - IWorkflow, - IProject, - IEventCallbacks, - IBuilderChatProps, - BuilderChatRef, -} from './builder-chat'; +import { type ContentType, type Message } from '@coze-common/chat-core'; +export { type ImageMessageContent } from '@coze-common/chat-core'; +export { type OnboardingSuggestionItem } from '@coze-common/chat-area'; + +export interface ImagePreview { + visible: boolean; + url: string; +} +export type OnImageClick = (extra: { url: string }) => void; + +export type CoreMessage = Message; + +export enum MessageRole { + User = 'user', + Assistant = 'assistant', +} + +export enum MessageType { + Answer = 'answer', + Verbose = 'verbose', +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/types/open.ts b/frontend/packages/studio/open-platform/open-chat/src/types/open.ts new file mode 100644 index 00000000..1c0f03a5 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/types/open.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +export enum OpenApiSource { + WebSdk = 'web_sdk', + ChatFlow = 'chat_flow', + MiniProgram = 'mini_program', + MiniProgramV2 = 'mini_program_v2', +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/types/post.ts b/frontend/packages/studio/open-platform/open-chat/src/types/post.ts new file mode 100644 index 00000000..40023e72 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/types/post.ts @@ -0,0 +1,31 @@ +/* + * 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. + */ + +export interface PostMessage { + event: PostMessageEvent; + chatStoreId: string; + payload: T; +} + +export enum PostMessageEvent { + ImageClick = 'ImageClick', +} + +export interface PostMessageData { + [PostMessageEvent.ImageClick]: { + url: string; + }; +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/types/props.ts b/frontend/packages/studio/open-platform/open-chat/src/types/props.ts new file mode 100644 index 00000000..ab84fafd --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/types/props.ts @@ -0,0 +1,142 @@ +/* + * 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. + */ + +import type { CSSProperties, FC, ReactNode } from 'react'; + +import { type InputNativeCallbacks } from '@coze-common/chat-uikit-shared'; +import { type RequestManagerOptions } from '@coze-common/chat-core'; +import { + type MixInitResponse, + type ChatAreaEventCallback, +} from '@coze-common/chat-area'; +import { type CozeAPI } from '@coze/api'; + +import { type SDKInitError } from '@/util/error'; +import { type OpenBotInfo, type OpenUserInfo } from '@/types/user'; +import { type CozeChatConfig, type Layout } from '@/types/client'; +import { type InitErrorFallback } from '@/components/error-fallback'; + +export { type CozeChatConfig }; +export interface ConversationInfo { + conversationId: string; + sectionId: string; +} + +export enum EInputMode { + Text = 'text', + Voice = 'voice', + VoiceCall = 'call', +} + +export interface DebugProps { + cozeApiRequestHeader?: Record; +} + +export interface OpenRequestInit { + prologue: string; + onboardingSuggestions: Array<{ + id: string; + content: string; + }>; + botInfo: OpenBotInfo; + backgroundInfo?: MixInitResponse['backgroundInfo']; + conversationId?: string; // 自定义生成的conversationId + sectionId?: string; // sectionId + defaultInputMode?: EInputMode; + isCustomBackground?: boolean; + /** + * @description 在会话列表中,根据选中的会话初始化的时候需要复写 openRequestInit 这个方法,但是 webSDK 又不需要 botInfo 信息 + */ + isBuilderChat?: boolean; +} +export interface AutoBilling { + entityType: 'bot' | 'workflow'; + entityId: string; +} +export interface StudioChatProviderProps { + className?: string; + spaceId?: string; + userInfo?: OpenUserInfo; + chatConfig: CozeChatConfig; + layout?: Layout; + openRequestInit?: + | OpenRequestInit + | { + (cozeApi?: CozeAPI): Promise | OpenRequestInit; + }; + requestManagerOptions?: RequestManagerOptions; // 仅仅 coze-sdk可用 + onImageClick?: ChatAreaEventCallback['onImageClick']; + onMessageLinkClick?: ChatAreaEventCallback['onMessageLinkClick']; + /** + * 对话区加载 成功/失败 事件 + */ + onInitStateChange?: ( + status: 'initSuccess' | 'initFail', + errorInfo?: SDKInitError, + ) => void; + /** + * 允许自定义 + * 初始化接口(onboarding)报错 + * or + * chatArea 初始化错误 + * 的 fallback 组件 + */ + initErrorFallbackFC?: FC; + + /** + * 生效条件: userInfo.nickname && !openRequestInit + * replace(prologue, '{{user_name}}', userInfo.nickname) + */ + enableReplacePrologueNicknameWithVar?: boolean; + onThemeChange?: (theme: 'bg-theme' | 'light') => void; + onDefaultHistoryClear?: () => void; + defaultHistoryMessage?: MixInitResponse['messageList']; + debug?: DebugProps; + isCustomBackground?: boolean; + isNeedVoiceCall?: boolean; + readonly?: boolean; +} + +export interface StudioChatAreaProps { + className?: string; + coreAreaClassName?: string; + showInputArea?: boolean; + inputPlaceholder?: string; + isDisabled?: boolean; + inputNativeCallbacks?: InputNativeCallbacks; + messageGroupListClassName?: string; + isShowClearContextDivider?: boolean; + renderChatInputTopSlot?: () => React.ReactNode; + headerNode?: React.ReactNode; + messageMaxWidth?: string; + isMiniScreen?: boolean; + enableMultimodalUpload?: boolean; +} + +export interface WebSdkChatProps { + // 聊天容器相关配置 + title: string; // 标题 + style?: CSSProperties; // 容器的style样式 + className?: string; // 容器的class类 + icon?: string; // 左上角的Icon + headerExtra?: ReactNode; // 右侧的位置 + layout?: Layout; // 布局 + useInIframe?: boolean; //是否在Iframe中,对样式有影响 + chatConfig: CozeChatConfig; + userInfo?: OpenUserInfo; + onImageClick?: ChatAreaEventCallback['onImageClick']; + onThemeChange?: (theme: 'bg-theme' | 'light') => void; +} diff --git a/frontend/packages/studio/open-platform/open-chat/src/types/user.ts b/frontend/packages/studio/open-platform/open-chat/src/types/user.ts new file mode 100644 index 00000000..67a2c5a0 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/types/user.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +import { type SenderInfo } from '@coze-common/chat-area'; + +export type OpenUserInfo = Omit; +export type OpenBotInfo = OpenUserInfo; diff --git a/frontend/packages/studio/open-platform/open-chat/src/util/connector.ts b/frontend/packages/studio/open-platform/open-chat/src/util/connector.ts new file mode 100644 index 00000000..ccf678a0 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/util/connector.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + +export const webSdkDefaultConnectorId = '999'; +export const chatflowDraftConnectorId = '10000010'; diff --git a/frontend/packages/studio/open-platform/open-chat/src/util/env.ts b/frontend/packages/studio/open-platform/open-chat/src/util/env.ts new file mode 100644 index 00000000..72e4c61c --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/util/env.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ +export { + openApiHostByRegion, + openApiHostByRegionWithToken, + openApiCdnUrlByRegion, + openSdkPrefix, + getOpenSDKUrl, + getOpenSDKPath, + eventMeta, +} from '@coze-studio/open-env-adapter'; diff --git a/frontend/packages/studio/open-platform/open-chat/src/util/error.ts b/frontend/packages/studio/open-platform/open-chat/src/util/error.ts new file mode 100644 index 00000000..5d36443e --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/util/error.ts @@ -0,0 +1,121 @@ +/* + * 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. + */ + +import { I18n } from '@coze-arch/i18n'; + +// 业务代码内部错误码,用于标识业务内的错误。 +export enum SDKErrorCode { + Base = 1000, + OpenApiUpload = 1001, + NoClearAPI = 1002, + StoreProvider = 1003, + Iframe = 2000, + IframeParams = 2001, + Core = 3000, + NotError = 4000, +} + +export class ChatSdkError extends Error { + sdkCode: SDKErrorCode; + + constructor(options: { sdkCode: SDKErrorCode } | SDKErrorCode) { + super(); + if (typeof options === 'number') { + this.sdkCode = options; + } else { + this.sdkCode = options.sdkCode; + } + } + + static wrap(err: Error, code = SDKErrorCode.Base) { + const newErr = new ChatSdkError({ sdkCode: code }); + newErr.message = err.message; + newErr.name = err.name; + newErr.cause = err.cause; + newErr.stack = err.stack; + + return newErr; + } + + static create(sdkCode: SDKErrorCode) { + return new ChatSdkError({ sdkCode }); + } +} + +export interface SDKInitError { + code: number; + msg: string; +} + +export enum ServerErrorCode { + BotUnbind = 702242003, +} + +export const specCodeList: number[] = [ServerErrorCode.BotUnbind]; + +export const getServerError = ( + error: SDKInitError, +): SDKInitError | undefined => { + const code = error?.code; + + switch (code) { + case ServerErrorCode.BotUnbind: + return { + code, + msg: I18n.t('unbind_notification'), + }; + default: + } +}; + +// coze api中接口返回的错误码 +export enum OpenApiError { + ERROR_FORBIDDEN = 401, + ERROR_INVALID_TOKEN = 4100, + ERROR_TOKEN_FORBIDDEN = 4101, + ERROR_TOKEN_FAILED = 700012006, + BOT_NOT_PUBLISH = 4015, +} + +export const isAuthError = (errorCode: number) => + [ + OpenApiError.ERROR_FORBIDDEN, + OpenApiError.ERROR_INVALID_TOKEN, + OpenApiError.ERROR_TOKEN_FORBIDDEN, + OpenApiError.ERROR_TOKEN_FAILED, + ].includes(errorCode); + +export enum ChatSdkErrorType { + /** botId 错误 */ + INVALID_BOT_ID = 'INVALID_BOT_ID', + /** OpenAPI 错误 */ + OPEN_API_ERROR = 'OPEN_API_ERROR', +} +export interface ChatSDKErrorData { + type: ChatSdkErrorType; + code?: number; + message?: string; +} + +export const postErrorMessage = (data: ChatSDKErrorData) => { + window.parent.postMessage( + { + type: 'chat-sdk-error', + data, + }, + '*', + ); +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/util/index.ts b/frontend/packages/studio/open-platform/open-chat/src/util/index.ts new file mode 100644 index 00000000..6c1f2dc9 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/util/index.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +export { isPromiseLike } from './is-promise-like'; +export { catchStringify, catchParse, STRINGIFY_ERROR } from './json-handle'; +export { + chatflowDraftConnectorId, + webSdkDefaultConnectorId, +} from './connector'; + +export { + type StorageKey, + LocalStorageKey, + getStorageKey, + storageUtil, +} from './storage'; diff --git a/frontend/packages/studio/open-platform/open-chat/src/util/is-promise-like.ts b/frontend/packages/studio/open-platform/open-chat/src/util/is-promise-like.ts new file mode 100644 index 00000000..6dc485a9 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/util/is-promise-like.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + +export const isPromiseLike = (it: unknown): it is PromiseLike => + it instanceof Promise || typeof (it as { then: string })?.then === 'function'; diff --git a/frontend/packages/studio/open-platform/open-chat/src/util/json-handle.ts b/frontend/packages/studio/open-platform/open-chat/src/util/json-handle.ts new file mode 100644 index 00000000..3645762f --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/util/json-handle.ts @@ -0,0 +1,36 @@ +/* + * 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. + */ + +export const STRINGIFY_ERROR = Symbol(); + +export const catchStringify = (obj: unknown) => { + try { + return JSON.stringify(obj); + } catch (err) { + console.log('catchStringify error', err); + return STRINGIFY_ERROR; + } +}; + +export const catchParse = (objStr: string, defaultValue?: T) => { + try { + return JSON.parse(objStr) as T; + } catch (err) { + console.log('catchParse error', err); + } + + return defaultValue; +}; diff --git a/frontend/packages/studio/open-platform/open-chat/src/util/storage.ts b/frontend/packages/studio/open-platform/open-chat/src/util/storage.ts new file mode 100644 index 00000000..90646996 --- /dev/null +++ b/frontend/packages/studio/open-platform/open-chat/src/util/storage.ts @@ -0,0 +1,57 @@ +/* + * 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. + */ + +import { catchParse, catchStringify, STRINGIFY_ERROR } from './json-handle'; + +export enum LocalStorageKey { + ChatHistory = 'ChatHistory', + UID = 'OpenSDKUID', +} + +export type StorageKey = `coze__${LocalStorageKey}__${string}`; + +export const getStorageKey = ( + key: LocalStorageKey, + suffix?: string, +): StorageKey => `coze__${key}__${suffix ?? ''}`; + +export const setItem = (key: string, obj: unknown) => { + const str = catchStringify(obj); + if (str === STRINGIFY_ERROR) { + return; + } + + if (obj) { + localStorage.setItem(key, str); + } else { + localStorage.removeItem(key); + } +}; + +export const getItem = (key: StorageKey, defaultValue: T) => { + const str = localStorage.getItem(key); + + if (!str) { + return defaultValue; + } + + return catchParse(str, defaultValue); +}; + +export const storageUtil = { + setItem, + getItem, +}; diff --git a/frontend/packages/studio/open-platform/open-chat/tsconfig.build.json b/frontend/packages/studio/open-platform/open-chat/tsconfig.build.json index afc4ba0e..4c7c183e 100644 --- a/frontend/packages/studio/open-platform/open-chat/tsconfig.build.json +++ b/frontend/packages/studio/open-platform/open-chat/tsconfig.build.json @@ -17,15 +17,66 @@ "include": ["src", "src/**/*.json"], "exclude": ["./src/**/*.test.ts", "src/**/__tests__/**", "src/test/setup.ts"], "references": [ + { + "path": "../../../arch/bot-api/tsconfig.build.json" + }, { "path": "../../../arch/bot-env/tsconfig.build.json" }, { "path": "../../../arch/bot-typings/tsconfig.build.json" }, + { + "path": "../../../arch/bot-utils/tsconfig.build.json" + }, { "path": "../../../arch/i18n/tsconfig.build.json" }, + { + "path": "../../../arch/idl/tsconfig.build.json" + }, + { + "path": "../../../arch/logger/tsconfig.build.json" + }, + { + "path": "../../../arch/slardar-adapter/tsconfig.build.json" + }, + { + "path": "../../../common/chat-area/chat-answer-action/tsconfig.build.json" + }, + { + "path": "../../../common/chat-area/chat-area-plugin-reasoning/tsconfig.build.json" + }, + { + "path": "../../../common/chat-area/chat-area/tsconfig.build.json" + }, + { + "path": "../../../common/chat-area/chat-core/tsconfig.build.json" + }, + { + "path": "../../../common/chat-area/chat-uikit-shared/tsconfig.build.json" + }, + { + "path": "../../../common/chat-area/chat-uikit/tsconfig.build.json" + }, + { + "path": "../../../common/chat-area/chat-workflow-render/tsconfig.build.json" + }, + { + "path": "../../../common/chat-area/plugin-chat-background/tsconfig.build.json" + }, + { + "path": "../../../common/chat-area/plugin-chat-shortcuts/tsconfig.build.json" + }, + { + "path": "../../../common/chat-area/plugin-message-grab/tsconfig.build.json" + }, + { + "path": "../../common/file-kit/tsconfig.build.json" + }, + { + "path": "../../../components/bot-semi/tsconfig.build.json" + }, { "path": "../../../../config/eslint-config/tsconfig.build.json" }, diff --git a/frontend/packages/studio/workspace/entry-base/src/pages/library/hooks/use-columns.tsx b/frontend/packages/studio/workspace/entry-base/src/pages/library/hooks/use-columns.tsx index 2b37d9b3..5fa360fc 100644 --- a/frontend/packages/studio/workspace/entry-base/src/pages/library/hooks/use-columns.tsx +++ b/frontend/packages/studio/workspace/entry-base/src/pages/library/hooks/use-columns.tsx @@ -26,8 +26,9 @@ import { } from '@coze-arch/coze-design'; import { responsiveTableColumn, formatDate } from '@coze-arch/bot-utils'; import { + ResType, + WorkflowMode, type ResourceInfo, - type ResType, } from '@coze-arch/bot-api/plugin_develop'; import { type LibraryEntityConfig } from '../types'; @@ -53,6 +54,16 @@ const getResTypeLabelFromConfigMap = ( if (item.res_type === undefined) { return '-'; } + + // 单独判断一下 Chatflow 类型 + if ( + item.res_type === ResType.Workflow && + item.res_sub_type === WorkflowMode.ChatFlow + ) { + const label = I18n.t('wf_chatflow_76'); + return label; + } + const target = entityConfigs.find(config => config.target.includes(item.res_type as ResType), )?.typeFilter; diff --git a/frontend/packages/studio/workspace/entry-base/src/pages/library/hooks/use-entity-configs/use-workflow-config.tsx b/frontend/packages/studio/workspace/entry-base/src/pages/library/hooks/use-entity-configs/use-workflow-config.tsx index ab7ea3c8..1f03c41e 100644 --- a/frontend/packages/studio/workspace/entry-base/src/pages/library/hooks/use-entity-configs/use-workflow-config.tsx +++ b/frontend/packages/studio/workspace/entry-base/src/pages/library/hooks/use-entity-configs/use-workflow-config.tsx @@ -77,18 +77,15 @@ export const useWorkflowConfig: UseEntityConfigHook = ({ > {I18n.t('library_resource_type_workflow')} - {/* The open-source version does not support conversation streaming for the time being */} - {!IS_OPEN_SOURCE ? ( - } - onClick={() => { - openCreateModal(WorkflowMode.ChatFlow); - }} - > - {I18n.t('wf_chatflow_76')} - - ) : null} + } + onClick={() => { + openCreateModal(WorkflowMode.ChatFlow); + }} + > + {I18n.t('wf_chatflow_76')} + ), target: [ResType.Workflow, ResType.Imageflow], diff --git a/frontend/packages/workflow/adapter/base/src/utils/get-enabled-node-types.ts b/frontend/packages/workflow/adapter/base/src/utils/get-enabled-node-types.ts index eb8afe2c..6ed90fb7 100644 --- a/frontend/packages/workflow/adapter/base/src/utils/get-enabled-node-types.ts +++ b/frontend/packages/workflow/adapter/base/src/utils/get-enabled-node-types.ts @@ -46,9 +46,9 @@ export const getEnabledNodeTypes = (_params: { [StandardNodeType.Input]: true, [StandardNodeType.Comment]: true, [StandardNodeType.VariableMerge]: true, - // [StandardNodeType.QueryMessageList]: true, - // [StandardNodeType.ClearContext]: true, - // [StandardNodeType.CreateConversation]: true, + [StandardNodeType.QueryMessageList]: true, + [StandardNodeType.ClearContext]: true, + [StandardNodeType.CreateConversation]: true, [StandardNodeType.VariableAssign]: true, [StandardNodeType.Http]: true, [StandardNodeType.DatabaseUpdate]: true, @@ -57,13 +57,13 @@ export const getEnabledNodeTypes = (_params: { [StandardNodeType.DatabaseCreate]: true, // [StandardNodeType.JsonParser]: true, [StandardNodeType.JsonStringify]: true, - // [StandardNodeType.UpdateConversation]: true, - // [StandardNodeType.DeleteConversation]: true, - // [StandardNodeType.QueryConversationList]: true, - // [StandardNodeType.QueryConversationHistory]: true, - // [StandardNodeType.CreateMessage]: true, - // [StandardNodeType.UpdateMessage]: true, - // [StandardNodeType.DeleteMessage]: true, + [StandardNodeType.UpdateConversation]: true, + [StandardNodeType.DeleteConversation]: true, + [StandardNodeType.QueryConversationList]: true, + [StandardNodeType.QueryConversationHistory]: true, + [StandardNodeType.CreateMessage]: true, + [StandardNodeType.UpdateMessage]: true, + [StandardNodeType.DeleteMessage]: true, }; const enabledNodeTypes: StandardNodeType[] = Object.keys(nodesMap) .filter(key => nodesMap[key]) diff --git a/frontend/packages/workflow/components/src/workflow-modal/filter/index.tsx b/frontend/packages/workflow/components/src/workflow-modal/filter/index.tsx index d7a0eddd..9a3593fc 100644 --- a/frontend/packages/workflow/components/src/workflow-modal/filter/index.tsx +++ b/frontend/packages/workflow/components/src/workflow-modal/filter/index.tsx @@ -55,13 +55,8 @@ const flowModeOptions = [ label: I18n.t('wf_chatflow_76'), value: WorkflowMode.ChatFlow, }, -].filter(item => { - // The open-source version does not currently support conversation streaming - if (item.value === WorkflowMode.ChatFlow && IS_OPEN_SOURCE) { - return false; - } - return true; -}); +]; + const WorkflowModalFilter: FC = props => { const context = useContext(WorkflowModalContext); const { i18nText, ModalI18nKey } = useI18nText(); diff --git a/frontend/packages/workflow/components/src/workflow-modal/sider/create-workflow-btn.tsx b/frontend/packages/workflow/components/src/workflow-modal/sider/create-workflow-btn.tsx index 9bfc2280..8c6c6bb4 100644 --- a/frontend/packages/workflow/components/src/workflow-modal/sider/create-workflow-btn.tsx +++ b/frontend/packages/workflow/components/src/workflow-modal/sider/create-workflow-btn.tsx @@ -80,8 +80,7 @@ export const CreateWorkflowBtn: FC< return ( <> - {/* will support soon */} - {showSingleButton || IS_OPEN_SOURCE ? ( + {showSingleButton ? (