feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
@@ -0,0 +1,11 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0_3530_1794" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
|
||||
<path d="M0 0H16V16H0V0Z" fill="black" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_3530_1794)">
|
||||
<path
|
||||
d="M5.00781 11.1694L7.22266 4.82812H8.78271L10.9932 11.1694H9.6001L9.12109 9.63135H6.87988L6.40088 11.1694H5.00781ZM7.9873 6.05859L7.1875 8.63818H8.81348L8.01367 6.05859H7.9873Z"
|
||||
fill="currentColor" />
|
||||
<rect x="1.5" y="1.5" width="13" height="13" rx="6.5" stroke="currentColor" stroke-width="1.33" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 644 B |
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="36" height="36" fill="#FFBF00"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M28.1167 12.8149C28.1167 15.6088 23.5869 17.8737 17.9991 17.8737C12.4113 17.8737 7.88151 15.6088 7.88151 12.8149C7.88151 10.021 12.4113 7.7561 17.9991 7.7561C23.5869 7.7561 28.1167 10.021 28.1167 12.8149ZM28.1083 15.3452C28.1053 15.3452 28.1028 15.3474 28.1025 15.3504C27.834 18.0245 23.4127 20.1514 17.9978 20.1514C12.5838 20.1514 8.16296 18.0252 7.89325 15.3516C7.89287 15.348 7.88978 15.3452 7.88609 15.3452C7.88212 15.3452 7.87891 15.3484 7.87891 15.3523V18.1279V18.3739C7.87891 18.3775 7.88182 18.3804 7.88541 18.3804C7.88875 18.3804 7.89155 18.383 7.89188 18.3863C8.16098 21.0601 12.5821 23.1867 17.9965 23.1867C23.411 23.1867 27.832 21.0601 28.1011 18.3863C28.1015 18.383 28.1043 18.3804 28.1076 18.3804C28.1112 18.3804 28.1141 18.3775 28.1141 18.3739V18.1279V15.351C28.1141 15.3478 28.1115 15.3452 28.1083 15.3452ZM28.101 20.4093C28.1013 20.4059 28.1042 20.4033 28.1075 20.4033C28.1112 20.4033 28.1141 20.4063 28.1141 20.4099V23.186V23.4321C28.1141 23.4357 28.1112 23.4386 28.1076 23.4386C28.1043 23.4386 28.1015 23.4411 28.1011 23.4445C27.832 26.1183 23.411 28.2448 17.9965 28.2448C12.5821 28.2448 8.16098 26.1183 7.89188 23.4445C7.89155 23.4411 7.88875 23.4386 7.88541 23.4386C7.88182 23.4386 7.87891 23.4357 7.87891 23.4321V23.186V20.4099C7.87891 20.4063 7.88185 20.4033 7.88549 20.4033C7.88887 20.4033 7.8917 20.4059 7.89204 20.4093C8.16274 23.0823 12.5831 25.208 17.9965 25.208C23.4099 25.208 27.8303 23.0823 28.101 20.4093Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.33398 2.99992C8.33398 1.52716 9.52789 0.333252 11.0007 0.333252H27.0006C28.4734 0.333252 29.6673 1.52716 29.6673 2.99992V18.9999C29.6673 20.4727 28.4734 21.6666 27.0006 21.6666H21.6673V26.9999C21.6673 28.4727 20.4734 29.6666 19.0007 29.6666H3.00065C1.52789 29.6666 0.333984 28.4727 0.333984 26.9999V10.9999C0.333984 9.52716 1.52789 8.33325 3.00065 8.33325H8.33398V2.99992ZM8.33398 10.9999H3.00065V26.9999H19.0007V21.6666H11.0007C9.52789 21.6666 8.33398 20.4727 8.33398 18.9999V10.9999ZM19.0007 18.9999H11.0007V10.9999H19.0007V18.9999ZM21.6673 18.9999V10.9999C21.6673 9.52716 20.4734 8.33325 19.0007 8.33325H11.0007V2.99992H27.0006V18.9999H21.6673Z" fill="#1D1C23" fill-opacity="0.35"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 841 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,13 @@
|
||||
@common-box-shadow: 0px 2px 8px 0px rgba(31, 35, 41, 0.02),
|
||||
0px 2px 4px 0px rgba(31, 35, 41, 0.02), 0px 2px 2px 0px rgba(31, 35, 41, 0.02);
|
||||
|
||||
.common-svg-icon(@size:14px, @color:#3370ff) {
|
||||
> svg {
|
||||
width: @size;
|
||||
height: @size;
|
||||
|
||||
> path {
|
||||
fill: @color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
@import './common.less';
|
||||
|
||||
.wrapper {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: #f7f7fa;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
height: calc(100% - 80px);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.spin {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 100;
|
||||
background-color: rgb(255 255 255 / 50%);
|
||||
}
|
||||
|
||||
.playground-neat {
|
||||
.message-area {
|
||||
min-width: 258px;
|
||||
}
|
||||
}
|
||||
|
||||
.develop-area {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.develop-area-scroll {
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.setting-area {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex: 1 1;
|
||||
flex-direction: column;
|
||||
border-left: 1px solid rgb(28 29 35 / 12%);
|
||||
|
||||
.setting-title-block {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.setting-area-scroll {
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-collapse-item {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.semi-collapse-header {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.semi-collapse-content {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.semi-select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-collapsible-wrapper {
|
||||
padding-left: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-area {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
min-width: 404px;
|
||||
transition: min-width 0.2s ease;
|
||||
}
|
||||
|
||||
.playground-neat {
|
||||
.message-area {
|
||||
min-width: 258px;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 8px 0 0 4px !important;
|
||||
}
|
||||
|
||||
.sheet-title-node-cover {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.bj-cover {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 4px 0 rgb(0 0 0 / 4%),
|
||||
0 0 1px 0 rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
.border-cover {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.spin-wrapper.top-level {
|
||||
width: 100%;
|
||||
height: 100% !important;
|
||||
|
||||
:global {
|
||||
.semi-spin-children {
|
||||
.wrapper();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sheet-view-left-header {
|
||||
padding: 16px 28px !important;
|
||||
}
|
||||
|
||||
.icon-button-16 {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-button {
|
||||
&.semi-button-size-small {
|
||||
padding: 1px !important;
|
||||
height: 16px;
|
||||
|
||||
svg {
|
||||
@apply text-foreground-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 能力模块默认说明文案样式
|
||||
.tip-text {
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
color: var(--light-usage-text-color-text-2, rgb(28 29 35 / 60%));
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
.border-line(@radius: 8px, @color: #eceef0) {
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform-origin: 0 0;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
transform: scale(0.5, 0.5);
|
||||
box-sizing: border-box;
|
||||
pointer-events: none;
|
||||
color: @color;
|
||||
border-color: @color;
|
||||
border-radius: @radius;
|
||||
}
|
||||
}
|
||||
|
||||
.base-border-line(@color: #eceef0) {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
background: @color;
|
||||
}
|
||||
|
||||
.border-left-line(@color: #eceef0) {
|
||||
&::after {
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
transform: scaleX(0.5);
|
||||
.base-border-line(@color);
|
||||
}
|
||||
}
|
||||
.border-right-line(@color: #eceef0) {
|
||||
&::after {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
transform: scaleX(0.5);
|
||||
.base-border-line(@color);
|
||||
}
|
||||
}
|
||||
.border-top-line(@color: #eceef0) {
|
||||
&::after {
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 1px;
|
||||
transform: scaleY(0.5);
|
||||
.base-border-line(@color);
|
||||
}
|
||||
}
|
||||
.border-bottom-line(@color: #eceef0) {
|
||||
&::after {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 1px;
|
||||
transform: scaleY(0.5);
|
||||
.base-border-line(@color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
.semi-modal-body {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
// 统一button样式
|
||||
.semi-button {
|
||||
font-size: 12px;
|
||||
.semi-button-content-right {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
// borderless取消hover样式
|
||||
.semi-button-borderless:not(.semi-button-disabled):hover {
|
||||
background-color: inherit;
|
||||
border: none;
|
||||
}
|
||||
.semi-button.semi-button-primary:focus-visible,
|
||||
.semi-button.semi-button-secondary:focus-visible,
|
||||
.semi-button.semi-button-tertiary:focus-visible,
|
||||
.semi-button.semi-button-warning:focus-visible,
|
||||
.semi-button.semi-button-danger:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
@bg-gray-blue: rgba(
|
||||
240,
|
||||
245,
|
||||
255,
|
||||
0.81
|
||||
); // highlight the area of the background
|
||||
@text-gray-blue: #536eb1; // highlight the area of the text
|
||||
|
||||
@bg-white-smoke: #f5f5f5; // background of input/table/btn TODO:rgba(46, 50, 56, 0.05)) #2e3238
|
||||
@border-light-gray: rgba(28, 29, 35, 0.12);
|
||||
@error-red: #f93920;
|
||||
@text-title-black: #2e3238;
|
||||
@text-highlight-blue: #3370ff;
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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, type PropsWithChildren } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
|
||||
import { type ButtonProps, type Theme } from '@coze-arch/bot-semi/Button';
|
||||
import { UIButton } from '@coze-arch/bot-semi';
|
||||
import { BotE2e } from '@coze-data/e2e';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface AddButtonProps {
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
theme?: Theme;
|
||||
icon?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const AddButton: React.FC<
|
||||
PropsWithChildren<AddButtonProps & ButtonProps>
|
||||
> = ({
|
||||
onClick,
|
||||
className,
|
||||
style,
|
||||
children,
|
||||
theme,
|
||||
icon,
|
||||
disabled,
|
||||
type,
|
||||
...props
|
||||
}) => {
|
||||
const isReadonly = useBotDetailIsReadonly();
|
||||
|
||||
if (isReadonly) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<UIButton
|
||||
data-testid={BotE2e.BotVariableAddModalAddBtn}
|
||||
disabled={disabled}
|
||||
style={style}
|
||||
className={classNames(s.add, className)}
|
||||
type={type || 'tertiary'}
|
||||
theme={theme || 'light'}
|
||||
icon={icon}
|
||||
onClick={onClick}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</UIButton>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
.add {
|
||||
min-width: 96px;
|
||||
}
|
||||
@@ -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 { AddButton } from './add-button';
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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 ConnectorConfigStatus } from '@coze-arch/idl/intelligence_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Button, type ButtonProps } from '@coze-arch/coze-design';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { useUIModal, UIButton, Typography } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
AuthStatus,
|
||||
type AuthLoginInfo,
|
||||
ConfigStatus,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { IconAlertCircle } from '@douyinfe/semi-icons';
|
||||
|
||||
import {
|
||||
checkAuthInfoValid,
|
||||
executeAuthRedirect,
|
||||
logAndToastAuthInfoError,
|
||||
useRevokeAuth,
|
||||
} from '../../util/auth';
|
||||
|
||||
export interface AuthorizeButtonProps {
|
||||
origin: 'setting' | 'publish';
|
||||
id: string;
|
||||
agentType?: 'bot' | 'project';
|
||||
channelName: string;
|
||||
status: ConfigStatus | AuthStatus | ConnectorConfigStatus;
|
||||
revokeSuccess: (id: string) => void;
|
||||
authInfo: AuthLoginInfo;
|
||||
isMouseIn?: boolean;
|
||||
/** 是否使用 Coze 2.0 的 Button 组件,默认 false */
|
||||
isV2?: boolean;
|
||||
/** 自定义 Coze 2.0 Button 的 props */
|
||||
v2ButtonProps?: ButtonProps;
|
||||
onBeforeAuthRedirect?: (
|
||||
parameters: Pick<AuthorizeButtonProps, 'id' | 'authInfo' | 'origin'>,
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const AuthorizeButton = ({
|
||||
status,
|
||||
id,
|
||||
agentType = 'bot',
|
||||
channelName,
|
||||
revokeSuccess,
|
||||
origin,
|
||||
authInfo,
|
||||
isMouseIn = true,
|
||||
isV2 = false,
|
||||
v2ButtonProps = {
|
||||
color: 'highlight',
|
||||
size: 'small',
|
||||
},
|
||||
onBeforeAuthRedirect,
|
||||
}: AuthorizeButtonProps) => {
|
||||
const isConfiguredOrConfiguring = [
|
||||
ConfigStatus.Configured,
|
||||
ConfigStatus.Configuring,
|
||||
].includes(status as ConfigStatus);
|
||||
|
||||
const handleAuth = () => {
|
||||
if (!checkAuthInfoValid(authInfo)) {
|
||||
logAndToastAuthInfoError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(origin === 'publish' && status === ConfigStatus.NotConfigured) ||
|
||||
(origin === 'setting' && status === AuthStatus.Unauthorized)
|
||||
) {
|
||||
sendTeaEvent(
|
||||
origin === 'publish'
|
||||
? EVENT_NAMES.publish_oauth_button_click
|
||||
: EVENT_NAMES.settings_oauth_button_click,
|
||||
{ action: '授权', channel_name: channelName },
|
||||
);
|
||||
onBeforeAuthRedirect?.({ id, authInfo, origin });
|
||||
executeAuthRedirect({ id, authInfo, origin });
|
||||
}
|
||||
|
||||
if (
|
||||
(origin === 'publish' && isConfiguredOrConfiguring) ||
|
||||
(origin === 'setting' && status === AuthStatus.Authorized)
|
||||
) {
|
||||
sendTeaEvent(
|
||||
origin === 'publish'
|
||||
? EVENT_NAMES.publish_oauth_button_click
|
||||
: EVENT_NAMES.settings_oauth_button_click,
|
||||
{ action: '解除授权', channel_name: channelName },
|
||||
);
|
||||
openRevokeAuthModal();
|
||||
}
|
||||
};
|
||||
|
||||
const { revokeLoading, runRevoke } = useRevokeAuth({
|
||||
id,
|
||||
onRevokeSuccess: revokeSuccess,
|
||||
onRevokeFinally: () => closeRevokeAuthModal(),
|
||||
});
|
||||
|
||||
const {
|
||||
open: openRevokeAuthModal,
|
||||
close: closeRevokeAuthModal,
|
||||
modal: revokeModal,
|
||||
visible: revokeModalVisible,
|
||||
} = useUIModal({
|
||||
confirmLoading: revokeLoading,
|
||||
type: 'info',
|
||||
title: I18n.t('user_revoke_authorization_title'),
|
||||
onOk: runRevoke,
|
||||
okText: I18n.t('Confirm'),
|
||||
cancelText: I18n.t('Cancel'),
|
||||
icon: (
|
||||
<IconAlertCircle
|
||||
style={{ color: 'var(--semi-color-danger)' }}
|
||||
size="extra-large"
|
||||
/>
|
||||
),
|
||||
onCancel: () => {
|
||||
closeRevokeAuthModal();
|
||||
},
|
||||
okButtonProps: {
|
||||
type: 'danger',
|
||||
},
|
||||
});
|
||||
|
||||
const buttonText = I18n.t(
|
||||
isConfiguredOrConfiguring
|
||||
? 'bot_publish_columns_action_revoke_authorize'
|
||||
: 'bot_publish_columns_action_authorize',
|
||||
);
|
||||
|
||||
const authButton = isV2 ? (
|
||||
<Button onClick={handleAuth} {...v2ButtonProps}>
|
||||
{buttonText}
|
||||
</Button>
|
||||
) : (
|
||||
<UIButton onClick={handleAuth} theme="borderless">
|
||||
{buttonText}
|
||||
</UIButton>
|
||||
);
|
||||
|
||||
return status === ConfigStatus.Configured ? (
|
||||
<>
|
||||
{/* 在 hover 渠道表单对应行,或“撤销授权”弹窗显示中时,显示“撤销授权”按钮 */}
|
||||
{isMouseIn || revokeModalVisible ? authButton : null}
|
||||
{revokeModal(
|
||||
agentType === 'project' ? (
|
||||
<Typography.Text type="secondary">
|
||||
{I18n.t('project_release_cancel1_desc')}
|
||||
</Typography.Text>
|
||||
) : null,
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
authButton
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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 { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Tooltip } from '@coze-arch/coze-design';
|
||||
import { Popconfirm, UIIconButton } from '@coze-arch/bot-semi';
|
||||
import { IconStopOutlined, IconAuto } from '@coze-arch/bot-icons';
|
||||
|
||||
import commonStyles from '../../assets/styles/index.module.less';
|
||||
|
||||
interface AutoGenerateProps {
|
||||
needConfirmAgain: boolean;
|
||||
confirmAgainTexts: {
|
||||
title: string;
|
||||
content: string;
|
||||
};
|
||||
autoTrigger: boolean;
|
||||
loading: boolean;
|
||||
setLoading?: (autoLoading: boolean) => void;
|
||||
generate: () => void;
|
||||
cancel: () => void;
|
||||
}
|
||||
|
||||
export const AutoGenerateButton: React.FC<AutoGenerateProps> = ({
|
||||
needConfirmAgain,
|
||||
confirmAgainTexts,
|
||||
loading,
|
||||
autoTrigger = false,
|
||||
setLoading,
|
||||
generate,
|
||||
cancel,
|
||||
}) => {
|
||||
const isReadonly = useBotDetailIsReadonly();
|
||||
|
||||
useEffect(() => {
|
||||
setLoading?.(loading);
|
||||
}, [loading]);
|
||||
|
||||
const handleClick = () => {
|
||||
// loading时 触发stop生成
|
||||
if (loading) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
// 有开场白时 点击触发二次确认弹窗
|
||||
if (needConfirmAgain) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 其余触发自动生成开场白逻辑
|
||||
generate();
|
||||
};
|
||||
|
||||
const btn = (
|
||||
<span>
|
||||
<Tooltip
|
||||
content={
|
||||
loading
|
||||
? I18n.t('stop_generating')
|
||||
: I18n.t('bot_edit_opening_tooltip')
|
||||
}
|
||||
>
|
||||
<UIIconButton
|
||||
className={commonStyles['icon-button-16']}
|
||||
iconSize="small"
|
||||
icon={loading ? <IconStopOutlined /> : <IconAuto />}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{autoTrigger
|
||||
? loading
|
||||
? I18n.t('stop_generating')
|
||||
: I18n.t('bot_edit_opening_tooltip')
|
||||
: null}
|
||||
</UIIconButton>
|
||||
</Tooltip>
|
||||
</span>
|
||||
);
|
||||
return needConfirmAgain && !loading ? (
|
||||
<Popconfirm
|
||||
disabled={isReadonly}
|
||||
trigger="click"
|
||||
okType="danger"
|
||||
okText={I18n.t('bot_opening_remarks_replace_confirm_button')}
|
||||
cancelText={I18n.t('bot_opening_remarks_replace_cancel_button')}
|
||||
onConfirm={generate}
|
||||
{...confirmAgainTexts}
|
||||
>
|
||||
{btn}
|
||||
</Popconfirm>
|
||||
) : (
|
||||
<span style={{ display: 'inline-block' }}>{btn}</span>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
.error-container {
|
||||
.error-link {
|
||||
.error-link-underline {
|
||||
text-decoration: underline;
|
||||
color: var(--semi-color-danger);
|
||||
font-size: 14px;
|
||||
margin-left: 2px;
|
||||
max-width: 200px;
|
||||
|
||||
a {
|
||||
color: var(--semi-color-danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 BindConnectorResponse,
|
||||
type GetBindConnectorConfigResponse,
|
||||
type SaveBindConnectorConfigResponse,
|
||||
} from '@coze-arch/idl/developer_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Form, Typography } from '@coze-arch/bot-semi';
|
||||
import { type ApiError } from '@coze-arch/bot-http';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
type ErrorResponse =
|
||||
| GetBindConnectorConfigResponse
|
||||
| SaveBindConnectorConfigResponse
|
||||
| BindConnectorResponse;
|
||||
|
||||
function isBindConnectorResponse(
|
||||
res: ErrorResponse,
|
||||
): res is BindConnectorResponse {
|
||||
return ['bind_bot_id', 'bind_bot_name', 'bind_space_id'].every(
|
||||
key => key in res,
|
||||
);
|
||||
}
|
||||
|
||||
export interface ConnectorErrorProps {
|
||||
errorMessage: ApiError;
|
||||
}
|
||||
|
||||
export const ConnectorError = ({ errorMessage }: ConnectorErrorProps) => {
|
||||
const res = (errorMessage?.raw ?? {}) as ErrorResponse;
|
||||
|
||||
return (
|
||||
<Form.ErrorMessage
|
||||
error={
|
||||
isBindConnectorResponse(res) ? (
|
||||
<div className={styles['error-link']}>
|
||||
{I18n.t('bot_publish_bind_error', {
|
||||
bot_name: (
|
||||
<Typography.Text
|
||||
className={styles['error-link-underline']}
|
||||
link={{
|
||||
href: `/space/${res.bind_space_id}/${res.bind_agent_type === 1 ? 'project-ide' : 'bot'}/${res.bind_bot_id}`,
|
||||
}}
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: {
|
||||
content: res.bind_bot_name,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{res.bind_bot_name}
|
||||
</Typography.Text>
|
||||
),
|
||||
key_name: 'token',
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
errorMessage?.msg
|
||||
)
|
||||
}
|
||||
className={styles['error-container']}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
.disable-field {
|
||||
padding: 12px 0 24px;
|
||||
|
||||
.title {
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
&& {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-input-suffix {
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.link-button {
|
||||
&&& {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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 { logger } from '@coze-arch/logger';
|
||||
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
|
||||
import { IconCozTrashCan, IconCozPlus } from '@coze-arch/coze-design/icons';
|
||||
import { TagGroup, ArrayField, Button } from '@coze-arch/coze-design';
|
||||
import { typeSafeJSONParse } from '@coze-arch/bot-utils';
|
||||
import { type RuleItem } from '@coze-arch/bot-semi/Form';
|
||||
import { UIFormInput, Form, Typography } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
type Options,
|
||||
type FormSchemaItem,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { type TFormData } from '../types';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
function formatMultiSelectValue(rawValue: string, enums?: Options[]) {
|
||||
const arrayValue = typeSafeJSONParse(rawValue) as string[] | undefined;
|
||||
if (!arrayValue) {
|
||||
return [];
|
||||
}
|
||||
return arrayValue.map(value => ({
|
||||
children: enums?.find(option => option.value === value)?.label ?? value,
|
||||
}));
|
||||
}
|
||||
|
||||
export interface ConnectorFieldProps {
|
||||
formItemSchema: FormSchemaItem;
|
||||
isReadOnly: boolean;
|
||||
initValue?: TFormData;
|
||||
}
|
||||
|
||||
export const ConnectorField = (props: ConnectorFieldProps) => {
|
||||
const { formItemSchema, isReadOnly, initValue } = props;
|
||||
const rawInitValue = initValue?.[formItemSchema.name];
|
||||
|
||||
if (isReadOnly) {
|
||||
return (
|
||||
<div className={styles['disable-field']}>
|
||||
<div className={styles.title}>{formItemSchema.title}</div>
|
||||
{formItemSchema.type === 'array' ? (
|
||||
<TagGroup
|
||||
tagList={formatMultiSelectValue(rawInitValue, formItemSchema.enums)}
|
||||
/>
|
||||
) : (
|
||||
<Typography.Text
|
||||
style={{ width: '100%' }}
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: {
|
||||
content: rawInitValue,
|
||||
style: { wordBreak: 'break-word' },
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{rawInitValue}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function createRules(fieldSchema: FormSchemaItem): RuleItem[] {
|
||||
// 确保 formItemSchema.rules 是一个数组
|
||||
const itemRules = fieldSchema.rules ?? [];
|
||||
|
||||
const rules = itemRules.map(rule => {
|
||||
const ruleMessage = rule.message
|
||||
? I18n.t(rule.message as I18nKeysNoOptionsType, {
|
||||
field: fieldSchema.name,
|
||||
})
|
||||
: undefined;
|
||||
|
||||
return { ...rule, ...(ruleMessage && { message: ruleMessage }) };
|
||||
});
|
||||
|
||||
// 添加 'required' 规则
|
||||
rules.push({
|
||||
required: fieldSchema.required,
|
||||
message: I18n.t('bot_publish_field_placeholder', {
|
||||
field: fieldSchema.title ?? '',
|
||||
}),
|
||||
});
|
||||
|
||||
return rules as RuleItem[];
|
||||
}
|
||||
|
||||
if (!formItemSchema.name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (formItemSchema.component) {
|
||||
case 'Input':
|
||||
if (formItemSchema.type === 'array') {
|
||||
let values: string[] = [];
|
||||
try {
|
||||
values = JSON.parse(rawInitValue);
|
||||
} catch (e) {
|
||||
logger.error({ error: e as Error });
|
||||
values = [];
|
||||
}
|
||||
// 添加一个默认空值
|
||||
if (!values.length) {
|
||||
values.push('');
|
||||
}
|
||||
|
||||
return (
|
||||
<ArrayField field={formItemSchema.name} initValue={values}>
|
||||
{({ arrayFields, add }) => (
|
||||
<>
|
||||
{arrayFields.map(({ key, field, remove }, i) => (
|
||||
<UIFormInput
|
||||
key={key}
|
||||
placeholder={I18n.t('bot_publish_field_placeholder', {
|
||||
field: formItemSchema.title ?? '',
|
||||
})}
|
||||
field={field}
|
||||
label={formItemSchema.title}
|
||||
noLabel={i > 0}
|
||||
required={formItemSchema.required}
|
||||
rules={createRules(formItemSchema)}
|
||||
fieldClassName={styles.input}
|
||||
suffix={
|
||||
arrayFields.length <= 1 ? null : (
|
||||
<IconCozTrashCan onClick={remove} />
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
className={styles['link-button']}
|
||||
color="highlight"
|
||||
size="small"
|
||||
icon={<IconCozPlus />}
|
||||
onClick={add}
|
||||
>
|
||||
{I18n.t('binding_add_card')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</ArrayField>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<UIFormInput
|
||||
key={formItemSchema.name}
|
||||
placeholder={I18n.t('bot_publish_field_placeholder', {
|
||||
field: formItemSchema.title ?? '',
|
||||
})}
|
||||
field={formItemSchema.name}
|
||||
label={formItemSchema.title}
|
||||
required={formItemSchema.required}
|
||||
showClear
|
||||
rules={createRules(formItemSchema)}
|
||||
initValue={rawInitValue}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'Select': {
|
||||
const isMultiple = formItemSchema.type === 'array';
|
||||
const selectInitValue = isMultiple
|
||||
? (typeSafeJSONParse(rawInitValue) as string[] | undefined)
|
||||
: rawInitValue;
|
||||
return (
|
||||
<Form.Select
|
||||
key={formItemSchema.name}
|
||||
placeholder={`Enter ${formItemSchema.title}`}
|
||||
field={formItemSchema.name}
|
||||
label={formItemSchema.title}
|
||||
optionList={formItemSchema.enums}
|
||||
multiple={isMultiple}
|
||||
rules={createRules(formItemSchema)}
|
||||
initValue={selectInitValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
.step-order {
|
||||
margin-top: 2px;
|
||||
display: flex;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
background: var(--light-color-brand-brand-5, #4d53e8);
|
||||
color: var(--light-color-white-white, #fff);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.step-title {
|
||||
color: #000;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.markdown {
|
||||
color: var(--light-usage-text-color-text-0, #1D1C23);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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 -- ignore */
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import {
|
||||
forwardRef,
|
||||
type Ref,
|
||||
useRef,
|
||||
useImperativeHandle,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
|
||||
import { useUpdate } from 'ahooks';
|
||||
import type { FormApi } from '@coze-arch/bot-semi/Form';
|
||||
import { Space, Form } from '@coze-arch/bot-semi';
|
||||
import { type ApiError } from '@coze-arch/bot-http';
|
||||
import { type SchemaAreaInfo } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { type FormActions, type TFormData } from '../types';
|
||||
import { ConnectorField } from '../connector-field';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface ConnectorFormProps {
|
||||
schemaAreaInfo?: SchemaAreaInfo;
|
||||
initValue?: TFormData;
|
||||
getFormDisable: (disable: boolean) => void;
|
||||
isReadOnly: boolean;
|
||||
setErrorMessage: (error?: ApiError) => void;
|
||||
}
|
||||
|
||||
const DEFAULT_FORM_STEP = 2;
|
||||
|
||||
// 多选 Select 在 Form 中的 value 是 string[],但提交到后端需要转换成 JSON string
|
||||
type FormValues = Record<string, string | string[]>;
|
||||
|
||||
export const ConnectorForm = forwardRef(
|
||||
(props: ConnectorFormProps, ref: Ref<FormActions>) => {
|
||||
const {
|
||||
schemaAreaInfo,
|
||||
initValue,
|
||||
getFormDisable,
|
||||
isReadOnly,
|
||||
setErrorMessage,
|
||||
} = props;
|
||||
|
||||
const formApiRef = useRef<FormApi<FormValues>>();
|
||||
const update = useUpdate();
|
||||
|
||||
useImperativeHandle<FormActions, FormActions>(ref, () => ({
|
||||
submit: async () => {
|
||||
const values = await formApiRef.current?.validate();
|
||||
return Object.fromEntries(
|
||||
Object.entries(values ?? {}).map(([key, value]) => [
|
||||
key,
|
||||
Array.isArray(value) ? JSON.stringify(value) : value,
|
||||
]),
|
||||
);
|
||||
},
|
||||
reset: () => formApiRef.current?.reset(),
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
// 解决formApiRef.current取值不实时问题
|
||||
update();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- ignore
|
||||
}, [schemaAreaInfo]);
|
||||
|
||||
const formDisabled =
|
||||
schemaAreaInfo?.schema_list
|
||||
?.filter(item => item.required)
|
||||
.some(field => {
|
||||
const value = formApiRef.current?.getValue(field.name);
|
||||
if (Array.isArray(value)) {
|
||||
return !value.length || (value.length === 1 && !value[0]);
|
||||
}
|
||||
return !value;
|
||||
}) || !schemaAreaInfo?.schema_list?.length;
|
||||
|
||||
useEffect(() => {
|
||||
getFormDisable(formDisabled);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- ignore
|
||||
}, [formDisabled]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{schemaAreaInfo?.title_text ? (
|
||||
<Space spacing={12} align="start">
|
||||
<span className={styles['step-order']}>
|
||||
{schemaAreaInfo.step_order || DEFAULT_FORM_STEP}
|
||||
</span>
|
||||
|
||||
<div className={styles['step-content']}>
|
||||
<div className={styles['step-title']}>
|
||||
{schemaAreaInfo.title_text}
|
||||
</div>
|
||||
</div>
|
||||
</Space>
|
||||
) : null}
|
||||
{schemaAreaInfo?.description ? (
|
||||
<ReactMarkdown skipHtml={true} className={styles.markdown}>
|
||||
{schemaAreaInfo?.description}
|
||||
</ReactMarkdown>
|
||||
) : null}
|
||||
|
||||
{schemaAreaInfo?.schema_list?.length ? (
|
||||
<Form<FormValues>
|
||||
initValues={initValue}
|
||||
className={styles['config-form']}
|
||||
onValueChange={() => {
|
||||
update();
|
||||
setErrorMessage(undefined);
|
||||
}}
|
||||
getFormApi={formApi => (formApiRef.current = formApi)}
|
||||
autoScrollToError
|
||||
allowEmpty
|
||||
>
|
||||
{schemaAreaInfo?.schema_list?.map(item => (
|
||||
<ConnectorField
|
||||
initValue={initValue}
|
||||
formItemSchema={item}
|
||||
isReadOnly={isReadOnly}
|
||||
key={item.name}
|
||||
/>
|
||||
))}
|
||||
</Form>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
.start-text {
|
||||
color: var(--light-usage-text-color-text-0, #1D1C23);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.config-link {
|
||||
color: var(--light-color-brand-brand-5, #4D53E8);
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
|
||||
}
|
||||
|
||||
.markdown {
|
||||
color: var(--light-usage-text-color-text-0, #1D1C23);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.guide {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
@@ -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 ReactMarkdown from 'react-markdown';
|
||||
|
||||
import { Typography } from '@coze-arch/bot-semi';
|
||||
import { type QuerySchemaConfig } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export const ConnectorGuide = ({
|
||||
connectorConfigInfo = {},
|
||||
}: {
|
||||
connectorConfigInfo?: QuerySchemaConfig;
|
||||
}) => (
|
||||
<div className={styles.guide}>
|
||||
{connectorConfigInfo?.start_text ? (
|
||||
<ReactMarkdown
|
||||
skipHtml={true}
|
||||
linkTarget="_blank"
|
||||
className={styles.markdown}
|
||||
>
|
||||
{connectorConfigInfo?.start_text}
|
||||
</ReactMarkdown>
|
||||
) : null}
|
||||
{connectorConfigInfo?.guide_link_url &&
|
||||
connectorConfigInfo?.guide_link_text ? (
|
||||
<div>
|
||||
<Typography.Text
|
||||
link={{
|
||||
href: connectorConfigInfo?.guide_link_url,
|
||||
}}
|
||||
className={styles['config-link']}
|
||||
>
|
||||
{connectorConfigInfo?.guide_link_text}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,49 @@
|
||||
.step-order {
|
||||
margin-top: 2px;
|
||||
display: flex;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
background: var(--light-color-brand-brand-5, #4d53e8);
|
||||
color: var(--light-color-white-white, #fff);
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.step-title {
|
||||
color: #000;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.markdown {
|
||||
color: var(--light-usage-text-color-text-0, #1D1C23);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.link-area .link-list {
|
||||
margin-top: 16px;
|
||||
|
||||
.title {
|
||||
color: var(--light-usage-text-color-text-0, #1D1C23);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
|
||||
}
|
||||
|
||||
.link {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.semi-form-field-error-message {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 ReactMarkdown from 'react-markdown';
|
||||
|
||||
import { Space, Typography } from '@coze-arch/bot-semi';
|
||||
import { type CopyLinkAreaInfo } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { type TFormData } from '../types';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export const ConnectorLink = ({
|
||||
copyLinkAreaInfo = {},
|
||||
agentType = 'bot',
|
||||
botId = '',
|
||||
initValue = {},
|
||||
}: {
|
||||
copyLinkAreaInfo?: CopyLinkAreaInfo;
|
||||
agentType?: 'bot' | 'project';
|
||||
botId: string;
|
||||
initValue?: TFormData;
|
||||
}) => {
|
||||
//支持通配URL
|
||||
const formatUrl = (url?: string) => {
|
||||
let newUrl = url ?? '';
|
||||
if (newUrl) {
|
||||
if (agentType === 'project') {
|
||||
newUrl = newUrl.replace(/{project_id}/g, botId);
|
||||
} else {
|
||||
newUrl = newUrl.replace(/{bot_id}/g, botId);
|
||||
}
|
||||
newUrl = newUrl
|
||||
.replace(/{hostname}/g, window.location.hostname)
|
||||
.replace(/{corp_id}/g, initValue.corp_id);
|
||||
}
|
||||
|
||||
return newUrl;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles['link-area']}>
|
||||
{copyLinkAreaInfo?.title_text ? (
|
||||
<Space spacing={12} align="start">
|
||||
<span className={styles['step-order']}>
|
||||
{copyLinkAreaInfo.step_order || 1}
|
||||
</span>
|
||||
|
||||
<div className={styles['step-content']}>
|
||||
<div className={styles['step-title']}>
|
||||
{copyLinkAreaInfo.title_text}
|
||||
</div>
|
||||
</div>
|
||||
</Space>
|
||||
) : null}
|
||||
{copyLinkAreaInfo?.description ? (
|
||||
<ReactMarkdown skipHtml={true} className={styles.markdown}>
|
||||
{copyLinkAreaInfo.description}
|
||||
</ReactMarkdown>
|
||||
) : null}
|
||||
|
||||
{copyLinkAreaInfo?.link_list?.length ? (
|
||||
<div className={styles['link-list']}>
|
||||
{copyLinkAreaInfo?.link_list.map(item => (
|
||||
<div key={item.link} style={{ marginBottom: 32 }}>
|
||||
<Typography.Title className={styles.title}>
|
||||
{item.title}
|
||||
</Typography.Title>
|
||||
<Typography.Text className={styles.link} copyable>
|
||||
{formatUrl(item.link)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
import {
|
||||
type SchemaAreaPage,
|
||||
SchemaAreaPageApi,
|
||||
type GetBindConnectorConfigResponse,
|
||||
type SaveBindConnectorConfigResponse,
|
||||
type BindConnectorResponse,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { DeveloperApi } from '@coze-arch/bot-api';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
export type ActionResponse =
|
||||
| {
|
||||
action: SchemaAreaPageApi.BindConnector;
|
||||
data: BindConnectorResponse;
|
||||
}
|
||||
| {
|
||||
action: SchemaAreaPageApi.GetBindConnectorConfig;
|
||||
data: GetBindConnectorConfigResponse;
|
||||
}
|
||||
| {
|
||||
action: SchemaAreaPageApi.SaveBindConnectorConfig;
|
||||
data: SaveBindConnectorConfigResponse;
|
||||
}
|
||||
| {
|
||||
action: SchemaAreaPageApi.NotQuery;
|
||||
data: undefined;
|
||||
};
|
||||
|
||||
interface StepActionProps {
|
||||
botId: string;
|
||||
origin?: 'bot' | 'project';
|
||||
schemaPages: SchemaAreaPage[];
|
||||
onNextStepSuccess: (resp: ActionResponse) => void;
|
||||
onNextStepError: (error: Error) => void;
|
||||
}
|
||||
interface StepRunParams {
|
||||
connectorId: string;
|
||||
assignFormValue: Record<string, string>;
|
||||
}
|
||||
|
||||
export const useStepAction = ({
|
||||
botId,
|
||||
origin = 'bot',
|
||||
schemaPages,
|
||||
onNextStepSuccess,
|
||||
onNextStepError,
|
||||
}: StepActionProps) => {
|
||||
const [step, setStep] = useState(0);
|
||||
|
||||
const { space_id = '' } = useParams<DynamicParams>();
|
||||
|
||||
const agentType = origin === 'bot' ? 0 : 1;
|
||||
|
||||
const currentAction =
|
||||
schemaPages?.[step]?.api_action ?? SchemaAreaPageApi.BindConnector;
|
||||
|
||||
const SERVICE_MAP = {
|
||||
[SchemaAreaPageApi.NotQuery]: async () => await Promise.resolve(),
|
||||
[SchemaAreaPageApi.GetBindConnectorConfig]: async (
|
||||
params?: StepRunParams,
|
||||
) => {
|
||||
const data = await DeveloperApi.GetBindConnectorConfig({
|
||||
connector_id: params?.connectorId ?? '',
|
||||
detail: params?.assignFormValue ?? {},
|
||||
agent_type: agentType,
|
||||
bot_id: botId,
|
||||
space_id,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
[SchemaAreaPageApi.SaveBindConnectorConfig]: async (
|
||||
params?: StepRunParams,
|
||||
) => {
|
||||
const data = await DeveloperApi.SaveBindConnectorConfig({
|
||||
connector_id: params?.connectorId ?? '',
|
||||
detail: params?.assignFormValue ?? {},
|
||||
agent_type: agentType,
|
||||
bot_id: botId,
|
||||
space_id,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
[SchemaAreaPageApi.BindConnector]: async (params?: StepRunParams) => {
|
||||
const res = await DeveloperApi.BindConnector(
|
||||
{
|
||||
connector_id: params?.connectorId ?? '',
|
||||
connector_info: params?.assignFormValue ?? {},
|
||||
agent_type: agentType,
|
||||
bot_id: botId,
|
||||
space_id,
|
||||
},
|
||||
{ __disableErrorToast: true },
|
||||
);
|
||||
return res;
|
||||
},
|
||||
};
|
||||
|
||||
const { run, loading } = useRequest(
|
||||
async (params?: StepRunParams) => await SERVICE_MAP[currentAction](params),
|
||||
{
|
||||
manual: true,
|
||||
ready: Object.keys(SERVICE_MAP).includes(String(currentAction)),
|
||||
onSuccess: data => {
|
||||
const action = currentAction as
|
||||
| SchemaAreaPageApi.BindConnector
|
||||
| SchemaAreaPageApi.GetBindConnectorConfig;
|
||||
onNextStepSuccess?.({ data, action });
|
||||
},
|
||||
onError: error => {
|
||||
onNextStepError(error);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
run,
|
||||
loading,
|
||||
step,
|
||||
setStep,
|
||||
};
|
||||
};
|
||||
@@ -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 type TFormData = Record<string, string>;
|
||||
|
||||
export type TSubmitValue = Record<string, string>;
|
||||
|
||||
export interface FormActions {
|
||||
submit: () => Promise<TSubmitValue>;
|
||||
reset: () => void;
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* 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 @coze-arch/max-line-per-function */
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
import { type PublishConnectorInfo } from '@coze-arch/idl/intelligence_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { UIButton, useUIModal, UIToast, Spin } from '@coze-arch/bot-semi';
|
||||
import { isApiError, type ApiError } from '@coze-arch/bot-http';
|
||||
import {
|
||||
type PublishConnectorInfo as BotPublishConnectorInfo,
|
||||
type QuerySchemaConfig,
|
||||
BindType,
|
||||
SchemaAreaPageApi,
|
||||
type BindConnectorResponse,
|
||||
type SchemaAreaInfo,
|
||||
type CopyLinkAreaInfo,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { DeveloperApi } from '@coze-arch/bot-api';
|
||||
import { connector2Redirect } from '@coze-foundation/account-adapter';
|
||||
|
||||
import styles from '../../pages/publish/index.module.less';
|
||||
import { useUnbindPlatformModal } from '../../hook/use-unbind-platform';
|
||||
import { type FormActions, type TSubmitValue } from './types';
|
||||
import { type ActionResponse, useStepAction } from './hooks/use-step-action';
|
||||
import { ConnectorLink } from './connector-link';
|
||||
import { ConnectorGuide } from './connector-guide';
|
||||
import { ConnectorForm } from './connector-form';
|
||||
import { ConnectorError } from './connector-error';
|
||||
|
||||
interface ConnectorConfigureProps {
|
||||
botId: string;
|
||||
origin?: 'project' | 'bot';
|
||||
onSuccess: (
|
||||
val: BotPublishConnectorInfo | PublishConnectorInfo | undefined,
|
||||
) => void;
|
||||
onUnbind?: () => void;
|
||||
}
|
||||
|
||||
interface ConnectorConfigureValueType {
|
||||
initValue: BotPublishConnectorInfo | PublishConnectorInfo;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
export const useConnectorFormModal = ({
|
||||
botId,
|
||||
origin = 'bot',
|
||||
onSuccess,
|
||||
onUnbind,
|
||||
}: ConnectorConfigureProps) => {
|
||||
const formRef = useRef<FormActions>(null);
|
||||
|
||||
const [propsValue, setPropsValue] = useState<ConnectorConfigureValueType>();
|
||||
|
||||
const { initValue } = propsValue ?? {};
|
||||
const [errorMessage, setErrorMessage] = useState<ApiError>();
|
||||
|
||||
const [formDisabled, setFormDisabled] = useState(false);
|
||||
|
||||
const [assignValue, setAssignValue] = useState<TSubmitValue>();
|
||||
const bindId = useRef('');
|
||||
const handleClose = () => {
|
||||
setErrorMessage(undefined);
|
||||
setStep(0);
|
||||
setAssignValue(undefined);
|
||||
formRef.current?.reset();
|
||||
close();
|
||||
};
|
||||
|
||||
const handleUnbind = () => {
|
||||
handleClose();
|
||||
if (onUnbind) {
|
||||
onUnbind();
|
||||
} else {
|
||||
// 兼容历史逻辑,未传入 onUnbind 时,解绑后也调用 onSuccess
|
||||
onSuccess({
|
||||
...(initValue as BotPublishConnectorInfo),
|
||||
bind_info: {},
|
||||
bind_id: '',
|
||||
});
|
||||
}
|
||||
UIToast.success(I18n.t('bot_publish_disconnect_success'));
|
||||
};
|
||||
|
||||
const [connectorConfigInfo, setConnectorConfigInfo] =
|
||||
useState<QuerySchemaConfig>();
|
||||
|
||||
const lastConnectId = useRef<string>();
|
||||
|
||||
const { loading: formSchemaLoading } = useRequest(
|
||||
async () => {
|
||||
const data = await DeveloperApi.QuerySchemaList({
|
||||
connector_id: initValue?.id ?? '',
|
||||
scene: origin,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
{
|
||||
ready: Boolean(initValue?.id),
|
||||
refreshDeps: [initValue?.id],
|
||||
onBefore: () => {
|
||||
if (initValue?.id !== lastConnectId.current) {
|
||||
lastConnectId.current = initValue?.id;
|
||||
setConnectorConfigInfo({});
|
||||
}
|
||||
},
|
||||
onSuccess: data => {
|
||||
if (!data.schema_area_pages?.length) {
|
||||
data.schema_area_pages = [
|
||||
{
|
||||
schema_area: data.schema_area,
|
||||
copy_link_area: data.copy_link_area,
|
||||
},
|
||||
];
|
||||
}
|
||||
setConnectorConfigInfo(data);
|
||||
},
|
||||
onError: () => {
|
||||
setConnectorConfigInfo({});
|
||||
},
|
||||
},
|
||||
);
|
||||
const { schema_area_pages: schemaPages = [] } = connectorConfigInfo ?? {};
|
||||
|
||||
const bindCb = (data: BindConnectorResponse) => {
|
||||
/** 适用Kv+Auth授权场景:KvAuthBind = 4
|
||||
* reddit渠道:若成功返回client_id,则覆盖auth_login_info中的client_id,并附带加密state跳转授权页面
|
||||
* 其余渠道:若成功返回auth_params,则合并auth_login_info作为授权链接参数跳转
|
||||
* */
|
||||
if (
|
||||
initValue?.bind_type === BindType.KvAuthBind &&
|
||||
(data?.client_id || data?.auth_params)
|
||||
) {
|
||||
connector2Redirect(
|
||||
{
|
||||
navigatePath: `${location.pathname}${location.search}`,
|
||||
type: 'oauth',
|
||||
extra: {
|
||||
origin: 'publish',
|
||||
encrypt_state: data?.encrypt_state,
|
||||
},
|
||||
},
|
||||
initValue?.id || '',
|
||||
{
|
||||
...initValue?.auth_login_info,
|
||||
client_id: data?.client_id,
|
||||
...data.auth_params,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
bindId.current = data?.bind_id ?? '';
|
||||
}
|
||||
};
|
||||
|
||||
const stepCallback = () => {
|
||||
const isLastStep = step === schemaPages?.length - 1;
|
||||
if (isLastStep) {
|
||||
if (initValue) {
|
||||
onSuccess({
|
||||
...initValue,
|
||||
bind_info: { ...assignValue },
|
||||
bind_id: bindId.current,
|
||||
});
|
||||
}
|
||||
handleClose();
|
||||
} else {
|
||||
setStep(step + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
loading,
|
||||
run: nextStepRun,
|
||||
step,
|
||||
setStep,
|
||||
} = useStepAction({
|
||||
botId,
|
||||
origin,
|
||||
schemaPages,
|
||||
onNextStepSuccess: (resp: ActionResponse) => {
|
||||
if (resp.action === SchemaAreaPageApi.BindConnector) {
|
||||
bindCb(resp.data);
|
||||
}
|
||||
if (resp.action === SchemaAreaPageApi.GetBindConnectorConfig) {
|
||||
setAssignValue({
|
||||
...assignValue,
|
||||
...resp.data.config?.detail,
|
||||
});
|
||||
}
|
||||
stepCallback();
|
||||
},
|
||||
onNextStepError: error => {
|
||||
if (isApiError(error)) {
|
||||
setErrorMessage(error);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const { node: unbindPlatformModal, open: openUnbindPlatformModal } =
|
||||
useUnbindPlatformModal({
|
||||
botId,
|
||||
origin,
|
||||
platformInfo: initValue as BotPublishConnectorInfo,
|
||||
onUnbind: () => {
|
||||
handleUnbind();
|
||||
},
|
||||
});
|
||||
|
||||
const nextBtnClick = async () => {
|
||||
const value = await formRef.current?.submit();
|
||||
setAssignValue({ ...assignValue, ...value });
|
||||
nextStepRun({
|
||||
connectorId: initValue?.id ?? '',
|
||||
assignFormValue: { ...assignValue, ...value },
|
||||
});
|
||||
};
|
||||
|
||||
const renderFooter = () =>
|
||||
initValue?.bind_id ? (
|
||||
<>
|
||||
<UIButton
|
||||
theme="light"
|
||||
type="tertiary"
|
||||
onClick={() => {
|
||||
close();
|
||||
setStep(0);
|
||||
}}
|
||||
>
|
||||
{I18n.t('Cancel')}
|
||||
</UIButton>
|
||||
<UIButton theme="solid" type="danger" onClick={openUnbindPlatformModal}>
|
||||
{I18n.t('bot_publish_disconnect', {
|
||||
platform: initValue?.name ?? '',
|
||||
})}
|
||||
</UIButton>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{schemaPages?.length &&
|
||||
step !== 0 &&
|
||||
schemaPages[step]?.api_action !== SchemaAreaPageApi.NotQuery ? (
|
||||
// 页面按钮不执行任何操作时 不展示上一步
|
||||
<UIButton
|
||||
theme="solid"
|
||||
onClick={() => {
|
||||
setErrorMessage(undefined);
|
||||
setStep(step - 1);
|
||||
}}
|
||||
>
|
||||
{I18n.t('Previous_1')}
|
||||
</UIButton>
|
||||
) : null}
|
||||
<UIButton
|
||||
theme="solid"
|
||||
onClick={nextBtnClick}
|
||||
disabled={formDisabled}
|
||||
loading={loading}
|
||||
>
|
||||
{step === (schemaPages?.length ?? 0) - 1
|
||||
? schemaPages[step]?.api_action !== SchemaAreaPageApi.NotQuery
|
||||
? I18n.t('Save')
|
||||
: I18n.t('Complete')
|
||||
: I18n.t('Next_1')}
|
||||
</UIButton>
|
||||
</>
|
||||
);
|
||||
|
||||
const { modal, open, close } = useUIModal({
|
||||
type: 'action-small',
|
||||
footer: renderFooter(),
|
||||
onCancel: handleClose,
|
||||
title: connectorConfigInfo?.title_text,
|
||||
});
|
||||
|
||||
const renderConnectorArea = (
|
||||
copyArea?: CopyLinkAreaInfo,
|
||||
schemaArea?: SchemaAreaInfo,
|
||||
) => (
|
||||
<>
|
||||
{copyArea ? (
|
||||
<ConnectorLink
|
||||
copyLinkAreaInfo={copyArea}
|
||||
agentType={origin}
|
||||
botId={botId}
|
||||
initValue={{ ...initValue?.bind_info, ...assignValue }}
|
||||
/>
|
||||
) : null}
|
||||
{schemaArea ? (
|
||||
<ConnectorForm
|
||||
schemaAreaInfo={schemaArea}
|
||||
initValue={{ ...initValue?.bind_info, ...assignValue }}
|
||||
ref={formRef}
|
||||
getFormDisable={disable => setFormDisabled(disable)}
|
||||
isReadOnly={Boolean(initValue?.bind_id)}
|
||||
setErrorMessage={setErrorMessage}
|
||||
/>
|
||||
) : null}
|
||||
{errorMessage ? <ConnectorError errorMessage={errorMessage} /> : null}
|
||||
</>
|
||||
);
|
||||
return {
|
||||
node: modal(
|
||||
<Spin
|
||||
wrapperClassName={styles['config-area']}
|
||||
spinning={formSchemaLoading}
|
||||
>
|
||||
<ConnectorGuide connectorConfigInfo={connectorConfigInfo} />
|
||||
|
||||
{schemaPages?.length && !initValue?.bind_id ? (
|
||||
<div>
|
||||
{renderConnectorArea(
|
||||
schemaPages[step]?.copy_link_area,
|
||||
schemaPages[step]?.schema_area,
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
{initValue?.bind_id && schemaPages?.length ? (
|
||||
<>
|
||||
{schemaPages?.map((item, i) => (
|
||||
<div key={i}>
|
||||
{renderConnectorArea(item.copy_link_area, item.schema_area)}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
) : null}
|
||||
{unbindPlatformModal}
|
||||
</Spin>,
|
||||
),
|
||||
open: (props: ConnectorConfigureValueType) => {
|
||||
setPropsValue(props);
|
||||
open();
|
||||
},
|
||||
close,
|
||||
};
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import { type Ref, forwardRef, type FC } from 'react';
|
||||
|
||||
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
|
||||
import { type IconButtonProps } from '@coze-arch/coze-design/types';
|
||||
import { Button, IconButton } from '@coze-arch/coze-design';
|
||||
import { type UIButton } from '@coze-arch/bot-semi';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export const BotDebugButton: FC<IconButtonProps> = forwardRef(
|
||||
(props: IconButtonProps, ref: Ref<UIButton>) => {
|
||||
const isReadonly = useBotDetailIsReadonly();
|
||||
|
||||
const className = props.theme || '';
|
||||
if (isReadonly) {
|
||||
return null;
|
||||
}
|
||||
if (props.icon && !props.children) {
|
||||
return <IconButton {...props} className={s[className]} ref={ref} />;
|
||||
}
|
||||
return <Button {...props} className={s[className]} ref={ref} />;
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
.borderless {
|
||||
// padding: 0 !important;
|
||||
}
|
||||
// .solid {
|
||||
// font-size: 12px;
|
||||
// }
|
||||
// .primary {
|
||||
// padding: 6px 12px;
|
||||
// }
|
||||
@@ -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 { BotDebugButton } from './bot-debug-button';
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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 { IconCozDebug } from '@coze-arch/coze-design/icons';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { OperateTypeEnum, ToolPane } from '@coze-agent-ide/debug-tool-list';
|
||||
|
||||
import { useEvaluationPanelStore } from '@/store/evaluation-panel';
|
||||
|
||||
import { useDebugStore } from '../../store/debug-panel';
|
||||
|
||||
export const BotDebugToolPane: React.FC = () => {
|
||||
const { isDebugPanelShow, setIsDebugPanelShow, setCurrentDebugQueryId } =
|
||||
useDebugStore();
|
||||
const { setIsEvaluationPanelVisible } = useEvaluationPanelStore();
|
||||
return (
|
||||
<ToolPane
|
||||
visible={true}
|
||||
itemKey={'key_debug'}
|
||||
title={I18n.t('debug_btn')}
|
||||
operateType={OperateTypeEnum.CUSTOM}
|
||||
icon={(<IconCozDebug />) as React.ReactNode}
|
||||
customShowOperateArea={isDebugPanelShow}
|
||||
beforeVisible={async () => {
|
||||
await sendTeaEvent(EVENT_NAMES.open_debug_panel, {
|
||||
path: 'preview_debug',
|
||||
});
|
||||
setCurrentDebugQueryId('');
|
||||
if (!isDebugPanelShow) {
|
||||
setIsEvaluationPanelVisible(false);
|
||||
}
|
||||
setIsDebugPanelShow(!isDebugPanelShow);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
.container {
|
||||
height: 100%;
|
||||
width: 400px;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-shrink: 0;
|
||||
z-index: 101;
|
||||
|
||||
.debug-panel-lazy-loading {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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 { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { Suspense, lazy, useEffect } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import { setPCBody } from '@coze-arch/bot-utils';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { Spin } from '@coze-arch/bot-semi';
|
||||
|
||||
import { setPCBodyWithDebugPanel } from '../../util';
|
||||
import { useDebugStore } from '../../store/debug-panel';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
const DebugPanel = lazy(() => import('@coze-devops/debug-panel'));
|
||||
|
||||
export const BotDebugPanel = () => {
|
||||
const {
|
||||
isDebugPanelShow,
|
||||
currentDebugQueryId,
|
||||
setIsDebugPanelShow,
|
||||
setCurrentDebugQueryId,
|
||||
} = useDebugStore();
|
||||
const { botId } = useBotInfoStore(
|
||||
useShallow(state => ({
|
||||
botId: state.botId,
|
||||
})),
|
||||
);
|
||||
|
||||
const userID = userStoreService.useUserInfo()?.user_id_str ?? '';
|
||||
|
||||
const { id: spaceID } = useSpaceStore(state => state.space);
|
||||
|
||||
useHotkeys('ctrl+k, meta+k', () => {
|
||||
if (!isDebugPanelShow) {
|
||||
sendTeaEvent(EVENT_NAMES.open_debug_panel, {
|
||||
path: 'shortcut_debug',
|
||||
});
|
||||
}
|
||||
setCurrentDebugQueryId('');
|
||||
setIsDebugPanelShow(!isDebugPanelShow);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isDebugPanelShow) {
|
||||
setPCBodyWithDebugPanel();
|
||||
window.scrollTo(document.body.scrollWidth, 0);
|
||||
} else {
|
||||
setPCBody();
|
||||
}
|
||||
return () => {
|
||||
setPCBody();
|
||||
};
|
||||
}, [isDebugPanelShow]);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
setCurrentDebugQueryId('');
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return isDebugPanelShow ? (
|
||||
<div className={s.container}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className={s['debug-panel-lazy-loading']}>
|
||||
<Spin />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<DebugPanel
|
||||
isShow={isDebugPanelShow}
|
||||
botId={botId}
|
||||
userID={userID}
|
||||
spaceID={spaceID}
|
||||
placement="left"
|
||||
currentQueryLogId={currentDebugQueryId}
|
||||
onClose={() => {
|
||||
setIsDebugPanelShow(false);
|
||||
setCurrentDebugQueryId('');
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
@@ -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 React from 'react';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
import { Spin } from '@coze-arch/bot-semi';
|
||||
import { useFlags } from '@coze-arch/bot-flags';
|
||||
import { Branch } from '@coze-arch/bot-api/dp_manage_api';
|
||||
import { dpManageApi } from '@coze-arch/bot-api';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { NewBotDiffView } from './new-diff-view';
|
||||
import { BotDiffView } from '.';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export const BotSubmitModalDiffView: React.FC<{ visible: boolean }> = props => {
|
||||
const params = useParams<DynamicParams>();
|
||||
const [Flags] = useFlags();
|
||||
const isUseNewTemplate = !!Flags?.['bot.devops.merge_prompt_diff'];
|
||||
const {
|
||||
data: botDiffData,
|
||||
loading,
|
||||
error,
|
||||
} = useRequest(
|
||||
async () => {
|
||||
const { bot_id = '', space_id = '' } = params;
|
||||
const resp = await dpManageApi.BotDiff({
|
||||
space_id,
|
||||
bot_id,
|
||||
left: {
|
||||
branch: Branch.Base,
|
||||
},
|
||||
template_key: isUseNewTemplate ? 'diff_template_v2' : '',
|
||||
right: { branch: Branch.PersonalDraft },
|
||||
});
|
||||
return resp.data;
|
||||
},
|
||||
{ refreshDeps: [] },
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles['modal-diff-container']}
|
||||
style={{ display: props.visible ? 'block' : 'none' }}
|
||||
>
|
||||
{loading ? (
|
||||
<Spin spinning={loading} style={{ height: '100%', width: '100%' }} />
|
||||
) : isUseNewTemplate ? (
|
||||
<NewBotDiffView
|
||||
diffData={botDiffData?.diff_display_node || []}
|
||||
hasError={error !== undefined}
|
||||
/>
|
||||
) : (
|
||||
<BotDiffView
|
||||
diffData={botDiffData?.diff_display_node || []}
|
||||
hasError={error !== undefined}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,129 @@
|
||||
/* stylelint-disable */
|
||||
.info-title {
|
||||
margin-bottom: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.info-subtitle {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.diff-table {
|
||||
margin-bottom: 24px;
|
||||
|
||||
:global {
|
||||
.semi-table-row-head {
|
||||
padding: 4px 8px !important;
|
||||
font-size: 12px;
|
||||
background-color: #2e2e380a !important;
|
||||
border-bottom: 1px solid var(--semi-color-border);
|
||||
}
|
||||
|
||||
.semi-table-row-cell {
|
||||
padding: 10px 8px !important;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cell-span {
|
||||
font-size: 12px !important;
|
||||
font-weight: 400;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.property-tooltip {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: 100%;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.empty-info {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
// .leftNode
|
||||
|
||||
|
||||
|
||||
.list {
|
||||
background-color: white !important;
|
||||
border: 1px solid var(--Stroke-COZ-stroke-plus, rgba(6, 7, 9, 15%)) !important;
|
||||
border-radius: 8px;
|
||||
|
||||
:global {
|
||||
.semi-list-item {
|
||||
border-bottom: 1px solid
|
||||
var(--Stroke-COZ-stroke-plus, rgba(6, 7, 9, 15%)) !important;
|
||||
}
|
||||
|
||||
.semi-list-item:last-child {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 120px 1fr;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tag-1 {
|
||||
color: #3ec254;
|
||||
background-color: #d2f3d5;
|
||||
}
|
||||
|
||||
.tag-2 {
|
||||
color: #ff441e;
|
||||
background-color: #ffe0d2;
|
||||
}
|
||||
|
||||
.tag-4 {
|
||||
color: #ff441e;
|
||||
background-color: #ffe0d2;
|
||||
}
|
||||
|
||||
.tag-3 {
|
||||
color: #ff9600;
|
||||
background-color: #fff1cc;
|
||||
}
|
||||
|
||||
.property-title {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--Fg-COZ-fg-primary, rgba(6, 7, 9, 80%));
|
||||
}
|
||||
|
||||
.info-block&:not(:first-child){
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.mask{
|
||||
pointer-events: none;
|
||||
|
||||
position: absolute;
|
||||
bottom: 80px;
|
||||
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
|
||||
background: linear-gradient(to top, rgba(var(--coze-bg-2), 1) 0, rgba(var(--coze-bg-2), 0) 100%);
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Table, Typography, UITag } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
type DiffDisplayNode,
|
||||
DiffActionType,
|
||||
} from '@coze-arch/bot-api/dp_manage_api';
|
||||
import {
|
||||
DIFF_TABLE_INDENT_BASE,
|
||||
DIFF_TABLE_INDENT_LENGTH,
|
||||
DiffNodeRender,
|
||||
} from '@coze-agent-ide/agent-ide-commons';
|
||||
|
||||
import { flatDataSource } from '../../util';
|
||||
import EmptyIcon from '../../assets/image/diff-empty.svg';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
const ActionTypeEnum = {
|
||||
[DiffActionType.Add]: 'devops_publish_multibranch_changeset_add',
|
||||
[DiffActionType.Delete]: 'devops_publish_multibranch_changeset_delete',
|
||||
[DiffActionType.Modify]: 'devops_publish_multibranch_changeset_modify',
|
||||
[DiffActionType.Remove]: 'devops_publish_multibranch_changeset_remove',
|
||||
};
|
||||
|
||||
export const BotDiffView: React.FC<{
|
||||
diffData: DiffDisplayNode[];
|
||||
hasError: boolean;
|
||||
}> = ({ diffData, hasError }) => (
|
||||
<div className={styles.container}>
|
||||
{diffData?.length > 0 ? (
|
||||
diffData.map(item => (
|
||||
<div className={styles['info-block']} key={item.display_name}>
|
||||
<div className={styles['info-title']}>{item.display_name}</div>
|
||||
{item?.sub_nodes?.length ? (
|
||||
<BotDiffBlockTable blockDiffData={item.sub_nodes} />
|
||||
) : null}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className={styles['empty-container']}>
|
||||
<img src={EmptyIcon} />
|
||||
<Typography.Text className={styles['empty-info']}>
|
||||
{I18n.t(
|
||||
hasError
|
||||
? 'devops_publish_multibranch_NetworkError'
|
||||
: 'devops_publish_multibranch_nodiff',
|
||||
)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const BotDiffBlockTable: React.FC<{
|
||||
blockDiffData: DiffDisplayNode[];
|
||||
}> = ({ blockDiffData }) => {
|
||||
const columns = [
|
||||
{
|
||||
title: I18n.t('devops_publish_multibranch_property'),
|
||||
width: 280,
|
||||
render: node => (
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: {
|
||||
content: node.display_name,
|
||||
className: styles['property-tooltip'],
|
||||
},
|
||||
},
|
||||
}}
|
||||
className={styles['cell-span']}
|
||||
>
|
||||
{node.level > 0 ? (
|
||||
<Typography.Text
|
||||
style={{
|
||||
marginLeft:
|
||||
DIFF_TABLE_INDENT_BASE +
|
||||
DIFF_TABLE_INDENT_LENGTH * (node.level - 1),
|
||||
marginRight: 8,
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
{node.display_name}
|
||||
</Typography.Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: I18n.t('devops_publish_multibranch_changetype'),
|
||||
render: (node: DiffDisplayNode) => {
|
||||
if (
|
||||
!node.diff_res ||
|
||||
node.diff_res?.action === DiffActionType.Unknown
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
return (
|
||||
<UITag className={styles[`tag-${node.diff_res.action}`]}>
|
||||
{I18n.t(ActionTypeEnum[node.diff_res.action])}
|
||||
</UITag>
|
||||
);
|
||||
},
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: I18n.t('devops_publish_multibranch_changes'),
|
||||
render: (node: DiffDisplayNode) =>
|
||||
node?.diff_res?.action === DiffActionType.Modify ? (
|
||||
<DiffNodeRender
|
||||
node={node}
|
||||
left={node?.diff_res?.display_left || ''}
|
||||
right={node?.diff_res?.display_right || ''}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
),
|
||||
ellipsis: true,
|
||||
},
|
||||
];
|
||||
|
||||
if (!blockDiffData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Table
|
||||
dataSource={flatDataSource(blockDiffData)}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
onRow={() => ({
|
||||
className: styles['table-row'],
|
||||
})}
|
||||
className={styles['diff-table']}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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 { List, Typography, UITag } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
type DiffDisplayNode,
|
||||
DiffActionType,
|
||||
} from '@coze-arch/bot-api/dp_manage_api';
|
||||
import {
|
||||
DIFF_TABLE_INDENT_BASE,
|
||||
DIFF_TABLE_INDENT_LENGTH,
|
||||
DiffNodeRender,
|
||||
} from '@coze-agent-ide/agent-ide-commons';
|
||||
|
||||
import { flatDataSource } from '../../util';
|
||||
import EmptyIcon from '../../assets/image/diff-empty.svg';
|
||||
import { type FlatDiffDisplayNode } from './type';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
const ActionTypeEnum = {
|
||||
[DiffActionType.Add]: 'devops_publish_multibranch_changeset_add',
|
||||
[DiffActionType.Delete]: 'devops_publish_multibranch_changeset_delete',
|
||||
[DiffActionType.Modify]: 'devops_publish_multibranch_changeset_modify',
|
||||
[DiffActionType.Remove]: 'devops_publish_multibranch_changeset_remove',
|
||||
};
|
||||
|
||||
export const NewBotDiffView: React.FC<{
|
||||
diffData: DiffDisplayNode[];
|
||||
hasError: boolean;
|
||||
type?: 'diff' | 'publish';
|
||||
}> = ({ diffData, hasError, type = 'diff' }) => (
|
||||
<div className={styles.container}>
|
||||
{diffData?.length > 0 ? (
|
||||
diffData.map(item => (
|
||||
<div className={styles['info-block']} key={item.display_name}>
|
||||
<div className={styles['info-title']}>{item.display_name}</div>
|
||||
{item?.sub_nodes?.length
|
||||
? item?.sub_nodes?.map((node, index) => (
|
||||
<BotSubNode node={node} key={index} type={type} />
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className={styles['empty-container']}>
|
||||
<img src={EmptyIcon} />
|
||||
<Typography.Text className={styles['empty-info']}>
|
||||
{I18n.t(
|
||||
hasError
|
||||
? 'devops_publish_multibranch_NetworkError'
|
||||
: 'devops_publish_multibranch_nodiff',
|
||||
)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
<div className="h-[32px]"></div>
|
||||
<div className={styles.mask}></div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const BotSubNode: React.FC<{
|
||||
node: DiffDisplayNode;
|
||||
type?: 'diff' | 'publish';
|
||||
}> = ({ node, type = 'diff' }) => {
|
||||
const { display_name } = node;
|
||||
return (
|
||||
<div>
|
||||
{display_name ? (
|
||||
<div className={styles['info-subtitle']}>{display_name}</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{node?.sub_nodes?.length ? (
|
||||
<BotDiffBlockTable blockDiffData={node?.sub_nodes} type={type} />
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const BotDiffBlockTable: React.FC<{
|
||||
blockDiffData: DiffDisplayNode[];
|
||||
type: 'diff' | 'publish';
|
||||
}> = ({ blockDiffData, type = 'diff' }) => {
|
||||
if (!blockDiffData) {
|
||||
return null;
|
||||
}
|
||||
const renderTitle = (node: FlatDiffDisplayNode) => (
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: {
|
||||
content: node.display_name,
|
||||
className: styles['property-tooltip'],
|
||||
},
|
||||
},
|
||||
}}
|
||||
className={styles['property-title']}
|
||||
>
|
||||
{node.level > 0 ? (
|
||||
<Typography.Text
|
||||
style={{
|
||||
marginLeft:
|
||||
DIFF_TABLE_INDENT_BASE +
|
||||
DIFF_TABLE_INDENT_LENGTH * (node.level - 1),
|
||||
marginRight: 8,
|
||||
}}
|
||||
>
|
||||
-
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
{node.display_name}
|
||||
</Typography.Text>
|
||||
);
|
||||
const renderModify = (node: DiffDisplayNode) => {
|
||||
if (!node.diff_res || node.diff_res?.action === DiffActionType.Unknown) {
|
||||
return '';
|
||||
}
|
||||
return (
|
||||
<UITag className={styles[`tag-${node.diff_res.action}`]}>
|
||||
{I18n.t(ActionTypeEnum[node.diff_res.action])}
|
||||
</UITag>
|
||||
);
|
||||
};
|
||||
const renderView = (node: DiffDisplayNode) =>
|
||||
node?.diff_res?.action === DiffActionType.Modify ? (
|
||||
<DiffNodeRender
|
||||
left={node?.diff_res?.display_left || ''}
|
||||
right={node?.diff_res?.display_right || ''}
|
||||
node={node}
|
||||
type={type}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
);
|
||||
|
||||
return (
|
||||
<List
|
||||
dataSource={flatDataSource(blockDiffData)}
|
||||
bordered
|
||||
className={styles.list}
|
||||
renderItem={item => (
|
||||
<List.Item>
|
||||
<div className={styles['list-item']}>
|
||||
{renderTitle(item)}
|
||||
<div> {renderModify(item)}</div>
|
||||
|
||||
{renderView(item)}
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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 DiffDisplayNode } from '@coze-arch/bot-api/dp_manage_api';
|
||||
|
||||
export interface FlatDiffDisplayNode extends DiffDisplayNode {
|
||||
level?: number;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { size } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
IconCozCheckMarkCircleFill,
|
||||
IconCozInfoCircleFill,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import { Typography } from '@coze-arch/bot-semi';
|
||||
import { type TransferResourceInfo } from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
interface IResource extends TransferResourceInfo {
|
||||
spaceID: string;
|
||||
}
|
||||
interface IItemGridView {
|
||||
title: string;
|
||||
resources: Array<IResource>;
|
||||
onResourceClick?: (id: string, spaceID: string) => void;
|
||||
showStatus?: boolean;
|
||||
}
|
||||
|
||||
export function ItemGridView(props: IItemGridView) {
|
||||
const { title, resources, showStatus = false, onResourceClick } = props;
|
||||
// HACK: 由于 grid 布局下边界线是透出的背景色,所以 resource 数量为单数的时候需要补齐一个
|
||||
const isEven = size(resources) % 2 === 0;
|
||||
const finalResources = isEven
|
||||
? resources
|
||||
: [...resources, { name: '', id: '', icon: '', spaceID: '' }];
|
||||
return (
|
||||
<>
|
||||
<p className="text-[12px] leading-[16px] font-[500] coz-fg-secondary text-left align-top w-full mb-[6px]">
|
||||
{title}
|
||||
</p>
|
||||
<div className="mb-[12px]">
|
||||
<div className="grid grid-cols-2 rounded-[6px] overflow-hidden border border-solid coz-stroke-primary gap-[1px] bg-[var(--coz-stroke-primary)] rounded-[4px]">
|
||||
{finalResources.map(item => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={classNames(
|
||||
'flex justify-center items-center gap-x-[4px] p-[8px] w-full coz-bg-plus',
|
||||
item.id ? 'hover:cursor-pointer' : '',
|
||||
)}
|
||||
onClick={() => {
|
||||
if (item.id) {
|
||||
onResourceClick?.(item.id, item.spaceID);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={item.icon}
|
||||
className="w-[16px] h-[16px] rounded-[2px]"
|
||||
/>
|
||||
<Typography.Text
|
||||
ellipsis={{ showTooltip: true }}
|
||||
className="text-[12px] leading-[16px] font-[500] coz-fg-primary text-left align-top grow"
|
||||
>
|
||||
{item.name}
|
||||
</Typography.Text>
|
||||
{showStatus && item.status === 1 ? (
|
||||
<div className="coz-fg-hglt-green flex justify-center items-center">
|
||||
<IconCozCheckMarkCircleFill />
|
||||
</div>
|
||||
) : null}
|
||||
{showStatus && item.status === 0 ? (
|
||||
<div className="coz-fg-hglt-red flex justify-center items-center">
|
||||
<IconCozInfoCircleFill />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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 from 'react';
|
||||
|
||||
import { size } from 'lodash-es';
|
||||
import { useRequest, useUnmount } from 'ahooks';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { MoveAction } from '@coze-arch/bot-api/playground_api';
|
||||
import { type BotSpace } from '@coze-arch/bot-api/developer_api';
|
||||
import { PlaygroundApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { SelectorItem } from '../selector-item';
|
||||
import { ItemGridView } from '../item-grid-view';
|
||||
|
||||
interface IMoveDetailPaneProps {
|
||||
targetSpace: BotSpace | null;
|
||||
botID: string;
|
||||
fromSpaceID: string;
|
||||
onUnmount?: () => void;
|
||||
onDetailLoaded?: () => void;
|
||||
}
|
||||
|
||||
export function MoveDetailPane(props: IMoveDetailPaneProps) {
|
||||
const { targetSpace, botID, fromSpaceID, onUnmount, onDetailLoaded } = props;
|
||||
|
||||
const { data: moveDetails } = useRequest(
|
||||
async () => {
|
||||
const data = await PlaygroundApi.MoveDraftBot({
|
||||
bot_id: botID,
|
||||
target_spaceId: targetSpace.id,
|
||||
from_spaceId: fromSpaceID,
|
||||
move_action: MoveAction.Preview,
|
||||
});
|
||||
return {
|
||||
...data?.async_task,
|
||||
cannotMove: data?.forbid_move,
|
||||
};
|
||||
},
|
||||
{
|
||||
onSuccess: data => {
|
||||
if (data && !data.cannotMove) {
|
||||
onDetailLoaded?.();
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
useUnmount(() => {
|
||||
onUnmount?.();
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="w-full border-[0.5px] border-solid coz-stroke-primary mb-[12px]"></div>
|
||||
<div className="flex flex-col max-h-[406px] overflow-y-auto">
|
||||
<div className="text-[12px] leading-[16px] font-[500] coz-fg-primary text-left align-top w-full mb-[6px]">
|
||||
{I18n.t('resource_move_target_team')}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex flex-col rounded-[6px] overflow-hidden mb-[16px]">
|
||||
<SelectorItem space={targetSpace} selected disabled />
|
||||
</div>
|
||||
</div>
|
||||
{moveDetails?.cannotMove ? (
|
||||
<div className="flex items-center gap-x-[8px] p-[12px] w-full coz-mg-hglt-red rounded-[4px] mb-[12px]">
|
||||
<p className="text-[12px] leading-[16px] font-[400] coz-fg-hglt-red text-left align-top grow">
|
||||
{I18n.t('move_not_allowed_contain_bot_nodes')}
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
{!moveDetails?.cannotMove &&
|
||||
(size(moveDetails?.transfer_resource_plugin_list) ||
|
||||
size(moveDetails?.transfer_resource_workflow_list) ||
|
||||
size(moveDetails?.transfer_resource_knowledge_list)) ? (
|
||||
<>
|
||||
<div className="text-[12px] leading-[16px] font-[500] coz-fg-primary text-left align-top w-full mb-[6px]">
|
||||
{I18n.t('resource_move_together')}
|
||||
</div>
|
||||
<div className="flex items-center gap-x-[8px] p-[8px] w-full coz-mg-hglt-red rounded-[4px] mb-[12px]">
|
||||
<p className="text-[12px] leading-[16px] font-[400] coz-fg-hglt-red text-left align-top grow">
|
||||
{I18n.t('resource_move_together_desc')}
|
||||
</p>
|
||||
</div>
|
||||
{size(moveDetails?.transfer_resource_plugin_list) > 0 ? (
|
||||
<ItemGridView
|
||||
title={I18n.t('store_search_recommend_result2')}
|
||||
resources={moveDetails.transfer_resource_plugin_list.map(
|
||||
item => ({
|
||||
...item,
|
||||
spaceID: fromSpaceID,
|
||||
}),
|
||||
)}
|
||||
onResourceClick={(id, spaceID) => {
|
||||
window.open(`/space/${spaceID}/plugin/${id}`);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{size(moveDetails?.transfer_resource_workflow_list) > 0 ? (
|
||||
<ItemGridView
|
||||
title={I18n.t('store_search_recommend_result3')}
|
||||
resources={moveDetails.transfer_resource_workflow_list.map(
|
||||
item => ({
|
||||
...item,
|
||||
spaceID: fromSpaceID,
|
||||
}),
|
||||
)}
|
||||
onResourceClick={(id, spaceID) => {
|
||||
window.open(
|
||||
`/work_flow?space_id=${spaceID}&workflow_id=${id}`,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{size(moveDetails?.transfer_resource_knowledge_list) > 0 ? (
|
||||
<ItemGridView
|
||||
title={I18n.t('performance_knowledge')}
|
||||
resources={moveDetails.transfer_resource_knowledge_list.map(
|
||||
item => ({
|
||||
...item,
|
||||
spaceID: fromSpaceID,
|
||||
}),
|
||||
)}
|
||||
onResourceClick={(id, spaceID) => {
|
||||
window.open(`/space/${spaceID}/knowledge/${id}`);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 React, { useState } from 'react';
|
||||
|
||||
import { size } from 'lodash-es';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useSpaceList } from '@coze-arch/bot-studio-store';
|
||||
import { type BotSpace, SpaceType } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { SelectorItem } from '../selector-item';
|
||||
|
||||
export function useSelectSpacePane() {
|
||||
const { spaces } = useSpaceList();
|
||||
const [targetSpace, setTargetSpace] = useState<BotSpace | null>(null);
|
||||
|
||||
const personalSpace = spaces.find(
|
||||
item => item.space_type === SpaceType.Personal,
|
||||
);
|
||||
const teamSpaces = spaces.filter(item => item.space_type === SpaceType.Team);
|
||||
|
||||
const selectSpacePane = (
|
||||
<div className="flex flex-col">
|
||||
<div className="w-full border-[0.5px] border-solid coz-stroke-primary mb-[12px]"></div>
|
||||
<div className="flex flex-col max-h-[406px] overflow-y-auto">
|
||||
<div className="text-[12px] leading-[16px] font-[500] coz-fg-primary text-left align-top w-full mb-[6px]">
|
||||
{I18n.t('menu_title_personal_space')}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex flex-col rounded-[6px] overflow-hidden mb-[16px]">
|
||||
<SelectorItem space={personalSpace} disabled />
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-[12px] leading-[16px] font-[500] coz-fg-primary text-left align-top w-full mb-[6px]">
|
||||
{I18n.t('resource_move_target_team')}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex flex-col rounded-[6px] overflow-hidden">
|
||||
{size(teamSpaces) > 0 ? (
|
||||
spaces
|
||||
.filter(item => item.space_type !== SpaceType.Personal)
|
||||
.map(item => (
|
||||
<SelectorItem
|
||||
key={item.id}
|
||||
space={item}
|
||||
selected={item.id === targetSpace?.id}
|
||||
onSelect={space => {
|
||||
setTargetSpace(space);
|
||||
}}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<SelectorItem
|
||||
space={{
|
||||
// MOCK: 用于展示未加入任何空间的兜底情况
|
||||
name: I18n.t('resource_move_no_team_joined'),
|
||||
}}
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return {
|
||||
targetSpace,
|
||||
setTargetSpace,
|
||||
selectSpacePane,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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 classnames from 'classnames';
|
||||
import { IconCozCheckMarkFill } from '@coze-arch/coze-design/icons';
|
||||
import { type BotSpace } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
interface ISelectorItemProps {
|
||||
space: BotSpace;
|
||||
disabled?: boolean;
|
||||
selected?: boolean;
|
||||
onSelect?: (space: BotSpace) => void;
|
||||
}
|
||||
|
||||
export function SelectorItem(props: ISelectorItemProps) {
|
||||
const { space, disabled = false, selected = false, onSelect } = props;
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
'flex justify-between items-center gap-x-[8px] p-[8px] w-full coz-mg-primary',
|
||||
disabled ? '' : 'hover:coz-mg-primary-hovered cursor-pointer',
|
||||
)}
|
||||
onClick={() => {
|
||||
if (!disabled) {
|
||||
onSelect?.(space);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{space.icon_url ? (
|
||||
<img
|
||||
src={space.icon_url}
|
||||
className="w-[24px] h-[24px] rounded-full mr-[8px]"
|
||||
/>
|
||||
) : null}
|
||||
<p
|
||||
className={classnames(
|
||||
'text-[14px] leading-[20px] font-[400] text-left align-middle whitespace-normal -webkit-box line-clamp-1 overflow-hidden grow',
|
||||
disabled ? 'coz-fg-secondary' : 'coz-fg-primary',
|
||||
)}
|
||||
>
|
||||
{space.name}
|
||||
</p>
|
||||
</div>
|
||||
{selected ? (
|
||||
<div className="w-[24px] h-[24px] flex justify-center items-center">
|
||||
<IconCozCheckMarkFill className="coz-fg-secondary" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 { useBotMoveModal } from './move-modal';
|
||||
export { useBotMoveFailedModal } from './move-failed-modal';
|
||||
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* 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 @coze-arch/max-line-per-function -- 不好拆 */
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { size } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { useBoolean, useRequest } from 'ahooks';
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozCross } from '@coze-arch/coze-design/icons';
|
||||
import { Button, Modal, Toast } from '@coze-arch/coze-design';
|
||||
import { useSpaceList } from '@coze-arch/bot-studio-store';
|
||||
import { MoveAction } from '@coze-arch/bot-api/playground_api';
|
||||
import {
|
||||
DraftBotStatus,
|
||||
type DraftBot,
|
||||
SpaceType,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { PlaygroundApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { ItemGridView } from './components/item-grid-view';
|
||||
|
||||
interface BotMoveFailedModalOptions {
|
||||
/**
|
||||
* botInfo
|
||||
*/
|
||||
botInfo: Pick<DraftBot, 'id' | 'name'> | null;
|
||||
/**
|
||||
* 更新 bot 状态
|
||||
*/
|
||||
onUpdateBotStatus?: (status: DraftBotStatus) => void;
|
||||
/**
|
||||
* 迁移成功等效于删除 bot
|
||||
*/
|
||||
onMoveSuccess?: () => void;
|
||||
}
|
||||
|
||||
interface UseBotMoveFailedModalValue {
|
||||
/**
|
||||
* 打开弹窗的方法
|
||||
* @param {BotMoveModalOptions} [options] - 此次打开Modal的配置项
|
||||
*/
|
||||
open: (options?: BotMoveFailedModalOptions) => void;
|
||||
/**
|
||||
* 关闭弹窗的方法
|
||||
*/
|
||||
close: () => void;
|
||||
/**
|
||||
* 弹窗组件实例,需要手动挂载一下
|
||||
*/
|
||||
modalNode: React.ReactNode;
|
||||
}
|
||||
const DefaultOptions: BotMoveFailedModalOptions = { botInfo: null };
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
export function useBotMoveFailedModal(): UseBotMoveFailedModalValue {
|
||||
const [options, setOptions] =
|
||||
useState<BotMoveFailedModalOptions>(DefaultOptions);
|
||||
const [visible, { setTrue: setVisibleTrue, setFalse: setVisibleFalse }] =
|
||||
useBoolean(false);
|
||||
|
||||
const [paneType, setPaneType] = useState<
|
||||
'detail' | 'confirm_cancel' | 'confirm_force'
|
||||
>('detail');
|
||||
|
||||
const title = (
|
||||
<span className="mb-[20px] coz-fg-plus text-[16px] font-medium leading-[22px]">
|
||||
{paneType === 'detail'
|
||||
? I18n.t('move_failed')
|
||||
: paneType === 'confirm_cancel'
|
||||
? I18n.t('move_failed_cancel_confirm_title')
|
||||
: paneType === 'confirm_force'
|
||||
? I18n.t('move_failed_force_confirm_title')
|
||||
: ''}
|
||||
</span>
|
||||
);
|
||||
|
||||
const open = useCallback((opts?: BotMoveFailedModalOptions) => {
|
||||
setOptions(opts || DefaultOptions);
|
||||
setPaneType('detail');
|
||||
setVisibleTrue();
|
||||
}, []);
|
||||
const close = useCallback(() => {
|
||||
setVisibleFalse();
|
||||
}, []);
|
||||
|
||||
const { spaces } = useSpaceList();
|
||||
const fromSpaceID =
|
||||
spaces?.find(s => s.space_type === SpaceType.Personal)?.id ?? '';
|
||||
|
||||
const { data: moveDetails } = useRequest(
|
||||
async () => {
|
||||
if (!options.botInfo) {
|
||||
return;
|
||||
}
|
||||
const data = await PlaygroundApi.MoveDraftBot({
|
||||
bot_id: options.botInfo.id,
|
||||
from_spaceId: fromSpaceID,
|
||||
move_action: MoveAction.ViewTask,
|
||||
});
|
||||
return data.async_task;
|
||||
},
|
||||
{ refreshDeps: [options.botInfo] },
|
||||
);
|
||||
|
||||
const { loading, run } = useRequest(
|
||||
async (moveAction: MoveAction) => {
|
||||
const data = await PlaygroundApi.MoveDraftBot({
|
||||
bot_id: options.botInfo.id,
|
||||
from_spaceId: fromSpaceID,
|
||||
move_action: moveAction,
|
||||
});
|
||||
return { ...data, moveAction };
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: data => {
|
||||
if (data.bot_status === DraftBotStatus.Using) {
|
||||
if (data.moveAction === MoveAction.CancelTask) {
|
||||
options.onUpdateBotStatus?.(data.bot_status);
|
||||
} else {
|
||||
Toast.success(I18n.t('resource_move_bot_success_toast'));
|
||||
options.onMoveSuccess?.();
|
||||
}
|
||||
close();
|
||||
} else if (data.bot_status === DraftBotStatus.MoveFail) {
|
||||
options.onUpdateBotStatus?.(data.bot_status);
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(I18n.t('move_failed_toast')),
|
||||
});
|
||||
close();
|
||||
}
|
||||
},
|
||||
onError: error => {
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(
|
||||
error?.message || I18n.t('move_failed_toast'),
|
||||
),
|
||||
showClose: false,
|
||||
});
|
||||
close();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const retry = async () => {
|
||||
await run(MoveAction.RetryMove);
|
||||
};
|
||||
const forceMove = async () => {
|
||||
await run(MoveAction.ForcedMove);
|
||||
};
|
||||
const cancelMove = async () => {
|
||||
await run(MoveAction.CancelTask);
|
||||
};
|
||||
|
||||
const footer = (
|
||||
<div
|
||||
className={classNames(
|
||||
'coz-modal-footer flex gap-2 justify-end',
|
||||
'w-full',
|
||||
)}
|
||||
>
|
||||
{paneType === 'detail' ? (
|
||||
<>
|
||||
<Button
|
||||
className="flex-1 !ml-0"
|
||||
size="large"
|
||||
color="primary"
|
||||
disabled={!moveDetails || loading}
|
||||
onClick={() => {
|
||||
setPaneType('confirm_cancel');
|
||||
}}
|
||||
>
|
||||
{I18n.t('move_failed_btn_cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1 !ml-0"
|
||||
size="large"
|
||||
color="primary"
|
||||
disabled={!moveDetails || loading}
|
||||
onClick={() => {
|
||||
setPaneType('confirm_force');
|
||||
}}
|
||||
>
|
||||
{I18n.t('move_failed_btn_force')}
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1 !ml-0"
|
||||
color="brand"
|
||||
size="large"
|
||||
loading={loading}
|
||||
disabled={!moveDetails || loading}
|
||||
onClick={() => {
|
||||
retry();
|
||||
}}
|
||||
>
|
||||
{I18n.t('Retry')}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
{paneType === 'confirm_cancel' ? (
|
||||
<>
|
||||
<Button
|
||||
className="!ml-0"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setPaneType('detail');
|
||||
}}
|
||||
>
|
||||
{I18n.t('back')}
|
||||
</Button>
|
||||
<Button
|
||||
className="!ml-0"
|
||||
color="brand"
|
||||
loading={loading}
|
||||
onClick={() => {
|
||||
cancelMove();
|
||||
}}
|
||||
>
|
||||
{I18n.t('confirm')}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
{paneType === 'confirm_force' ? (
|
||||
<>
|
||||
<Button
|
||||
className="!ml-0"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setPaneType('detail');
|
||||
}}
|
||||
>
|
||||
{I18n.t('back')}
|
||||
</Button>
|
||||
<Button
|
||||
className="!ml-0"
|
||||
color="brand"
|
||||
loading={loading}
|
||||
onClick={() => {
|
||||
forceMove();
|
||||
}}
|
||||
>
|
||||
{I18n.t('confirm')}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
const modalNode = (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={title}
|
||||
footer={footer}
|
||||
width={paneType !== 'detail' ? '448px' : '480px'}
|
||||
footerFill
|
||||
onCancel={close}
|
||||
closable={!['confirm_cancel', 'confirm_force'].includes(paneType)}
|
||||
maskClosable={false}
|
||||
keepDOM={false}
|
||||
closeIcon={<IconCozCross className="coz-fg-secondary" />}
|
||||
>
|
||||
{paneType === 'detail' ? (
|
||||
<div className="flex flex-col">
|
||||
<div className="w-full border-[0.5px] border-solid coz-stroke-primary mb-[12px]"></div>
|
||||
<div className="flex flex-col max-h-[406px] overflow-y-auto">
|
||||
<div className="flex items-center gap-x-[8px] p-[8px] w-full coz-mg-primary rounded-[6px] mb-[12px]">
|
||||
<p className="text-[12px] leading-[16px] font-[400] coz-fg-secondary text-left align-top grow">
|
||||
{I18n.t('move_failed_desc')}
|
||||
</p>
|
||||
</div>
|
||||
{size(moveDetails?.transfer_resource_plugin_list) > 0 ? (
|
||||
<ItemGridView
|
||||
title={I18n.t('store_search_recommend_result2')}
|
||||
resources={moveDetails?.transfer_resource_plugin_list.map(
|
||||
item => ({
|
||||
...item,
|
||||
spaceID: item.status
|
||||
? moveDetails?.task_info.TargetSpaceId
|
||||
: moveDetails?.task_info.OriSpaceId,
|
||||
}),
|
||||
)}
|
||||
onResourceClick={(id, spaceID) => {
|
||||
window.open(`/space/${spaceID}/plugin/${id}`);
|
||||
}}
|
||||
showStatus
|
||||
/>
|
||||
) : null}
|
||||
{size(moveDetails?.transfer_resource_workflow_list) > 0 ? (
|
||||
<ItemGridView
|
||||
title={I18n.t('store_search_recommend_result3')}
|
||||
resources={moveDetails?.transfer_resource_workflow_list.map(
|
||||
item => ({
|
||||
...item,
|
||||
spaceID: item.status
|
||||
? moveDetails?.task_info.TargetSpaceId
|
||||
: moveDetails?.task_info.OriSpaceId,
|
||||
}),
|
||||
)}
|
||||
onResourceClick={(id, spaceID) => {
|
||||
window.open(
|
||||
`/work_flow?space_id=${spaceID}&workflow_id=${id}`,
|
||||
);
|
||||
}}
|
||||
showStatus
|
||||
/>
|
||||
) : null}
|
||||
{size(moveDetails?.transfer_resource_knowledge_list) > 0 ? (
|
||||
<ItemGridView
|
||||
title={I18n.t('performance_knowledge')}
|
||||
resources={moveDetails?.transfer_resource_knowledge_list.map(
|
||||
item => ({
|
||||
...item,
|
||||
spaceID: item.status
|
||||
? moveDetails?.task_info.TargetSpaceId
|
||||
: moveDetails?.task_info.OriSpaceId,
|
||||
}),
|
||||
)}
|
||||
onResourceClick={(id, spaceID) => {
|
||||
window.open(`/space/${spaceID}/knowledge/${id}`);
|
||||
}}
|
||||
showStatus
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{paneType === 'confirm_force' ? (
|
||||
<div className="mt-[20px]">
|
||||
{I18n.t('move_failed_force_confirm_content')}
|
||||
</div>
|
||||
) : null}
|
||||
{paneType === 'confirm_cancel' ? (
|
||||
<div className="mt-[20px]">
|
||||
{I18n.t('move_failed_cancel_confirm_content')}
|
||||
</div>
|
||||
) : null}
|
||||
</Modal>
|
||||
);
|
||||
|
||||
return {
|
||||
modalNode,
|
||||
open,
|
||||
close,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* 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 @coze-arch/max-line-per-function -- 难拆*/
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useBoolean, useRequest } from 'ahooks';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useSpaceList } from '@coze-arch/bot-studio-store';
|
||||
import { MoveAction } from '@coze-arch/bot-api/playground_api';
|
||||
import {
|
||||
DraftBotStatus,
|
||||
type DraftBot,
|
||||
SpaceType,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { PlaygroundApi } from '@coze-arch/bot-api';
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { cozeMitt } from '@coze-common/coze-mitt';
|
||||
import { IconInfo } from '@coze-arch/bot-icons';
|
||||
import { IconCozCross } from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
Modal,
|
||||
Toast,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { useSelectSpacePane } from './components/select-space-pane';
|
||||
import { MoveDetailPane } from './components/move-detail-pane';
|
||||
|
||||
interface BotMoveModalOptions {
|
||||
/**
|
||||
* botInfo
|
||||
*/
|
||||
botInfo: Pick<DraftBot, 'name' | 'id'> | null;
|
||||
/**
|
||||
* 更新 bot 状态
|
||||
*/
|
||||
onUpdateBotStatus?: (status: DraftBotStatus) => void;
|
||||
/**
|
||||
* 迁移成功等效于删除 bot
|
||||
*/
|
||||
onMoveSuccess?: () => void;
|
||||
/**
|
||||
* 关闭 modal
|
||||
*/
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
interface UseBotMoveModalValue {
|
||||
/**
|
||||
* 打开弹窗的方法
|
||||
* @param {BotMoveModalOptions} [options] - 此次打开Modal的配置项
|
||||
*/
|
||||
open: (options?: BotMoveModalOptions) => void;
|
||||
/**
|
||||
* 关闭弹窗的方法
|
||||
*/
|
||||
close: () => void;
|
||||
/**
|
||||
* 弹窗组件实例,需要手动挂载一下
|
||||
*/
|
||||
modalNode: React.ReactNode;
|
||||
}
|
||||
const DefaultOptions: BotMoveModalOptions = { botInfo: null };
|
||||
|
||||
export function useBotMoveModal(): UseBotMoveModalValue {
|
||||
const [options, setOptions] = useState<BotMoveModalOptions>(DefaultOptions);
|
||||
const [visible, { setTrue: setVisibleTrue, setFalse: setVisibleFalse }] =
|
||||
useBoolean(false);
|
||||
|
||||
const [paneType, setPaneType] = useState<'select' | 'move' | 'confirm'>(
|
||||
'select',
|
||||
);
|
||||
const { targetSpace, selectSpacePane, setTargetSpace } = useSelectSpacePane();
|
||||
|
||||
const title =
|
||||
paneType !== 'confirm' ? (
|
||||
<div className="flex justify-start items-center mb-[24px] w-[380px]">
|
||||
<div className="coz-fg-plus text-[16px] font-medium leading-[22px] max-w-full">
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
showTooltip: true,
|
||||
}}
|
||||
className="text-[16px]"
|
||||
>
|
||||
{I18n.t('resource_move_title', {
|
||||
bot_name: options.botInfo?.name ?? '',
|
||||
})}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<Tooltip content={I18n.t('resource_move_notice')}>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="secondary"
|
||||
icon={<IconInfo className="coz-fg-secondary" />}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
I18n.t('resource_move_confirm_title')
|
||||
);
|
||||
|
||||
const open = useCallback((opts?: BotMoveModalOptions) => {
|
||||
setOptions(opts || DefaultOptions);
|
||||
setPaneType('select');
|
||||
setTargetSpace(null);
|
||||
setVisibleTrue();
|
||||
}, []);
|
||||
const close = useCallback(() => {
|
||||
setVisibleFalse();
|
||||
}, []);
|
||||
|
||||
const { spaces } = useSpaceList();
|
||||
const fromSpaceID = spaces.find(s => s.space_type === SpaceType.Personal)?.id;
|
||||
|
||||
const { loading: moveLoading, run: moveBot } = useRequest(
|
||||
async () => {
|
||||
const data = await PlaygroundApi.MoveDraftBot({
|
||||
bot_id: options.botInfo.id,
|
||||
target_spaceId: targetSpace.id,
|
||||
from_spaceId: fromSpaceID,
|
||||
move_action: MoveAction.Move,
|
||||
});
|
||||
return data;
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: data => {
|
||||
if (data.bot_status === DraftBotStatus.Using) {
|
||||
Toast.success(I18n.t('resource_move_bot_success_toast'));
|
||||
options.onMoveSuccess?.();
|
||||
close();
|
||||
cozeMitt.emit('refreshFavList', {
|
||||
numDelta: -1,
|
||||
});
|
||||
} else if (data.bot_status === DraftBotStatus.MoveFail) {
|
||||
options.onUpdateBotStatus?.(data.bot_status);
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(I18n.t('move_failed_toast')),
|
||||
});
|
||||
close();
|
||||
}
|
||||
},
|
||||
onError: error => {
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(
|
||||
error?.message || I18n.t('move_failed_toast'),
|
||||
),
|
||||
showClose: false,
|
||||
});
|
||||
close();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const onConfirm = async () => {
|
||||
await moveBot();
|
||||
};
|
||||
|
||||
const [moveDisabled, setMoveDisabled] = useState(true);
|
||||
const footer = (
|
||||
<div
|
||||
className={classNames(
|
||||
'coz-modal-footer flex gap-2 justify-end',
|
||||
paneType !== 'confirm' && 'w-full',
|
||||
)}
|
||||
>
|
||||
{paneType === 'select' ? (
|
||||
<Button
|
||||
className="flex-1 !ml-0"
|
||||
color="brand"
|
||||
size="large"
|
||||
disabled={!targetSpace}
|
||||
onClick={() => {
|
||||
setPaneType('move');
|
||||
}}
|
||||
>
|
||||
{I18n.t('next')}
|
||||
</Button>
|
||||
) : null}
|
||||
{paneType === 'move' ? (
|
||||
<>
|
||||
<Button
|
||||
className="flex-1 !ml-0"
|
||||
size="large"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setPaneType('select');
|
||||
}}
|
||||
>
|
||||
{I18n.t('back')}
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1 !ml-0"
|
||||
color="brand"
|
||||
size="large"
|
||||
disabled={moveDisabled}
|
||||
onClick={() => {
|
||||
setPaneType('confirm');
|
||||
}}
|
||||
>
|
||||
{I18n.t('resource_move')}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
{paneType === 'confirm' ? (
|
||||
<>
|
||||
<Button
|
||||
className="!ml-0"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setPaneType('move');
|
||||
}}
|
||||
>
|
||||
{I18n.t('back')}
|
||||
</Button>
|
||||
<Button
|
||||
className="!ml-0"
|
||||
color="brand"
|
||||
loading={moveLoading}
|
||||
onClick={() => {
|
||||
onConfirm();
|
||||
}}
|
||||
>
|
||||
{I18n.t('confirm')}
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
const modalNode = (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={title}
|
||||
footer={footer}
|
||||
width={paneType === 'confirm' ? '448px' : '480px'}
|
||||
footerFill
|
||||
onCancel={() => {
|
||||
close?.();
|
||||
options.onClose?.();
|
||||
}}
|
||||
closable={paneType !== 'confirm'}
|
||||
maskClosable={false}
|
||||
keepDOM={false}
|
||||
closeIcon={<IconCozCross className="coz-fg-secondary" />}
|
||||
>
|
||||
{paneType === 'select' ? selectSpacePane : null}
|
||||
{paneType === 'move' ? (
|
||||
<>
|
||||
<MoveDetailPane
|
||||
targetSpace={targetSpace}
|
||||
botID={options.botInfo.id}
|
||||
fromSpaceID={fromSpaceID}
|
||||
onUnmount={() => setMoveDisabled(true)}
|
||||
onDetailLoaded={() => setMoveDisabled(false)}
|
||||
/>
|
||||
{IS_CN_REGION ? (
|
||||
<div className="coz-fg-hglt-red">{I18n.t('move_desc1')}</div>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
{paneType === 'confirm' ? (
|
||||
<div className="mt-[20px]">
|
||||
{I18n.t('resource_move_confirm_content')}
|
||||
</div>
|
||||
) : null}
|
||||
</Modal>
|
||||
);
|
||||
|
||||
return {
|
||||
modalNode,
|
||||
open,
|
||||
close,
|
||||
};
|
||||
}
|
||||
@@ -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 React, { useEffect } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { userStoreService } from '@coze-studio/user-store';
|
||||
import { SkillKeyEnum } from '@coze-agent-ide/tool-config';
|
||||
import {
|
||||
AddButton,
|
||||
ToolContentBlock,
|
||||
useToolValidData,
|
||||
type ToolEntryCommonProps,
|
||||
} from '@coze-agent-ide/tool';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { OpenBlockEvent, emitEvent } from '@coze-arch/bot-utils';
|
||||
import { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
|
||||
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
|
||||
import { useDefaultExPandCheck } from '@coze-arch/bot-hooks';
|
||||
import { useBackgroundContent } from '@coze-agent-ide/chat-background-shared';
|
||||
import { type UseChatBackgroundUploaderProps } from '@coze-agent-ide/chat-background';
|
||||
import {
|
||||
useChatBackgroundUploader,
|
||||
ChatBackGroundContent,
|
||||
} from '@coze-agent-ide/chat-background';
|
||||
|
||||
type ITextToSpeechProps = ToolEntryCommonProps;
|
||||
export const ChatBackground: React.FC<ITextToSpeechProps> = ({ title }) => {
|
||||
const setToolValidData = useToolValidData();
|
||||
|
||||
const { backgroundImageInfoList, setBackgroundImageInfoList } =
|
||||
useBotSkillStore(
|
||||
useShallow($store => ({
|
||||
backgroundImageInfoList: $store.backgroundImageInfoList,
|
||||
setBackgroundImageInfoList: $store.setBackgroundImageInfoList,
|
||||
})),
|
||||
);
|
||||
|
||||
const isReadonly = useBotDetailIsReadonly();
|
||||
|
||||
const { showDot } = useBackgroundContent();
|
||||
|
||||
const hasBackGroundImage = Boolean(
|
||||
backgroundImageInfoList?.[0]?.web_background_image?.origin_image_url,
|
||||
);
|
||||
|
||||
const defaultExpand = useDefaultExPandCheck({
|
||||
blockKey: SkillKeyEnum.BACKGROUND_IMAGE_BLOCK,
|
||||
configured: hasBackGroundImage || showDot, // 无图 有进行中的状态也展示背景图模块不允许被隐藏
|
||||
});
|
||||
|
||||
const userInfo = userStoreService.useUserInfo();
|
||||
const getUserId: UseChatBackgroundUploaderProps['getUserId'] = () => ({
|
||||
userId: userInfo?.user_id_str ?? '',
|
||||
});
|
||||
|
||||
const { node, open } = useChatBackgroundUploader({
|
||||
getUserId,
|
||||
onSuccess: value => {
|
||||
setBackgroundImageInfoList(value);
|
||||
emitEvent(OpenBlockEvent.BACKGROUND_IMAGE_BLOCK);
|
||||
},
|
||||
backgroundValue: backgroundImageInfoList,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setToolValidData(hasBackGroundImage);
|
||||
}, [hasBackGroundImage]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ToolContentBlock
|
||||
showBottomBorder
|
||||
tooltipType={'tooltip'}
|
||||
header={title}
|
||||
defaultExpand={defaultExpand}
|
||||
actionButton={
|
||||
<>
|
||||
<AddButton
|
||||
tooltips={
|
||||
hasBackGroundImage ? I18n.t('bgi_already_set') : undefined
|
||||
}
|
||||
onClick={() => {
|
||||
open();
|
||||
}}
|
||||
disabled={hasBackGroundImage}
|
||||
enableAutoHidden={true}
|
||||
data-testid="bot.editor.tool.background.add-button"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ChatBackGroundContent
|
||||
isReadOnly={isReadonly}
|
||||
backgroundImageInfoList={backgroundImageInfoList}
|
||||
openConfig={open}
|
||||
setBackgroundImageInfoList={setBackgroundImageInfoList}
|
||||
/>
|
||||
</ToolContentBlock>
|
||||
{node}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
.container {
|
||||
width: 100%;
|
||||
|
||||
span {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ellipse {
|
||||
&>textarea {
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.auto-size {
|
||||
background-color: var(--semi-color-white);
|
||||
|
||||
&>textarea {
|
||||
border-radius: 8px;
|
||||
overflow-y: var(--chatflow-custom-textarea-overflow-y, hidden);
|
||||
max-height: var(--chatflow-custom-textarea-focused-max-height, unset);
|
||||
color: var(--semi-color-text-0, rgb(56, 55, 67));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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 CSSProperties,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
forwardRef,
|
||||
type ForwardedRef,
|
||||
useImperativeHandle,
|
||||
} from 'react';
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import classNames from 'classnames';
|
||||
import { useBoolean } from 'ahooks';
|
||||
import { type TextAreaProps } from '@coze-arch/bot-semi/Input';
|
||||
import { TextArea } from '@coze-arch/bot-semi';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
interface CommonTextareaType {
|
||||
textAreaClassName?: string;
|
||||
textAreaProps?: Partial<TextAreaProps>;
|
||||
// 一种特殊的针对placeholder处理方式,::placeholder达不到预期
|
||||
emptyClassName?: string;
|
||||
}
|
||||
interface ChatflowCustomTextareaProps extends TextAreaProps {
|
||||
value: string;
|
||||
onChange: (
|
||||
value: string,
|
||||
e: React.MouseEvent<HTMLTextAreaElement, MouseEvent>,
|
||||
) => void;
|
||||
/** 展示模式(即需要省略时)的配置 */
|
||||
ellipse?: {
|
||||
rows?: number;
|
||||
} & CommonTextareaType;
|
||||
/** 编辑模式(即需要自动适应)的配置 */
|
||||
autoSize?: {
|
||||
maxHeight?: number;
|
||||
} & CommonTextareaType;
|
||||
readonly?: boolean;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export const CollapsibleTextarea = forwardRef(
|
||||
(
|
||||
{
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
ellipse = { rows: 4 },
|
||||
autoSize = { maxHeight: 340 },
|
||||
readonly,
|
||||
className,
|
||||
style,
|
||||
maxCount,
|
||||
maxLength,
|
||||
onFocus,
|
||||
...restCommonTextAreaProps
|
||||
}: ChatflowCustomTextareaProps,
|
||||
ref: ForwardedRef<HTMLTextAreaElement>,
|
||||
) => {
|
||||
const textAreaId = useMemo(() => nanoid(), []);
|
||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [focused, { setTrue: setFocusedTrue, setFalse: setFocusedFalse }] =
|
||||
useBoolean(false);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
...(textAreaRef.current as HTMLTextAreaElement),
|
||||
focus: () => setFocusedTrue(),
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (focused) {
|
||||
// 加timeout可以实现focus的时候滚动到最底并光标在最后
|
||||
setTimeout(() => {
|
||||
if (textAreaRef.current) {
|
||||
// 默认光标在最后
|
||||
textAreaRef.current.setSelectionRange(
|
||||
Number.MAX_SAFE_INTEGER,
|
||||
Number.MAX_SAFE_INTEGER,
|
||||
);
|
||||
textAreaRef.current.focus();
|
||||
textAreaRef.current.scroll({ top: textAreaRef.current.scrollTop });
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [focused]);
|
||||
|
||||
const renderTextArea = () => {
|
||||
if (focused) {
|
||||
return (
|
||||
<TextArea
|
||||
autosize
|
||||
// key是保证readonly变化后重新渲染
|
||||
key="not-readonly"
|
||||
style={
|
||||
autoSize?.maxHeight
|
||||
? // 这里的 style 会应用到 wrapper 上,不限定高度时会意外出现滚动条,只能通过变量修改 textarea 的 overflow
|
||||
// 此外,max-height 会导致预期外的 blur 事件,也只能通过 css 变量将 max-height 动态传给 textarea
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- 传递 css 变量
|
||||
({
|
||||
'--chatflow-custom-textarea-overflow-y': 'auto',
|
||||
'--chatflow-custom-textarea-focused-max-height': `${autoSize.maxHeight}px`,
|
||||
} as CSSProperties)
|
||||
: undefined
|
||||
}
|
||||
id={textAreaId}
|
||||
ref={textAreaRef}
|
||||
value={value}
|
||||
onBlur={e => {
|
||||
setFocusedFalse();
|
||||
onBlur?.(e);
|
||||
}}
|
||||
onChange={onChange}
|
||||
readonly={readonly}
|
||||
className={classNames(
|
||||
styles['auto-size'],
|
||||
autoSize?.textAreaClassName,
|
||||
{ [autoSize?.emptyClassName || '']: !value },
|
||||
)}
|
||||
maxCount={maxCount}
|
||||
maxLength={maxLength}
|
||||
{...restCommonTextAreaProps}
|
||||
{...autoSize?.textAreaProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TextArea
|
||||
// key是保证readonly变化后重新渲染
|
||||
key="readonly"
|
||||
style={{ WebkitLineClamp: ellipse?.rows }}
|
||||
value={value}
|
||||
rows={ellipse?.rows}
|
||||
onFocus={e => {
|
||||
onFocus?.(e);
|
||||
setFocusedTrue();
|
||||
}}
|
||||
className={classNames(styles.ellipse, ellipse?.textAreaClassName, {
|
||||
[ellipse?.emptyClassName || '']: !value,
|
||||
})}
|
||||
{...restCommonTextAreaProps}
|
||||
{...ellipse?.textAreaProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.container, className)} style={style}>
|
||||
{renderTextArea()}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -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 { I18n } from '@coze-arch/i18n';
|
||||
import { EVENT_NAMES } from '@coze-arch/bot-tea';
|
||||
import { Tooltip, UIIconButton } from '@coze-arch/bot-semi';
|
||||
import { IconViewDiff } from '@coze-arch/bot-icons';
|
||||
import { type PublishConnectorInfo } from '@coze-arch/bot-api/developer_api';
|
||||
import { sendTeaEventInBot } from '@coze-agent-ide/agent-ide-commons';
|
||||
|
||||
import { useBotModeStore } from '../../store/bot-mode';
|
||||
import { useConnectorDiffModal } from '../../hook/use-connector-diff-modal';
|
||||
|
||||
export const DiffViewButton: React.FC<{
|
||||
record: PublishConnectorInfo;
|
||||
isMouseIn: boolean;
|
||||
}> = ({ record, isMouseIn }) => {
|
||||
const { open: connectorDiffModalOpen, node: connectorDiffModalNode } =
|
||||
useConnectorDiffModal();
|
||||
const isCollaboration = useBotModeStore(s => s.isCollaboration);
|
||||
const openConnectorDiffModal = (info: PublishConnectorInfo) => {
|
||||
sendTeaEventInBot(EVENT_NAMES.bot_publish_difference, {
|
||||
platform_type: info.name,
|
||||
});
|
||||
connectorDiffModalOpen(info);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMouseIn && isCollaboration ? (
|
||||
<Tooltip content={I18n.t('devops_publish_multibranch_viewdiff')}>
|
||||
<UIIconButton
|
||||
onClick={() => {
|
||||
openConnectorDiffModal(record);
|
||||
}}
|
||||
icon={<IconViewDiff color="#4D53E8" />}
|
||||
></UIIconButton>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
{connectorDiffModalNode}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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 { KvBindButton } from './kv-bind-button';
|
||||
export { DiffViewButton } from './diff-view-button';
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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 SetStateAction } from 'react';
|
||||
|
||||
import { type PublishConnectorInfo } from '@coze-arch/idl/intelligence_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Button } from '@coze-arch/coze-design';
|
||||
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
import { UIButton } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
type PublishConnectorInfo as BotPublishConnectorInfo,
|
||||
ConfigStatus,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { useConnectorFormModal } from '../bind-connector-modal/use-connector-form-modal';
|
||||
import { OLD_WX_FWH_ID } from '../../util';
|
||||
|
||||
interface KvBindButtonProps {
|
||||
setDataSource?: (value: SetStateAction<BotPublishConnectorInfo[]>) => void;
|
||||
setSelectedPlatforms?: (id: SetStateAction<string[]>) => void;
|
||||
record: BotPublishConnectorInfo | PublishConnectorInfo;
|
||||
/** 渠道配置成功的回调。若不传入 `unbindCallback`,解绑渠道也会调用该回调,且 bind_id 为空字符串 `''` */
|
||||
bindSuccessCallback?: (value: PublishConnectorInfo | undefined) => void;
|
||||
/** 解绑渠道的回调 */
|
||||
unbindCallback?: () => void;
|
||||
/** 绑定的 agent_type 。默认为 bot */
|
||||
origin?: 'project' | 'bot';
|
||||
/** 绑定的 bot_id/project_id 。不传则根据 origin 从路由参数中获取 */
|
||||
originId?: string;
|
||||
}
|
||||
|
||||
export const KvBindButton = ({
|
||||
setDataSource,
|
||||
setSelectedPlatforms,
|
||||
record,
|
||||
bindSuccessCallback,
|
||||
unbindCallback,
|
||||
origin = 'bot',
|
||||
originId,
|
||||
}: KvBindButtonProps) => {
|
||||
const { bot_id = '', project_id = '' } = useParams<DynamicParams>();
|
||||
// 传给后端的参数名字是 bot_id,另外使用参数 agent_type 来区分 0-bot 1-project
|
||||
const botId = originId ?? (origin === 'bot' ? bot_id : project_id);
|
||||
const bindSuccessCb = (
|
||||
value: BotPublishConnectorInfo | PublishConnectorInfo | undefined,
|
||||
) => {
|
||||
if (bindSuccessCallback) {
|
||||
bindSuccessCallback(value as PublishConnectorInfo);
|
||||
return;
|
||||
}
|
||||
setDataSource?.((list: BotPublishConnectorInfo[]) => {
|
||||
const target = list.find(l => l.id === value?.id);
|
||||
if (target) {
|
||||
// 解绑旧的服务号后,需要隐藏掉旧的服务号渠道,不允许再绑定
|
||||
if (target.id === OLD_WX_FWH_ID && !value?.bind_id) {
|
||||
return list.filter(item => item.id !== OLD_WX_FWH_ID);
|
||||
}
|
||||
target.bind_id = value?.bind_id;
|
||||
target.bind_info = value?.bind_info ?? {};
|
||||
target.config_status = value?.bind_id
|
||||
? ConfigStatus.Configured
|
||||
: ConfigStatus.NotConfigured;
|
||||
}
|
||||
|
||||
return [...list];
|
||||
});
|
||||
|
||||
if (!value?.bind_id) {
|
||||
setSelectedPlatforms?.(list => list.filter(item => item !== value?.id));
|
||||
}
|
||||
};
|
||||
const { node: connectorFormModal, open: openConnectorsForm } =
|
||||
useConnectorFormModal({
|
||||
botId,
|
||||
origin,
|
||||
onSuccess: bindSuccessCb,
|
||||
onUnbind: unbindCallback,
|
||||
});
|
||||
|
||||
const handleConfigure = () => openConnectorsForm({ initValue: record });
|
||||
const buttonText = I18n.t('bot_publish_action_configure');
|
||||
|
||||
return (
|
||||
<>
|
||||
{origin === 'project' ? (
|
||||
<Button onClick={handleConfigure} size="small" color="primary">
|
||||
{buttonText}
|
||||
</Button>
|
||||
) : (
|
||||
<UIButton onClick={handleConfigure} theme="borderless">
|
||||
{buttonText}
|
||||
</UIButton>
|
||||
)}
|
||||
{connectorFormModal}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
.wrapper-multi {
|
||||
position: relative; // sheet按钮定位
|
||||
|
||||
:global {
|
||||
.semi-sidesheet.semi-sidesheet-popup {
|
||||
z-index: 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper-single {
|
||||
display: grid;
|
||||
grid-template-columns: 26fr 14fr;
|
||||
flex: 1 1;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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, type PropsWithChildren } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { BotMode } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface ContentViewProps {
|
||||
mode: number;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
export const ContentView: React.FC<PropsWithChildren<ContentViewProps>> = ({
|
||||
mode = 1,
|
||||
className,
|
||||
style,
|
||||
children,
|
||||
}) => {
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const isSingle = mode === BotMode.SingleMode;
|
||||
const isMulti = mode === BotMode.MultiMode;
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'w-full h-full overflow-hidden',
|
||||
isSingle && s['wrapper-single'],
|
||||
isMulti && s['wrapper-multi'],
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
ref={wrapperRef}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContentView;
|
||||
@@ -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 { I18n } from '@coze-arch/i18n';
|
||||
import { IconButton } from '@coze-arch/coze-design';
|
||||
import { IconAdd } from '@coze-arch/bot-icons';
|
||||
|
||||
import { type ISysConfigItemGroup } from '../../hooks';
|
||||
|
||||
const DEFAULT_VARIABLE_LENGTH = 10;
|
||||
export const AddVariable = (props: {
|
||||
groupConfig?: ISysConfigItemGroup;
|
||||
isReadonly: boolean;
|
||||
hideAddButton?: boolean;
|
||||
forceShow?: boolean;
|
||||
handleInputedClick: () => void;
|
||||
}) => {
|
||||
const {
|
||||
groupConfig,
|
||||
isReadonly,
|
||||
hideAddButton = false,
|
||||
forceShow = false,
|
||||
handleInputedClick,
|
||||
} = props;
|
||||
const enableVariables = groupConfig?.var_info_list ?? [];
|
||||
return (enableVariables.length < DEFAULT_VARIABLE_LENGTH &&
|
||||
!isReadonly &&
|
||||
!hideAddButton) ||
|
||||
forceShow ? (
|
||||
<div className="my-3 px-[22px] text-left">
|
||||
<IconButton
|
||||
className="!m-0"
|
||||
onClick={() => handleInputedClick()}
|
||||
icon={<IconAdd />}
|
||||
>
|
||||
{I18n.t('bot_userProfile_add')}
|
||||
</IconButton>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
@@ -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 cls from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Spin, IconButton } from '@coze-arch/coze-design';
|
||||
import { IconAdd } from '@coze-arch/bot-icons';
|
||||
|
||||
import { VariableTree } from '../variable-tree';
|
||||
import { VariableGroupWrapper } from '../group-wrapper';
|
||||
import s from '../../index.module.less';
|
||||
import { type ISysConfigItemGroup, type ISysConfigItem } from '../../hooks';
|
||||
|
||||
const DEFAULT_VARIABLE_LENGTH = 10;
|
||||
|
||||
export const GroupTable = (props: {
|
||||
isReadonly?: boolean;
|
||||
loading?: boolean;
|
||||
highLight?: boolean;
|
||||
activeId?: string;
|
||||
subGroupConfig?: ISysConfigItemGroup[];
|
||||
variablesConfig?: ISysConfigItem[];
|
||||
handleInputedClick: () => void;
|
||||
hideAddButton?: boolean;
|
||||
header?: React.ReactNode;
|
||||
}) => {
|
||||
const {
|
||||
isReadonly,
|
||||
loading,
|
||||
highLight,
|
||||
activeId,
|
||||
subGroupConfig,
|
||||
variablesConfig,
|
||||
handleInputedClick,
|
||||
hideAddButton,
|
||||
header,
|
||||
} = props;
|
||||
const showAddButton = !isReadonly && !hideAddButton;
|
||||
|
||||
return (
|
||||
<table className={cls(s['memory-edit-table'], 'pl-6')}>
|
||||
{header}
|
||||
{loading ? (
|
||||
<Spin
|
||||
spinning={loading}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
></Spin>
|
||||
) : (
|
||||
<>
|
||||
{subGroupConfig?.map(subGroup => (
|
||||
<VariableGroupWrapper variableGroup={subGroup} level={1}>
|
||||
<VariableTree
|
||||
isReadonly={isReadonly}
|
||||
highLight={highLight}
|
||||
activeId={activeId}
|
||||
configList={subGroup.var_info_list}
|
||||
/>
|
||||
{showAddButton &&
|
||||
subGroup.var_info_list?.length < DEFAULT_VARIABLE_LENGTH ? (
|
||||
<div className="my-3 text-left">
|
||||
<IconButton
|
||||
className="!m-0"
|
||||
onClick={() => handleInputedClick()}
|
||||
icon={<IconAdd />}
|
||||
>
|
||||
{I18n.t('bot_userProfile_add')}
|
||||
</IconButton>
|
||||
</div>
|
||||
) : null}
|
||||
</VariableGroupWrapper>
|
||||
))}
|
||||
<VariableTree
|
||||
isReadonly={isReadonly}
|
||||
highLight={highLight}
|
||||
activeId={activeId}
|
||||
configList={variablesConfig}
|
||||
/>
|
||||
{showAddButton &&
|
||||
variablesConfig?.length < DEFAULT_VARIABLE_LENGTH ? (
|
||||
<div className="my-3 text-left">
|
||||
<IconButton
|
||||
className="!m-0"
|
||||
onClick={() => handleInputedClick()}
|
||||
icon={<IconAdd />}
|
||||
>
|
||||
{I18n.t('bot_userProfile_add')}
|
||||
</IconButton>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</table>
|
||||
);
|
||||
};
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import { type PropsWithChildren, type ReactNode, useState } from 'react';
|
||||
|
||||
import cls from 'classnames';
|
||||
import { IconCozArrowRight } from '@coze-arch/coze-design/icons';
|
||||
import { Collapsible } from '@coze-arch/coze-design';
|
||||
|
||||
export const VariableGroupWrapper = (
|
||||
props: PropsWithChildren<{
|
||||
variableGroup: {
|
||||
key: string | ReactNode;
|
||||
description: string | ReactNode;
|
||||
};
|
||||
defaultOpen?: boolean; // 添加默认展开属性
|
||||
level?: number;
|
||||
}>,
|
||||
) => {
|
||||
const { variableGroup, children, defaultOpen = true, level = 0 } = props;
|
||||
const [isOpen, setIsOpen] = useState(defaultOpen);
|
||||
const isTopLevel = level === 0;
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cls(
|
||||
'flex w-full cursor-pointer flex-col px-1 py-2',
|
||||
isTopLevel && 'hover:coz-mg-secondary-hovered hover:rounded-lg ',
|
||||
)}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<div className="flex w-full items-center">
|
||||
<div className="w-6 flex items-center">
|
||||
<IconCozArrowRight
|
||||
className={cls(
|
||||
'w-[14px] h-[14px] transition-all',
|
||||
isOpen ? 'rotate-90' : '',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className={cls(
|
||||
'coz-stroke-primary text-xxl font-medium coz-fg-plus',
|
||||
{
|
||||
'!text-sm my-[10px]': !isTopLevel,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{variableGroup.key}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isTopLevel ? (
|
||||
<div className="text-sm coz-fg-secondary pl-6">
|
||||
{variableGroup.description}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Collapsible keepDOM isOpen={isOpen}>
|
||||
<div
|
||||
className={cls({
|
||||
'pl-3': !isTopLevel,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</Collapsible>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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 {
|
||||
SysParamHeader,
|
||||
getSysItemConfig,
|
||||
type ISysHeaderItem,
|
||||
} from './sys-header';
|
||||
export {
|
||||
UserParamHeader,
|
||||
getUserItemConfig,
|
||||
type IUserHeaderItem,
|
||||
} from './user-header';
|
||||
export { type IHeaderItemProps, type ItemType } from './types';
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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 { exhaustiveCheckSimple } from '../../utils/exhaustive-check';
|
||||
import { type IHeaderItemProps, type ItemType } from './types';
|
||||
|
||||
export type SysItemType = ItemType;
|
||||
|
||||
export type ISysHeaderItem = IHeaderItemProps;
|
||||
|
||||
export const SysParamHeader = (props: { isReadonly: boolean }) => {
|
||||
const { isReadonly } = props;
|
||||
const sysHeaderItems = [
|
||||
getSysItemConfig('filed', isReadonly),
|
||||
getSysItemConfig('description', isReadonly),
|
||||
getSysItemConfig('default', isReadonly),
|
||||
getSysItemConfig('channel', isReadonly),
|
||||
getSysItemConfig('action', isReadonly),
|
||||
];
|
||||
return (
|
||||
<thead>
|
||||
<tr className="flex gap-x-4 flex-nowrap">
|
||||
{sysHeaderItems.map(item =>
|
||||
item ? <th className={item.className}>{item.title}</th> : null,
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
);
|
||||
};
|
||||
|
||||
export const getSysItemConfig = (
|
||||
item: SysItemType,
|
||||
isReadonly: boolean,
|
||||
): ISysHeaderItem => {
|
||||
if (item === 'filed') {
|
||||
return {
|
||||
type: 'filed',
|
||||
className: 'w-[140px] flex-none basis-[140px] coz-fg-secondary',
|
||||
title: (
|
||||
<>
|
||||
{I18n.t('bot_edit_memory_title_filed')}
|
||||
<span className="coz-fg-hglt-red">*</span>
|
||||
</>
|
||||
),
|
||||
};
|
||||
}
|
||||
if (item === 'description') {
|
||||
return {
|
||||
type: 'description',
|
||||
className: 'w-[128px] flex-none basis-[128px] coz-fg-secondary',
|
||||
title: I18n.t('bot_edit_memory_title_description'),
|
||||
};
|
||||
}
|
||||
if (item === 'default') {
|
||||
return {
|
||||
type: 'default',
|
||||
className: 'w-[128px] flex-none basis-[128px] coz-fg-secondary',
|
||||
title: I18n.t('bot_edit_memory_title_default'),
|
||||
};
|
||||
}
|
||||
if (item === 'channel') {
|
||||
return {
|
||||
type: 'channel',
|
||||
className: 'w-[128px] flex-none basis-[128px] coz-fg-secondary',
|
||||
title: I18n.t('variable_Table_Title_support_channels'),
|
||||
};
|
||||
}
|
||||
if (item === 'action') {
|
||||
if (isReadonly) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: 'action',
|
||||
className: 'w-[122px] flex-none basis-[122px] coz-fg-secondary',
|
||||
title: I18n.t('bot_edit_memory_title_action'),
|
||||
};
|
||||
}
|
||||
exhaustiveCheckSimple(item);
|
||||
};
|
||||
@@ -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 ReactNode } from 'react';
|
||||
|
||||
export type ItemType =
|
||||
| 'filed'
|
||||
| 'description'
|
||||
| 'default'
|
||||
| 'channel'
|
||||
| 'action';
|
||||
|
||||
export interface IHeaderItemProps {
|
||||
type: ItemType;
|
||||
className: string;
|
||||
title: string | ReactNode;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 { exhaustiveCheckSimple } from '../../utils/exhaustive-check';
|
||||
import { type IHeaderItemProps, type ItemType } from './types';
|
||||
|
||||
export type UserItemType = Exclude<ItemType, 'channel'>;
|
||||
|
||||
export type IUserHeaderItem = IHeaderItemProps;
|
||||
|
||||
export const UserParamHeader = (props: { isReadonly: boolean }) => {
|
||||
const { isReadonly } = props;
|
||||
const userHeaderItems = [
|
||||
getUserItemConfig('filed', isReadonly),
|
||||
getUserItemConfig('description', isReadonly),
|
||||
getUserItemConfig('default', isReadonly),
|
||||
getUserItemConfig('action', isReadonly),
|
||||
];
|
||||
return (
|
||||
<thead>
|
||||
<tr className="flex gap-x-4 flex-nowrap">
|
||||
{userHeaderItems.map(item =>
|
||||
item ? <th className={item.className}>{item.title}</th> : null,
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
);
|
||||
};
|
||||
|
||||
export const getUserItemConfig = (
|
||||
item: UserItemType,
|
||||
isReadonly: boolean,
|
||||
): IUserHeaderItem => {
|
||||
if (item === 'filed') {
|
||||
return {
|
||||
type: 'filed',
|
||||
className: 'flex-1 coz-fg-secondary',
|
||||
title: (
|
||||
<>
|
||||
{I18n.t('bot_edit_memory_title_filed')}
|
||||
<span className="coz-fg-hglt-red">*</span>
|
||||
</>
|
||||
),
|
||||
};
|
||||
}
|
||||
if (item === 'description') {
|
||||
return {
|
||||
type: 'description',
|
||||
className: 'flex-1 coz-fg-secondary',
|
||||
title: I18n.t('bot_edit_memory_title_description'),
|
||||
};
|
||||
}
|
||||
if (item === 'default') {
|
||||
return {
|
||||
type: 'default',
|
||||
className: 'w-[164px] flex-none basis-[164px] coz-fg-secondary',
|
||||
title: I18n.t('bot_edit_memory_title_default'),
|
||||
};
|
||||
}
|
||||
if (item === 'action') {
|
||||
if (isReadonly) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: 'action',
|
||||
className: 'w-[122px] flex-none basis-[122px] coz-fg-secondary',
|
||||
title: I18n.t('bot_edit_memory_title_action'),
|
||||
};
|
||||
}
|
||||
exhaustiveCheckSimple(item);
|
||||
};
|
||||
@@ -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 classNames from 'classnames';
|
||||
|
||||
import s from '../../index.module.less';
|
||||
import { type ISysConfigItem } from '../../hooks';
|
||||
|
||||
export const VariableTree = (props: {
|
||||
isReadonly?: boolean;
|
||||
highLight?: boolean;
|
||||
activeId?: string;
|
||||
configList: ISysConfigItem[];
|
||||
}) => {
|
||||
const { isReadonly, highLight, activeId, configList } = props;
|
||||
|
||||
return (
|
||||
<tbody className="overflow-visible flex-1 h-0">
|
||||
{configList.map((item: ISysConfigItem, index: number) => (
|
||||
<tr
|
||||
key={`memory-row-list_${index}`}
|
||||
className={classNames(
|
||||
s['memory-row'],
|
||||
activeId === item.id && highLight && s['active-row'],
|
||||
activeId === item.id && highLight && 'active-row',
|
||||
'flex gap-x-4 flex-nowrap',
|
||||
)}
|
||||
>
|
||||
{item.key ? <td>{item.key}</td> : null}
|
||||
{item.description ? <td>{item.description}</td> : null}
|
||||
{item.default_value ? <td>{item.default_value}</td> : null}
|
||||
{item.channel ? <td>{item.channel}</td> : null}
|
||||
{item.method && !isReadonly ? <td>{item.method}</td> : null}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
);
|
||||
};
|
||||
@@ -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 { useParams } from 'react-router-dom';
|
||||
import React, { type FC, useState, useEffect } from 'react';
|
||||
|
||||
import { SkillKeyEnum } from '@coze-agent-ide/tool-config';
|
||||
import {
|
||||
AddButton,
|
||||
ToolContentBlock,
|
||||
useToolValidData,
|
||||
type ToolEntryCommonProps,
|
||||
} from '@coze-agent-ide/tool';
|
||||
import { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
|
||||
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { OpenBlockEvent, emitEvent } from '@coze-arch/bot-utils';
|
||||
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { useDefaultExPandCheck } from '@coze-arch/bot-hooks';
|
||||
import { DataErrorBoundary, DataNamespace } from '@coze-data/reporter';
|
||||
|
||||
import { MemoryList } from './memory-list';
|
||||
import { MemoryAddModal } from './memory-add-modal';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
const MAX_SIZE = 10;
|
||||
|
||||
type IDataMemoryProps = ToolEntryCommonProps;
|
||||
|
||||
const BaseDataMemory: FC<IDataMemoryProps> = ({ title }) => {
|
||||
const setToolValidData = useToolValidData();
|
||||
const variables = useBotSkillStore($store => $store.variables);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const isReadonly = useBotDetailIsReadonly();
|
||||
const [activeId, setActiveId] = useState<undefined | string>();
|
||||
|
||||
const params = useParams<DynamicParams>();
|
||||
|
||||
const onOpenMemoryAdd = ($activeId?: string) => {
|
||||
if (isReadonly) {
|
||||
return;
|
||||
}
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
bot_id: params?.bot_id || '',
|
||||
resource_type: 'variable',
|
||||
action: 'turn_on',
|
||||
source: 'bot_detail_page',
|
||||
source_detail: 'memory_manage',
|
||||
});
|
||||
setVisible(true);
|
||||
setActiveId($activeId);
|
||||
};
|
||||
|
||||
const defaultExpand = useDefaultExPandCheck({
|
||||
blockKey: SkillKeyEnum.DATA_MEMORY_BLOCK,
|
||||
configured: variables.length > 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setToolValidData(Boolean(variables?.length));
|
||||
}, [variables?.length]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ToolContentBlock
|
||||
blockEventName={OpenBlockEvent.DATA_MEMORY_BLOCK_OPEN}
|
||||
showBottomBorder
|
||||
header={title}
|
||||
defaultExpand={defaultExpand}
|
||||
// icon={userInfo}
|
||||
actionButton={
|
||||
<>
|
||||
<AddButton
|
||||
tooltips={
|
||||
variables.length < MAX_SIZE
|
||||
? I18n.t('bot_edit_variable_add_tooltip')
|
||||
: I18n.t('bot_edit_variable_add_tooltip_edit')
|
||||
}
|
||||
onClick={() => onOpenMemoryAdd()}
|
||||
enableAutoHidden={true}
|
||||
data-testid="bot.editor.tool.data-memory.add-button"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className={s['memory-content']}>
|
||||
<MemoryList onOpenMemoryAdd={onOpenMemoryAdd} />
|
||||
</div>
|
||||
</ToolContentBlock>
|
||||
<MemoryAddModal
|
||||
visible={visible}
|
||||
activeId={activeId}
|
||||
onCancel={() => {
|
||||
setVisible(false);
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
bot_id: params?.bot_id || '',
|
||||
resource_type: 'variable',
|
||||
action: 'turn_off',
|
||||
source: 'bot_detail_page',
|
||||
source_detail: 'memory_manage',
|
||||
});
|
||||
}}
|
||||
onOk={() => {
|
||||
setVisible(false);
|
||||
emitEvent(OpenBlockEvent.DATA_MEMORY_BLOCK_OPEN);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const DataMemory: FC<IDataMemoryProps> = props => (
|
||||
<DataErrorBoundary namespace={DataNamespace.VARIABLE}>
|
||||
<BaseDataMemory {...props} />
|
||||
</DataErrorBoundary>
|
||||
);
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* 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 @coze-arch/max-line-per-function */
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { nanoid } from 'nanoid';
|
||||
import classNames from 'classnames';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
|
||||
import { type VariableItem } from '@coze-studio/bot-detail-store';
|
||||
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
|
||||
import {
|
||||
Checkbox,
|
||||
Space,
|
||||
Switch,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from '@coze-arch/coze-design';
|
||||
import { IconInfo } from '@coze-arch/bot-icons';
|
||||
import { type GetSysVariableConfResponse } from '@coze-arch/bot-api/memory';
|
||||
import { MemoryApi } from '@coze-arch/bot-api';
|
||||
import { BotE2e } from '@coze-data/e2e';
|
||||
const { Text } = Typography;
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export type TVariable = VariableItem & {
|
||||
enable?: boolean;
|
||||
must_not_use_in_prompt?: string; // 服务端类型已上线无法改boolean。""、"false"、"true"
|
||||
ext_desc?: string;
|
||||
prompt_disabled?: boolean;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
EffectiveChannelList?: string[];
|
||||
};
|
||||
|
||||
export interface ISysConfigItem {
|
||||
id: string;
|
||||
key: React.ReactNode;
|
||||
default_value: React.ReactNode;
|
||||
description: React.ReactNode;
|
||||
channel?: React.ReactNode;
|
||||
method?: React.ReactNode;
|
||||
}
|
||||
export interface ISysConfigItemGroup {
|
||||
id: string;
|
||||
key: React.ReactNode;
|
||||
default_value: React.ReactNode;
|
||||
description: React.ReactNode;
|
||||
method?: React.ReactNode;
|
||||
channel?: React.ReactNode;
|
||||
var_info_list?: ISysConfigItem[];
|
||||
}
|
||||
export interface SystemConfig {
|
||||
sysConfigList: ISysConfigItemGroup[];
|
||||
sysVariables: TVariable[];
|
||||
enableVariables: VariableItem[];
|
||||
loading: boolean;
|
||||
}
|
||||
export interface SysConfigData {
|
||||
conf: TVariable[];
|
||||
groupConf: GetSysVariableConfResponse['group_conf'];
|
||||
}
|
||||
|
||||
export const useSystemVariables = (
|
||||
variables: VariableItem[],
|
||||
visible: boolean,
|
||||
): SystemConfig => {
|
||||
const { run, loading } = useRequest(async () => {
|
||||
const res = await MemoryApi.GetSysVariableConf();
|
||||
const resData = res?.group_conf?.reduce(
|
||||
(prev, cur) => {
|
||||
cur.group_name
|
||||
? prev.group_conf.push(cur)
|
||||
: (prev.conf = prev.conf?.concat(cur.var_info_list || []));
|
||||
return prev;
|
||||
},
|
||||
{
|
||||
conf: [],
|
||||
group_conf: [],
|
||||
},
|
||||
);
|
||||
// 分组新逻辑
|
||||
const configInfo = initSysVarStatus(resData);
|
||||
setConfig(configInfo);
|
||||
});
|
||||
|
||||
const { variables: values } = useBotSkillStore(
|
||||
useShallow(state => ({
|
||||
variables: state.variables,
|
||||
})),
|
||||
);
|
||||
const [sysConfig, setConfig] = useState<SysConfigData>({
|
||||
conf: [],
|
||||
groupConf: [],
|
||||
});
|
||||
// 这里需要根据config来设置sysVariables
|
||||
const sysVariables = useMemo(() => {
|
||||
const group = sysConfig.groupConf?.reduce(
|
||||
(prev, cur) => prev.concat(cur?.var_info_list),
|
||||
[],
|
||||
);
|
||||
return [...sysConfig.conf, ...group];
|
||||
}, [sysConfig]);
|
||||
|
||||
// 拼接已启用的系统变量和自定义变量
|
||||
const enableVariables = useMemo(() => {
|
||||
const enableSysVariables =
|
||||
sysVariables
|
||||
.filter(v => v.enable)
|
||||
?.map(sys => ({ ...sys, is_system: true })) || [];
|
||||
const customVariables =
|
||||
variables.filter(variable => !variable.is_system) || [];
|
||||
return [...enableSysVariables, ...customVariables];
|
||||
}, [variables, sysVariables]);
|
||||
|
||||
const initSysVarStatus = data => {
|
||||
const { conf = [], group_conf = [] } = data || {};
|
||||
const setItem = varItem => {
|
||||
const enableItem: TVariable | undefined = values?.find(
|
||||
item => item.key === varItem.key && item.is_system,
|
||||
);
|
||||
return {
|
||||
...varItem,
|
||||
is_system: enableItem?.is_system,
|
||||
enable: !!enableItem,
|
||||
prompt_disabled: enableItem?.prompt_disabled ?? true,
|
||||
};
|
||||
};
|
||||
const confLIst = conf?.map(setItem);
|
||||
const groupConfList = group_conf?.map(group => ({
|
||||
...group,
|
||||
var_info_list: group.var_info_list?.map(groupItem => ({
|
||||
...setItem(groupItem),
|
||||
prompt_disabled: true,
|
||||
channel: groupItem?.EffectiveChannelList?.join(','),
|
||||
})),
|
||||
}));
|
||||
return {
|
||||
conf: confLIst || [],
|
||||
groupConf: groupConfList || [],
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
run();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
const setSysConfigStatus = (key, prop, checked) => {
|
||||
const { conf = [], groupConf = [] } = sysConfig;
|
||||
const configIndex = conf.findIndex(confItem => confItem.key === key);
|
||||
if (configIndex !== -1) {
|
||||
conf[configIndex][prop] = checked;
|
||||
if (prop === 'enable') {
|
||||
conf[configIndex].prompt_disabled = !checked;
|
||||
}
|
||||
}
|
||||
groupConf.forEach(groupItem => {
|
||||
const index = groupItem?.var_info_list.findIndex(
|
||||
item => item.key === key,
|
||||
);
|
||||
if (index !== -1) {
|
||||
groupItem.var_info_list[index][prop] = checked;
|
||||
}
|
||||
setConfig({ conf, groupConf });
|
||||
});
|
||||
};
|
||||
|
||||
const changeEnable = (checked: boolean, key: string) => {
|
||||
setSysConfigStatus(key, 'enable', checked);
|
||||
};
|
||||
|
||||
const changeCheckbox = (checked: boolean, key: string) => {
|
||||
setSysConfigStatus(key, 'prompt_disabled', checked);
|
||||
};
|
||||
|
||||
const SysVarConfigRender = ({
|
||||
value,
|
||||
enable,
|
||||
e2e,
|
||||
extDesc,
|
||||
className,
|
||||
}: {
|
||||
value: string;
|
||||
enable: boolean | undefined;
|
||||
e2e?: string;
|
||||
extDesc?: string;
|
||||
className?: string;
|
||||
}): JSX.Element => (
|
||||
<div
|
||||
className={classNames(
|
||||
[s.sys_item_box, !enable && s.disabled, className],
|
||||
'flex items-center',
|
||||
)}
|
||||
data-dtestid={e2e}
|
||||
>
|
||||
<Text ellipsis={{ showTooltip: true }}>{value}</Text>
|
||||
{!!extDesc && (
|
||||
<Tooltip content={I18n.t(extDesc as I18nKeysNoOptionsType)}>
|
||||
<IconInfo
|
||||
style={{
|
||||
color: '#C6CACD',
|
||||
marginLeft: 4,
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
const SysVarGroupConfigRender = ({
|
||||
value,
|
||||
e2e,
|
||||
enable = true,
|
||||
extDesc,
|
||||
className,
|
||||
}: {
|
||||
value: string;
|
||||
e2e?: string;
|
||||
enable?: boolean;
|
||||
extDesc?: string;
|
||||
className?: string;
|
||||
}): JSX.Element => (
|
||||
<div
|
||||
className={classNames([
|
||||
s.sys_item_group,
|
||||
!enable && s.disabled,
|
||||
className,
|
||||
])}
|
||||
data-dtestid={e2e}
|
||||
>
|
||||
<div>{value}</div>
|
||||
{!!extDesc && (
|
||||
<Tooltip content={I18n.t(extDesc as I18nKeysNoOptionsType)}>
|
||||
<IconInfo
|
||||
style={{
|
||||
color: '#C6CACD',
|
||||
marginLeft: 4,
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
const configItem = (
|
||||
item: TVariable,
|
||||
promptDisabled = false,
|
||||
): ISysConfigItem => ({
|
||||
id: item.key,
|
||||
key: SysVarConfigRender({
|
||||
value: item.key ?? '',
|
||||
enable: item.enable,
|
||||
e2e: `${BotE2e.BotVariableAddModalNameText}.${item.key}`,
|
||||
extDesc: item.ext_desc,
|
||||
className: 'w-[140px] flex-none basis-[140px]',
|
||||
}),
|
||||
description: SysVarConfigRender({
|
||||
value: item.description ?? '',
|
||||
enable: item.enable,
|
||||
e2e: `${BotE2e.BotVariableAddModalDescText}.${item.key}`,
|
||||
className: 'w-[128px] flex-none basis-[128px]',
|
||||
}),
|
||||
default_value: SysVarConfigRender({
|
||||
value: item.default_value || '--',
|
||||
enable: item.enable,
|
||||
e2e: `${BotE2e.BotVariableAddModalDefaultValueText}.${item.key}`,
|
||||
className: 'w-[128px] flex-none basis-[128px]',
|
||||
}),
|
||||
channel: SysVarConfigRender({
|
||||
value: item.channel || '--',
|
||||
enable: item.enable,
|
||||
className: 'w-[128px] flex-none basis-[128px]',
|
||||
}),
|
||||
method: (
|
||||
<Space className={s['memory-method']} spacing={24}>
|
||||
<Tooltip content={I18n.t('variable_240520_03')} theme="dark">
|
||||
<div className={s['memory-method-checkbox']}>
|
||||
<Checkbox
|
||||
disabled={
|
||||
promptDisabled ||
|
||||
!item.enable ||
|
||||
item.must_not_use_in_prompt === 'true'
|
||||
}
|
||||
checked={item?.prompt_disabled ? false : true}
|
||||
onChange={v => {
|
||||
changeCheckbox(!v.target.checked, item.key);
|
||||
}}
|
||||
></Checkbox>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
showArrow
|
||||
position="top"
|
||||
theme="dark"
|
||||
zIndex={1031}
|
||||
style={{
|
||||
backgroundColor: '#41464c',
|
||||
color: '#fff',
|
||||
maxWidth: '276px',
|
||||
}}
|
||||
content={I18n.t('variable_240407_01')}
|
||||
>
|
||||
<Switch
|
||||
data-dtestid={`${BotE2e.BotVariableAddModalSwitch}.${item.key}`}
|
||||
size="small"
|
||||
checked={item?.enable ?? false}
|
||||
onChange={checked => changeEnable(checked, item.key)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
),
|
||||
});
|
||||
|
||||
const groupList: ISysConfigItemGroup[] = sysConfig?.groupConf?.map(item => ({
|
||||
id: nanoid(),
|
||||
key: SysVarGroupConfigRender({
|
||||
value: item.group_name ?? '--',
|
||||
e2e: `${BotE2e.BotVariableAddModalNameText}.${item.group_name}`,
|
||||
extDesc: item?.group_ext_desc,
|
||||
className: 'w-[140px] flex-none basis-[140px]',
|
||||
}),
|
||||
description: SysVarConfigRender({
|
||||
value: item.group_desc || '--',
|
||||
enable: true,
|
||||
e2e: `${BotE2e.BotVariableAddModalDescText}.${item.group_name}`,
|
||||
className: 'w-[128px] flex-none basis-[128px]',
|
||||
}),
|
||||
default_value: SysVarConfigRender({
|
||||
value: '--',
|
||||
enable: true,
|
||||
e2e: `${BotE2e.BotVariableAddModalDefaultValueText}.${item.group_name}`,
|
||||
className: 'w-[128px] flex-none basis-[128px]',
|
||||
}),
|
||||
channel: SysVarConfigRender({
|
||||
value: '--',
|
||||
enable: true,
|
||||
className: 'w-[128px] flex-none basis-[128px]',
|
||||
}),
|
||||
var_info_list: item?.var_info_list?.length
|
||||
? item?.var_info_list.map(childItem => configItem(childItem, true))
|
||||
: undefined,
|
||||
}));
|
||||
|
||||
// 系统变量
|
||||
const sysConfigList: ISysConfigItem[] = sysConfig?.conf?.map(item =>
|
||||
configItem(item),
|
||||
);
|
||||
return {
|
||||
sysConfigList: [...sysConfigList, ...groupList],
|
||||
sysVariables,
|
||||
enableVariables,
|
||||
loading,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,446 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
@import '../../assets/styles/common.less';
|
||||
@import '../../assets/styles/mixins.less';
|
||||
@import '../../assets/styles/index.module.less';
|
||||
|
||||
.memory-content {
|
||||
user-select: none !important;
|
||||
}
|
||||
|
||||
.memory-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
||||
:global {
|
||||
.semi-tag-grey-light {
|
||||
cursor: pointer;
|
||||
|
||||
margin: 0 10px 12px 0;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
color: var(--light-color-grey-grey-5, #6B6B75);
|
||||
|
||||
background: var(--light-usage-fill-color-fill-1, rgba(46, 46, 56, 8%));
|
||||
border-radius: 6px;
|
||||
|
||||
&:hover {
|
||||
background: var(--light-usage-fill-color-fill-2, rgba(46, 46, 56, 12%));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.template-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
.template-cancel-button {
|
||||
min-width: 98px;
|
||||
background-color: #fff;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(46, 46, 56, 8%) !important;
|
||||
}
|
||||
|
||||
>span {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
color: var(--light-usage-text-color-text-0, #1C1D23);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.template-demo {
|
||||
.desc {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
margin: 16px 0 8px;
|
||||
|
||||
background: #FFF;
|
||||
border: 1px solid #ededee;
|
||||
border-radius: 10px;
|
||||
|
||||
.image-template {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
||||
>img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tip {
|
||||
margin-bottom: 8px;
|
||||
font-size: 10px;
|
||||
line-height: 16px;
|
||||
color: var(--light-usage-text-color-text-2, rgba(29, 28, 35, 60%));
|
||||
}
|
||||
}
|
||||
|
||||
.template-variable-list {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
|
||||
>img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.use-template-pop-confirm {
|
||||
:global {
|
||||
.semi-button.semi-button-with-icon-only.semi-button-size-small {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tip-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 560px;
|
||||
padding: 12px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 18px;
|
||||
color: var(--light-color-grey-grey-8, #2e3238);
|
||||
|
||||
.tip-top {
|
||||
padding: 12px 8px;
|
||||
background: var(--light-color-grey-grey-0, #f9f9f9);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.tip-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.default-text {
|
||||
.tip-text;
|
||||
}
|
||||
|
||||
.view-examples {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
margin-bottom: 12px;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: var(--light-color-brand-brand-5, #4D53E8);
|
||||
|
||||
.view-examples-text,
|
||||
.view-examples-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.memory-add-modal {
|
||||
background-color: #F7F7FA;
|
||||
|
||||
:global {
|
||||
.semi-modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.semi-modal-content {
|
||||
height: calc(100vh - 140px);
|
||||
background-color: #F7F7FA;
|
||||
}
|
||||
|
||||
.semi-modal-footer {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.add-button-row-fix {
|
||||
margin-bottom: 38px;
|
||||
padding: 0 16px 12px 32px;
|
||||
text-align: left;
|
||||
|
||||
.add-button {
|
||||
width: 217px;
|
||||
margin: 0 !important;
|
||||
padding: 0 48px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.modal-add-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
.memory-add-empty {
|
||||
margin-top: -8.5%;
|
||||
|
||||
:global {
|
||||
.semi-empty-content {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.semi-empty-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
color: var(--light-usage-text-color-text-0, #1D1C23);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.use-template {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
justify-content: flex-end;
|
||||
|
||||
margin-bottom: 16px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.memory-edit-table {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
width: 100%;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
color: var(--light-usage-text-color-text-2, rgba(28, 31, 35, 60%));
|
||||
|
||||
thead {
|
||||
flex-shrink: 0;
|
||||
|
||||
tr {
|
||||
height: 28px;
|
||||
padding: 6px 16px 6px 0;
|
||||
border-bottom: 1px solid var(--light-usage-border-color-border-1, rgba(29, 28, 35, 12%));
|
||||
}
|
||||
|
||||
th {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
color: var(--Fg-COZ-fg-secondary, rgba(27, 41, 73, 62%));
|
||||
text-align: start;
|
||||
|
||||
// padding: 0 12px;
|
||||
&:last-child {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.memory-row {
|
||||
position: relative;
|
||||
|
||||
align-items: flex-start;
|
||||
|
||||
padding: 12px 16px 12px 0;
|
||||
|
||||
border-radius: 8px;
|
||||
|
||||
transition: background linear 300ms;
|
||||
|
||||
&.active-row {
|
||||
background: var(--light-color-brand-brand-1, #D9DCFA);
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.add-button-row {
|
||||
margin: 12px 0;
|
||||
padding: 0 22px;
|
||||
text-align: left;
|
||||
|
||||
.add-button {
|
||||
width: 217px;
|
||||
margin: 0 !important;
|
||||
padding: 0 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.memory-key-err {
|
||||
position: relative;
|
||||
color: var(--light-color-red-red-5, #f93920);
|
||||
|
||||
.key-error-tip {
|
||||
position: absolute;
|
||||
bottom: -20px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-input-wrapper {
|
||||
border: 1px solid var(--light-color-red-red-5, #f93920);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.memory-key-readonly {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: var(--light-color-grey-grey-8, #2e3238);
|
||||
}
|
||||
|
||||
.memory-description-readonly {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: var(--light-color-grey-grey-8, #2e3238);
|
||||
}
|
||||
|
||||
.readonly-none {
|
||||
cursor: not-allowed;
|
||||
|
||||
width: 100%;
|
||||
padding: 6px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
color: var(--semi-color-disabled-text);
|
||||
|
||||
background: var(--light-color-grey-grey-1, #edeff2);
|
||||
border: 1px solid var(--semi-color-border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.memory-description-readonly,
|
||||
.memory-key-readonly {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
padding: 6px;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 20px;
|
||||
color: var(--light-usage-text-color-text-1, rgba(28, 29, 35, 80%));
|
||||
|
||||
background: var(--light-color-grey-grey-1, #edeff2);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.memory-method {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
height: 32px;
|
||||
|
||||
text-align: center;
|
||||
|
||||
:global {
|
||||
.semi-space {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
&-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sys_item_box {
|
||||
min-height: 32px;
|
||||
padding-left: 12px;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 32px;
|
||||
color: var(--Light-usage-text---color-text-0, #1D1C23);
|
||||
|
||||
// &.disabled {
|
||||
// color: var(--Light-usage-text---color-text-3, rgba(29, 28, 35, 35%));
|
||||
// }
|
||||
}
|
||||
|
||||
.sys_item_group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: var(--Light-usage-text---color-text-0, #1D1C23);
|
||||
|
||||
&.disabled {
|
||||
color: var(--Light-usage-text---color-text-3, rgba(29, 28, 35, 35%));
|
||||
}
|
||||
}
|
||||
|
||||
.group-collapsible {
|
||||
display: flex;
|
||||
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--Light-usage-text---color-text-0, #1D1C23);
|
||||
|
||||
&-key {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&-value {
|
||||
div {
|
||||
padding-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&-desc {
|
||||
div {
|
||||
padding-left:8px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 { DataMemory } from './data-memory';
|
||||
@@ -0,0 +1,489 @@
|
||||
/*
|
||||
* 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 @coze-arch/max-line-per-function */
|
||||
/* eslint-disable max-lines-per-function */
|
||||
import { type ComponentProps, useState, useRef, useEffect } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { uniqBy } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import {
|
||||
useBotDetailIsReadonly,
|
||||
type VariableItem,
|
||||
uniqMemoryList,
|
||||
VariableKeyErrType,
|
||||
} from '@coze-studio/bot-detail-store';
|
||||
import { useBotInfoAuditor } from '@coze-studio/bot-audit-adapter';
|
||||
import { BotE2e } from '@coze-data/e2e';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozTrashCan, IconCozPlus } from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
IconButton,
|
||||
Modal,
|
||||
Input,
|
||||
Typography,
|
||||
Tooltip,
|
||||
Space,
|
||||
Form,
|
||||
Checkbox,
|
||||
Switch,
|
||||
Button,
|
||||
} from '@coze-arch/coze-design';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
|
||||
import { AddButton } from '../add-button';
|
||||
import { MemoryTemplateModal } from './memory-template-modal';
|
||||
import { useSystemVariables } from './hooks';
|
||||
import { SysParamHeader, UserParamHeader } from './components/parma-header';
|
||||
import { VariableGroupWrapper } from './components/group-wrapper';
|
||||
import { GroupTable } from './components/group-table';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
const DEFAULT_VARIABLE_LENGTH = 10;
|
||||
const ACTIVE_ID_TIMER_INTERVAL = 1000;
|
||||
const INPUT_TIMER_INTERVAL = 100;
|
||||
|
||||
export type MemoryAddModalProps = ComponentProps<typeof Modal> & {
|
||||
activeId?: string;
|
||||
onOk?: () => void;
|
||||
onCancel?: () => void;
|
||||
};
|
||||
|
||||
export const MemoryAddModal: React.FC<MemoryAddModalProps> = props => {
|
||||
const isReadonly = useBotDetailIsReadonly();
|
||||
const botInfoAuditor = useBotInfoAuditor();
|
||||
const { variables: variablesInStore, setBotSkillByImmer } = useBotSkillStore(
|
||||
useShallow(state => ({
|
||||
variables: state.variables,
|
||||
setBotSkillByImmer: state.setBotSkillByImmer,
|
||||
})),
|
||||
);
|
||||
const { botId } = useBotInfoStore(
|
||||
useShallow(state => ({
|
||||
botId: state.botId,
|
||||
})),
|
||||
);
|
||||
|
||||
const [variables, setVariables] = useState<VariableItem[]>([]);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [highLight, setHighLight] = useState(false);
|
||||
|
||||
const [timer, setTimer] = useState<undefined | NodeJS.Timeout>();
|
||||
|
||||
const inputingRef = useRef<HTMLInputElement>(null);
|
||||
const tbodyRef = useRef<HTMLTableSectionElement>(null);
|
||||
const [addButtonFix, setAddButtonFix] = useState(false);
|
||||
|
||||
const { sysConfigList, sysVariables, enableVariables, loading } =
|
||||
useSystemVariables(variables, !!props.visible);
|
||||
|
||||
const onBlur = () => {
|
||||
setVariables(uniqMemoryList(variables, sysVariables));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (props.visible) {
|
||||
setVariables(
|
||||
uniqMemoryList(
|
||||
variablesInStore?.filter(varItem => !varItem.is_system),
|
||||
sysVariables,
|
||||
),
|
||||
);
|
||||
if (!variablesInStore.length) {
|
||||
handleInputedClick('init');
|
||||
}
|
||||
if (props.activeId) {
|
||||
clearTimeout(timer);
|
||||
setHighLight(true);
|
||||
setTimer(
|
||||
setTimeout(() => {
|
||||
setHighLight(false);
|
||||
}, ACTIVE_ID_TIMER_INTERVAL),
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [props.activeId, props.visible]);
|
||||
|
||||
useEffect(() => {
|
||||
// 控制高亮的元素滚至视区内
|
||||
if (highLight) {
|
||||
document.getElementsByClassName('active-row')?.[0]?.scrollIntoView();
|
||||
}
|
||||
}, [highLight]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tbodyRef.current) {
|
||||
const tbodyScrollHeight = tbodyRef.current.scrollHeight;
|
||||
const tbodyClientHeight = tbodyRef.current.clientHeight;
|
||||
setAddButtonFix(tbodyScrollHeight > tbodyClientHeight);
|
||||
}
|
||||
}, [tbodyRef.current, variables.length]);
|
||||
|
||||
const handleInputedClick = (type?: 'init') => {
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
bot_id: botId,
|
||||
resource_type: 'variable',
|
||||
action: 'add',
|
||||
source: 'bot_detail_page',
|
||||
source_detail: 'memory_manage',
|
||||
});
|
||||
|
||||
setVariables([
|
||||
...(type === 'init' ? [] : variables),
|
||||
{
|
||||
id: nanoid(),
|
||||
key: '',
|
||||
description: '',
|
||||
default_value: '',
|
||||
prompt_disabled: false,
|
||||
},
|
||||
]);
|
||||
setTimeout(() => {
|
||||
inputingRef?.current?.focus();
|
||||
}, INPUT_TIMER_INTERVAL);
|
||||
};
|
||||
|
||||
const mutateItemByKey = (
|
||||
key: string,
|
||||
value: string | boolean | undefined,
|
||||
index: number,
|
||||
) => {
|
||||
const tempArr = [...variables];
|
||||
tempArr[index] = { ...tempArr[index], [key]: value };
|
||||
setVariables(uniqMemoryList([...tempArr], sysVariables));
|
||||
botInfoAuditor.reset();
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
botInfoAuditor.reset();
|
||||
props?.onCancel?.();
|
||||
};
|
||||
|
||||
const configList = variables.map((item: VariableItem, index: number) => {
|
||||
const sendTeaEventEdit = () => {
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
bot_id: botId,
|
||||
resource_id: item.id,
|
||||
resource_name: item.key,
|
||||
resource_type: 'variable',
|
||||
action: 'edit',
|
||||
source: 'bot_detail_page',
|
||||
source_detail: 'memory_manage',
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
id: item.key,
|
||||
key: !isReadonly ? (
|
||||
<div
|
||||
className={classNames(s['memory-key'], {
|
||||
[s['memory-key-err']]: item.errType,
|
||||
})}
|
||||
>
|
||||
<Input
|
||||
data-testid={`${BotE2e.BotVariableAddModalNameInput}.${item.key}`}
|
||||
data-dtestid={`${BotE2e.BotVariableAddModalNameInput}.${item.key}`}
|
||||
disabled={isReadonly}
|
||||
placeholder={I18n.t('variable_name_placeholder')}
|
||||
className="flex-1"
|
||||
value={item.key}
|
||||
ref={inputingRef}
|
||||
onChange={v => {
|
||||
mutateItemByKey('key', v, index);
|
||||
}}
|
||||
autoFocus={!item.key}
|
||||
maxLength={50}
|
||||
onBlur={() => {
|
||||
sendTeaEventEdit();
|
||||
onBlur();
|
||||
}}
|
||||
/>
|
||||
{item.errType === VariableKeyErrType.KEY_NAME_USED && (
|
||||
<span className={s['key-error-tip']}>
|
||||
{I18n.t('bot_edit_variable_field_occupied_error')}
|
||||
</span>
|
||||
)}
|
||||
{item.errType === VariableKeyErrType.KEY_IS_NULL && (
|
||||
<span className={s['key-error-tip']}>
|
||||
{I18n.t('bot_edit_variable_field_required_error')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Typography.Text
|
||||
data-testid={`${BotE2e.BotVariableAddModalNameInput}.${item.key}`}
|
||||
className={classNames(
|
||||
s['memory-key-readonly'],
|
||||
!item.key && s['readonly-none'],
|
||||
'flex-1',
|
||||
)}
|
||||
ellipsis={{ showTooltip: true }}
|
||||
>
|
||||
{item.key || I18n.t('bot_element_unset')}
|
||||
</Typography.Text>
|
||||
),
|
||||
description: !isReadonly ? (
|
||||
<Input
|
||||
data-testid={`${BotE2e.BotVariableAddModalDescInput}.${item.key}`}
|
||||
disabled={isReadonly}
|
||||
className={classNames(s['memory-description'], 'flex-1')}
|
||||
placeholder={I18n.t('bot_edit_variable_description_placeholder')}
|
||||
value={item.description}
|
||||
onChange={v => {
|
||||
mutateItemByKey('description', v, index);
|
||||
}}
|
||||
maxLength={200}
|
||||
onBlur={() => {
|
||||
sendTeaEventEdit();
|
||||
onBlur();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography.Text
|
||||
data-testid={`${BotE2e.BotVariableAddModalDescInput}.${item.key}`}
|
||||
className={classNames(
|
||||
s['memory-description-readonly'],
|
||||
!item.description && s['readonly-none'],
|
||||
'flex-1',
|
||||
)}
|
||||
ellipsis={{ showTooltip: true }}
|
||||
>
|
||||
{item.description || I18n.t('bot_element_unset')}
|
||||
</Typography.Text>
|
||||
),
|
||||
default_value: !isReadonly ? (
|
||||
<Input
|
||||
data-testid={`${BotE2e.BotVariableAddModalDefaultValueInput}.${item.key}`}
|
||||
disabled={isReadonly}
|
||||
className={classNames(
|
||||
s['memory-description'],
|
||||
'w-[164px] basis-[164px] flex-none',
|
||||
)}
|
||||
placeholder={I18n.t('bot_edit_variable_default_value_placeholder')}
|
||||
value={item.default_value}
|
||||
onChange={v => {
|
||||
mutateItemByKey('default_value', v, index);
|
||||
}}
|
||||
maxLength={1000}
|
||||
onBlur={() => {
|
||||
sendTeaEventEdit();
|
||||
onBlur();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Typography.Text
|
||||
data-testid={`${BotE2e.BotVariableAddModalDefaultValueInput}.${item.key}`}
|
||||
className={classNames(
|
||||
s['memory-description-readonly'],
|
||||
!item.default_value && s['readonly-none'],
|
||||
'w-[164px] basis-[164px] flex-none',
|
||||
)}
|
||||
ellipsis={{ showTooltip: true }}
|
||||
>
|
||||
{item.default_value || I18n.t('bot_element_unset')}
|
||||
</Typography.Text>
|
||||
),
|
||||
method: (
|
||||
<Space className={s['memory-method']} spacing={14}>
|
||||
<Tooltip content={I18n.t('variable_240520_03')} theme="dark">
|
||||
<div className={s['memory-method-checkbox']}>
|
||||
<Checkbox
|
||||
checked={item?.prompt_disabled ? false : true}
|
||||
onChange={v => {
|
||||
mutateItemByKey('prompt_disabled', !v.target.checked, index);
|
||||
}}
|
||||
></Checkbox>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Switch
|
||||
data-testid={`${BotE2e.BotVariableAddModalSwitch}.${item.key}`}
|
||||
size="small"
|
||||
checked={!item?.is_disabled}
|
||||
onChange={checked => {
|
||||
mutateItemByKey('is_disabled', !checked, index);
|
||||
}}
|
||||
/>
|
||||
<Tooltip content={I18n.t('bot_datamemory_remove_field')} theme="dark">
|
||||
<IconButton
|
||||
data-dtestid={`${BotE2e.BotVariableAddModalDelBtn}.${item.key}`}
|
||||
icon={<IconCozTrashCan />}
|
||||
color="secondary"
|
||||
onClick={() => {
|
||||
if (isReadonly) {
|
||||
return;
|
||||
}
|
||||
sendTeaEvent(EVENT_NAMES.memory_click_front, {
|
||||
bot_id: botId,
|
||||
resource_id: item.id,
|
||||
resource_name: item.key,
|
||||
resource_type: 'variable',
|
||||
action: 'delete',
|
||||
source: 'bot_detail_page',
|
||||
source_detail: 'memory_manage',
|
||||
});
|
||||
variables.splice(index, 1);
|
||||
onBlur();
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
{...props}
|
||||
centered
|
||||
onCancel={onCancel}
|
||||
footer={
|
||||
<>
|
||||
{enableVariables.length < DEFAULT_VARIABLE_LENGTH && addButtonFix ? (
|
||||
<div className={s['add-button-row-fix']}>
|
||||
<AddButton
|
||||
className={s['add-button']}
|
||||
type="tertiary"
|
||||
onClick={() => handleInputedClick()}
|
||||
icon={<IconCozPlus />}
|
||||
>
|
||||
{I18n.t('bot_userProfile_add')}
|
||||
</AddButton>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={s['template-footer']}>
|
||||
<Button
|
||||
data-testid={BotE2e.BotVariableAddModalCancelBtn}
|
||||
color="primary"
|
||||
onClick={onCancel}
|
||||
>
|
||||
{I18n.t('edit_variables_modal_cancel_text')}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid={BotE2e.BotVariableAddModalSaveBtn}
|
||||
disabled={variables.some(
|
||||
item =>
|
||||
item.errType === VariableKeyErrType.KEY_NAME_USED ||
|
||||
item.errType === VariableKeyErrType.KEY_IS_NULL,
|
||||
)}
|
||||
onClick={async () => {
|
||||
const checkPass = await botInfoAuditor.check({
|
||||
variable_list: variables.map(i => ({
|
||||
key: i.key,
|
||||
description: i.description,
|
||||
default_value: i.default_value,
|
||||
})),
|
||||
});
|
||||
if (checkPass.check_not_pass) {
|
||||
return;
|
||||
}
|
||||
setBotSkillByImmer(botSkill => {
|
||||
botSkill.variables = [...enableVariables];
|
||||
});
|
||||
props?.onOk?.();
|
||||
}}
|
||||
>
|
||||
{I18n.t('edit_variables_modal_ok_text')}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
width={800}
|
||||
title={I18n.t('edit_variables_modal_title')}
|
||||
className={classNames(s['memory-add-modal'], props.className)}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
s['modal-add-container'],
|
||||
!variables.length && s.center,
|
||||
'gap-y-2',
|
||||
)}
|
||||
>
|
||||
{/* 用户变量 */}
|
||||
<VariableGroupWrapper
|
||||
variableGroup={{
|
||||
key: I18n.t('variable_user_name'),
|
||||
description: I18n.t('variable_user_description'),
|
||||
}}
|
||||
>
|
||||
<GroupTable
|
||||
isReadonly={isReadonly}
|
||||
loading={loading}
|
||||
highLight={highLight}
|
||||
activeId={props.activeId}
|
||||
variablesConfig={configList}
|
||||
handleInputedClick={handleInputedClick}
|
||||
header={<UserParamHeader isReadonly={isReadonly} />}
|
||||
/>
|
||||
</VariableGroupWrapper>
|
||||
{/* 系统变量 */}
|
||||
<VariableGroupWrapper
|
||||
variableGroup={{
|
||||
key: I18n.t('variable_system_name'),
|
||||
description: I18n.t('variable_system_describtion'),
|
||||
}}
|
||||
>
|
||||
<GroupTable
|
||||
isReadonly={isReadonly}
|
||||
loading={loading}
|
||||
highLight={highLight}
|
||||
activeId={props.activeId}
|
||||
subGroupConfig={sysConfigList.filter(item => item.var_info_list)}
|
||||
variablesConfig={sysConfigList.filter(item => !item.var_info_list)}
|
||||
handleInputedClick={handleInputedClick}
|
||||
header={<SysParamHeader isReadonly={isReadonly} />}
|
||||
hideAddButton={true}
|
||||
/>
|
||||
</VariableGroupWrapper>
|
||||
{!botInfoAuditor.pass && (
|
||||
<Form.ErrorMessage
|
||||
error={I18n.t('variable_edit_not_pass')}
|
||||
></Form.ErrorMessage>
|
||||
)}
|
||||
<MemoryTemplateModal
|
||||
visible={visible}
|
||||
needSecondConfirm={!!variables.length}
|
||||
showType="variableList"
|
||||
addTemplate={(arr: VariableItem[]) => {
|
||||
const result = [
|
||||
// 使用模版时覆盖历史变量
|
||||
// ...variables,
|
||||
...arr.map(q => ({
|
||||
id: nanoid(),
|
||||
...q,
|
||||
key: q.key,
|
||||
description: q.description,
|
||||
default_value: q.default_value,
|
||||
})),
|
||||
];
|
||||
setVariables(uniqBy(result, 'key').filter(i => i.key));
|
||||
setVisible(false);
|
||||
}}
|
||||
onCancel={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
onOk={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -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 { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Tag, Tooltip } from '@coze-arch/bot-semi';
|
||||
import { IconChevronRight } from '@douyinfe/semi-icons';
|
||||
|
||||
import { MemoryTemplateModal } from './memory-template-modal';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export const MemoryList = ({
|
||||
onOpenMemoryAdd,
|
||||
}: {
|
||||
onOpenMemoryAdd: (activeKey?: string) => void;
|
||||
}) => {
|
||||
const variables = useBotSkillStore(innerS => innerS.variables);
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const ELLIPSIS_SIZE = 13;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{variables.some(item => item.key) ? (
|
||||
<div className={s['memory-list']}>
|
||||
{variables.map(item => {
|
||||
if (!item.key) {
|
||||
return;
|
||||
}
|
||||
return item.key.length > ELLIPSIS_SIZE ? (
|
||||
<Tooltip content={item.key}>
|
||||
<Tag
|
||||
color="grey"
|
||||
key={`config-item_${item.key}`}
|
||||
onClick={() => onOpenMemoryAdd(item.key)}
|
||||
>
|
||||
{item.key.slice(0, ELLIPSIS_SIZE)}...
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tag
|
||||
color="grey"
|
||||
key={`config-item_${item.key}`}
|
||||
onClick={() => onOpenMemoryAdd(item.key)}
|
||||
>
|
||||
{item.key}
|
||||
</Tag>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className={s['default-text']}>
|
||||
{I18n.t('user_profile_intro')}
|
||||
</div>
|
||||
{FEATURE_ENABLE_VARIABLE ? (
|
||||
<div className={s['view-examples']}>
|
||||
<div
|
||||
className={s['view-examples-text']}
|
||||
onClick={() => setVisible(true)}
|
||||
>
|
||||
View examples
|
||||
</div>
|
||||
<IconChevronRight
|
||||
className={s['view-examples-icon']}
|
||||
size="small"
|
||||
style={{ marginLeft: 4 }}
|
||||
onClick={() => setVisible(true)}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<MemoryTemplateModal
|
||||
visible={visible}
|
||||
onCancel={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
onOk={() => {
|
||||
setVisible(false);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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 ComponentProps } from 'react';
|
||||
|
||||
import { type VariableItem } from '@coze-studio/bot-detail-store';
|
||||
import { UIModal, Image, type Modal } from '@coze-arch/bot-semi';
|
||||
import { Button, Popconfirm } from '@coze-arch/bot-semi';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconAlertCircle } from '@douyinfe/semi-icons';
|
||||
|
||||
import { BotDebugButton } from '../bot-debug-button';
|
||||
import IMG_TEMPLATE_USE_I18N from '../../assets/image/template_i18n.png';
|
||||
import templateSample from '../../assets/image/sample3_i18n.png';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export type MemoryTemplateModalProps = ComponentProps<typeof Modal> & {
|
||||
addTemplate?: (arr: VariableItem[]) => void;
|
||||
needSecondConfirm?: boolean;
|
||||
showType?: 'variableList';
|
||||
};
|
||||
|
||||
const list: VariableItem[] = [
|
||||
{
|
||||
key: 'Name',
|
||||
description: I18n.t('profile_memory_sample_description_name'),
|
||||
},
|
||||
{
|
||||
key: 'Address',
|
||||
description: I18n.t('profile_memory_sample_description_address'),
|
||||
},
|
||||
{
|
||||
key: 'PhoneNumber',
|
||||
description: I18n.t('profile_memory_sample_description_mobile'),
|
||||
},
|
||||
{
|
||||
key: 'Height',
|
||||
description: I18n.t('profile_memory_sample_description_height'),
|
||||
},
|
||||
{
|
||||
key: 'Weight',
|
||||
description: I18n.t('profile_memory_sample_description_weight'),
|
||||
},
|
||||
];
|
||||
|
||||
export const MemoryTemplateModal: React.FC<
|
||||
MemoryTemplateModalProps
|
||||
> = props => (
|
||||
<UIModal
|
||||
{...props}
|
||||
type="action"
|
||||
centered
|
||||
footer={
|
||||
props.showType === 'variableList' ? (
|
||||
<div className={s['template-footer']}>
|
||||
<Button
|
||||
theme="solid"
|
||||
className={s['template-cancel-button']}
|
||||
onClick={props.onCancel}
|
||||
>
|
||||
{I18n.t('cancel_template')}
|
||||
</Button>
|
||||
{props.needSecondConfirm ? (
|
||||
<Popconfirm
|
||||
className={s['use-template-pop-confirm']}
|
||||
position="top"
|
||||
icon={
|
||||
<IconAlertCircle
|
||||
size="extra-large"
|
||||
style={{ color: '#ff9600' }}
|
||||
/>
|
||||
}
|
||||
title={I18n.t('use_template_confirm_title')}
|
||||
content={I18n.t('use_template_confirm_info')}
|
||||
okText={I18n.t('use_template_confirm_ok_text')}
|
||||
cancelText={I18n.t('use_template_confirm_ cancel_text')}
|
||||
okButtonProps={{ type: 'warning' }}
|
||||
onConfirm={() => props.addTemplate?.(list)}
|
||||
>
|
||||
<BotDebugButton
|
||||
theme="solid"
|
||||
type="primary"
|
||||
style={{ padding: '8px 12px' }}
|
||||
>
|
||||
{I18n.t('Use_template')}
|
||||
</BotDebugButton>
|
||||
</Popconfirm>
|
||||
) : (
|
||||
<BotDebugButton
|
||||
theme="solid"
|
||||
type="primary"
|
||||
style={{ padding: '8px 12px' }}
|
||||
onClick={() => props.addTemplate?.(list)}
|
||||
>
|
||||
{I18n.t('Use_template')}
|
||||
</BotDebugButton>
|
||||
)}
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
width={props.showType === 'variableList' ? 562 : 448}
|
||||
title={I18n.t('variable_template_title')}
|
||||
className={props.className}
|
||||
>
|
||||
<div className={s['modal-container']}>
|
||||
{props.showType === 'variableList' ? (
|
||||
<Image
|
||||
className={s['template-variable-list']}
|
||||
src={IMG_TEMPLATE_USE_I18N}
|
||||
preview={false}
|
||||
/>
|
||||
) : (
|
||||
<div className={s['template-demo']}>
|
||||
<div className={s.desc}>{I18n.t('variable_template_demo_desc')}</div>
|
||||
<div className={s.image}>
|
||||
<Image
|
||||
className={s['image-template']}
|
||||
src={templateSample}
|
||||
preview={false}
|
||||
/>
|
||||
</div>
|
||||
<div className={s.tip}>{I18n.t('variable_template_demo_text')}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</UIModal>
|
||||
);
|
||||
@@ -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 const exhaustiveCheckSimple = (_: never) => undefined;
|
||||
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
* 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 @coze-arch/max-line-per-function */
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import React, { type FC, useEffect, useRef, useState, useMemo } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
|
||||
import { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
|
||||
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
|
||||
import { FilterKnowledgeType } from '@coze-data/utils';
|
||||
import { type UnitType } from '@coze-data/knowledge-resource-processor-core';
|
||||
import { RagModeConfiguration } from '@coze-data/knowledge-modal-base';
|
||||
import { useKnowledgeListModal } from '@coze-data/knowledge-modal-adapter';
|
||||
import { ActionType } from '@coze-data/knowledge-ide-base/types';
|
||||
import { useDatasetStore } from '@coze-data/knowledge-data-set-for-agent';
|
||||
import { BotE2e } from '@coze-data/e2e';
|
||||
import { REPORT_EVENTS as ReportEventNames } from '@coze-arch/report-events';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozCopy, IconCozMinusCircle } from '@coze-arch/coze-design/icons';
|
||||
import { Tooltip, Popover } from '@coze-arch/coze-design';
|
||||
import { OpenBlockEvent, emitEvent } from '@coze-arch/bot-utils';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { UIButton, UITag, Toast } from '@coze-arch/bot-semi';
|
||||
import { IconRobot, IconStyleSet, IconDownArrow } from '@coze-arch/bot-icons';
|
||||
import { useDefaultExPandCheck } from '@coze-arch/bot-hooks';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import { DatasetSource, FormatType } from '@coze-arch/bot-api/knowledge';
|
||||
import { KnowledgeApi } from '@coze-arch/bot-api';
|
||||
import { SkillKeyEnum } from '@coze-agent-ide/tool-config';
|
||||
import {
|
||||
ToolContentBlock,
|
||||
useToolValidData,
|
||||
type ToolEntryCommonProps,
|
||||
ToolItemList,
|
||||
ToolItem,
|
||||
ToolItemAction,
|
||||
AddButton,
|
||||
} from '@coze-agent-ide/tool';
|
||||
import { useBotEditor } from '@coze-agent-ide/bot-editor-context-store';
|
||||
|
||||
import { usePopoverLock } from '../../hook/use-popover-lock';
|
||||
import { useDatasetAutoChangeConfirm } from '../../hook/use-dataset-auto-change-confirm';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
const E2E_NAME_MAP = {
|
||||
[FormatType.Image]: 'image',
|
||||
[FormatType.Table]: 'table',
|
||||
[FormatType.Text]: 'text',
|
||||
};
|
||||
|
||||
export const Setting: React.FC<{ modelId: string }> = ({ modelId }) => {
|
||||
const { knowledge, updateSkillKnowledgeDatasetInfo } = useBotSkillStore(
|
||||
useShallow(state => ({
|
||||
knowledge: state.knowledge,
|
||||
updateSkillKnowledgeDatasetInfo: state.updateSkillKnowledgeDatasetInfo,
|
||||
})),
|
||||
);
|
||||
const isReadonly = useBotDetailIsReadonly();
|
||||
|
||||
const { props, setLocked, visible, setVisible } = usePopoverLock();
|
||||
|
||||
const confirm = useDatasetAutoChangeConfirm();
|
||||
const hasTableDataSet = useDatasetStore(state =>
|
||||
state.dataSetList.some(dataSet => dataSet.format_type === FormatType.Table),
|
||||
);
|
||||
return (
|
||||
<Popover
|
||||
className={s['setting-content-popover']}
|
||||
content={
|
||||
<RagModeConfiguration
|
||||
showNL2SQLConfig={hasTableDataSet}
|
||||
dataSetInfo={knowledge.dataSetInfo}
|
||||
onDataSetInfoChange={async newVal => {
|
||||
const { auto } = newVal;
|
||||
// 修改调用模式时做前置检查
|
||||
if (auto !== knowledge.dataSetInfo.auto) {
|
||||
try {
|
||||
setLocked(true);
|
||||
const res = await confirm(auto, modelId);
|
||||
if (res) {
|
||||
updateSkillKnowledgeDatasetInfo(newVal);
|
||||
}
|
||||
} finally {
|
||||
setLocked(false);
|
||||
}
|
||||
} else {
|
||||
updateSkillKnowledgeDatasetInfo(newVal);
|
||||
}
|
||||
}}
|
||||
isReadonly={isReadonly}
|
||||
/>
|
||||
}
|
||||
position="bottomLeft"
|
||||
trigger="click"
|
||||
zIndex={1031}
|
||||
{...props}
|
||||
>
|
||||
<UIButton
|
||||
data-testid={BotE2e.BotKnowledgeAutoMaticBtn}
|
||||
theme="borderless"
|
||||
size="small"
|
||||
icon={knowledge.dataSetInfo.auto ? <IconRobot /> : <IconStyleSet />}
|
||||
className={s['setting-trigger']}
|
||||
onClick={() => {
|
||||
setVisible(!visible);
|
||||
}}
|
||||
>
|
||||
{knowledge.dataSetInfo.auto
|
||||
? I18n.t('dataset_automatic_call')
|
||||
: I18n.t('dataset_on_demand_call')}
|
||||
<IconDownArrow className={s['setting-trigger-icon']} />
|
||||
</UIButton>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
type IDataSetAreaProps = ToolEntryCommonProps & {
|
||||
formatType?: FormatType;
|
||||
tooltip?: string;
|
||||
initRef: React.MutableRefObject<boolean>;
|
||||
desc?: string;
|
||||
};
|
||||
|
||||
const renderTableToolNode = (title: string) => (
|
||||
<div className={s['tip-content']}>{title}</div>
|
||||
);
|
||||
|
||||
export const DataSetAreaItem: FC<IDataSetAreaProps> = ({
|
||||
title,
|
||||
desc,
|
||||
formatType,
|
||||
initRef,
|
||||
tooltip,
|
||||
}) => {
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [removedIds, setRemovedIds] = useState<string[]>([]);
|
||||
const dataSetList = useDatasetStore(state => state.dataSetList);
|
||||
const setDataSetList = useDatasetStore(state => state.setDataSetList);
|
||||
const setToolValidData = useToolValidData();
|
||||
const defaultKnowledgeType = useMemo(() => {
|
||||
switch (formatType) {
|
||||
case FormatType.Table:
|
||||
return FilterKnowledgeType.TABLE;
|
||||
case FormatType.Text:
|
||||
return FilterKnowledgeType.TEXT;
|
||||
case FormatType.Image:
|
||||
return FilterKnowledgeType.IMAGE;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}, [formatType]);
|
||||
|
||||
const { knowledge, updateSkillKnowledgeDatasetList } = useBotSkillStore(
|
||||
useShallow(state => ({
|
||||
knowledge: state.knowledge,
|
||||
updateSkillKnowledgeDatasetList: state.updateSkillKnowledgeDatasetList,
|
||||
})),
|
||||
);
|
||||
|
||||
const isReadonly = useBotDetailIsReadonly();
|
||||
const jumpToDetail = (datasetID: string) => {
|
||||
const actionType = dataSetList.find(
|
||||
dataset => dataset.dataset_id === datasetID,
|
||||
)
|
||||
? ActionType.REMOVE
|
||||
: ActionType.ADD;
|
||||
|
||||
const queryParams = {
|
||||
biz: 'agentIDE',
|
||||
bot_id: params.bot_id,
|
||||
page_mode: 'modal',
|
||||
action_type: actionType,
|
||||
};
|
||||
|
||||
navigate(
|
||||
`/space/${params.space_id}/knowledge/${datasetID}?${new URLSearchParams(queryParams).toString()}`,
|
||||
);
|
||||
};
|
||||
const jumpToAdd = (datasetID: string, type: UnitType) => {
|
||||
const queryParams = {
|
||||
biz: 'agentIDE',
|
||||
type,
|
||||
bot_id: params.bot_id,
|
||||
action_type: ActionType.ADD,
|
||||
page_mode: 'modal',
|
||||
};
|
||||
navigate(
|
||||
`/space/${params.space_id}/knowledge/${datasetID}/upload?${new URLSearchParams(queryParams).toString()}`,
|
||||
);
|
||||
};
|
||||
const { node: addModal, open: openAddModal } = useKnowledgeListModal({
|
||||
datasetList: dataSetList,
|
||||
defaultType: defaultKnowledgeType,
|
||||
onDatasetListChange: list => {
|
||||
emitEvent(OpenBlockEvent.DATA_SET_BLOCK_OPEN);
|
||||
setDataSetList(list);
|
||||
},
|
||||
onClickAddKnowledge: jumpToAdd,
|
||||
onClickKnowledgeDetail: jumpToDetail,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// 排除首次初始化和删除更新,原因:
|
||||
// 因为删除会快速操作,useEffect 追踪到数据可能是最终结果,无法保证每次删除都能监听到
|
||||
if (initRef.current && removedIds.length === 0) {
|
||||
updateSkillKnowledgeDatasetList(
|
||||
dataSetList.map(d => ({
|
||||
dataset_id: d.dataset_id ?? '',
|
||||
name: d.name,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}, [dataSetList]);
|
||||
|
||||
useEffect(() => {
|
||||
if (removedIds.length > 0) {
|
||||
const updatedDataSetList = dataSetList.filter(
|
||||
d => !removedIds.includes(d?.dataset_id ?? ''),
|
||||
);
|
||||
|
||||
const updateParam = updatedDataSetList.map(d => ({
|
||||
dataset_id: d.dataset_id ?? '',
|
||||
name: d.name,
|
||||
}));
|
||||
|
||||
updateSkillKnowledgeDatasetList(updateParam);
|
||||
setRemovedIds([]);
|
||||
}
|
||||
}, [removedIds]);
|
||||
|
||||
const onCopy = (text: string) => {
|
||||
const res = copy(text);
|
||||
if (!res) {
|
||||
throw new CustomError(ReportEventNames.parmasValidation, 'empty copy');
|
||||
}
|
||||
Toast.success({
|
||||
content: I18n.t('copy_success'),
|
||||
showClose: false,
|
||||
id: 'dataset_copy_id',
|
||||
});
|
||||
};
|
||||
|
||||
const defaultExpand = useDefaultExPandCheck({
|
||||
blockKey: SkillKeyEnum.DATA_SET_BLOCK,
|
||||
configured: knowledge.dataSetList.length > 0,
|
||||
});
|
||||
|
||||
const currentDatasetList = useMemo(
|
||||
() =>
|
||||
dataSetList.filter(
|
||||
item => formatType === undefined || item.format_type === formatType,
|
||||
),
|
||||
[dataSetList],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setToolValidData(Boolean(currentDatasetList.length));
|
||||
}, [currentDatasetList.length]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{addModal}
|
||||
<ToolContentBlock
|
||||
className={s['data-set-container']}
|
||||
blockEventName={OpenBlockEvent.DATA_SET_BLOCK_OPEN}
|
||||
header={title}
|
||||
setting={null}
|
||||
tooltipType={tooltip ? 'tooltip' : undefined}
|
||||
tooltip={tooltip ? renderTableToolNode(tooltip) : null}
|
||||
defaultExpand={defaultExpand}
|
||||
actionButton={
|
||||
<AddButton
|
||||
tooltips={I18n.t('bot_edit_dataset_add_tooltip')}
|
||||
onClick={openAddModal}
|
||||
enableAutoHidden={true}
|
||||
data-testid={`bot.editor.tool.data-set-${
|
||||
E2E_NAME_MAP[formatType as keyof typeof E2E_NAME_MAP]
|
||||
}.add-button`}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className={s['data-set-content']}>
|
||||
{currentDatasetList.length ? (
|
||||
<>
|
||||
{currentDatasetList.length && !knowledge.dataSetInfo.auto ? (
|
||||
<div className={s['dataset-setting-tip']}>
|
||||
{I18n.t('bot_edit_dataset_on_demand_prompt1')}
|
||||
<Tooltip content={I18n.t('bot_edit_datasets_copyName')}>
|
||||
<UITag
|
||||
onClick={() =>
|
||||
onCopy(I18n.t('dataset_recall_copy_value'))
|
||||
}
|
||||
type="light"
|
||||
className={s['copy-trigger']}
|
||||
>
|
||||
<IconCozCopy className={s['icon-copy']} />
|
||||
{I18n.t('dataset_recall_copy_label')}
|
||||
</UITag>
|
||||
</Tooltip>
|
||||
{I18n.t('bot_edit_dataset_on_demand_prompt2')}
|
||||
</div>
|
||||
) : null}
|
||||
<ToolItemList>
|
||||
{currentDatasetList.map((item, index) => (
|
||||
<ToolItem
|
||||
key={item.dataset_id}
|
||||
title={item?.name ?? ''}
|
||||
description={item?.description ?? ''}
|
||||
avatar={item?.icon_url ?? ''}
|
||||
onClick={() =>
|
||||
item?.dataset_id && jumpToDetail(item?.dataset_id)
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
{!isReadonly && (
|
||||
<ToolItemAction
|
||||
tooltips={I18n.t('Copy_name')}
|
||||
onClick={() => onCopy(item?.name ?? '')}
|
||||
data-testid="bot.editor.tool.plugin.copy-button"
|
||||
>
|
||||
<IconCozCopy className="text-sm coz-fg-secondary" />
|
||||
</ToolItemAction>
|
||||
)}
|
||||
|
||||
{!isReadonly && (
|
||||
<ToolItemAction
|
||||
tooltips={I18n.t('remove_dataset')}
|
||||
onClick={() => {
|
||||
setDataSetList(
|
||||
dataSetList.filter(
|
||||
d => d.dataset_id !== item.dataset_id,
|
||||
),
|
||||
);
|
||||
if (item?.dataset_id) {
|
||||
setRemovedIds([
|
||||
...removedIds,
|
||||
item?.dataset_id,
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<IconCozMinusCircle className="text-sm coz-fg-secondary" />
|
||||
</ToolItemAction>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</ToolItemList>
|
||||
</>
|
||||
) : (
|
||||
<div className={s['default-text']}>
|
||||
{desc ?? I18n.t('bot_edit_dataset_explain')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ToolContentBlock>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const useDataSetArea = () => {
|
||||
const spaceId = useSpaceStore(v => v.space.id);
|
||||
const {
|
||||
storeSet: { useDraftBotDataSetStore },
|
||||
} = useBotEditor();
|
||||
|
||||
const initRef = useRef(false);
|
||||
const setDataSetList = useDatasetStore(state => state.setDataSetList);
|
||||
const { knowledge } = useBotSkillStore(
|
||||
useShallow(state => ({
|
||||
knowledge: state.knowledge,
|
||||
})),
|
||||
);
|
||||
const { pageFrom, init } = usePageRuntimeStore(
|
||||
useShallow(state => ({
|
||||
pageFrom: state.pageFrom,
|
||||
init: state.init,
|
||||
})),
|
||||
);
|
||||
const getDataSetList = async () => {
|
||||
if (knowledge.dataSetList.length) {
|
||||
const resp = await KnowledgeApi.ListDataset({
|
||||
space_id: spaceId,
|
||||
filter: {
|
||||
dataset_ids: knowledge.dataSetList.map(i => i.dataset_id ?? ''),
|
||||
source_type:
|
||||
pageFrom === 'explore' ? DatasetSource.SourceExplore : undefined,
|
||||
},
|
||||
});
|
||||
const validDatasetList = (resp?.dataset_list ?? []).filter(item =>
|
||||
knowledge.dataSetList.some(i => i.dataset_id === item.dataset_id),
|
||||
);
|
||||
// 方便数据复用
|
||||
useDraftBotDataSetStore.getState().batchUpdate(validDatasetList);
|
||||
setDataSetList(validDatasetList);
|
||||
}
|
||||
initRef.current = true;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (init) {
|
||||
getDataSetList();
|
||||
}
|
||||
}, [init]);
|
||||
useEffect(
|
||||
() => () => {
|
||||
setDataSetList([]);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
node: DataSetAreaItem,
|
||||
initRef,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
@import '../../assets/styles/common.less';
|
||||
@import '../../assets/styles/index.module.less';
|
||||
|
||||
.icon-copy {
|
||||
.common-svg-icon(14px, rgba(107, 109, 117, 1));
|
||||
|
||||
&:hover {
|
||||
background-color: var(--semi-color-fill-0);
|
||||
}
|
||||
}
|
||||
|
||||
.data-set-content {
|
||||
.dataset-setting-tip {
|
||||
margin-bottom: 4px;
|
||||
padding: 12px;
|
||||
|
||||
color: rgba(6, 7, 9, 80%);
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
|
||||
background: rgba(186, 192, 255, 20%);
|
||||
border-radius: 8px;
|
||||
|
||||
|
||||
.copy-trigger {
|
||||
cursor: pointer;
|
||||
|
||||
margin: 0 4px;
|
||||
|
||||
color: rgba(6, 7, 9, 80%);
|
||||
background: rgba(6, 7, 9, 4%);
|
||||
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 14px;
|
||||
|
||||
.icon-copy {
|
||||
.common-svg-icon(14px, rgba(6, 7, 9, 0.04));
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
margin-right: 2px !important;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-tag-grey-light {
|
||||
/* stylelint-disable-next-line declaration-no-important */
|
||||
background: rgba(6, 7, 9, 4%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.default-text {
|
||||
.tip-text;
|
||||
}
|
||||
|
||||
.setting-trigger {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
column-gap: 4px;
|
||||
align-items: center;
|
||||
|
||||
margin-left: 8px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
|
||||
&-icon {
|
||||
svg {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-button-content-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@apply coz-fg-secondary;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.setting-content-popover {
|
||||
background: #f7f7fa;
|
||||
border-radius: 12px;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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 { useParams } from 'react-router-dom';
|
||||
import React, { type FC, type PropsWithChildren } from 'react';
|
||||
|
||||
import { useCreation } from 'ahooks';
|
||||
import { logger as rawLogger, LoggerContext } from '@coze-arch/logger';
|
||||
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
|
||||
const botDebugLogger = rawLogger.createLoggerWith({
|
||||
ctx: {
|
||||
meta: {},
|
||||
namespace: 'bot_debug',
|
||||
},
|
||||
});
|
||||
|
||||
const BotEditorLoggerContextProvider: FC<PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const params = useParams<DynamicParams>();
|
||||
|
||||
const loggerWithId = useCreation(
|
||||
() =>
|
||||
botDebugLogger.createLoggerWith({
|
||||
ctx: {
|
||||
meta: {
|
||||
bot_id: params.bot_id,
|
||||
},
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<LoggerContext.Provider value={loggerWithId}>
|
||||
{children}
|
||||
</LoggerContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export { BotEditorLoggerContextProvider };
|
||||
69
frontend/packages/agent-ide/space-bot/src/component/index.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**comp */
|
||||
export {
|
||||
TableMemory,
|
||||
reloadDatabaseList,
|
||||
useExpertModeConfig,
|
||||
} from './table-memory';
|
||||
export { SuggestionBlock } from './suggestion/suggestion-block';
|
||||
export { SheetView, SingleSheet, MultipleSheet } from './sheet-view';
|
||||
export {
|
||||
OnboardingMessage,
|
||||
settingAreaScrollId,
|
||||
EditorExpendModal,
|
||||
SuggestionList,
|
||||
type OnboardingEditorAction,
|
||||
} from './onboarding-message';
|
||||
export { ModeSelect, type ModeSelectProps } from './mode-select';
|
||||
export {
|
||||
ModeLabel,
|
||||
type ModeLabelProps,
|
||||
type ModeOption,
|
||||
} from './mode-select/mode-change-view';
|
||||
export { DataMemory } from './data-memory';
|
||||
export { ContentView } from './content-view';
|
||||
export { ChatBackground } from './chat-background';
|
||||
export { BotDebugToolPane } from './bot-debug-panel/button';
|
||||
export { BotDebugPanel } from './bot-debug-panel';
|
||||
export { BotEditorLoggerContextProvider } from './error-boundary-with-logger';
|
||||
|
||||
export { AutoGenerateButton } from './auto-generate-btn';
|
||||
export { BotDebugButton } from './bot-debug-button';
|
||||
export { CollapsibleTextarea } from './collapsible-textarea';
|
||||
export { SuggestionContent } from './suggestion/suggestion-content/suggestion-content';
|
||||
export { BotSubmitModalDiffView } from './bot-diff-view/bot-submit-modal';
|
||||
export { InputSlider } from './input-slider';
|
||||
export { Setting } from './data-set/data-set-area';
|
||||
export { AuthorizeButton } from './authorize-button';
|
||||
|
||||
export {
|
||||
NavModal,
|
||||
NAV_MODAL_MAIN_CONTENT_HEIGHT,
|
||||
NavModalItem,
|
||||
NavModalProps,
|
||||
} from './nav-modal';
|
||||
export { KvBindButton, DiffViewButton } from './connector-action';
|
||||
export { MemoryToolPane, type MemoryToolPaneProps } from './memory-tool-pane';
|
||||
|
||||
export {
|
||||
PluginPermissionManageList,
|
||||
PermissionManageTitle,
|
||||
} from './plugin-permission-manage-list';
|
||||
export { PublishPlatformSetting } from './publish-platform-setting';
|
||||
import PublishPlatformDescription from './publish-platform-description';
|
||||
export { PublishPlatformDescription };
|
||||
@@ -0,0 +1,67 @@
|
||||
.input-slider {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
:global {
|
||||
.semi-slider {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.slider {
|
||||
width: 174px;
|
||||
height: 52px;
|
||||
:global {
|
||||
.semi-slider-marks {
|
||||
top: 32px;
|
||||
font-size: 12px;
|
||||
color: var(--light-usage-text-color-text-2, rgba(28, 31, 35, 0.6));
|
||||
}
|
||||
.semi-slider-mark {
|
||||
transform: unset;
|
||||
}
|
||||
.semi-slider-mark:last-child {
|
||||
left: unset;
|
||||
right: 0;
|
||||
transform: translateX(-100%);
|
||||
width: fit-content;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.semi-slider-dot.semi-slider-dot-active {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
.input-number {
|
||||
flex: 1;
|
||||
:global {
|
||||
.semi-input-wrapper {
|
||||
border: 1px solid
|
||||
var(--light-usage-border-color-border, rgba(28, 31, 35, 0.08));
|
||||
background-color: #fff;
|
||||
&:focus-within {
|
||||
border-color: var(--semi-color-focus-border);
|
||||
}
|
||||
}
|
||||
input {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
.input-btn {
|
||||
position: absolute;
|
||||
padding: 10px 8px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
color: rgba(28, 29, 35, 0.8);
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
&:first-child {
|
||||
left: 0;
|
||||
}
|
||||
&:last-child {
|
||||
right: 0;
|
||||
}
|
||||
&-disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 { InputSlider } from './input-slider';
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* 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 { isInteger, isNumber, isUndefined } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { type SliderProps } from '@coze-arch/bot-semi/Slider';
|
||||
import { type CommonFieldProps } from '@coze-arch/bot-semi/Form';
|
||||
import { withField, InputNumber, Slider } from '@coze-arch/bot-semi';
|
||||
import { IconMinus, IconPlus } from '@douyinfe/semi-icons';
|
||||
|
||||
import { RCSliderWrapper, type RCSliderProps } from '../rc-slider-wrapper';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface InputSliderProps {
|
||||
value?: number;
|
||||
onChange?: (v: number) => void;
|
||||
max?: number;
|
||||
min?: number;
|
||||
step?: number;
|
||||
disabled?: boolean;
|
||||
decimalPlaces?: number;
|
||||
marks?: SliderProps['marks'];
|
||||
className?: string;
|
||||
|
||||
/** 是否使用 rc-slider 替换 semi-slider,目前 semi-slider 存在一个比较明显的 bug,在缩放场景下,拖拽定位存在问题,已经反馈等待修复 */
|
||||
useRcSlider?: boolean;
|
||||
}
|
||||
|
||||
const POWVAL = 10;
|
||||
const formateDecimalPlacesString = (
|
||||
value: string | number,
|
||||
prevValue?: number,
|
||||
decimalPlaces?: number,
|
||||
) => {
|
||||
if (isUndefined(decimalPlaces)) {
|
||||
return value.toString();
|
||||
}
|
||||
const numberValue = Number(value);
|
||||
const stringValue = value.toString();
|
||||
if (Number.isNaN(numberValue)) {
|
||||
return `${value}`;
|
||||
}
|
||||
if (decimalPlaces === 0 && !isInteger(Number(value)) && prevValue) {
|
||||
return `${prevValue}`;
|
||||
}
|
||||
const decimalPointIndex = stringValue.indexOf('.');
|
||||
|
||||
if (decimalPointIndex < 0) {
|
||||
return stringValue;
|
||||
}
|
||||
const formattedValue = stringValue.substring(
|
||||
0,
|
||||
decimalPointIndex + 1 + decimalPlaces,
|
||||
);
|
||||
|
||||
if (formattedValue.endsWith('.') && decimalPlaces === 0) {
|
||||
return formattedValue.substring(0, formattedValue.length - 1);
|
||||
}
|
||||
return formattedValue;
|
||||
};
|
||||
|
||||
const formateDecimalPlacesNumber = (
|
||||
value: number,
|
||||
prevValue?: number,
|
||||
decimalPlaces?: number,
|
||||
) => {
|
||||
if (isUndefined(decimalPlaces)) {
|
||||
return value;
|
||||
}
|
||||
if (decimalPlaces === 0 && !isInteger(value) && prevValue) {
|
||||
return prevValue;
|
||||
}
|
||||
const pow = Math.pow(POWVAL, decimalPlaces);
|
||||
return Math.round(value * pow) / pow;
|
||||
};
|
||||
|
||||
const BaseInputSlider: React.FC<InputSliderProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
max = 1,
|
||||
min = 0,
|
||||
step = 1,
|
||||
disabled,
|
||||
decimalPlaces,
|
||||
marks,
|
||||
className,
|
||||
useRcSlider = false,
|
||||
}) => {
|
||||
const onNumberChange = (numberValue: number) => {
|
||||
const formattedValue = formateDecimalPlacesNumber(
|
||||
numberValue,
|
||||
value,
|
||||
decimalPlaces,
|
||||
);
|
||||
onChange?.(formattedValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(s['input-slider'], className)}>
|
||||
{useRcSlider ? (
|
||||
<RCSliderWrapper
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
max={max}
|
||||
min={min}
|
||||
step={step}
|
||||
marks={marks as RCSliderProps['marks']}
|
||||
onChange={v => {
|
||||
if (typeof v === 'number') {
|
||||
onChange?.(v);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Slider
|
||||
className={s.slider}
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
max={max}
|
||||
min={min}
|
||||
step={step}
|
||||
marks={marks}
|
||||
onChange={v => {
|
||||
if (typeof v === 'number') {
|
||||
onChange?.(v);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div style={{ position: 'relative', marginLeft: 24 }}>
|
||||
<IconMinus
|
||||
className={classNames(
|
||||
s['input-btn'],
|
||||
disabled && s['input-btn-disabled'],
|
||||
)}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
if (isNumber(value) && value <= min) {
|
||||
return;
|
||||
}
|
||||
if (!disabled && value !== undefined) {
|
||||
onNumberChange(value - step);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<InputNumber
|
||||
className={s['input-number']}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
formatter={inputValue =>
|
||||
formateDecimalPlacesString(inputValue, value)
|
||||
}
|
||||
hideButtons
|
||||
onNumberChange={onNumberChange}
|
||||
max={max}
|
||||
min={min}
|
||||
/>
|
||||
<IconPlus
|
||||
className={classNames(
|
||||
s['input-btn'],
|
||||
disabled && s['input-btn-disabled'],
|
||||
)}
|
||||
onClick={e => {
|
||||
if (isNumber(value) && value >= max) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
if (!disabled && value !== undefined) {
|
||||
onNumberChange(value + step);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export const InputSlider: FC<CommonFieldProps & InputSliderProps> =
|
||||
withField(BaseInputSlider);
|
||||
@@ -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 { useState, type FC } from 'react';
|
||||
|
||||
import { type ReactElement } from 'react-markdown/lib/react-markdown';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type ButtonProps } from '@coze-arch/coze-design';
|
||||
import { IconMemoryDownMenu } from '@coze-arch/bot-icons';
|
||||
import { DataErrorBoundary, DataNamespace } from '@coze-data/reporter';
|
||||
import { BotE2e } from '@coze-data/e2e';
|
||||
import {
|
||||
MemoryDebugDropdown,
|
||||
useMemoryDebugModal,
|
||||
type MemoryDebugDropdownMenuItem,
|
||||
type MemoryModule,
|
||||
useSendTeaEventForMemoryDebug,
|
||||
} from '@coze-data/database';
|
||||
import { OperateTypeEnum, ToolPane } from '@coze-agent-ide/debug-tool-list';
|
||||
|
||||
export interface MemoryToolPaneProps {
|
||||
menuList: MemoryDebugDropdownMenuItem[];
|
||||
}
|
||||
|
||||
export const MemoryToolPane: FC<MemoryToolPaneProps> = ({ menuList }) => {
|
||||
const isStore = false;
|
||||
|
||||
const sendTeaEventForMemoryDebug = useSendTeaEventForMemoryDebug({
|
||||
isStore,
|
||||
});
|
||||
|
||||
const [curMemoryModule, setCurMemoryModule] = useState<MemoryModule>();
|
||||
|
||||
const defaultModule = menuList[0]?.name;
|
||||
|
||||
const { open, node: memoryModal } = useMemoryDebugModal({
|
||||
memoryModule: curMemoryModule || defaultModule,
|
||||
menuList,
|
||||
setMemoryModule: setCurMemoryModule,
|
||||
isStore,
|
||||
});
|
||||
|
||||
return (
|
||||
<DataErrorBoundary namespace={DataNamespace.MEMORY}>
|
||||
{memoryModal}
|
||||
{
|
||||
(
|
||||
<ToolPane
|
||||
visible={menuList.length > 0}
|
||||
itemKey={`key_${I18n.t('database_memory_menu')}`}
|
||||
operateType={OperateTypeEnum.DROPDOWN}
|
||||
title={I18n.t('database_memory_menu')}
|
||||
icon={<IconMemoryDownMenu />}
|
||||
onEntryButtonClick={() => {
|
||||
sendTeaEventForMemoryDebug(defaultModule);
|
||||
setCurMemoryModule(defaultModule);
|
||||
open();
|
||||
}}
|
||||
dropdownProps={{
|
||||
showTick: true,
|
||||
clickToHide: true,
|
||||
render: (
|
||||
<MemoryDebugDropdown
|
||||
menuList={menuList}
|
||||
onClickItem={memoryModule => {
|
||||
setCurMemoryModule(memoryModule);
|
||||
open();
|
||||
}}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
buttonProps={
|
||||
{
|
||||
'data-testid': BotE2e.BotMemoryDebugBtn,
|
||||
} as unknown as ButtonProps
|
||||
}
|
||||
/>
|
||||
) as ReactElement
|
||||
}
|
||||
</DataErrorBoundary>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { IconCozArrowDown } from '@coze-arch/coze-design/icons';
|
||||
import { Tooltip } from '@coze-arch/coze-design';
|
||||
import { UIButton } from '@coze-arch/bot-semi';
|
||||
import { useFlags } from '@coze-arch/bot-flags';
|
||||
|
||||
import { type ModeOption } from './mode-change-view';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface ChangeButtonProps {
|
||||
disabled: boolean;
|
||||
tooltip?: string;
|
||||
modeInfo: ModeOption | undefined;
|
||||
}
|
||||
|
||||
export function ChangeButton({
|
||||
modeInfo,
|
||||
disabled,
|
||||
tooltip,
|
||||
}: ChangeButtonProps) {
|
||||
const [FLAGS] = useFlags();
|
||||
|
||||
// 社区版暂不支持该功能
|
||||
const showText = modeInfo?.showText || FLAGS['bot.studio.prompt_diff'];
|
||||
const ToolTipFragment = tooltip ? Tooltip : React.Fragment;
|
||||
|
||||
const content = (
|
||||
<ToolTipFragment content={tooltip}>
|
||||
<UIButton
|
||||
theme="outline"
|
||||
size="small"
|
||||
className={classNames(s['mode-change-title-space'], {
|
||||
'!coz-mg-primary': disabled,
|
||||
})}
|
||||
icon={
|
||||
<div className="coz-fg-primary text-[16px] flex items-center">
|
||||
{modeInfo?.icon}
|
||||
</div>
|
||||
}
|
||||
disabled={disabled}
|
||||
data-testid="bot-edit-agent-mode-open-button"
|
||||
>
|
||||
<div
|
||||
className={classNames(s['mode-change-title'], 'flex items-center')}
|
||||
>
|
||||
{showText ? modeInfo?.title : null}
|
||||
<IconCozArrowDown className="w-4 h-5 coz-fg-secondary" />
|
||||
</div>
|
||||
</UIButton>
|
||||
</ToolTipFragment>
|
||||
);
|
||||
return showText ? (
|
||||
content
|
||||
) : (
|
||||
<Tooltip content={modeInfo?.title}>{content}</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
.font-normal {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px; /* 133.333% */
|
||||
|
||||
@apply text-foreground-3;
|
||||
}
|
||||
|
||||
.mode-change-title-space {
|
||||
margin-left: 4px !important;
|
||||
padding: 2px 8px !important;
|
||||
|
||||
.mode-change-title {
|
||||
.font-normal();
|
||||
}
|
||||
|
||||
.mode-change-icon {
|
||||
@apply text-foreground-3;
|
||||
|
||||
margin-left: 4px;
|
||||
|
||||
svg {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mode-change-title-space:active {
|
||||
background: var(--light-usage-fill-color-fill-1, rgba(46, 46, 56, 8%));
|
||||
}
|
||||
|
||||
.mode-change-title-space:focus {
|
||||
background: var(--light-usage-fill-color-fill-2, rgba(46, 46, 56, 12%));
|
||||
}
|
||||
|
||||
.mode-change-popover {
|
||||
width: 455px;
|
||||
|
||||
background: #f7f7fa;
|
||||
border: 1px solid
|
||||
var(--light-usage-border-color-border, rgba(28, 31, 35, 8%));
|
||||
border-radius: 12px;
|
||||
|
||||
/* --shadow-elevated */
|
||||
box-shadow: 0 4px 14px 0 rgba(0, 0, 0, 10%),
|
||||
0 0 1px 0 rgba(0, 0, 0, 30%);
|
||||
|
||||
.mode-change-popover-content {
|
||||
padding: 16px;
|
||||
|
||||
:global {
|
||||
.semi-radio {
|
||||
border: 1px solid
|
||||
var(--light-usage-border-color-border, rgba(29, 28, 35, 8%));
|
||||
}
|
||||
|
||||
.semi-radio-cardRadioGroup_checked {
|
||||
border: 1px solid var(--light-color-brand-brand-5, #4d53e8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mode-change-disabled {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 4px;
|
||||
padding: 2px 8px;
|
||||
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.label {
|
||||
.font-normal();
|
||||
}
|
||||
}
|
||||
@@ -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 React from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import {
|
||||
autosaveManager,
|
||||
getBotDetailDtoInfo,
|
||||
initBotDetailStore,
|
||||
multiAgentSaveManager,
|
||||
updateBotRequest,
|
||||
updateHeaderStatus,
|
||||
useBotDetailIsReadonly,
|
||||
} from '@coze-studio/bot-detail-store';
|
||||
import {
|
||||
AgentVersionCompat,
|
||||
BotMode,
|
||||
} from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
import { useBotPageStore } from '../../store/bot-page/store';
|
||||
import { ModeChangeView, type ModeChangeViewProps } from './mode-change-view';
|
||||
|
||||
export interface ModeSelectProps
|
||||
extends Pick<ModeChangeViewProps, 'optionList'> {
|
||||
readonly?: boolean;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export const ModeSelect: React.FC<ModeSelectProps> = ({
|
||||
readonly,
|
||||
tooltip,
|
||||
optionList,
|
||||
}) => {
|
||||
const { mode } = useBotInfoStore(useShallow(store => ({ mode: store.mode })));
|
||||
|
||||
const { modeSwitching, setBotState } = useBotPageStore(
|
||||
useShallow(state => ({
|
||||
modeSwitching: state.bot.modeSwitching,
|
||||
setBotState: state.setBotState,
|
||||
})),
|
||||
);
|
||||
|
||||
const isReadonly = useBotDetailIsReadonly() || readonly;
|
||||
|
||||
const handleModeChange = async (value: BotMode) => {
|
||||
try {
|
||||
setBotState({ modeSwitching: true });
|
||||
// bot信息全量保存
|
||||
const { botSkillInfo } = getBotDetailDtoInfo();
|
||||
await updateBotRequest(botSkillInfo);
|
||||
|
||||
// 服务端约定 切换模式需要单独调一次只传 bot_mode 的 update
|
||||
const switchModeParams = {
|
||||
bot_mode: value,
|
||||
...(value === BotMode.MultiMode
|
||||
? { version_compat: AgentVersionCompat.NewVersion }
|
||||
: {}),
|
||||
};
|
||||
const { data } = await updateBotRequest(switchModeParams);
|
||||
|
||||
updateHeaderStatus(data);
|
||||
autosaveManager.close();
|
||||
multiAgentSaveManager.close();
|
||||
await initBotDetailStore();
|
||||
multiAgentSaveManager.start();
|
||||
autosaveManager.start();
|
||||
} finally {
|
||||
setBotState({ modeSwitching: false });
|
||||
}
|
||||
};
|
||||
return (
|
||||
<ModeChangeView
|
||||
modeSelectLoading={modeSwitching}
|
||||
modeValue={mode}
|
||||
onModeChange={handleModeChange}
|
||||
isReadOnly={isReadonly}
|
||||
tooltip={tooltip}
|
||||
optionList={optionList}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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 ReactNode } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Typography, Popover, Radio } from '@coze-arch/bot-semi';
|
||||
import { BotMode } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import { ChangeButton } from './change-button';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface ModeLabelProps {
|
||||
icon: ReactNode;
|
||||
isDisabled: boolean;
|
||||
isSelected: boolean;
|
||||
title: ReactNode;
|
||||
desc: ReactNode;
|
||||
}
|
||||
export const ModeLabel: React.FC<ModeLabelProps> = ({
|
||||
icon,
|
||||
isDisabled,
|
||||
isSelected,
|
||||
title,
|
||||
desc,
|
||||
}) => (
|
||||
<div className={classNames('flex items-center gap-[12px]')}>
|
||||
<div
|
||||
className={
|
||||
(classNames('text-[16px]'),
|
||||
isDisabled ? 'coz-fg-dim' : 'coz-fg-primary')
|
||||
}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
<div data-testid={`bot-edit-agent-select-mode-button-${title}`}>
|
||||
<div
|
||||
className={classNames(
|
||||
'text-[16px] leading-[22px]',
|
||||
isSelected ? 'font-[500]' : 'font-[400]',
|
||||
isDisabled ? 'coz-fg-dim' : 'coz-fg-primary',
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
<Typography.Text
|
||||
className={classNames(
|
||||
'mt-[4px]',
|
||||
'text-[14px] font-[400] leading-[20px]',
|
||||
isDisabled ? 'coz-fg-dim' : 'coz-fg-secondary',
|
||||
)}
|
||||
>
|
||||
{desc}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export interface ModeOption
|
||||
extends Omit<ModeLabelProps, 'isSelected' | 'isDisabled'> {
|
||||
value: BotMode;
|
||||
showText: boolean;
|
||||
getIsDisabled: (params: { currentMode: BotMode }) => boolean;
|
||||
}
|
||||
|
||||
export interface ModeChangeViewProps {
|
||||
modeSelectLoading: boolean;
|
||||
modeValue: BotMode;
|
||||
onModeChange: (value: BotMode) => Promise<void>;
|
||||
isReadOnly: boolean;
|
||||
optionList: ModeOption[];
|
||||
tooltip?: string;
|
||||
}
|
||||
export const ModeChangeView = (props: ModeChangeViewProps) => {
|
||||
const {
|
||||
modeValue = BotMode.SingleMode,
|
||||
onModeChange,
|
||||
modeSelectLoading,
|
||||
isReadOnly,
|
||||
tooltip,
|
||||
optionList,
|
||||
} = props;
|
||||
const disabled = isReadOnly || modeSelectLoading;
|
||||
const modeInfo = optionList.find(option => option.value === modeValue);
|
||||
if (disabled) {
|
||||
return (
|
||||
<ChangeButton disabled={disabled} tooltip={tooltip} modeInfo={modeInfo} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
className={s['mode-change-popover']}
|
||||
data-testid="bot-detail.mode-chage-view.popover"
|
||||
trigger="click"
|
||||
position="bottomLeft"
|
||||
autoAdjustOverflow={false}
|
||||
content={
|
||||
<div className={s['mode-change-popover-content']}>
|
||||
<div className="coz-fg-plus text-[14px] font-[500] leading-[20px] mb-[12px]">
|
||||
{I18n.t('chatflow_switch_mode_title')}
|
||||
</div>
|
||||
<Radio.Group
|
||||
type="pureCard"
|
||||
direction="vertical"
|
||||
value={modeValue}
|
||||
defaultValue={modeValue}
|
||||
disabled={disabled}
|
||||
options={optionList.map(option => {
|
||||
const isSelected = modeValue === option.value;
|
||||
const isDisabled = option.getIsDisabled({
|
||||
currentMode: modeValue,
|
||||
});
|
||||
return {
|
||||
value: option.value,
|
||||
disabled: isDisabled,
|
||||
label: (
|
||||
<ModeLabel
|
||||
{...option}
|
||||
key={option.value}
|
||||
isDisabled={isDisabled}
|
||||
isSelected={isSelected}
|
||||
/>
|
||||
),
|
||||
};
|
||||
})}
|
||||
onChange={e => onModeChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<ChangeButton disabled={false} modeInfo={modeInfo} />
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
.coz-nav-modal.coz-modal.as-modal .semi-modal-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.coz-nav-modal {
|
||||
.semi-modal-body {
|
||||
display: flex;
|
||||
height: var(--nav-modal-body-height);
|
||||
}
|
||||
|
||||
.semi-modal-footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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 ReactNode, type HtmlHTMLAttributes } from 'react';
|
||||
|
||||
import { merge } from 'lodash-es';
|
||||
import { IconCozCross } from '@coze-arch/coze-design/icons';
|
||||
import { Button, Modal, type ModalProps } from '@coze-arch/coze-design';
|
||||
|
||||
import './index.less';
|
||||
|
||||
export type NavModalProps = Omit<ModalProps, 'children' | 'icon'> & {
|
||||
navigation: ReactNode;
|
||||
mainContent: ReactNode;
|
||||
mainContentTitle?: ReactNode | string;
|
||||
};
|
||||
|
||||
const NAV_MODAL_BODY_HEIGHT = 604;
|
||||
const NAV_MODAL_PADDING_TOP = 24;
|
||||
const NAV_MODAL_CLOSE_BUTTON_SIDE_LENGTH = 40;
|
||||
export const NAV_MODAL_MAIN_CONTENT_HEIGHT =
|
||||
NAV_MODAL_BODY_HEIGHT -
|
||||
NAV_MODAL_PADDING_TOP -
|
||||
NAV_MODAL_CLOSE_BUTTON_SIDE_LENGTH;
|
||||
|
||||
export const NavModal: FC<NavModalProps> = props => {
|
||||
const {
|
||||
title,
|
||||
navigation,
|
||||
mainContent,
|
||||
mainContentTitle,
|
||||
className,
|
||||
onCancel,
|
||||
closeIcon,
|
||||
style,
|
||||
...restProps
|
||||
} = props;
|
||||
return (
|
||||
<Modal
|
||||
header={null}
|
||||
footer={null}
|
||||
className={`coz-nav-modal ${className || ''}`}
|
||||
style={merge(style, {
|
||||
'--nav-modal-body-height': `${NAV_MODAL_BODY_HEIGHT}px`,
|
||||
})}
|
||||
{...restProps}
|
||||
>
|
||||
<div className="flex w-full h-full">
|
||||
<div className="flex pt-[30px] px-[8px] coz-bg-max w-[200px] shrink-0 flex-col">
|
||||
<div className="text-[20px] coz-fg-plus mx-[8px] leading-[28px] font-medium mb-[16px]">
|
||||
{title}
|
||||
</div>
|
||||
{navigation}
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-col coz-bg-plus overflow-auto px-[24px] w-full"
|
||||
style={{
|
||||
paddingTop: NAV_MODAL_PADDING_TOP,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- .
|
||||
// @ts-expect-error
|
||||
'--nav-modal-main-content-height': `${NAV_MODAL_MAIN_CONTENT_HEIGHT}px`,
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-end">
|
||||
{mainContentTitle ? (
|
||||
<div className="mr-auto content-center text-[20px] coz-fg-plus mx-[8px] leading-[28px] font-medium">
|
||||
{mainContentTitle}
|
||||
</div>
|
||||
) : null}
|
||||
{closeIcon || (
|
||||
<Button
|
||||
style={{
|
||||
height: NAV_MODAL_CLOSE_BUTTON_SIDE_LENGTH,
|
||||
width: NAV_MODAL_CLOSE_BUTTON_SIDE_LENGTH,
|
||||
}}
|
||||
size="large"
|
||||
color="secondary"
|
||||
onClick={onCancel}
|
||||
icon={<IconCozCross />}
|
||||
></Button>
|
||||
)}
|
||||
</div>
|
||||
{mainContent}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export interface NavModalItemProps extends HtmlHTMLAttributes<HTMLDivElement> {
|
||||
selectedIcon?: ReactNode;
|
||||
unselectedIcon?: ReactNode;
|
||||
text: string;
|
||||
selected?: boolean;
|
||||
onClick?: () => void;
|
||||
suffix?: ReactNode;
|
||||
}
|
||||
|
||||
export const NavModalItem: FC<NavModalItemProps> = props => {
|
||||
const {
|
||||
text,
|
||||
selected = false,
|
||||
selectedIcon = <></>,
|
||||
unselectedIcon = <></>,
|
||||
suffix,
|
||||
onClick,
|
||||
className,
|
||||
} = props;
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={[
|
||||
'flex',
|
||||
'flex-row',
|
||||
'cursor-pointer',
|
||||
'items-center',
|
||||
'justify-between',
|
||||
'rounded-normal',
|
||||
'px-[8px]',
|
||||
'py-[6px]',
|
||||
'mb-[6px]',
|
||||
'text-lg',
|
||||
'text-foreground-4',
|
||||
'w-full',
|
||||
'hover:bg-background-5',
|
||||
'active:bg-background-6',
|
||||
selected ? 'bg-background-4' : '',
|
||||
className,
|
||||
].join(' ')}
|
||||
>
|
||||
<div className="flex flex-row gap-[8px] items-center flex-1 overflow-hidden">
|
||||
{selected ? selectedIcon : unselectedIcon}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<div className="font-medium">{text}</div>
|
||||
</div>
|
||||
</div>
|
||||
{typeof suffix === 'string' ? (
|
||||
<div className="font-base text-foreground-2">{suffix}</div>
|
||||
) : (
|
||||
suffix ?? <></>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
NavModal.displayName = 'NavModal';
|
||||
NavModalItem.displayName = 'NavModalItem';
|
||||
@@ -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 { type PropsWithChildren, useRef } from 'react';
|
||||
|
||||
import { useBotDetailIsReadonly } from '@coze-studio/bot-detail-store';
|
||||
import { AIButton, type ButtonProps } from '@coze-arch/coze-design';
|
||||
|
||||
import { usePromptEditor } from '../../context/editor-kit';
|
||||
import { useBotEditorService } from '../../context/bot-editor-service';
|
||||
|
||||
export const NLPromptButton: React.FC<PropsWithChildren<ButtonProps>> = ({
|
||||
children,
|
||||
...buttonProps
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const { nLPromptModalVisibilityService } = useBotEditorService();
|
||||
const { promptEditor } = usePromptEditor();
|
||||
const isReadonly = useBotDetailIsReadonly();
|
||||
|
||||
const isDisabled = !promptEditor || isReadonly;
|
||||
|
||||
const onClick = () => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
const { offsetHeight, offsetTop } = ref.current;
|
||||
const { top, left } = ref.current.getBoundingClientRect();
|
||||
nLPromptModalVisibilityService.open(
|
||||
{
|
||||
top: top + offsetHeight,
|
||||
left: left + offsetTop,
|
||||
},
|
||||
'ai-button',
|
||||
);
|
||||
};
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<AIButton
|
||||
color="aihglt"
|
||||
iconPosition="left"
|
||||
size="small"
|
||||
disabled={isDisabled}
|
||||
onClick={onClick}
|
||||
{...buttonProps}
|
||||
>
|
||||
{children}
|
||||
</AIButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
@@ -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, type PropsWithChildren, type ReactNode } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Tooltip } from '@coze-arch/coze-design';
|
||||
import { IconInfo } from '@coze-arch/bot-icons';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export const ToolTipNode: FC<
|
||||
PropsWithChildren<{
|
||||
content: ReactNode;
|
||||
className?: string;
|
||||
tipContentClassName?: string;
|
||||
}>
|
||||
> = ({ content, children, className, tipContentClassName }) => (
|
||||
<Tooltip
|
||||
className={tipContentClassName}
|
||||
content={<div className={classNames(s['tip-content'])}>{content}</div>}
|
||||
>
|
||||
<div className={classNames(className, 'flex items-center')}>
|
||||
<IconInfo
|
||||
className={classNames(
|
||||
s['icon-info'],
|
||||
'cursor-pointer coz-fg-secondary',
|
||||
)}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
@@ -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 const settingAreaScrollId = 'setting_area_scroll';
|
||||
@@ -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 { type FC, type PropsWithChildren } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Tooltip } from '@coze-arch/coze-design';
|
||||
import { UIModal, type UIModalProps } from '@coze-arch/bot-semi';
|
||||
import { IconMinimizeOutlined } from '@coze-arch/bot-icons';
|
||||
|
||||
import styles from '../index.module.less';
|
||||
|
||||
export const EditorExpendModal: FC<PropsWithChildren<UIModalProps>> = ({
|
||||
children,
|
||||
...modalProps
|
||||
}) => (
|
||||
<UIModal
|
||||
{...modalProps}
|
||||
title={
|
||||
<div className="coz-fg-plus text-[20px] leading-8">
|
||||
{I18n.t('bot_edit_opening_text_title')}
|
||||
</div>
|
||||
}
|
||||
centered
|
||||
style={{
|
||||
maxWidth: 640,
|
||||
aspectRatio: 640 / 668,
|
||||
height: 'auto',
|
||||
}}
|
||||
bodyStyle={{
|
||||
padding: 0,
|
||||
}}
|
||||
className={styles['editor-expend-modal']}
|
||||
footer={null}
|
||||
type="base-composition"
|
||||
closeIcon={
|
||||
<Tooltip content={I18n.t('collapse')}>
|
||||
<IconMinimizeOutlined
|
||||
size="extra-large"
|
||||
className="cursor-pointer"
|
||||
onClick={modalProps.onCancel}
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</UIModal>
|
||||
);
|
||||
@@ -0,0 +1,138 @@
|
||||
@import '../../assets/styles/common.less';
|
||||
@import '../../assets/styles/mixins.less';
|
||||
|
||||
.text {
|
||||
margin-bottom: 8px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
color: var(--light-usage-text-color-text-1, rgba(28, 29, 35, 80%));
|
||||
}
|
||||
|
||||
|
||||
.onboarding-message-blur {
|
||||
textarea {
|
||||
display: -webkit-box;
|
||||
|
||||
max-height: 98px;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 4;
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding-message-title {
|
||||
.text;
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.mt-20 {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.suggestion-message-item {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.apis-no-icon {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
|
||||
@apply coz-fg-hglt-purple;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-input-textarea-wrapper {
|
||||
padding-right: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-button-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
display: flex;
|
||||
|
||||
>img {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding-add-icon,
|
||||
.msg-replace-icon {
|
||||
// cursor: pointer;
|
||||
}
|
||||
|
||||
.msg-replace-icon:hover {
|
||||
background-color: var(--semi-color-fill-0);
|
||||
}
|
||||
|
||||
.text-readonly {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: var(--light-color-grey-grey-8, #2e3238);
|
||||
white-space: pre-wrap;
|
||||
|
||||
&.mb-8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.text-none {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
color: var(--light-color-grey-grey-3, #a7abb0);
|
||||
}
|
||||
|
||||
@keyframes suggestion-highlight {
|
||||
0% {
|
||||
background-color: rgb(255, 248, 234);
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: rgb(244, 244, 245);
|
||||
}
|
||||
}
|
||||
|
||||
.suggestion-item-highlight {
|
||||
animation: suggestion-highlight 0.8s infinite alternate;
|
||||
animation-timing-function: ease;
|
||||
}
|
||||
|
||||
.markdown-editor-btn {
|
||||
margin-right: 8px;
|
||||
padding: 0;
|
||||
|
||||
.markdown-editor-btn-text {
|
||||
font-weight: 400;
|
||||
|
||||
@apply coz-fg-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-expend-modal {
|
||||
:global {
|
||||
.semi-modal-header .semi-button-with-icon-only {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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,
|
||||
useMemo,
|
||||
lazy,
|
||||
Suspense,
|
||||
type ReactNode,
|
||||
forwardRef,
|
||||
} from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { debounce, isFunction } from 'lodash-es';
|
||||
import { produce } from 'immer';
|
||||
import { useBotSkillStore } from '@coze-studio/bot-detail-store/bot-skill';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import {
|
||||
botSkillSaveManager,
|
||||
useBotDetailIsReadonly,
|
||||
} from '@coze-studio/bot-detail-store';
|
||||
import { OpenBlockEvent } from '@coze-arch/bot-utils';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { Spin } from '@coze-arch/bot-semi';
|
||||
import { useDefaultExPandCheck } from '@coze-arch/bot-hooks';
|
||||
import { ItemType } from '@coze-arch/bot-api/developer_api';
|
||||
import { SkillKeyEnum } from '@coze-agent-ide/tool-config';
|
||||
import {
|
||||
ToolContentBlock,
|
||||
useToolValidData,
|
||||
type ToolEntryCommonProps,
|
||||
} from '@coze-agent-ide/tool';
|
||||
import {
|
||||
BotCreatorScene,
|
||||
useBotCreatorContext,
|
||||
} from '@coze-agent-ide/bot-creator-context';
|
||||
|
||||
import { SuggestionList } from './suggestion-list';
|
||||
import { useSubmitEditor } from './onboarding-editor/hooks/use-submit-editor';
|
||||
import { type OnboardingEditorAction } from './onboarding-editor';
|
||||
import { EditorExpendModal } from './editor-expend-modal';
|
||||
import { settingAreaScrollId } from './const';
|
||||
const OnboardingEditor = lazy(() => import('./onboarding-editor'));
|
||||
|
||||
export {
|
||||
SuggestionList,
|
||||
EditorExpendModal,
|
||||
settingAreaScrollId,
|
||||
type OnboardingEditorAction,
|
||||
};
|
||||
|
||||
type IOnboardingMessageProps = ToolEntryCommonProps & {
|
||||
actionButton?: ReactNode;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
const eventWaitTime = 5000;
|
||||
|
||||
export const OnboardingMessage = forwardRef<
|
||||
OnboardingEditorAction,
|
||||
IOnboardingMessageProps
|
||||
>(({ title, actionButton, isLoading }, ref) => {
|
||||
const { botId } = useBotInfoStore(
|
||||
useShallow(state => ({
|
||||
botId: state.botId,
|
||||
})),
|
||||
);
|
||||
const { scene } = useBotCreatorContext();
|
||||
const { onboardingContent, updateSkillOnboarding } = useBotSkillStore(
|
||||
useShallow(state => ({
|
||||
onboardingContent: state.onboardingContent,
|
||||
updateSkillOnboarding: state.updateSkillOnboarding,
|
||||
})),
|
||||
);
|
||||
|
||||
const setToolValidData = useToolValidData();
|
||||
|
||||
const isReadonly = useBotDetailIsReadonly();
|
||||
const defaultExpand = useDefaultExPandCheck({
|
||||
blockKey: SkillKeyEnum.ONBORDING_MESSAGE_BLOCK,
|
||||
configured:
|
||||
onboardingContent.prologue.length > 0 ||
|
||||
onboardingContent.suggested_questions.length > 1,
|
||||
});
|
||||
|
||||
const [submitEditor] = useSubmitEditor();
|
||||
|
||||
const sendEvent = useMemo(
|
||||
() =>
|
||||
debounce((type: 'welcome_message' | 'suggestion') => {
|
||||
sendTeaEvent(EVENT_NAMES.click_welcome_message_edit, {
|
||||
type,
|
||||
bot_id: botId,
|
||||
});
|
||||
}, eventWaitTime),
|
||||
[botId],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setToolValidData(
|
||||
Boolean(
|
||||
onboardingContent.prologue ||
|
||||
onboardingContent.suggested_questions?.some?.(q => q.content),
|
||||
),
|
||||
);
|
||||
}, [onboardingContent]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ToolContentBlock
|
||||
blockEventName={OpenBlockEvent.ONBORDING_MESSAGE_BLOCK_OPEN}
|
||||
header={title}
|
||||
showBottomBorder
|
||||
defaultExpand={defaultExpand}
|
||||
actionButton={actionButton}
|
||||
>
|
||||
<Suspense fallback={<Spin />}>
|
||||
<OnboardingEditor
|
||||
ref={ref}
|
||||
initValues={onboardingContent}
|
||||
isReadonly={isReadonly}
|
||||
isGenerating={isLoading}
|
||||
// 社区版暂不支持该功能
|
||||
plainText={scene === BotCreatorScene.DouyinBot}
|
||||
onChange={submitEditor}
|
||||
onBlur={() => {
|
||||
botSkillSaveManager.saveFlush(ItemType.ONBOARDING);
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
<SuggestionList
|
||||
isReadonly={isReadonly}
|
||||
initValues={onboardingContent}
|
||||
onBlur={() => {
|
||||
botSkillSaveManager.saveFlush(ItemType.ONBOARDING);
|
||||
}}
|
||||
onChange={update => {
|
||||
updateSkillOnboarding(pre => {
|
||||
sendEvent('suggestion');
|
||||
return produce(pre, isFunction(update) ? update : () => update);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</ToolContentBlock>
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -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 { useEffect, useRef } from 'react';
|
||||
|
||||
import { initEditorByPrologue } from '../method/init-editor';
|
||||
import { type OnboardingEditorContext } from '../index';
|
||||
|
||||
export const useInitEditor = ({
|
||||
props,
|
||||
editorRef,
|
||||
}: OnboardingEditorContext) => {
|
||||
const { initValues } = props;
|
||||
const { prologue } = initValues || {};
|
||||
const hasInit = useRef(false);
|
||||
useEffect(() => {
|
||||
if (hasInit.current) {
|
||||
return;
|
||||
}
|
||||
if (!prologue) {
|
||||
return;
|
||||
}
|
||||
if (!editorRef.current) {
|
||||
return;
|
||||
}
|
||||
hasInit.current = true;
|
||||
if (props.plainText) {
|
||||
editorRef.current.setText(prologue);
|
||||
} else {
|
||||
initEditorByPrologue({
|
||||
prologue,
|
||||
editorRef,
|
||||
});
|
||||
}
|
||||
// 滚动到顶部
|
||||
editorRef.current?.scrollModule?.scrollTo({
|
||||
top: 0,
|
||||
});
|
||||
}, [prologue, editorRef.current]);
|
||||
};
|
||||