feat: undo-redo & edit snapshots (#2436)
* feat: undo-redo & edit snapshots * fix merge * add simple history back * fix some undo * change app latest version * fix * chatconfig * fix snapshot * fix * fix * fix * fix compare * fix initial * fix merge: * fix useEffect * fix snapshot initial and saved state * chore * fix * compare snapshot * nodes edges useEffct * fix chatconfig * fix * delete unused method * fix * fix * fix * default version name
@@ -7,6 +7,8 @@ dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
export const formatTime2YMDHMW = (time?: Date) => dayjs(time).format('YYYY-MM-DD HH:mm:ss dddd');
|
||||
export const formatTime2YMDHMS = (time?: Date) =>
|
||||
time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '';
|
||||
export const formatTime2YMDHM = (time?: Date) =>
|
||||
time ? dayjs(time).format('YYYY-MM-DD HH:mm') : '';
|
||||
export const formatTime2YMD = (time?: Date) => (time ? dayjs(time).format('YYYY-MM-DD') : '');
|
||||
|
3
packages/global/core/app/version.d.ts
vendored
@@ -8,4 +8,7 @@ export type AppVersionSchemaType = {
|
||||
nodes: AppSchema['modules'];
|
||||
edges: AppSchema['edges'];
|
||||
chatConfig: AppSchema['chatConfig'];
|
||||
isPublish?: boolean;
|
||||
versionName: string;
|
||||
tmbId: string;
|
||||
};
|
||||
|
@@ -48,7 +48,8 @@ export const beforeUpdateAppFormat = <T extends AppSchema['modules'] | undefined
|
||||
|
||||
export const getAppLatestVersion = async (appId: string, app?: AppSchema) => {
|
||||
const version = await MongoAppVersion.findOne({
|
||||
appId
|
||||
appId,
|
||||
isPublish: true
|
||||
}).sort({
|
||||
time: -1
|
||||
});
|
||||
|
@@ -25,6 +25,16 @@ const AppVersionSchema = new Schema({
|
||||
},
|
||||
chatConfig: {
|
||||
type: chatConfigType
|
||||
},
|
||||
isPublish: {
|
||||
type: Boolean
|
||||
},
|
||||
versionName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
tmbId: {
|
||||
type: String
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -46,6 +46,7 @@ export const iconPaths = {
|
||||
'common/openai': () => import('./icons/common/openai.svg'),
|
||||
'common/overviewLight': () => import('./icons/common/overviewLight.svg'),
|
||||
'common/paramsLight': () => import('./icons/common/paramsLight.svg'),
|
||||
'common/paused': () => import('./icons/common/paused.svg'),
|
||||
'common/playFill': () => import('./icons/common/playFill.svg'),
|
||||
'common/playLight': () => import('./icons/common/playLight.svg'),
|
||||
'common/publishFill': () => import('./icons/common/publishFill.svg'),
|
||||
@@ -61,6 +62,7 @@ export const iconPaths = {
|
||||
'common/select': () => import('./icons/common/select.svg'),
|
||||
'common/selectLight': () => import('./icons/common/selectLight.svg'),
|
||||
'common/settingLight': () => import('./icons/common/settingLight.svg'),
|
||||
'common/subtract': () => import('./icons/common/subtract.svg'),
|
||||
'common/text/t': () => import('./icons/common/text/t.svg'),
|
||||
'common/tickFill': () => import('./icons/common/tickFill.svg'),
|
||||
'common/trash': () => import('./icons/common/trash.svg'),
|
||||
@@ -68,6 +70,7 @@ export const iconPaths = {
|
||||
'common/uploadFileFill': () => import('./icons/common/uploadFileFill.svg'),
|
||||
'common/viewLight': () => import('./icons/common/viewLight.svg'),
|
||||
'common/voiceLight': () => import('./icons/common/voiceLight.svg'),
|
||||
'common/warn': () => import('./icons/common/warn.svg'),
|
||||
'common/wechatFill': () => import('./icons/common/wechatFill.svg'),
|
||||
copy: () => import('./icons/copy.svg'),
|
||||
'core/app/aiFill': () => import('./icons/core/app/aiFill.svg'),
|
||||
@@ -184,6 +187,8 @@ export const iconPaths = {
|
||||
import('./icons/core/workflow/inputType/selectLLM.svg'),
|
||||
'core/workflow/inputType/switch': () => import('./icons/core/workflow/inputType/switch.svg'),
|
||||
'core/workflow/inputType/textarea': () => import('./icons/core/workflow/inputType/textarea.svg'),
|
||||
'core/workflow/publish': () => import('./icons/core/workflow/publish.svg'),
|
||||
'core/workflow/redo': () => import('./icons/core/workflow/redo.svg'),
|
||||
'core/workflow/revertVersion': () => import('./icons/core/workflow/revertVersion.svg'),
|
||||
'core/workflow/runError': () => import('./icons/core/workflow/runError.svg'),
|
||||
'core/workflow/runSkip': () => import('./icons/core/workflow/runSkip.svg'),
|
||||
@@ -232,6 +237,8 @@ export const iconPaths = {
|
||||
import('./icons/core/workflow/template/variableUpdate.svg'),
|
||||
'core/workflow/template/workflowStart': () =>
|
||||
import('./icons/core/workflow/template/workflowStart.svg'),
|
||||
'core/workflow/undo': () => import('./icons/core/workflow/undo.svg'),
|
||||
'core/workflow/upload': () => import('./icons/core/workflow/upload.svg'),
|
||||
'core/workflow/versionHistories': () => import('./icons/core/workflow/versionHistories.svg'),
|
||||
date: () => import('./icons/date.svg'),
|
||||
delete: () => import('./icons/delete.svg'),
|
||||
|
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 14 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Polygon 8 (Stroke)" fill-rule="evenodd" clip-rule="evenodd" d="M4.24277 12.4952L10.0403 9.23726C10.9183 8.74387 11.4621 8.43615 11.8323 8.18154C11.9539 8.09794 12.0294 8.03772 12.0732 7.99999C12.0294 7.96226 11.9539 7.90203 11.8323 7.81844C11.4621 7.56383 10.9183 7.25611 10.0403 6.76272L4.24277 3.50479C3.36511 3.01159 2.81898 2.70681 2.40643 2.52163C2.23511 2.44473 2.1322 2.41044 2.08127 2.3957C2.05236 2.40455 2.02714 2.41733 2.00603 2.43216C1.9951 2.48511 1.98005 2.5748 1.96697 2.71367C1.92612 3.14704 1.9241 3.7532 1.9241 4.74206L1.9241 11.2579C1.9241 12.2468 1.92612 12.8529 1.96697 13.2863C1.98006 13.4252 1.99511 13.5149 2.00603 13.5678C2.02714 13.5826 2.05235 13.5954 2.08127 13.6043C2.1322 13.5895 2.23511 13.5553 2.40643 13.4784C2.81898 13.2932 3.36511 12.9884 4.24277 12.4952ZM13.7929 7.14993C13.5091 6.52957 12.6635 6.05439 10.9724 5.10405L5.17486 1.84612C3.48371 0.895784 2.63813 0.420614 1.94427 0.491596C1.33906 0.55351 0.789265 0.862468 0.431571 1.34166C0.0214848 1.89104 0.0214847 2.84138 0.0214847 4.74206L0.0214844 11.2579C0.0214843 13.1586 0.0214843 14.1089 0.43157 14.6583C0.789264 15.1375 1.33906 15.4465 1.94427 15.5084C2.63813 15.5794 3.48371 15.1042 5.17486 14.1539L10.9724 10.8959C12.6635 9.94558 13.5091 9.47042 13.7929 8.85005C14.0404 8.30895 14.0404 7.69103 13.7929 7.14993Z" fill="#3370FF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.14996 9C3.14996 8.58579 3.48575 8.25 3.89996 8.25H14.1C14.5142 8.25 14.85 8.58579 14.85 9C14.85 9.41421 14.5142 9.75 14.1 9.75H3.89996C3.48575 9.75 3.14996 9.41421 3.14996 9Z" fill="#485264"/>
|
||||
</svg>
|
After Width: | Height: | Size: 325 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.7733 1.94544C9.14771 1.73464 9.57012 1.6239 9.99979 1.6239C10.4295 1.6239 10.8519 1.73464 11.2263 1.94544C11.6007 2.15623 11.9145 2.45996 12.1373 2.82733L12.1397 2.83129L19.198 14.6146L19.2048 14.6261C19.4231 15.0042 19.5386 15.4328 19.5398 15.8693C19.541 16.3058 19.4279 16.7351 19.2118 17.1143C18.9956 17.4935 18.6839 17.8096 18.3076 18.031C17.9314 18.2523 17.5038 18.3713 17.0673 18.3761L17.0581 18.3762L2.9323 18.3762C2.4958 18.3714 2.06816 18.2523 1.69194 18.031C1.31571 17.8096 1.004 17.4935 0.787835 17.1143C0.571665 16.7351 0.458565 16.3058 0.459788 15.8693C0.46101 15.4328 0.576512 15.0042 0.794801 14.6261L0.801571 14.6146L7.86228 2.82732C8.08512 2.45996 8.39889 2.15623 8.7733 1.94544ZM9.99979 3.29057C9.85657 3.29057 9.71576 3.32748 9.59096 3.39775C9.46672 3.46769 9.36252 3.56834 9.2883 3.69005L2.23537 15.4644C2.16438 15.5892 2.12685 15.7303 2.12645 15.874C2.12604 16.0195 2.16374 16.1625 2.2358 16.289C2.30785 16.4154 2.41176 16.5207 2.53716 16.5945C2.66148 16.6677 2.80262 16.7073 2.94679 16.7095H17.0528C17.197 16.7073 17.3381 16.6677 17.4624 16.5945C17.5878 16.5207 17.6917 16.4154 17.7638 16.289C17.8358 16.1625 17.8735 16.0195 17.8731 15.874C17.8727 15.7303 17.8352 15.5892 17.7642 15.4644L10.7123 3.69171C10.712 3.69116 10.7116 3.69061 10.7113 3.69005C10.6371 3.56834 10.5329 3.46769 10.4086 3.39775C10.2838 3.32748 10.143 3.29057 9.99979 3.29057ZM9.99979 6.70952C10.46 6.70952 10.8331 7.08262 10.8331 7.54285V10.8762C10.8331 11.3364 10.46 11.7095 9.99979 11.7095C9.53956 11.7095 9.16646 11.3364 9.16646 10.8762V7.54285C9.16646 7.08262 9.53956 6.70952 9.99979 6.70952ZM9.16646 14.2095C9.16646 13.7493 9.53956 13.3762 9.99979 13.3762H10.0081C10.4684 13.3762 10.8415 13.7493 10.8415 14.2095C10.8415 14.6698 10.4684 15.0429 10.0081 15.0429H9.99979C9.53956 15.0429 9.16646 14.6698 9.16646 14.2095Z" fill="#F79009"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
@@ -1,5 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 30">
|
||||
<path
|
||||
d="M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z">
|
||||
</path>
|
||||
<svg viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.22529 2.86335C6.22529 2.53198 5.95666 2.26335 5.62529 2.26335H5.45985C3.94771 2.26335 3.55179 2.26335 2.97423 2.55763C2.46619 2.81649 2.05314 3.22954 1.79428 3.73758C1.56349 4.19053 1.5137 4.75328 1.50296 5.70441C1.49921 6.03595 1.76901 6.30516 2.10057 6.30516H2.40072C2.73185 6.30516 2.99925 6.03678 3.00307 5.70567C3.00671 5.39056 3.01462 5.14242 3.0318 4.93214C3.06101 4.57468 3.10935 4.46064 3.13079 4.41856C3.24584 4.19277 3.42942 4.00919 3.65521 3.89414C3.69729 3.8727 3.81132 3.82436 4.16879 3.79516C4.54377 3.76452 4.67903 3.76335 5.45985 3.76335H5.62529C5.95666 3.76335 6.22529 3.49472 6.22529 3.16335V2.86335Z"/>
|
||||
<path d="M12.8254 3.76451C12.4941 3.76269 12.2253 3.49475 12.2253 3.1634V2.86335C12.2253 2.53198 12.4945 2.26268 12.8258 2.26433C13.9515 2.26992 14.5347 2.3074 15.0258 2.55763C15.5338 2.81649 15.9469 3.22954 16.2057 3.73758C16.4365 4.19053 16.4863 4.75328 16.497 5.70441C16.5008 6.03595 16.231 6.30516 15.8994 6.30516H15.5993C15.2681 6.30516 15.0007 6.03678 14.9969 5.70567C14.9933 5.39056 14.9854 5.14242 14.9682 4.93214C14.939 4.57468 14.8906 4.46064 14.8692 4.41856C14.7542 4.19277 14.5706 4.00919 14.3448 3.89414C14.3027 3.8727 14.1887 3.82436 13.8312 3.79516C13.5718 3.77396 13.2547 3.76687 12.8254 3.76451Z"/>
|
||||
<path d="M12.2253 14.8366C12.2253 14.5052 12.4941 14.2373 12.8254 14.2355C13.2547 14.2331 13.5718 14.226 13.8312 14.2048C14.1887 14.1756 14.3027 14.1273 14.3448 14.1058C14.5706 13.9908 14.7542 13.8072 14.8692 13.5814C14.8906 13.5393 14.939 13.4253 14.9682 13.0678C14.9988 12.6929 15 11.9529 15 11.7936C15 11.4962 15.241 11.2552 15.5384 11.2552L15.9616 11.2552C16.259 11.2552 16.5 11.4962 16.5 11.7936C16.5 12.1705 16.5 13.6848 16.2057 14.2624C15.9469 14.7704 15.5338 15.1835 15.0258 15.4424C14.537 15.6914 13.9203 15.7297 12.8257 15.7356C12.4944 15.7373 12.2253 15.468 12.2253 15.1366V14.8366Z"/>
|
||||
<path d="M2.50544 11.2552C2.77858 11.2552 3 11.4766 3 11.7497C3 12.5305 3.00117 12.6929 3.0318 13.0678C3.06101 13.4253 3.10935 13.5393 3.13079 13.5814C3.24584 13.8072 3.42942 13.9908 3.65521 14.1058C3.69729 14.1273 3.81132 14.1756 4.16879 14.2048C4.54377 14.2355 4.69955 14.2366 5.48037 14.2366H5.62529C5.95666 14.2366 6.22529 14.5053 6.22529 14.8366V15.1366C6.22529 15.468 5.95666 15.7366 5.62529 15.7366H5.48037C3.96823 15.7366 3.55179 15.7366 2.97423 15.4424C2.46619 15.1835 2.05314 14.7704 1.79428 14.2624C1.5 13.6848 1.5 13.2619 1.5 11.7497C1.5 11.4766 1.72142 11.2552 1.99456 11.2552H2.50544Z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.47925 6.92426C4.47925 5.93015 5.28514 5.12427 6.27925 5.12427H11.7207C12.7149 5.12427 13.5207 5.93015 13.5207 6.92427V11.0757C13.5207 12.0698 12.7149 12.8757 11.7207 12.8757H6.27925C5.28514 12.8757 4.47925 12.0698 4.47925 11.0757V6.92426ZM6.27925 6.62426C6.11357 6.62426 5.97925 6.75858 5.97925 6.92426V11.0757C5.97925 11.2414 6.11357 11.3757 6.27925 11.3757H11.7207C11.8864 11.3757 12.0207 11.2414 12.0207 11.0757V6.92427C12.0207 6.75858 11.8864 6.62426 11.7207 6.62426H6.27925Z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 482 B After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M17.6 2.4c-.45-.45-1.244-.562-1.844-.645l-.227-.028c-.94-.101-1.89-.075-2.824.078-2.326.378-5.103 1.616-7.252 4.727a5.632 5.632 0 0 1-.736.006h-.02L4.5 6.53c-.326-.008-.65-.003-.961.086-.588.17-.975.772-1.253 1.335l-.131.276-.158.356-.098.244c-.24.63-.408 1.388.09 1.886.195.195.446.342.721.464a8.5 8.5 0 0 0 .968.335l.247.076c.1.03.197.062.295.096l.009.003.158.059.057.023-.071.068a1.475 1.475 0 0 0-.034.035l-.006.006c-.286.306-.488.742-.631 1.104a8.412 8.412 0 0 0-.112.297c-.118.33-.222.681-.312 1.011l-.084.32-.11.455-.138.621-.076.384a.899.899 0 0 0 1.048 1.048l.288-.056.46-.098.385-.089.424-.106.222-.058a13.744 13.744 0 0 0 1.31-.424l.137-.055c.347-.147.736-.346 1.007-.617.063-.063.124-.13.18-.2.052.133.1.266.145.396.02.061.037.122.055.184.048.167.089.335.123.505l.042.185c.044.191.092.383.154.566.097.288.23.553.436.759.564.564 1.463.274 2.13-.01l.355-.156.276-.13c.564-.279 1.167-.666 1.336-1.254.109-.378.094-.766.08-1.155l-.009-.253a4.178 4.178 0 0 1 .014-.506c3.11-2.15 4.35-4.926 4.727-7.252.191-1.18.162-2.24.05-3.051l-.05-.344c-.085-.534-.227-1.132-.595-1.5Zm-1.1 1.758a3.034 3.034 0 0 0-.125-.534 1.313 1.313 0 0 0-.114-.037 5.467 5.467 0 0 0-.73-.133l-.2-.025a8.794 8.794 0 0 0-2.35.067h-.002c-1.935.314-4.27 1.336-6.118 4.01l-.452.655-.792.076a6.905 6.905 0 0 1-.938.014L4.64 8.25l-.19-.006a3.022 3.022 0 0 0-.35.005 2.4 2.4 0 0 0-.272.45l-.115.242-.136.309-.08.201c-.025.063-.045.12-.061.173 2.618.408 4.253 1.428 5.276 2.614l.002-.002.023.03c.214.25.4.508.563.769.845 1.349 1.03 2.748 1.126 3.464l.006.048c.093-.032.194-.071.305-.118l.323-.142.242-.115c.22-.11.365-.2.451-.272a4.5 4.5 0 0 0 0-.532v-.002l-.01-.265v-.012a5.826 5.826 0 0 1 .022-.714l.08-.787.65-.45c2.674-1.848 3.696-4.183 4.01-6.118.161-.993.135-1.88.044-2.536l-.047-.326ZM6.854 14.39a.89.89 0 0 0 .116-1.203l-.073-.084-.021-.02-.084-.071a.891.891 0 0 0-.98-.054l-.097.066-.083.074-.111.14a2.61 2.61 0 0 0-.308.656l-.002.007a6.95 6.95 0 0 0-.156.568l-.096.417-.046.19.17-.041.373-.086c.245-.057.502-.122.743-.209l.011-.004c.243-.088.468-.2.644-.346ZM11.847 10a1.845 1.845 0 1 0 0-3.69 1.845 1.845 0 0 0 0 3.69Z" clip-rule="evenodd"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.801 7.48755L11.4118 9.49229C11.0945 9.75854 11.0532 10.2316 11.3194 10.5489C11.5857 10.8662 12.0587 10.9076 12.376 10.6414L16.2829 7.36314C16.4617 7.21304 16.5529 6.99721 16.5507 6.78061C16.5529 6.56401 16.4617 6.34818 16.2829 6.19808L12.376 2.91986C12.0587 2.65361 11.5857 2.695 11.3194 3.0123C11.0532 3.32961 11.0945 3.80268 11.4118 4.06893L13.6984 5.98755H6.08326C3.52395 5.98755 1.44921 8.06227 1.44921 10.6216C1.44921 13.1809 3.52395 15.2556 6.08326 15.2556H8.51494C8.92915 15.2556 9.26494 14.9198 9.26494 14.5056C9.26494 14.0914 8.92915 13.7556 8.51494 13.7556H6.08326C4.35237 13.7556 2.94921 12.3525 2.94921 10.6216C2.94921 8.8907 4.35238 7.48755 6.08326 7.48755H13.801Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 774 B |
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.19899 7.48755L6.58815 9.49229C6.90546 9.75854 6.94685 10.2316 6.6806 10.5489C6.41434 10.8662 5.94128 10.9076 5.62397 10.6414L1.71715 7.36314C1.53826 7.21304 1.44707 6.99721 1.44926 6.78061C1.44707 6.56401 1.53826 6.34818 1.71715 6.19808L5.62397 2.91986C5.94128 2.65361 6.41434 2.695 6.6806 3.0123C6.94685 3.32961 6.90546 3.80268 6.58815 4.06893L4.30163 5.98755H11.9167C14.476 5.98755 16.5508 8.06227 16.5508 10.6216C16.5508 13.1809 14.4761 15.2556 11.9167 15.2556H9.48506C9.07085 15.2556 8.73506 14.9198 8.73506 14.5056C8.73506 14.0914 9.07085 13.7556 9.48506 13.7556H11.9167C13.6476 13.7556 15.0508 12.3525 15.0508 10.6216C15.0508 8.8907 13.6476 7.48755 11.9167 7.48755H4.19899Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 775 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.8375 2.35634C11.7639 2.33867 11.6761 2.33332 11.2288 2.33332H5.66666V4.33332C5.66666 4.58042 5.66731 4.71563 5.67524 4.81279C5.67556 4.81666 5.67588 4.82032 5.6762 4.82378C5.67966 4.82409 5.68332 4.82441 5.68719 4.82473C5.78434 4.83267 5.91955 4.83332 6.16666 4.83332H11.8333C12.0804 4.83332 12.2156 4.83267 12.3128 4.82473C12.3167 4.82441 12.3203 4.8241 12.3238 4.82378C12.3241 4.82032 12.3244 4.81666 12.3247 4.81279C12.3327 4.71563 12.3333 4.58042 12.3333 4.33332V2.68002C12.1765 2.52641 12.127 2.48593 12.0784 2.45612C12.0038 2.41043 11.9225 2.37676 11.8375 2.35634ZM13.7558 1.74395L13.7036 1.69178C13.6871 1.67528 13.6708 1.6589 13.6546 1.64266C13.415 1.40267 13.2038 1.19108 12.9492 1.03505C12.7255 0.897978 12.4817 0.796967 12.2266 0.735725C11.9362 0.666013 11.6373 0.666291 11.2982 0.666607C11.2752 0.666628 11.2521 0.66665 11.2288 0.66665L5.46557 0.666649C5.25803 0.666646 5.0617 0.666643 4.87616 0.667732C4.86197 0.667013 4.84769 0.66665 4.83332 0.66665C4.81493 0.66665 4.79668 0.667245 4.77859 0.668419C4.40512 0.671521 4.07688 0.68005 3.79014 0.703478C3.32172 0.741749 2.89114 0.823886 2.48669 1.02996C1.85948 1.34954 1.34955 1.85947 1.02997 2.48668C0.823893 2.89113 0.741756 3.32171 0.703485 3.79013C0.666637 4.24112 0.666646 4.79476 0.666657 5.46557V12.5344C0.666646 13.2052 0.666637 13.7588 0.703485 14.2098C0.741756 14.6783 0.823893 15.1088 1.02997 15.5133C1.34955 16.1405 1.85948 16.6504 2.48669 16.97C2.89114 17.1761 3.32172 17.2582 3.79014 17.2965C4.07688 17.3199 4.40512 17.3284 4.77858 17.3315C4.79668 17.3327 4.81493 17.3333 4.83332 17.3333C4.84769 17.3333 4.86197 17.333 4.87616 17.3322C5.06169 17.3333 5.25802 17.3333 5.46555 17.3333H12.5344C12.742 17.3333 12.9383 17.3333 13.1238 17.3322C13.138 17.333 13.1523 17.3333 13.1667 17.3333C13.185 17.3333 13.2033 17.3327 13.2214 17.3315C13.5949 17.3284 13.9231 17.3199 14.2098 17.2965C14.6783 17.2582 15.1088 17.1761 15.5133 16.97C16.1405 16.6504 16.6504 16.1405 16.97 15.5133C17.1761 15.1088 17.2582 14.6783 17.2965 14.2098C17.3333 13.7588 17.3333 13.2052 17.3333 12.5344V6.77122C17.3333 6.74788 17.3333 6.72474 17.3334 6.70179C17.3337 6.36271 17.334 6.06376 17.2642 5.77339C17.203 5.5183 17.102 5.27444 16.9649 5.05076C16.8089 4.79614 16.5973 4.58495 16.3573 4.34541C16.3411 4.32919 16.3247 4.31285 16.3082 4.29635L13.7558 1.74395C13.7559 1.74402 13.7557 1.74388 13.7558 1.74395ZM14 4.34516V4.35988C14 4.57021 14 4.77504 13.9859 4.94851C13.9703 5.139 13.9336 5.36378 13.8183 5.58997C13.6585 5.90357 13.4036 6.15854 13.09 6.31833C12.8638 6.43357 12.639 6.4703 12.4485 6.48586C12.275 6.50003 12.0702 6.50001 11.8599 6.49999L6.16666 6.49998C6.15779 6.49998 6.14893 6.49998 6.14008 6.49999C5.92976 6.50001 5.72493 6.50003 5.55147 6.48586C5.36097 6.4703 5.13619 6.43357 4.91001 6.31833C4.5964 6.15854 4.34144 5.90357 4.18165 5.58997C4.0664 5.36378 4.02968 5.139 4.01411 4.94851C3.99994 4.77504 3.99996 4.57022 3.99999 4.3599C3.99999 4.35105 3.99999 4.34219 3.99999 4.33332V2.35904C3.9748 2.36077 3.9501 2.36263 3.92586 2.36461C3.56051 2.39446 3.37367 2.44856 3.24334 2.51497C2.92974 2.67476 2.67477 2.92973 2.51498 3.24333C2.44857 3.37367 2.39447 3.5605 2.36462 3.92585C2.33397 4.30092 2.33332 4.78617 2.33332 5.49998V12.5C2.33332 13.2138 2.33397 13.699 2.36462 14.0741C2.39447 14.4395 2.44857 14.6263 2.51498 14.7566C2.67477 15.0702 2.92974 15.3252 3.24334 15.485C3.37367 15.5514 3.56051 15.6055 3.92586 15.6354C3.9501 15.6373 3.9748 15.6392 3.99999 15.6409L3.99999 11.1401C3.99996 10.9297 3.99994 10.7249 4.01411 10.5515C4.02968 10.361 4.0664 10.1362 4.18165 9.91C4.34144 9.59639 4.5964 9.34143 4.91001 9.18164C5.13619 9.06639 5.36097 9.02967 5.55147 9.0141C5.72494 8.99993 5.92977 8.99996 6.14009 8.99998H11.8599C12.0702 8.99996 12.275 8.99993 12.4485 9.0141C12.639 9.02967 12.8638 9.06639 13.09 9.18164C13.4036 9.34143 13.6585 9.59639 13.8183 9.91C13.9336 10.1362 13.9703 10.361 13.9859 10.5515C14 10.7249 14 10.9298 14 11.1401L14 15.6409C14.0252 15.6392 14.0499 15.6373 14.0741 15.6354C14.4395 15.6055 14.6263 15.5514 14.7566 15.485C15.0702 15.3252 15.3252 15.0702 15.485 14.7566C15.5514 14.6263 15.6055 14.4395 15.6354 14.0741C15.666 13.699 15.6667 13.2138 15.6667 12.5V6.77122C15.6667 6.3239 15.6613 6.23607 15.6436 6.16247C15.6232 6.07744 15.5895 5.99615 15.5439 5.92159C15.5043 5.85705 15.446 5.79116 15.1297 5.47486L14 4.34516ZM12.3333 15.6666V11.1667C12.3333 10.9195 12.3327 10.7843 12.3247 10.6872C12.3244 10.6833 12.3241 10.6796 12.3238 10.6762C12.3203 10.6759 12.3167 10.6756 12.3128 10.6752C12.2156 10.6673 12.0804 10.6666 11.8333 10.6666H6.16666C5.91955 10.6666 5.78434 10.6673 5.68719 10.6752C5.68332 10.6756 5.67966 10.6759 5.6762 10.6762C5.67588 10.6796 5.67556 10.6833 5.67524 10.6872C5.66731 10.7843 5.66666 10.9195 5.66666 11.1667V15.6666H12.3333Z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
@@ -17,6 +17,7 @@ interface Props extends PopoverContentProps {
|
||||
hasArrow?: boolean;
|
||||
children: (e: { onClose: () => void }) => React.ReactNode;
|
||||
onCloseFunc?: () => void;
|
||||
onOpenFunc?: () => void;
|
||||
closeOnBlur?: boolean;
|
||||
}
|
||||
|
||||
@@ -27,6 +28,7 @@ const MyPopover = ({
|
||||
trigger,
|
||||
hasArrow = true,
|
||||
children,
|
||||
onOpenFunc,
|
||||
onCloseFunc,
|
||||
closeOnBlur = false,
|
||||
...props
|
||||
@@ -39,7 +41,10 @@ const MyPopover = ({
|
||||
<Popover
|
||||
isOpen={isOpen}
|
||||
initialFocusRef={firstFieldRef}
|
||||
onOpen={onOpen}
|
||||
onOpen={() => {
|
||||
onOpen();
|
||||
onOpenFunc && onOpenFunc();
|
||||
}}
|
||||
onClose={() => {
|
||||
onClose();
|
||||
onCloseFunc && onCloseFunc();
|
||||
|
@@ -1,12 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Box, Tooltip, TooltipProps } from '@chakra-ui/react';
|
||||
import { useSystem } from '../../../hooks/useSystem';
|
||||
import { Tooltip, TooltipProps } from '@chakra-ui/react';
|
||||
|
||||
interface Props extends TooltipProps {}
|
||||
|
||||
const MyTooltip = ({ children, shouldWrapChildren = true, ...props }: Props) => {
|
||||
const { isPc } = useSystem();
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
className="chakra-tooltip"
|
||||
|
@@ -3,10 +3,20 @@
|
||||
"ai_settings": "AI Settings",
|
||||
"all_apps": "All Apps",
|
||||
"app": {
|
||||
"Version name": "Version name",
|
||||
"modules": {
|
||||
"click to update": "click to update",
|
||||
"has new version": "has new version"
|
||||
}
|
||||
},
|
||||
"version_back": "Back to initial",
|
||||
"version_copy": "Copy",
|
||||
"version_current": "current version",
|
||||
"version_initial": "Initial version",
|
||||
"version_initial_copy": "Copy-Initial state",
|
||||
"version_name_tips": "Version name can't be empty.",
|
||||
"version_past": "published",
|
||||
"version_publish_tips": "This version will be saved to the team's cloud, synchronized with the entire team, and update the application versions across all publishing channels.",
|
||||
"version_save_tips": "This version will be saved to the team's cloud and synchronized with the entire team."
|
||||
},
|
||||
"app_detail": "App Details",
|
||||
"chat_debug": "Chat Debug",
|
||||
|
@@ -138,6 +138,7 @@
|
||||
"Done": "Done",
|
||||
"Edit": "Edit",
|
||||
"Exit": "Exit",
|
||||
"Exit Directly": "Exit",
|
||||
"Expired Time": "Expired Time",
|
||||
"Field": "Field",
|
||||
"File": "File",
|
||||
@@ -184,6 +185,7 @@
|
||||
"Save": "Save",
|
||||
"Save Failed": "Saved failed",
|
||||
"Save Success": "Saved success",
|
||||
"Save_and_exit": "Save and exit",
|
||||
"Search": "Search",
|
||||
"Select File Failed": "Select File Failed",
|
||||
"Select template": "Select template",
|
||||
@@ -256,6 +258,8 @@
|
||||
"no_intro": "No introduction yet",
|
||||
"not_support": "not support",
|
||||
"page_center": "Center the page",
|
||||
"redo_tip": "redo ctrl shift z",
|
||||
"redo_tip_mac": "redo ⌘ shift z",
|
||||
"request_end": "All loaded",
|
||||
"request_more": "Click to load more",
|
||||
"speech": {
|
||||
@@ -275,7 +279,13 @@
|
||||
"textarea": {
|
||||
"Magnifying": "Magnify"
|
||||
}
|
||||
}
|
||||
},
|
||||
"undo_tip": "unde ctrl z",
|
||||
"undo_tip_mac": "undo ⌘ z ",
|
||||
"zoomin_tip": "zoomIn ctrl -",
|
||||
"zoomin_tip_mac": "zoomIn ⌘ -",
|
||||
"zoomout_tip": "zoomOut ctrl +",
|
||||
"zoomout_tip_mac": "zoomOut ⌘ +"
|
||||
},
|
||||
"confirm_choice": "Confirm selection",
|
||||
"contribute_app_template": "Contribution template",
|
||||
@@ -311,6 +321,7 @@
|
||||
"Max histories": "Chat history count",
|
||||
"Max tokens": "Reply limit",
|
||||
"Name and avatar": "Avatar & name",
|
||||
"Not saved": "Not saved",
|
||||
"Onclick to save": "Click to save",
|
||||
"Publish": "Publish",
|
||||
"Publish Confirm": "Confirm to publish the app? It will immediately update the app status across all published channels.",
|
||||
@@ -361,6 +372,7 @@
|
||||
"close custom feedback": "Close feedback"
|
||||
},
|
||||
"have_publish": "Published",
|
||||
"have_saved": "have saved",
|
||||
"loading": "loading",
|
||||
"logs": {
|
||||
"Source And Time": "Source & Time"
|
||||
@@ -375,6 +387,7 @@
|
||||
},
|
||||
"no_app": "There is no application yet, go and create one!",
|
||||
"not_published": "Unpublished",
|
||||
"not_saved": "not saved",
|
||||
"outLink": {
|
||||
"Can Drag": "Icon draggable",
|
||||
"Default open": "Open by default",
|
||||
@@ -475,6 +488,7 @@
|
||||
"Recent use": "Recent use",
|
||||
"Record": "Voice input",
|
||||
"Restart": "Restart conversation",
|
||||
"Run test": "Run test",
|
||||
"Select Image": "Select image",
|
||||
"Select dataset": "Select dataset",
|
||||
"Select dataset Desc": "Select a dataset to store the expected answer",
|
||||
@@ -935,7 +949,10 @@
|
||||
"Debug Node": "Debug mode",
|
||||
"Failed": "Execution failed",
|
||||
"Not intro": "This node has no introduction~",
|
||||
"Run": "Run",
|
||||
"Running": "Running",
|
||||
"Save and publish": "Save and publish",
|
||||
"Save to cloud": "Save",
|
||||
"Skipped": "Skipped execution",
|
||||
"Stop debug": "Stop debugging",
|
||||
"Success": "Execution successful",
|
||||
|
@@ -45,5 +45,12 @@
|
||||
},
|
||||
"tool_input": "Tool",
|
||||
"update_link_error": "Update link exception",
|
||||
"variable_picker_tips": "enter node name or variable name to search"
|
||||
"variable_picker_tips": "enter node name or variable name to search",
|
||||
"workflow": {
|
||||
"Back_to_current_version": "Back to current version",
|
||||
"My edit": "My edit",
|
||||
"Switch_success": "switch successfully",
|
||||
"Team cloud": "Team cloud",
|
||||
"exit_tips": "Your changes have not been saved. Exiting directly will not save your edits."
|
||||
}
|
||||
}
|
||||
|
@@ -3,10 +3,20 @@
|
||||
"ai_settings": "AI 配置",
|
||||
"all_apps": "全部应用",
|
||||
"app": {
|
||||
"Version name": "版本名称",
|
||||
"modules": {
|
||||
"click to update": "点击更新",
|
||||
"has new version": "有新版本"
|
||||
}
|
||||
},
|
||||
"version_back": "回到初始状态",
|
||||
"version_copy": "副本",
|
||||
"version_current": "当前版本",
|
||||
"version_initial": "初始版本",
|
||||
"version_initial_copy": "副本-初始状态",
|
||||
"version_name_tips": "版本名称不能为空",
|
||||
"version_past": "发布过",
|
||||
"version_publish_tips": "该版本将被保存至团队云端,同步给整个团队,同时更新所有发布渠道的应用版本",
|
||||
"version_save_tips": "该版本将被保存至团队云端,同步给整个团队"
|
||||
},
|
||||
"app_detail": "应用详情",
|
||||
"chat_debug": "调试预览",
|
||||
|
@@ -138,6 +138,7 @@
|
||||
"Done": "完成",
|
||||
"Edit": "编辑",
|
||||
"Exit": "退出",
|
||||
"Exit Directly": "直接退出",
|
||||
"Expired Time": "过期时间",
|
||||
"Field": "字段",
|
||||
"File": "文件",
|
||||
@@ -184,6 +185,7 @@
|
||||
"Save": "保存",
|
||||
"Save Failed": "保存异常",
|
||||
"Save Success": "保存成功",
|
||||
"Save_and_exit": "保存并退出",
|
||||
"Search": "搜索",
|
||||
"Select File Failed": "选择文件异常",
|
||||
"Select template": "选择模板",
|
||||
@@ -256,6 +258,8 @@
|
||||
"no_intro": "暂无介绍",
|
||||
"not_support": "不支持",
|
||||
"page_center": "页面居中",
|
||||
"redo_tip": "恢复 ctrl shift z",
|
||||
"redo_tip_mac": "恢复 ⌘ shift z",
|
||||
"request_end": "已加载全部",
|
||||
"request_more": "点击加载更多",
|
||||
"speech": {
|
||||
@@ -275,7 +279,13 @@
|
||||
"textarea": {
|
||||
"Magnifying": "放大"
|
||||
}
|
||||
}
|
||||
},
|
||||
"undo_tip": "撤销 ctrl z",
|
||||
"undo_tip_mac": "撤销 ⌘ z ",
|
||||
"zoomin_tip": "缩小 ctrl -",
|
||||
"zoomin_tip_mac": "缩小 ⌘ -",
|
||||
"zoomout_tip": "放大 ctrl +",
|
||||
"zoomout_tip_mac": "放大 ⌘ +"
|
||||
},
|
||||
"confirm_choice": "确认选择",
|
||||
"contribute_app_template": "贡献模板",
|
||||
@@ -311,6 +321,7 @@
|
||||
"Max histories": "聊天记录数量",
|
||||
"Max tokens": "回复上限",
|
||||
"Name and avatar": "头像 & 名称",
|
||||
"Not saved": "未保存",
|
||||
"Onclick to save": "点击保存",
|
||||
"Publish": "发布",
|
||||
"Publish Confirm": "确认发布应用?会立即更新所有发布渠道的应用状态。",
|
||||
@@ -361,6 +372,7 @@
|
||||
"close custom feedback": "关闭反馈"
|
||||
},
|
||||
"have_publish": "已发布",
|
||||
"have_saved": "已保存",
|
||||
"loading": "加载中",
|
||||
"logs": {
|
||||
"Source And Time": "来源 & 时间"
|
||||
@@ -375,6 +387,7 @@
|
||||
},
|
||||
"no_app": "还没有应用,快去创建一个吧!",
|
||||
"not_published": "未发布",
|
||||
"not_saved": "未保存",
|
||||
"outLink": {
|
||||
"Can Drag": "图标可拖拽",
|
||||
"Default open": "默认打开",
|
||||
@@ -475,6 +488,7 @@
|
||||
"Recent use": "最近使用",
|
||||
"Record": "语音输入",
|
||||
"Restart": "重开对话",
|
||||
"Run test": "运行预览",
|
||||
"Select Image": "选择图片",
|
||||
"Select dataset": "选择知识库",
|
||||
"Select dataset Desc": "选择一个知识库存储预期答案",
|
||||
@@ -617,7 +631,8 @@
|
||||
"success": "开始同步"
|
||||
}
|
||||
},
|
||||
"training": {}
|
||||
"training": {
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"Auxiliary Data": "辅助数据",
|
||||
@@ -934,7 +949,10 @@
|
||||
"Debug Node": "Debug 模式",
|
||||
"Failed": "运行失败",
|
||||
"Not intro": "这个节点没有介绍~",
|
||||
"Run": "运行",
|
||||
"Running": "运行中",
|
||||
"Save and publish": "保存并发布",
|
||||
"Save to cloud": "仅保存",
|
||||
"Skipped": "跳过运行",
|
||||
"Stop debug": "停止调试",
|
||||
"Success": "运行成功",
|
||||
|
@@ -45,5 +45,12 @@
|
||||
},
|
||||
"tool_input": "工具参数",
|
||||
"update_link_error": "更新链接异常",
|
||||
"variable_picker_tips": "可输入节点名或变量名搜索"
|
||||
"variable_picker_tips": "可输入节点名或变量名搜索",
|
||||
"workflow": {
|
||||
"Back_to_current_version": "回到初始状态",
|
||||
"My edit": "我的编辑",
|
||||
"Switch_success": "切换成功",
|
||||
"Team cloud": "团队云端",
|
||||
"exit_tips": "您的更改尚未保存,「直接退出」将不会保存您的编辑记录。"
|
||||
}
|
||||
}
|
||||
|
2
projects/app/src/global/core/app/api.d.ts
vendored
@@ -20,6 +20,8 @@ export type PostPublishAppProps = {
|
||||
nodes: AppSchema['modules'];
|
||||
edges: AppSchema['edges'];
|
||||
chatConfig: AppSchema['chatConfig'];
|
||||
isPublish?: boolean;
|
||||
versionName?: string;
|
||||
};
|
||||
|
||||
export type PostRevertAppProps = {
|
||||
|
@@ -14,6 +14,8 @@ import { defaultNodeVersion } from '@fastgpt/global/core/workflow/node/constant'
|
||||
import { ClientSession } from '@fastgpt/service/common/mongo';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
|
||||
export type CreateAppBody = {
|
||||
parentId?: ParentIdType;
|
||||
@@ -42,6 +44,8 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
|
||||
|
||||
// 上限校验
|
||||
await checkTeamAppLimit(teamId);
|
||||
const tmb = await MongoTeamMember.findById({ _id: tmbId });
|
||||
const user = await MongoUser.findById({ _id: tmb?.userId });
|
||||
|
||||
// 创建app
|
||||
const appId = await onCreateApp({
|
||||
@@ -53,7 +57,9 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
|
||||
edges,
|
||||
chatConfig,
|
||||
teamId,
|
||||
tmbId
|
||||
tmbId,
|
||||
userAvatar: user?.avatar,
|
||||
username: user?.username
|
||||
});
|
||||
|
||||
return appId;
|
||||
@@ -73,6 +79,8 @@ export const onCreateApp = async ({
|
||||
teamId,
|
||||
tmbId,
|
||||
pluginData,
|
||||
username,
|
||||
userAvatar,
|
||||
session
|
||||
}: {
|
||||
parentId?: ParentIdType;
|
||||
@@ -86,6 +94,8 @@ export const onCreateApp = async ({
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
pluginData?: AppSchema['pluginData'];
|
||||
username?: string;
|
||||
userAvatar?: string;
|
||||
session?: ClientSession;
|
||||
}) => {
|
||||
const create = async (session: ClientSession) => {
|
||||
@@ -117,7 +127,10 @@ export const onCreateApp = async ({
|
||||
appId,
|
||||
nodes: modules,
|
||||
edges,
|
||||
chatConfig
|
||||
chatConfig,
|
||||
versionName: name,
|
||||
username,
|
||||
avatar: userAvatar
|
||||
}
|
||||
],
|
||||
{ session }
|
||||
|
@@ -10,13 +10,18 @@ import { PostPublishAppProps } from '@/global/core/app/api';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
|
||||
type Response = {};
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<{}> {
|
||||
const { appId } = req.query as { appId: string };
|
||||
const { nodes = [], edges = [], chatConfig, type } = req.body as PostPublishAppProps;
|
||||
const {
|
||||
nodes = [],
|
||||
edges = [],
|
||||
chatConfig,
|
||||
type,
|
||||
isPublish,
|
||||
versionName
|
||||
} = req.body as PostPublishAppProps;
|
||||
|
||||
const { app } = await authApp({ appId, req, per: WritePermissionVal, authToken: true });
|
||||
const { app, tmbId } = await authApp({ appId, req, per: WritePermissionVal, authToken: true });
|
||||
|
||||
const { nodes: formatNodes } = beforeUpdateAppFormat({ nodes });
|
||||
|
||||
@@ -28,7 +33,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
|
||||
appId,
|
||||
nodes: formatNodes,
|
||||
edges,
|
||||
chatConfig
|
||||
chatConfig,
|
||||
isPublish,
|
||||
versionName,
|
||||
tmbId
|
||||
}
|
||||
],
|
||||
{ session }
|
||||
|
27
projects/app/src/pages/api/core/app/version/update.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||
|
||||
export type UpdateAppVersionBody = {
|
||||
appId: string;
|
||||
versionId: string;
|
||||
versionName: string;
|
||||
};
|
||||
|
||||
async function handler(req: ApiRequestProps<UpdateAppVersionBody>) {
|
||||
const { appId, versionId, versionName } = req.body;
|
||||
await authApp({ appId, req, per: WritePermissionVal, authToken: true });
|
||||
|
||||
await MongoAppVersion.findByIdAndUpdate(
|
||||
{ _id: versionId },
|
||||
{
|
||||
versionName
|
||||
}
|
||||
);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
@@ -45,7 +45,7 @@ async function handler(
|
||||
}
|
||||
|
||||
// get app and history
|
||||
const [{ histories }, { nodes }] = await Promise.all([
|
||||
const [{ histories }, { nodes, chatConfig }] = await Promise.all([
|
||||
getChatItems({
|
||||
appId,
|
||||
chatId,
|
||||
@@ -68,7 +68,7 @@ async function handler(
|
||||
history: app.type === AppTypeEnum.plugin ? histories : transformPreviewHistories(histories),
|
||||
app: {
|
||||
chatConfig: getAppChatConfig({
|
||||
chatConfig: app.chatConfig,
|
||||
chatConfig,
|
||||
systemConfigNode: getGuideModule(nodes),
|
||||
storeVariables: chat?.variableList,
|
||||
storeWelcomeText: chat?.welcomeText,
|
||||
|
@@ -42,7 +42,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
throw new Error(ChatErrEnum.unAuthChat);
|
||||
}
|
||||
|
||||
const [{ histories }, { nodes }] = await Promise.all([
|
||||
const [{ histories }, { nodes, chatConfig }] = await Promise.all([
|
||||
getChatItems({
|
||||
appId: app._id,
|
||||
chatId,
|
||||
@@ -75,7 +75,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
history: app.type === AppTypeEnum.plugin ? histories : transformPreviewHistories(histories),
|
||||
app: {
|
||||
chatConfig: getAppChatConfig({
|
||||
chatConfig: app.chatConfig,
|
||||
chatConfig,
|
||||
systemConfigNode: getGuideModule(nodes),
|
||||
storeVariables: chat?.variableList,
|
||||
storeWelcomeText: chat?.welcomeText,
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { getGuideModule, getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
|
||||
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
@@ -48,7 +47,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
}
|
||||
|
||||
// get app and history
|
||||
const [{ histories }, { nodes }] = await Promise.all([
|
||||
const [{ histories }, { nodes, chatConfig }] = await Promise.all([
|
||||
getChatItems({
|
||||
appId,
|
||||
chatId,
|
||||
@@ -76,7 +75,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
history: app.type === AppTypeEnum.plugin ? histories : transformPreviewHistories(histories),
|
||||
app: {
|
||||
chatConfig: getAppChatConfig({
|
||||
chatConfig: app.chatConfig,
|
||||
chatConfig,
|
||||
systemConfigNode: getGuideModule(nodes),
|
||||
storeVariables: chat?.variableList,
|
||||
storeWelcomeText: chat?.welcomeText,
|
||||
|
@@ -1,68 +1,123 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Box, Flex, Button, IconButton } from '@chakra-ui/react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
IconButton,
|
||||
HStack,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext, getWorkflowStore } from '../WorkflowComponents/context';
|
||||
import { useInterval } from 'ahooks';
|
||||
import { AppContext, TabEnum } from '../context';
|
||||
import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm';
|
||||
import RouteTab from '../RouteTab';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import AppCard from '../WorkflowComponents/AppCard';
|
||||
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
|
||||
import RouteTab from '../RouteTab';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
const PublishHistories = dynamic(() => import('../PublishHistoriesSlider'));
|
||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { compareSnapshot } from '@/web/core/workflow/utils';
|
||||
import SaveAndPublishModal from '../WorkflowComponents/Flow/components/SaveAndPublish';
|
||||
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
||||
|
||||
const PublishHistories = dynamic(() => import('../WorkflowPublishHistoriesSlider'));
|
||||
|
||||
const Header = () => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { isPc } = useSystem();
|
||||
const router = useRouter();
|
||||
|
||||
const { appDetail, onPublish, currentTab } = useContextSelector(AppContext, (v) => v);
|
||||
const isV2Workflow = appDetail?.version === 'v2';
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const {
|
||||
isOpen: isSaveAndPublishModalOpen,
|
||||
onOpen: onSaveAndPublishModalOpen,
|
||||
onClose: onSaveAndPublishModalClose
|
||||
} = useDisclosure();
|
||||
const [isSave, setIsSave] = useState(false);
|
||||
|
||||
const {
|
||||
flowData2StoreData,
|
||||
flowData2StoreDataAndCheck,
|
||||
onSaveWorkflow,
|
||||
setHistoriesDefaultData,
|
||||
setWorkflowTestData,
|
||||
setHistoriesDefaultData,
|
||||
historiesDefaultData,
|
||||
initData
|
||||
nodes,
|
||||
edges,
|
||||
past,
|
||||
future,
|
||||
setPast
|
||||
} = useContextSelector(WorkflowContext, (v) => v);
|
||||
|
||||
const onclickPublish = useCallback(async () => {
|
||||
const data = flowData2StoreDataAndCheck();
|
||||
if (data) {
|
||||
await onPublish({
|
||||
...data,
|
||||
chatConfig: appDetail.chatConfig,
|
||||
//@ts-ignore
|
||||
version: 'v2'
|
||||
});
|
||||
}
|
||||
}, [flowData2StoreDataAndCheck, onPublish, appDetail.chatConfig]);
|
||||
const isPublished = useMemo(() => {
|
||||
const savedSnapshot =
|
||||
future.findLast((snapshot) => snapshot.isSaved) || past.find((snapshot) => snapshot.isSaved);
|
||||
|
||||
const saveAndBack = useCallback(async () => {
|
||||
return compareSnapshot(
|
||||
{
|
||||
nodes: savedSnapshot?.nodes,
|
||||
edges: savedSnapshot?.edges,
|
||||
chatConfig: savedSnapshot?.chatConfig
|
||||
},
|
||||
{
|
||||
nodes: nodes,
|
||||
edges: edges,
|
||||
chatConfig: appDetail.chatConfig
|
||||
}
|
||||
);
|
||||
}, [future, past, nodes, edges, appDetail.chatConfig]);
|
||||
|
||||
const { runAsync: onClickSave, loading } = useRequest2(
|
||||
async ({
|
||||
isPublish,
|
||||
versionName = formatTime2YMDHMS(new Date())
|
||||
}: {
|
||||
isPublish?: boolean;
|
||||
versionName?: string;
|
||||
}) => {
|
||||
const data = flowData2StoreData();
|
||||
|
||||
if (data) {
|
||||
await onPublish({
|
||||
...data,
|
||||
isPublish,
|
||||
versionName,
|
||||
chatConfig: appDetail.chatConfig,
|
||||
//@ts-ignore
|
||||
version: 'v2'
|
||||
});
|
||||
setPast((prevPast) =>
|
||||
prevPast.map((item, index) =>
|
||||
index === prevPast.length - 1
|
||||
? {
|
||||
...item,
|
||||
isSaved: true
|
||||
}
|
||||
: item
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const onBack = useCallback(async () => {
|
||||
try {
|
||||
await onSaveWorkflow();
|
||||
localStorage.removeItem(`${appDetail._id}-past`);
|
||||
localStorage.removeItem(`${appDetail._id}-future`);
|
||||
router.push('/app/list');
|
||||
} catch (error) {}
|
||||
}, [onSaveWorkflow, router]);
|
||||
|
||||
// effect
|
||||
useBeforeunload({
|
||||
callback: onSaveWorkflow,
|
||||
tip: t('common:core.common.tip.leave page')
|
||||
});
|
||||
useInterval(() => {
|
||||
if (!appDetail._id) return;
|
||||
onSaveWorkflow();
|
||||
}, 40000);
|
||||
}, [appDetail._id, router]);
|
||||
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
@@ -78,9 +133,10 @@ const Header = () => {
|
||||
pl={[2, 4]}
|
||||
pr={[2, 6]}
|
||||
borderBottom={'base'}
|
||||
alignItems={'center'}
|
||||
alignItems={['flex-start', 'center']}
|
||||
userSelect={'none'}
|
||||
h={'67px'}
|
||||
h={['auto', '67px']}
|
||||
flexWrap={'wrap'}
|
||||
{...(currentTab === TabEnum.appEdit
|
||||
? {
|
||||
bg: 'myGray.25'
|
||||
@@ -95,15 +151,36 @@ const Header = () => {
|
||||
name={'common/leftArrowLight'}
|
||||
w={'1.75rem'}
|
||||
cursor={'pointer'}
|
||||
onClick={saveAndBack}
|
||||
onClick={isPublished ? onBack : onOpen}
|
||||
/>
|
||||
<MyModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
iconSrc="common/warn"
|
||||
title={t('common:common.Exit')}
|
||||
w={'400px'}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box>{t('workflow:workflow.exit_tips')}</Box>
|
||||
</ModalBody>
|
||||
<ModalFooter gap={3}>
|
||||
<Button variant={'whiteDanger'} onClick={onBack}>
|
||||
{t('common:common.Exit Directly')}
|
||||
</Button>
|
||||
<Button
|
||||
isLoading={loading}
|
||||
onClick={async () => {
|
||||
await onClickSave({});
|
||||
onBack();
|
||||
}}
|
||||
>
|
||||
{t('common:common.Save_and_exit')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
{/* app info */}
|
||||
<Box ml={1}>
|
||||
<AppCard
|
||||
showSaveStatus={
|
||||
!historiesDefaultData && isV2Workflow && currentTab === TabEnum.appEdit
|
||||
}
|
||||
/>
|
||||
<AppCard isPublished={isPublished} showSaveStatus={isV2Workflow} />
|
||||
</Box>
|
||||
|
||||
{isPc && (
|
||||
@@ -114,10 +191,9 @@ const Header = () => {
|
||||
<Box flex={1} />
|
||||
|
||||
{currentTab === TabEnum.appEdit && (
|
||||
<>
|
||||
<HStack flexDirection={['column', 'row']} spacing={[2, 3]}>
|
||||
{!historiesDefaultData && (
|
||||
<IconButton
|
||||
mr={[2, 4]}
|
||||
icon={<MyIcon name={'history'} w={'18px'} />}
|
||||
aria-label={''}
|
||||
size={'sm'}
|
||||
@@ -145,52 +221,109 @@ const Header = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('common:core.workflow.run_test')}
|
||||
{t('common:core.workflow.Run')}
|
||||
</Button>
|
||||
|
||||
{!historiesDefaultData && (
|
||||
<PopoverConfirm
|
||||
showCancel
|
||||
content={t('common:core.app.Publish Confirm')}
|
||||
<MyPopover
|
||||
placement={'bottom-end'}
|
||||
hasArrow={false}
|
||||
offset={[2, 4]}
|
||||
w={'116px'}
|
||||
onOpenFunc={() => setIsSave(true)}
|
||||
onCloseFunc={() => setIsSave(false)}
|
||||
trigger={'hover'}
|
||||
Trigger={
|
||||
<Button
|
||||
ml={[2, 4]}
|
||||
size={'sm'}
|
||||
leftIcon={<MyIcon name={'common/publishFill'} w={['14px', '16px']} />}
|
||||
rightIcon={
|
||||
<MyIcon
|
||||
name={isSave ? 'core/chat/chevronUp' : 'core/chat/chevronDown'}
|
||||
w={['14px', '16px']}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{t('common:core.app.Publish')}
|
||||
<Box>{t('common:common.Save')}</Box>
|
||||
</Button>
|
||||
}
|
||||
onConfirm={() => onclickPublish()}
|
||||
/>
|
||||
>
|
||||
{({}) => (
|
||||
<MyBox p={1.5}>
|
||||
<MyBox
|
||||
display={'flex'}
|
||||
size={'md'}
|
||||
px={1}
|
||||
py={1.5}
|
||||
rounded={'4px'}
|
||||
_hover={{ color: 'primary.600', bg: 'rgba(17, 24, 36, 0.05)' }}
|
||||
cursor={'pointer'}
|
||||
isLoading={loading}
|
||||
onClick={async () => {
|
||||
await onClickSave({});
|
||||
}}
|
||||
>
|
||||
<MyIcon name={'core/workflow/upload'} w={'16px'} mr={2} />
|
||||
<Box fontSize={'sm'}>{t('common:core.workflow.Save to cloud')}</Box>
|
||||
</MyBox>
|
||||
<Flex
|
||||
px={1}
|
||||
py={1.5}
|
||||
rounded={'4px'}
|
||||
_hover={{ color: 'primary.600', bg: 'rgba(17, 24, 36, 0.05)' }}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
const data = flowData2StoreDataAndCheck();
|
||||
if (data) {
|
||||
onSaveAndPublishModalOpen();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MyIcon name={'core/workflow/publish'} w={'16px'} mr={2} />
|
||||
<Box fontSize={'sm'}>{t('common:core.workflow.Save and publish')}</Box>
|
||||
{isSaveAndPublishModalOpen && (
|
||||
<SaveAndPublishModal
|
||||
isLoading={loading}
|
||||
onClose={onSaveAndPublishModalClose}
|
||||
onClickSave={onClickSave}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</MyBox>
|
||||
)}
|
||||
</MyPopover>
|
||||
)}
|
||||
</>
|
||||
</HStack>
|
||||
)}
|
||||
</Flex>
|
||||
{historiesDefaultData && (
|
||||
{historiesDefaultData && isV2Workflow && currentTab === TabEnum.appEdit && (
|
||||
<PublishHistories
|
||||
initData={initData}
|
||||
onClose={() => {
|
||||
setHistoriesDefaultData(undefined);
|
||||
}}
|
||||
defaultData={historiesDefaultData}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}, [
|
||||
appDetail.chatConfig,
|
||||
currentTab,
|
||||
flowData2StoreDataAndCheck,
|
||||
historiesDefaultData,
|
||||
initData,
|
||||
isPc,
|
||||
currentTab,
|
||||
isPublished,
|
||||
isOpen,
|
||||
onClose,
|
||||
t,
|
||||
loading,
|
||||
isV2Workflow,
|
||||
onclickPublish,
|
||||
saveAndBack,
|
||||
historiesDefaultData,
|
||||
isSave,
|
||||
onBack,
|
||||
onOpen,
|
||||
onClickSave,
|
||||
setHistoriesDefaultData,
|
||||
appDetail.chatConfig,
|
||||
flowData2StoreDataAndCheck,
|
||||
setWorkflowTestData,
|
||||
t
|
||||
isSaveAndPublishModalOpen,
|
||||
onSaveAndPublishModalClose,
|
||||
onSaveAndPublishModalOpen
|
||||
]);
|
||||
|
||||
return Render;
|
||||
|
@@ -1,23 +1,36 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Box, Flex, Button, IconButton, HStack } from '@chakra-ui/react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Button,
|
||||
IconButton,
|
||||
HStack,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext, getWorkflowStore } from '../WorkflowComponents/context';
|
||||
import { useInterval } from 'ahooks';
|
||||
import { AppContext, TabEnum } from '../context';
|
||||
import RouteTab from '../RouteTab';
|
||||
import PopoverConfirm from '@fastgpt/web/components/common/MyPopover/PopoverConfirm';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import AppCard from '../WorkflowComponents/AppCard';
|
||||
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
const PublishHistories = dynamic(() => import('../PublishHistoriesSlider'));
|
||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { compareSnapshot } from '@/web/core/workflow/utils';
|
||||
import SaveAndPublishModal from '../WorkflowComponents/Flow/components/SaveAndPublish';
|
||||
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
||||
|
||||
const PublishHistories = dynamic(() => import('../WorkflowPublishHistoriesSlider'));
|
||||
|
||||
const Header = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -26,44 +39,85 @@ const Header = () => {
|
||||
|
||||
const { appDetail, onPublish, currentTab } = useContextSelector(AppContext, (v) => v);
|
||||
const isV2Workflow = appDetail?.version === 'v2';
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const {
|
||||
isOpen: isSaveAndPublishModalOpen,
|
||||
onOpen: onSaveAndPublishModalOpen,
|
||||
onClose: onSaveAndPublishModalClose
|
||||
} = useDisclosure();
|
||||
const [isSave, setIsSave] = useState(false);
|
||||
|
||||
const {
|
||||
flowData2StoreData,
|
||||
flowData2StoreDataAndCheck,
|
||||
setWorkflowTestData,
|
||||
onSaveWorkflow,
|
||||
setHistoriesDefaultData,
|
||||
historiesDefaultData,
|
||||
initData
|
||||
nodes,
|
||||
edges,
|
||||
past,
|
||||
future,
|
||||
setPast
|
||||
} = useContextSelector(WorkflowContext, (v) => v);
|
||||
|
||||
const onclickPublish = useCallback(async () => {
|
||||
const data = flowData2StoreDataAndCheck();
|
||||
if (data) {
|
||||
await onPublish({
|
||||
...data,
|
||||
chatConfig: appDetail.chatConfig,
|
||||
//@ts-ignore
|
||||
version: 'v2'
|
||||
});
|
||||
}
|
||||
}, [flowData2StoreDataAndCheck, onPublish, appDetail.chatConfig]);
|
||||
const isPublished = useMemo(() => {
|
||||
const savedSnapshot =
|
||||
future.findLast((snapshot) => snapshot.isSaved) || past.find((snapshot) => snapshot.isSaved);
|
||||
|
||||
const saveAndBack = useCallback(async () => {
|
||||
return compareSnapshot(
|
||||
{
|
||||
nodes: savedSnapshot?.nodes,
|
||||
edges: savedSnapshot?.edges,
|
||||
chatConfig: savedSnapshot?.chatConfig
|
||||
},
|
||||
{
|
||||
nodes: nodes,
|
||||
edges: edges,
|
||||
chatConfig: appDetail.chatConfig
|
||||
}
|
||||
);
|
||||
}, [future, past, nodes, edges, appDetail.chatConfig]);
|
||||
|
||||
const { runAsync: onClickSave, loading } = useRequest2(
|
||||
async ({
|
||||
isPublish,
|
||||
versionName = formatTime2YMDHMS(new Date())
|
||||
}: {
|
||||
isPublish?: boolean;
|
||||
versionName?: string;
|
||||
}) => {
|
||||
const data = flowData2StoreData();
|
||||
|
||||
if (data) {
|
||||
await onPublish({
|
||||
...data,
|
||||
isPublish,
|
||||
versionName,
|
||||
chatConfig: appDetail.chatConfig,
|
||||
//@ts-ignore
|
||||
version: 'v2'
|
||||
});
|
||||
setPast((prevPast) =>
|
||||
prevPast.map((item, index) =>
|
||||
index === prevPast.length - 1
|
||||
? {
|
||||
...item,
|
||||
isSaved: true
|
||||
}
|
||||
: item
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const onBack = useCallback(async () => {
|
||||
try {
|
||||
await onSaveWorkflow();
|
||||
localStorage.removeItem(`${appDetail._id}-past`);
|
||||
localStorage.removeItem(`${appDetail._id}-future`);
|
||||
router.push('/app/list');
|
||||
} catch (error) {}
|
||||
}, [onSaveWorkflow, router]);
|
||||
|
||||
// effect
|
||||
useBeforeunload({
|
||||
callback: onSaveWorkflow,
|
||||
tip: t('common:core.common.tip.leave page')
|
||||
});
|
||||
useInterval(() => {
|
||||
if (!appDetail._id) return;
|
||||
onSaveWorkflow();
|
||||
}, 40000);
|
||||
}, [appDetail._id, router]);
|
||||
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
@@ -97,15 +151,36 @@ const Header = () => {
|
||||
name={'common/leftArrowLight'}
|
||||
w={'1.75rem'}
|
||||
cursor={'pointer'}
|
||||
onClick={saveAndBack}
|
||||
onClick={isPublished ? onBack : onOpen}
|
||||
/>
|
||||
<MyModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
iconSrc="common/warn"
|
||||
title={t('common:common.Exit')}
|
||||
w={'400px'}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box>{t('workflow:workflow.exit_tips')}</Box>
|
||||
</ModalBody>
|
||||
<ModalFooter gap={3}>
|
||||
<Button variant={'whiteDanger'} onClick={onBack}>
|
||||
{t('common:common.Exit Directly')}
|
||||
</Button>
|
||||
<Button
|
||||
isLoading={loading}
|
||||
onClick={async () => {
|
||||
await onClickSave({});
|
||||
onBack();
|
||||
}}
|
||||
>
|
||||
{t('common:common.Save_and_exit')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
{/* app info */}
|
||||
<Box ml={1}>
|
||||
<AppCard
|
||||
showSaveStatus={
|
||||
!historiesDefaultData && isV2Workflow && currentTab === TabEnum.appEdit
|
||||
}
|
||||
/>
|
||||
<AppCard isPublished={isPublished} showSaveStatus={isV2Workflow} />
|
||||
</Box>
|
||||
|
||||
{isPc && (
|
||||
@@ -146,38 +221,84 @@ const Header = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('common:core.workflow.run_test')}
|
||||
{t('common:core.workflow.Run')}
|
||||
</Button>
|
||||
|
||||
{!historiesDefaultData && (
|
||||
<PopoverConfirm
|
||||
showCancel
|
||||
content={t('common:core.app.Publish Confirm')}
|
||||
<MyPopover
|
||||
placement={'bottom-end'}
|
||||
hasArrow={false}
|
||||
offset={[2, 4]}
|
||||
w={'116px'}
|
||||
onOpenFunc={() => setIsSave(true)}
|
||||
onCloseFunc={() => setIsSave(false)}
|
||||
trigger={'hover'}
|
||||
Trigger={
|
||||
<Box>
|
||||
<MyTooltip label={t('common:core.app.Publish app tip')}>
|
||||
<Button
|
||||
size={'sm'}
|
||||
leftIcon={<MyIcon name={'common/publishFill'} w={['14px', '16px']} />}
|
||||
>
|
||||
{t('common:core.app.Publish')}
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
<Button
|
||||
size={'sm'}
|
||||
rightIcon={
|
||||
<MyIcon
|
||||
name={isSave ? 'core/chat/chevronUp' : 'core/chat/chevronDown'}
|
||||
w={['14px', '16px']}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Box>{t('common:common.Save')}</Box>
|
||||
</Button>
|
||||
}
|
||||
onConfirm={() => onclickPublish()}
|
||||
/>
|
||||
>
|
||||
{({}) => (
|
||||
<MyBox p={1.5}>
|
||||
<MyBox
|
||||
display={'flex'}
|
||||
size={'md'}
|
||||
px={1}
|
||||
py={1.5}
|
||||
rounded={'4px'}
|
||||
_hover={{ color: 'primary.600', bg: 'rgba(17, 24, 36, 0.05)' }}
|
||||
cursor={'pointer'}
|
||||
isLoading={loading}
|
||||
onClick={async () => {
|
||||
await onClickSave({});
|
||||
}}
|
||||
>
|
||||
<MyIcon name={'core/workflow/upload'} w={'16px'} mr={2} />
|
||||
<Box fontSize={'sm'}>{t('common:core.workflow.Save to cloud')}</Box>
|
||||
</MyBox>
|
||||
<Flex
|
||||
px={1}
|
||||
py={1.5}
|
||||
rounded={'4px'}
|
||||
_hover={{ color: 'primary.600', bg: 'rgba(17, 24, 36, 0.05)' }}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
const data = flowData2StoreDataAndCheck();
|
||||
if (data) {
|
||||
onSaveAndPublishModalOpen();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MyIcon name={'core/workflow/publish'} w={'16px'} mr={2} />
|
||||
<Box fontSize={'sm'}>{t('common:core.workflow.Save and publish')}</Box>
|
||||
{isSaveAndPublishModalOpen && (
|
||||
<SaveAndPublishModal
|
||||
isLoading={loading}
|
||||
onClose={onSaveAndPublishModalClose}
|
||||
onClickSave={onClickSave}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</MyBox>
|
||||
)}
|
||||
</MyPopover>
|
||||
)}
|
||||
</HStack>
|
||||
)}
|
||||
</Flex>
|
||||
{historiesDefaultData && (
|
||||
{historiesDefaultData && isV2Workflow && currentTab === TabEnum.appEdit && (
|
||||
<PublishHistories
|
||||
initData={initData}
|
||||
onClose={() => {
|
||||
setHistoriesDefaultData(undefined);
|
||||
}}
|
||||
defaultData={historiesDefaultData}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -185,16 +306,24 @@ const Header = () => {
|
||||
}, [
|
||||
isPc,
|
||||
currentTab,
|
||||
saveAndBack,
|
||||
historiesDefaultData,
|
||||
isV2Workflow,
|
||||
isPublished,
|
||||
isOpen,
|
||||
onClose,
|
||||
t,
|
||||
initData,
|
||||
loading,
|
||||
isV2Workflow,
|
||||
historiesDefaultData,
|
||||
isSave,
|
||||
onBack,
|
||||
onOpen,
|
||||
onClickSave,
|
||||
setHistoriesDefaultData,
|
||||
appDetail.chatConfig,
|
||||
flowData2StoreDataAndCheck,
|
||||
setWorkflowTestData,
|
||||
onclickPublish
|
||||
isSaveAndPublishModalOpen,
|
||||
onSaveAndPublishModalClose,
|
||||
onSaveAndPublishModalOpen
|
||||
]);
|
||||
|
||||
return Render;
|
||||
|
@@ -31,14 +31,15 @@ const WorkflowEdit = () => {
|
||||
useMount(() => {
|
||||
if (!isV2Workflow) {
|
||||
openConfirm(() => {
|
||||
initData(JSON.parse(JSON.stringify(v1Workflow2V2((appDetail.modules || []) as any))));
|
||||
initData(JSON.parse(JSON.stringify(v1Workflow2V2((appDetail.modules || []) as any))), true);
|
||||
})();
|
||||
} else {
|
||||
initData(
|
||||
cloneDeep({
|
||||
nodes: appDetail.modules || [],
|
||||
edges: appDetail.edges || []
|
||||
})
|
||||
}),
|
||||
true
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@@ -2,14 +2,13 @@ import React, { useCallback, useMemo } from 'react';
|
||||
import { Box, Flex, HStack, useDisclosure } from '@chakra-ui/react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext, TabEnum } from '../context';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { WorkflowContext } from './context';
|
||||
import { compareWorkflow, filterSensitiveNodesData } from '@/web/core/workflow/utils';
|
||||
import { filterSensitiveNodesData } from '@/web/core/workflow/utils';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useCopyData } from '@/web/common/hooks/useCopyData';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
@@ -18,15 +17,21 @@ import { publishStatusStyle } from '../constants';
|
||||
|
||||
const ImportSettings = dynamic(() => import('./Flow/ImportSettings'));
|
||||
|
||||
const AppCard = ({ showSaveStatus }: { showSaveStatus: boolean }) => {
|
||||
const AppCard = ({
|
||||
showSaveStatus,
|
||||
isPublished
|
||||
}: {
|
||||
showSaveStatus: boolean;
|
||||
isPublished: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
const { copyData } = useCopyData();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { appDetail, appLatestVersion, onOpenInfoEdit, onOpenTeamTagModal, onDelApp, currentTab } =
|
||||
const { appDetail, onOpenInfoEdit, onOpenTeamTagModal, onDelApp, currentTab } =
|
||||
useContextSelector(AppContext, (v) => v);
|
||||
const { historiesDefaultData, flowData2StoreDataAndCheck, onSaveWorkflow, isSaving, saveLabel } =
|
||||
const { historiesDefaultData, flowData2StoreDataAndCheck, onSaveWorkflow, isSaving } =
|
||||
useContextSelector(WorkflowContext, (v) => v);
|
||||
|
||||
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
|
||||
@@ -48,27 +53,6 @@ const AppCard = ({ showSaveStatus }: { showSaveStatus: boolean }) => {
|
||||
}
|
||||
}, [appDetail.chatConfig, appT, copyData, flowData2StoreDataAndCheck]);
|
||||
|
||||
const isPublished = (() => {
|
||||
const data = flowData2StoreDataAndCheck(true);
|
||||
if (!appLatestVersion) return true;
|
||||
|
||||
if (data) {
|
||||
return compareWorkflow(
|
||||
{
|
||||
nodes: appLatestVersion.nodes,
|
||||
edges: appLatestVersion.edges,
|
||||
chatConfig: appLatestVersion.chatConfig
|
||||
},
|
||||
{
|
||||
nodes: data.nodes,
|
||||
edges: data.edges,
|
||||
chatConfig: appDetail.chatConfig
|
||||
}
|
||||
);
|
||||
}
|
||||
return false;
|
||||
})();
|
||||
|
||||
const InfoMenu = useCallback(
|
||||
({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
@@ -169,35 +153,25 @@ const AppCard = ({ showSaveStatus }: { showSaveStatus: boolean }) => {
|
||||
</HStack>
|
||||
</InfoMenu>
|
||||
{showSaveStatus && (
|
||||
<MyTooltip label={t('common:core.app.Onclick to save')}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
h={'20px'}
|
||||
cursor={'pointer'}
|
||||
fontSize={'mini'}
|
||||
onClick={onSaveWorkflow}
|
||||
lineHeight={1}
|
||||
<Flex alignItems={'center'} h={'20px'} fontSize={'mini'} lineHeight={1}>
|
||||
<MyTag
|
||||
py={0}
|
||||
px={0}
|
||||
showDot
|
||||
bg={'transparent'}
|
||||
colorSchema={
|
||||
isPublished
|
||||
? publishStatusStyle.published.colorSchema
|
||||
: publishStatusStyle.unPublish.colorSchema
|
||||
}
|
||||
>
|
||||
{isSaving && <MyIcon name={'common/loading'} w={'0.8rem'} mr={0.5} />}
|
||||
<Box color={'myGray.500'}>{saveLabel}</Box>
|
||||
<MyTag
|
||||
py={0}
|
||||
showDot
|
||||
bg={'transparent'}
|
||||
colorSchema={
|
||||
isPublished
|
||||
? publishStatusStyle.published.colorSchema
|
||||
: publishStatusStyle.unPublish.colorSchema
|
||||
}
|
||||
>
|
||||
{t(
|
||||
isPublished
|
||||
? publishStatusStyle.published.text
|
||||
: publishStatusStyle.unPublish.text
|
||||
)}
|
||||
</MyTag>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
{t(
|
||||
isPublished
|
||||
? publishStatusStyle.published.text
|
||||
: publishStatusStyle.unPublish.text
|
||||
)}
|
||||
</MyTag>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -210,10 +184,7 @@ const AppCard = ({ showSaveStatus }: { showSaveStatus: boolean }) => {
|
||||
appDetail.name,
|
||||
isOpenImport,
|
||||
isPublished,
|
||||
isSaving,
|
||||
onCloseImport,
|
||||
onSaveWorkflow,
|
||||
saveLabel,
|
||||
showSaveStatus,
|
||||
t
|
||||
]);
|
||||
|
@@ -93,15 +93,16 @@ const ChatTest = ({
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex
|
||||
py={4}
|
||||
py={2.5}
|
||||
px={5}
|
||||
whiteSpace={'nowrap'}
|
||||
bg={isPlugin ? 'myGray.25' : ''}
|
||||
borderBottom={isPlugin ? '1px solid #F4F4F7' : ''}
|
||||
bg={'myGray.25'}
|
||||
borderBottom={'1px solid #F4F4F7'}
|
||||
>
|
||||
<Box fontSize={'lg'} fontWeight={'bold'} flex={1}>
|
||||
{t('common:core.chat.Debug test')}
|
||||
</Box>
|
||||
<Flex fontSize={'16px'} fontWeight={'bold'} flex={1} alignItems={'center'}>
|
||||
<MyIcon name={'common/paused'} w={'14px'} mr={2.5} />
|
||||
{t('common:core.chat.Run test')}
|
||||
</Flex>
|
||||
<MyTooltip label={t('common:core.chat.Restart')}>
|
||||
<IconButton
|
||||
className="chat"
|
||||
@@ -121,6 +122,7 @@ const ChatTest = ({
|
||||
size={'smSquare'}
|
||||
aria-label={''}
|
||||
onClick={onClose}
|
||||
bg={'none'}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
|
@@ -388,13 +388,12 @@ const RenderList = React.memo(function RenderList({
|
||||
setParentId
|
||||
}: RenderListProps) {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { feConfigs, setLoading } = useSystemStore();
|
||||
|
||||
const { isPc } = useSystem();
|
||||
const isSystemPlugin = type === TemplateTypeEnum.systemPlugin;
|
||||
|
||||
const { x, y, zoom } = useViewport();
|
||||
const { setLoading } = useSystemStore();
|
||||
const { toast } = useToast();
|
||||
const reactFlowWrapper = useContextSelector(WorkflowContext, (v) => v.reactFlowWrapper);
|
||||
const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes);
|
||||
@@ -466,15 +465,16 @@ const RenderList = React.memo(function RenderList({
|
||||
selected: true
|
||||
});
|
||||
|
||||
setNodes((state) =>
|
||||
state
|
||||
setNodes((state) => {
|
||||
const newState = state
|
||||
.map((node) => ({
|
||||
...node,
|
||||
selected: false
|
||||
}))
|
||||
// @ts-ignore
|
||||
.concat(node)
|
||||
);
|
||||
.concat(node);
|
||||
return newState;
|
||||
});
|
||||
},
|
||||
[computedNewNodeName, reactFlowWrapper, setLoading, setNodes, t, toast, x, y, zoom]
|
||||
);
|
||||
|
@@ -30,7 +30,10 @@ const ButtonEdge = (props: EdgeProps) => {
|
||||
|
||||
const onDelConnect = useCallback(
|
||||
(id: string) => {
|
||||
setEdges((state) => state.filter((item) => item.id !== id));
|
||||
setEdges((state) => {
|
||||
const newState = state.filter((item) => item.id !== id);
|
||||
return newState;
|
||||
});
|
||||
},
|
||||
[setEdges]
|
||||
);
|
||||
|
@@ -0,0 +1,149 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { Background, ControlButton, MiniMap, Panel, useReactFlow } from 'reactflow';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../context';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const FlowController = React.memo(function FlowController() {
|
||||
const { fitView, zoomIn, zoomOut } = useReactFlow();
|
||||
const { undo, redo, canRedo, canUndo } = useContextSelector(WorkflowContext, (v) => v);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isMac = !window ? false : window.navigator.userAgent.toLocaleLowerCase().includes('mac');
|
||||
|
||||
useEffect(() => {
|
||||
const keyDownHandler = (event: KeyboardEvent) => {
|
||||
if (event.key === 'z' && (event.ctrlKey || event.metaKey) && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
redo();
|
||||
} else if (event.key === 'z' && (event.ctrlKey || event.metaKey)) {
|
||||
event.preventDefault();
|
||||
undo();
|
||||
} else if ((event.key === '=' || event.key === '+') && (event.ctrlKey || event.metaKey)) {
|
||||
event.preventDefault();
|
||||
zoomIn();
|
||||
} else if (event.key === '-' && (event.ctrlKey || event.metaKey)) {
|
||||
event.preventDefault();
|
||||
zoomOut();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', keyDownHandler);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', keyDownHandler);
|
||||
};
|
||||
}, [undo, redo, zoomIn, zoomOut]);
|
||||
|
||||
const buttonStyle = {
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
padding: '7px'
|
||||
};
|
||||
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<MiniMap
|
||||
style={{
|
||||
height: 98,
|
||||
width: 184,
|
||||
marginBottom: 72,
|
||||
borderRadius: '10px',
|
||||
boxShadow: '0px 0px 1px rgba(19, 51, 107, 0.10), 0px 4px 10px rgba(19, 51, 107, 0.10)'
|
||||
}}
|
||||
pannable
|
||||
/>
|
||||
<Panel
|
||||
position={'bottom-right'}
|
||||
style={{
|
||||
display: 'flex',
|
||||
marginBottom: 24,
|
||||
padding: '5px 8px',
|
||||
background: 'white',
|
||||
borderRadius: '6px',
|
||||
overflow: 'hidden',
|
||||
alignItems: 'center',
|
||||
gap: '2px',
|
||||
boxShadow:
|
||||
'0px 0px 1px 0px rgba(19, 51, 107, 0.20), 0px 12px 16px -4px rgba(19, 51, 107, 0.20)'
|
||||
}}
|
||||
>
|
||||
{/* undo */}
|
||||
<MyTooltip label={isMac ? t('common:common.undo_tip_mac') : t('common:common.undo_tip')}>
|
||||
<ControlButton
|
||||
onClick={undo}
|
||||
style={buttonStyle}
|
||||
className={`${styles.customControlButton}`}
|
||||
disabled={!canUndo}
|
||||
>
|
||||
<MyIcon name={'core/workflow/undo'} />
|
||||
</ControlButton>
|
||||
</MyTooltip>
|
||||
|
||||
{/* redo */}
|
||||
<MyTooltip label={isMac ? t('common:common.redo_tip_mac') : t('common:common.redo_tip')}>
|
||||
<ControlButton
|
||||
onClick={redo}
|
||||
style={buttonStyle}
|
||||
className={`${styles.customControlButton}`}
|
||||
disabled={!canRedo}
|
||||
>
|
||||
<MyIcon name={'core/workflow/redo'} />
|
||||
</ControlButton>
|
||||
</MyTooltip>
|
||||
|
||||
<Box w="1px" h="20px" bg="gray.200" mx={1.5}></Box>
|
||||
|
||||
{/* zoom out */}
|
||||
<MyTooltip
|
||||
label={isMac ? t('common:common.zoomout_tip_mac') : t('common:common.zoomout_tip')}
|
||||
>
|
||||
<ControlButton
|
||||
onClick={() => zoomOut()}
|
||||
style={buttonStyle}
|
||||
className={`${styles.customControlButton}`}
|
||||
>
|
||||
<MyIcon name={'common/subtract'} />
|
||||
</ControlButton>
|
||||
</MyTooltip>
|
||||
|
||||
{/* zoom in */}
|
||||
<MyTooltip
|
||||
label={isMac ? t('common:common.zoomin_tip_mac') : t('common:common.zoomin_tip')}
|
||||
>
|
||||
<ControlButton
|
||||
onClick={() => zoomIn()}
|
||||
style={buttonStyle}
|
||||
className={`${styles.customControlButton}`}
|
||||
>
|
||||
<MyIcon name={'common/addLight'} />
|
||||
</ControlButton>
|
||||
</MyTooltip>
|
||||
|
||||
<Box w="1px" h="20px" bg="gray.200" mx={1.5}></Box>
|
||||
|
||||
{/* fit view */}
|
||||
<MyTooltip label={t('common:common.page_center')}>
|
||||
<ControlButton
|
||||
onClick={() => fitView()}
|
||||
style={buttonStyle}
|
||||
className={`custom-workflow-fix_view ${styles.customControlButton}`}
|
||||
>
|
||||
<MyIcon name={'core/modules/fixview'} />
|
||||
</ControlButton>
|
||||
</MyTooltip>
|
||||
</Panel>
|
||||
<Background />
|
||||
</>
|
||||
);
|
||||
}, [isMac, t, undo, buttonStyle, canUndo, redo, canRedo, zoomOut, zoomIn, fitView]);
|
||||
|
||||
return Render;
|
||||
});
|
||||
|
||||
export default FlowController;
|
@@ -0,0 +1,74 @@
|
||||
import { Box, Button, Input, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
type FormType = {
|
||||
versionName: string;
|
||||
isPublish: boolean | undefined;
|
||||
};
|
||||
|
||||
const SaveAndPublishModal = ({
|
||||
onClose,
|
||||
isLoading,
|
||||
onClickSave
|
||||
}: {
|
||||
onClose: () => void;
|
||||
isLoading: boolean;
|
||||
onClickSave: (data: { isPublish: boolean; versionName: string }) => Promise<void>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { register, handleSubmit } = useForm<FormType>({
|
||||
defaultValues: {
|
||||
versionName: '',
|
||||
isPublish: undefined
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
title={t('common:core.workflow.Save and publish')}
|
||||
iconSrc={'core/workflow/publish'}
|
||||
maxW={'400px'}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box mb={2.5} color={'myGray.900'} fontSize={'14px'} fontWeight={'500'}>
|
||||
{t('common:common.Name')}
|
||||
</Box>
|
||||
<Box mb={3}>
|
||||
<Input
|
||||
autoFocus
|
||||
placeholder={t('app:app.Version name')}
|
||||
bg={'myWhite.600'}
|
||||
{...register('versionName', {
|
||||
required: t('app:app.version_name_tips')
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
<Box fontSize={'14px'}>{t('app:app.version_publish_tips')}</Box>
|
||||
</ModalBody>
|
||||
<ModalFooter gap={3}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
variant={'whiteBase'}
|
||||
>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
onClick={handleSubmit(async (data) => {
|
||||
await onClickSave({ ...data, isPublish: true });
|
||||
onClose();
|
||||
})}
|
||||
>
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SaveAndPublishModal;
|
@@ -0,0 +1,8 @@
|
||||
.customControlButton {
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
max-width: 18px;
|
||||
max-height: 18px;
|
||||
}
|
||||
}
|
@@ -6,7 +6,6 @@ import {
|
||||
addEdge,
|
||||
EdgeChange,
|
||||
Edge,
|
||||
applyNodeChanges,
|
||||
Node,
|
||||
NodePositionChange,
|
||||
XYPosition
|
||||
@@ -15,7 +14,6 @@ import { EDGE_TYPE } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import 'reactflow/dist/style.css';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useKeyboard } from './useKeyboard';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../context';
|
||||
@@ -259,10 +257,6 @@ const computeHelperLines = (
|
||||
export const useWorkflow = () => {
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
const { openConfirm: onOpenConfirmDeleteNode, ConfirmModal: ConfirmDeleteModal } = useConfirm({
|
||||
content: t('common:core.module.Confirm Delete Node'),
|
||||
type: 'delete'
|
||||
});
|
||||
|
||||
const { isDowningCtrl } = useKeyboard();
|
||||
const {
|
||||
@@ -329,7 +323,7 @@ export const useWorkflow = () => {
|
||||
title: t('common:core.workflow.Can not delete node')
|
||||
});
|
||||
} else {
|
||||
return onOpenConfirmDeleteNode(() => {
|
||||
return (() => {
|
||||
onNodesChange(changes);
|
||||
setEdges((state) =>
|
||||
state.filter((edge) => edge.source !== change.id && edge.target !== change.id)
|
||||
@@ -387,6 +381,7 @@ export const useWorkflow = () => {
|
||||
title: t('common:core.module.Can not connect self')
|
||||
});
|
||||
}
|
||||
|
||||
onConnect({
|
||||
connect
|
||||
});
|
||||
@@ -406,7 +401,6 @@ export const useWorkflow = () => {
|
||||
}, [setHoverEdgeId]);
|
||||
|
||||
return {
|
||||
ConfirmDeleteModal,
|
||||
handleNodesChange,
|
||||
handleEdgeChange,
|
||||
onConnectStart,
|
||||
@@ -416,9 +410,7 @@ export const useWorkflow = () => {
|
||||
onEdgeMouseEnter,
|
||||
onEdgeMouseLeave,
|
||||
helperLineHorizontal,
|
||||
setHelperLineHorizontal,
|
||||
helperLineVertical,
|
||||
setHelperLineVertical
|
||||
helperLineVertical
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -1,13 +1,5 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
Controls,
|
||||
ControlButton,
|
||||
MiniMap,
|
||||
NodeProps,
|
||||
ReactFlowProvider,
|
||||
useReactFlow
|
||||
} from 'reactflow';
|
||||
import React from 'react';
|
||||
import ReactFlow, { NodeProps, ReactFlowProvider } from 'reactflow';
|
||||
import { Box, IconButton, useDisclosure } from '@chakra-ui/react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
@@ -19,14 +11,12 @@ import NodeTemplatesModal from './NodeTemplatesModal';
|
||||
|
||||
import 'reactflow/dist/style.css';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { connectionLineStyle, defaultEdgeOptions } from '../constants';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../context';
|
||||
import { useWorkflow } from './hooks/useWorkflow';
|
||||
import { t } from 'i18next';
|
||||
import HelperLines from './components/HelperLines';
|
||||
import FlowController from './components/FlowController';
|
||||
|
||||
const NodeSimple = dynamic(() => import('./nodes/NodeSimple'));
|
||||
const nodeTypes: Record<FlowNodeTypeEnum, any> = {
|
||||
@@ -67,7 +57,6 @@ const Workflow = () => {
|
||||
const { nodes, edges, reactFlowWrapper } = useContextSelector(WorkflowContext, (v) => v);
|
||||
|
||||
const {
|
||||
ConfirmDeleteModal,
|
||||
handleNodesChange,
|
||||
handleEdgeChange,
|
||||
onConnectStart,
|
||||
@@ -142,8 +131,6 @@ const Workflow = () => {
|
||||
<HelperLines horizontal={helperLineHorizontal} vertical={helperLineVertical} />
|
||||
</ReactFlow>
|
||||
</Box>
|
||||
|
||||
<ConfirmDeleteModal />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -157,45 +144,3 @@ const Render = () => {
|
||||
};
|
||||
|
||||
export default React.memo(Render);
|
||||
|
||||
const FlowController = React.memo(function FlowController() {
|
||||
const { fitView } = useReactFlow();
|
||||
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<MiniMap
|
||||
style={{
|
||||
height: 78,
|
||||
width: 126,
|
||||
marginBottom: 35
|
||||
}}
|
||||
pannable
|
||||
/>
|
||||
<Controls
|
||||
position={'bottom-right'}
|
||||
style={{
|
||||
display: 'flex',
|
||||
marginBottom: 5,
|
||||
background: 'white',
|
||||
borderRadius: '6px',
|
||||
overflow: 'hidden',
|
||||
boxShadow:
|
||||
'0px 0px 1px 0px rgba(19, 51, 107, 0.20), 0px 12px 16px -4px rgba(19, 51, 107, 0.20)'
|
||||
}}
|
||||
showInteractive={false}
|
||||
showFitView={false}
|
||||
>
|
||||
<MyTooltip label={t('common:common.page_center')}>
|
||||
<ControlButton className="custom-workflow-fix_view" onClick={() => fitView()}>
|
||||
<MyIcon name={'core/modules/fixview'} w={'14px'} />
|
||||
</ControlButton>
|
||||
</MyTooltip>
|
||||
</Controls>
|
||||
<Background />
|
||||
</>
|
||||
);
|
||||
}, [fitView]);
|
||||
|
||||
return Render;
|
||||
});
|
||||
|
@@ -8,7 +8,6 @@ import TTSSelect from '@/components/core/app/TTSSelect';
|
||||
import WhisperConfig from '@/components/core/app/WhisperConfig';
|
||||
import InputGuideConfig from '@/components/core/app/InputGuideConfig';
|
||||
import { getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { TTSTypeEnum } from '@/web/core/app/constants';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import ScheduledTriggerConfig from '@/components/core/app/ScheduledTriggerConfig';
|
||||
@@ -90,14 +89,12 @@ const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
export default React.memo(NodeUserGuide);
|
||||
|
||||
function WelcomeText({ chatConfig: { welcomeText }, setAppDetail }: ComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [, startTst] = useTransition();
|
||||
|
||||
return (
|
||||
<Box className="nodrag">
|
||||
<WelcomeTextConfig
|
||||
resize={'both'}
|
||||
defaultValue={welcomeText}
|
||||
value={welcomeText}
|
||||
onChange={(e) => {
|
||||
startTst(() => {
|
||||
setAppDetail((state) => ({
|
||||
@@ -164,8 +161,6 @@ function TTSGuide({ chatConfig: { ttsConfig }, setAppDetail }: ComponentProps) {
|
||||
}
|
||||
|
||||
function WhisperGuide({ chatConfig: { whisperConfig, ttsConfig }, setAppDetail }: ComponentProps) {
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
return (
|
||||
<WhisperConfig
|
||||
isOpenAudio={ttsConfig?.type !== TTSTypeEnum.none}
|
||||
@@ -205,7 +200,6 @@ function ScheduledTrigger({
|
||||
|
||||
function QuestionInputGuide({ chatConfig: { chatInputGuide }, setAppDetail }: ComponentProps) {
|
||||
const appId = useContextSelector(WorkflowContext, (v) => v.appId);
|
||||
|
||||
return appId ? (
|
||||
<InputGuideConfig
|
||||
appId={appId}
|
||||
|
@@ -386,7 +386,7 @@ const MenuRender = React.memo(function MenuRender({
|
||||
icon: 'delete',
|
||||
label: t('common:common.Delete'),
|
||||
variant: 'whiteDanger',
|
||||
onClick: onOpenConfirmDeleteNode(() => onDelNode(nodeId))
|
||||
onClick: () => onDelNode(nodeId)
|
||||
}
|
||||
])
|
||||
];
|
||||
@@ -429,7 +429,6 @@ const MenuRender = React.memo(function MenuRender({
|
||||
menuForbid?.copy,
|
||||
menuForbid?.delete,
|
||||
t,
|
||||
onOpenConfirmDeleteNode,
|
||||
ConfirmDeleteModal,
|
||||
DebugInputModal,
|
||||
openDebugNode,
|
||||
|
@@ -2,7 +2,6 @@ import React, { useCallback, useMemo } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
||||
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context';
|
||||
import { computedNodeInputReference } from '@/web/core/workflow/utils';
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { postWorkflowDebug } from '@/web/core/workflow/api';
|
||||
import {
|
||||
checkWorkflowNodeAndConnection,
|
||||
compareSnapshot,
|
||||
storeEdgesRenderEdge,
|
||||
storeNode2FlowNode
|
||||
} from '@/web/core/workflow/utils';
|
||||
@@ -14,7 +15,7 @@ import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/wor
|
||||
import { FlowNodeChangeProps } from '@fastgpt/global/core/workflow/type/fe';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useMemoizedFn, useUpdateEffect } from 'ahooks';
|
||||
import { useLocalStorageState, useMemoizedFn, useUpdateEffect } from 'ahooks';
|
||||
import React, {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
@@ -45,12 +46,19 @@ import { useDisclosure } from '@chakra-ui/react';
|
||||
import { uiWorkflow2StoreWorkflow } from './utils';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { formatTime2HM, formatTime2YMDHMW } from '@fastgpt/global/common/string/time';
|
||||
import { formatTime2YMDHMS, formatTime2YMDHMW } from '@fastgpt/global/common/string/time';
|
||||
import type { InitProps } from '@/pages/app/detail/components/PublishHistoriesSlider';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { cloneDeep, isEqual } from 'lodash';
|
||||
|
||||
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
|
||||
|
||||
export type SnapshotsType = {
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
title: string;
|
||||
chatConfig: AppChatConfigType;
|
||||
isSaved?: boolean;
|
||||
};
|
||||
type WorkflowContextType = {
|
||||
appId?: string;
|
||||
basicNodeTemplates: FlowNodeTemplateType[];
|
||||
@@ -82,6 +90,27 @@ type WorkflowContextType = {
|
||||
hoverEdgeId?: string;
|
||||
setHoverEdgeId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
|
||||
// snapshots
|
||||
saveSnapshot: ({
|
||||
pastNodes,
|
||||
pastEdges,
|
||||
customTitle,
|
||||
chatConfig
|
||||
}: {
|
||||
pastNodes?: Node[];
|
||||
pastEdges?: Edge[];
|
||||
customTitle?: string;
|
||||
chatConfig?: AppChatConfigType;
|
||||
}) => Promise<boolean>;
|
||||
resetSnapshot: (state: SnapshotsType) => void;
|
||||
past: SnapshotsType[];
|
||||
setPast: Dispatch<SetStateAction<SnapshotsType[]>>;
|
||||
future: SnapshotsType[];
|
||||
redo: () => void;
|
||||
undo: () => void;
|
||||
canRedo: boolean;
|
||||
canUndo: boolean;
|
||||
|
||||
// connect
|
||||
connectingEdge?: OnConnectStartParams;
|
||||
setConnectingEdge: React.Dispatch<React.SetStateAction<OnConnectStartParams | undefined>>;
|
||||
@@ -96,19 +125,27 @@ type WorkflowContextType = {
|
||||
toolInputs: FlowNodeInputItemType[];
|
||||
commonInputs: FlowNodeInputItemType[];
|
||||
};
|
||||
initData: (e: {
|
||||
nodes: StoreNodeItemType[];
|
||||
edges: StoreEdgeItemType[];
|
||||
chatConfig?: AppChatConfigType;
|
||||
}) => Promise<void>;
|
||||
initData: (
|
||||
e: {
|
||||
nodes: StoreNodeItemType[];
|
||||
edges: StoreEdgeItemType[];
|
||||
chatConfig?: AppChatConfigType;
|
||||
},
|
||||
isSetInitial?: boolean
|
||||
) => Promise<void>;
|
||||
flowData2StoreDataAndCheck: (hideTip?: boolean) =>
|
||||
| {
|
||||
nodes: StoreNodeItemType[];
|
||||
edges: StoreEdgeItemType[];
|
||||
}
|
||||
| undefined;
|
||||
flowData2StoreData: () =>
|
||||
| {
|
||||
nodes: StoreNodeItemType[];
|
||||
edges: StoreEdgeItemType[];
|
||||
}
|
||||
| undefined;
|
||||
onSaveWorkflow: () => Promise<null | undefined>;
|
||||
saveLabel: string;
|
||||
isSaving: boolean;
|
||||
|
||||
// debug
|
||||
@@ -252,17 +289,40 @@ export const WorkflowContext = createContext<WorkflowContextType>({
|
||||
| undefined {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
flowData2StoreData: function ():
|
||||
| { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }
|
||||
| undefined {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
onSaveWorkflow: function (): Promise<null | undefined> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
saveLabel: '',
|
||||
historiesDefaultData: undefined,
|
||||
setHistoriesDefaultData: function (value: React.SetStateAction<InitProps | undefined>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
getNodeDynamicInputs: function (nodeId: string): FlowNodeInputItemType[] {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
},
|
||||
saveSnapshot: function (): Promise<boolean> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
resetSnapshot: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
past: [],
|
||||
setPast: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
future: [],
|
||||
redo: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
undo: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
canRedo: false,
|
||||
canUndo: false
|
||||
});
|
||||
|
||||
const WorkflowContextProvider = ({
|
||||
@@ -363,8 +423,8 @@ const WorkflowContextProvider = ({
|
||||
|
||||
const onChangeNode = useMemoizedFn((props: FlowNodeChangeProps) => {
|
||||
const { nodeId, type } = props;
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) => {
|
||||
setNodes((nodes) => {
|
||||
const newNodes = nodes.map((node) => {
|
||||
if (node.id !== nodeId) return node;
|
||||
|
||||
const updateObj = cloneDeep(node.data);
|
||||
@@ -429,8 +489,9 @@ const WorkflowContextProvider = ({
|
||||
...node,
|
||||
data: updateObj
|
||||
};
|
||||
})
|
||||
);
|
||||
});
|
||||
return newNodes;
|
||||
});
|
||||
});
|
||||
const getNodeDynamicInputs = useCallback(
|
||||
(nodeId: string) => {
|
||||
@@ -469,18 +530,35 @@ const WorkflowContextProvider = ({
|
||||
};
|
||||
};
|
||||
|
||||
const initData = useMemoizedFn(async (e: Parameters<WorkflowContextType['initData']>[0]) => {
|
||||
setNodes(e.nodes?.map((item) => storeNode2FlowNode({ item })) || []);
|
||||
setEdges(e.edges?.map((item) => storeEdgesRenderEdge({ edge: item })) || []);
|
||||
const initData = useMemoizedFn(
|
||||
async (e: Parameters<WorkflowContextType['initData']>[0], isInit?: boolean) => {
|
||||
setNodes(e.nodes?.map((item) => storeNode2FlowNode({ item })) || []);
|
||||
setEdges(e.edges?.map((item) => storeEdgesRenderEdge({ edge: item })) || []);
|
||||
|
||||
const chatConfig = e.chatConfig;
|
||||
if (chatConfig) {
|
||||
setAppDetail((state) => ({
|
||||
...state,
|
||||
chatConfig
|
||||
}));
|
||||
const chatConfig = e.chatConfig;
|
||||
if (chatConfig) {
|
||||
setAppDetail((state) => ({
|
||||
...state,
|
||||
chatConfig
|
||||
}));
|
||||
}
|
||||
|
||||
// If it is the initial data, save the initial snapshot
|
||||
if (!isInit) return;
|
||||
// If it has been initialized, it will not be saved
|
||||
if (past.length > 0) {
|
||||
resetSnapshot(past[0]);
|
||||
return;
|
||||
}
|
||||
saveSnapshot({
|
||||
pastNodes: e.nodes?.map((item) => storeNode2FlowNode({ item })) || [],
|
||||
pastEdges: e.edges?.map((item) => storeEdgesRenderEdge({ edge: item })) || [],
|
||||
customTitle: t(`app:app.version_initial`),
|
||||
chatConfig: appDetail.chatConfig,
|
||||
isSaved: true
|
||||
});
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
/* ui flow to store data */
|
||||
const flowData2StoreDataAndCheck = useMemoizedFn((hideTip = false) => {
|
||||
@@ -499,8 +577,13 @@ const WorkflowContextProvider = ({
|
||||
}
|
||||
});
|
||||
|
||||
const flowData2StoreData = useMemoizedFn(() => {
|
||||
const storeNodes = uiWorkflow2StoreWorkflow({ nodes, edges });
|
||||
|
||||
return storeNodes;
|
||||
});
|
||||
|
||||
/* save workflow */
|
||||
const [saveLabel, setSaveLabel] = useState(t('common:core.app.Onclick to save'));
|
||||
const { runAsync: onSaveWorkflow, loading: isSaving } = useRequest2(async () => {
|
||||
const { nodes } = await getWorkflowStore();
|
||||
|
||||
@@ -522,11 +605,6 @@ const WorkflowContextProvider = ({
|
||||
//@ts-ignore
|
||||
version: 'v2'
|
||||
});
|
||||
setSaveLabel(
|
||||
t('common:core.app.Saved time', {
|
||||
time: formatTime2HM()
|
||||
})
|
||||
);
|
||||
} catch (error) {}
|
||||
|
||||
return null;
|
||||
@@ -750,6 +828,121 @@ const WorkflowContextProvider = ({
|
||||
onOpenTest();
|
||||
}, [workflowTestData]);
|
||||
|
||||
/* snapshots */
|
||||
const [past, setPast] = useLocalStorageState<SnapshotsType[]>(`${appId}-past`, {
|
||||
defaultValue: []
|
||||
}) as [SnapshotsType[], (value: SetStateAction<SnapshotsType[]>) => void];
|
||||
|
||||
const [future, setFuture] = useLocalStorageState<SnapshotsType[]>(`${appId}-future`, {
|
||||
defaultValue: []
|
||||
}) as [SnapshotsType[], (value: SetStateAction<SnapshotsType[]>) => void];
|
||||
|
||||
const resetSnapshot = useCallback(
|
||||
(state: SnapshotsType) => {
|
||||
setNodes(state.nodes);
|
||||
setEdges(state.edges);
|
||||
setAppDetail((detail) => ({
|
||||
...detail,
|
||||
chatConfig: state.chatConfig
|
||||
}));
|
||||
},
|
||||
[setAppDetail, setEdges, setNodes]
|
||||
);
|
||||
|
||||
const { runAsync: saveSnapshot } = useRequest2(
|
||||
async ({
|
||||
pastNodes,
|
||||
pastEdges,
|
||||
customTitle,
|
||||
chatConfig,
|
||||
isSaved
|
||||
}: {
|
||||
pastNodes?: Node[];
|
||||
pastEdges?: Edge[];
|
||||
customTitle?: string;
|
||||
chatConfig?: AppChatConfigType;
|
||||
isSaved?: boolean;
|
||||
}) => {
|
||||
const pastState = past[0];
|
||||
const currentNodes = pastNodes || nodes;
|
||||
const currentEdges = pastEdges || edges;
|
||||
const currentChatConfig = chatConfig || appDetail.chatConfig;
|
||||
const isPastEqual = compareSnapshot(
|
||||
{
|
||||
nodes: currentNodes,
|
||||
edges: currentEdges,
|
||||
chatConfig: currentChatConfig
|
||||
},
|
||||
{
|
||||
nodes: pastState?.nodes,
|
||||
edges: pastState?.edges,
|
||||
chatConfig: pastState?.chatConfig
|
||||
}
|
||||
);
|
||||
|
||||
if (isPastEqual) return false;
|
||||
|
||||
setPast((past) => [
|
||||
{
|
||||
nodes: currentNodes,
|
||||
edges: currentEdges,
|
||||
title: customTitle || formatTime2YMDHMS(new Date()),
|
||||
chatConfig: currentChatConfig,
|
||||
isSaved
|
||||
},
|
||||
...past.slice(0, 199)
|
||||
]);
|
||||
|
||||
setFuture([]);
|
||||
|
||||
return true;
|
||||
},
|
||||
{
|
||||
debounceWait: 500,
|
||||
refreshDeps: [nodes, edges, appDetail.chatConfig, past]
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!nodes.length) return;
|
||||
saveSnapshot({
|
||||
pastNodes: nodes,
|
||||
pastEdges: edges,
|
||||
customTitle: formatTime2YMDHMS(new Date()),
|
||||
chatConfig: appDetail.chatConfig
|
||||
});
|
||||
}, [nodes, edges, appDetail.chatConfig]);
|
||||
|
||||
const undo = useCallback(() => {
|
||||
if (past[1]) {
|
||||
setFuture((future) => [past[0], ...future]);
|
||||
setPast((past) => past.slice(1));
|
||||
resetSnapshot(past[1]);
|
||||
}
|
||||
}, [past, setFuture, setPast, resetSnapshot]);
|
||||
|
||||
const redo = useCallback(() => {
|
||||
const futureState = future[0];
|
||||
|
||||
if (futureState) {
|
||||
setPast((past) => [future[0], ...past]);
|
||||
setFuture((future) => future.slice(1));
|
||||
resetSnapshot(futureState);
|
||||
}
|
||||
}, [future, setPast, setFuture, resetSnapshot]);
|
||||
|
||||
// remove other app's snapshot
|
||||
useEffect(() => {
|
||||
const keys = Object.keys(localStorage);
|
||||
const snapshotKeys = keys.filter((key) => key.endsWith('-past') || key.endsWith('-future'));
|
||||
snapshotKeys.forEach((key) => {
|
||||
const keyAppId = key.split('-')[0];
|
||||
if (keyAppId !== appId) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
});
|
||||
}, [appId]);
|
||||
|
||||
const value = {
|
||||
appId,
|
||||
reactFlowWrapper,
|
||||
@@ -777,14 +970,25 @@ const WorkflowContextProvider = ({
|
||||
setConnectingEdge,
|
||||
onDelEdge,
|
||||
|
||||
// snapshots
|
||||
past,
|
||||
setPast,
|
||||
future,
|
||||
undo,
|
||||
redo,
|
||||
saveSnapshot,
|
||||
resetSnapshot,
|
||||
canUndo: past.length > 1,
|
||||
canRedo: !!future.length,
|
||||
|
||||
// function
|
||||
onFixView,
|
||||
splitToolInputs,
|
||||
initData,
|
||||
flowData2StoreDataAndCheck,
|
||||
flowData2StoreData,
|
||||
onSaveWorkflow,
|
||||
isSaving,
|
||||
saveLabel,
|
||||
|
||||
// debug
|
||||
workflowDebugData,
|
||||
|
@@ -0,0 +1,330 @@
|
||||
import React, { useState } from 'react';
|
||||
import { getPublishList, updateAppVersion } from '@/web/core/app/api/version';
|
||||
import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
|
||||
import CustomRightDrawer from '@fastgpt/web/components/common/MyDrawer/CustomRightDrawer';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Button, Flex, Input } from '@chakra-ui/react';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from './context';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import { WorkflowContext } from './WorkflowComponents/context';
|
||||
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { storeEdgesRenderEdge, storeNode2FlowNode } from '@/web/core/workflow/utils';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
|
||||
const WorkflowPublishHistoriesSlider = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const [currentTab, setCurrentTab] = useState<'myEdit' | 'teamCloud'>('myEdit');
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomRightDrawer
|
||||
onClose={() => onClose()}
|
||||
title={
|
||||
(
|
||||
<>
|
||||
<LightRowTabs
|
||||
list={[
|
||||
{ label: t('workflow:workflow.My edit'), value: 'myEdit' },
|
||||
{ label: t('workflow:workflow.Team cloud'), value: 'teamCloud' }
|
||||
]}
|
||||
value={currentTab}
|
||||
onChange={setCurrentTab}
|
||||
inlineStyles={{ px: 0.5, pb: 2 }}
|
||||
gap={5}
|
||||
py={0}
|
||||
fontSize={'sm'}
|
||||
/>
|
||||
</>
|
||||
) as any
|
||||
}
|
||||
maxW={'340px'}
|
||||
px={0}
|
||||
showMask={false}
|
||||
top={'60px'}
|
||||
overflow={'unset'}
|
||||
>
|
||||
{currentTab === 'myEdit' ? <MyEdit /> : <TeamCloud />}
|
||||
</CustomRightDrawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(WorkflowPublishHistoriesSlider);
|
||||
|
||||
const MyEdit = () => {
|
||||
const { past, saveSnapshot, resetSnapshot } = useContextSelector(WorkflowContext, (v) => v);
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
|
||||
return (
|
||||
<Flex px={5} flex={'1 0 0'} flexDirection={'column'}>
|
||||
{past.length > 0 && (
|
||||
<Box py={2} px={3}>
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
w={'full'}
|
||||
h={'30px'}
|
||||
onClick={async () => {
|
||||
const initialSnapshot = past[past.length - 1];
|
||||
const res = await saveSnapshot({
|
||||
pastNodes: initialSnapshot.nodes,
|
||||
pastEdges: initialSnapshot.edges,
|
||||
chatConfig: initialSnapshot.chatConfig,
|
||||
customTitle: t(`app:app.version_initial_copy`)
|
||||
});
|
||||
if (!res) return;
|
||||
resetSnapshot(initialSnapshot);
|
||||
toast({
|
||||
title: t('workflow:workflow.Switch_success'),
|
||||
status: 'success'
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('app:app.version_back')}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
<Flex flex={'1 0 0'} flexDirection={'column'} overflow={'auto'}>
|
||||
{past.map((item, index) => {
|
||||
return (
|
||||
<Flex
|
||||
key={index}
|
||||
alignItems={'center'}
|
||||
py={2}
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
fontWeight={500}
|
||||
_hover={{
|
||||
bg: 'primary.50'
|
||||
}}
|
||||
onClick={async () => {
|
||||
const res = await saveSnapshot({
|
||||
pastNodes: item.nodes,
|
||||
pastEdges: item.edges,
|
||||
chatConfig: item.chatConfig,
|
||||
customTitle: `${t('app:app.version_copy')}-${item.title}`
|
||||
});
|
||||
if (!res) return;
|
||||
resetSnapshot(item);
|
||||
toast({
|
||||
title: t('workflow:workflow.Switch_success'),
|
||||
status: 'success'
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
w={'12px'}
|
||||
h={'12px'}
|
||||
borderWidth={'2px'}
|
||||
borderColor={'primary.600'}
|
||||
borderRadius={'50%'}
|
||||
position={'relative'}
|
||||
{...(index !== past.length - 1 && {
|
||||
_after: {
|
||||
content: '""',
|
||||
height: '26px',
|
||||
width: '2px',
|
||||
bgColor: 'myGray.250',
|
||||
position: 'absolute',
|
||||
top: '10px',
|
||||
left: '3px'
|
||||
}
|
||||
})}
|
||||
></Box>
|
||||
<Box
|
||||
ml={3}
|
||||
flex={'1 0 0'}
|
||||
fontSize={'sm'}
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
whiteSpace="nowrap"
|
||||
color={'myGray.900'}
|
||||
>
|
||||
{item.title}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
<Box py={2} textAlign={'center'} color={'myGray.600'} fontSize={'xs'}>
|
||||
{t('common:common.No more data')}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const TeamCloud = () => {
|
||||
const { t } = useTranslation();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
const { saveSnapshot, resetSnapshot } = useContextSelector(WorkflowContext, (v) => v);
|
||||
const { loadAndGetTeamMembers } = useUserStore();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { list, ScrollList, isLoading, fetchData } = useScrollPagination(getPublishList, {
|
||||
itemHeight: 40,
|
||||
overscan: 20,
|
||||
|
||||
pageSize: 30,
|
||||
defaultParams: {
|
||||
appId: appDetail._id
|
||||
}
|
||||
});
|
||||
const { data: members = [] } = useRequest2(loadAndGetTeamMembers, {
|
||||
manual: !feConfigs.isPlus
|
||||
});
|
||||
const [editIndex, setEditIndex] = useState<number | undefined>(undefined);
|
||||
const [hoveredIndex, setHoveredIndex] = useState<number | undefined>(undefined);
|
||||
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const { toast } = useToast();
|
||||
|
||||
return (
|
||||
<ScrollList isLoading={isLoading} flex={'1 0 0'} px={5}>
|
||||
{list.map((data, index) => {
|
||||
const item = data.data;
|
||||
const firstPublishedIndex = list.findIndex((data) => data.data.isPublish);
|
||||
const tmb = members.find((member) => member.tmbId === item.tmbId);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
key={data.index}
|
||||
alignItems={'center'}
|
||||
py={editIndex !== index ? 2 : 1}
|
||||
px={3}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
fontWeight={500}
|
||||
onMouseEnter={() => setHoveredIndex(index)}
|
||||
onMouseLeave={() => setHoveredIndex(undefined)}
|
||||
_hover={{
|
||||
bg: 'primary.50'
|
||||
}}
|
||||
onClick={async () => {
|
||||
const state = {
|
||||
nodes: item.nodes?.map((item) => storeNode2FlowNode({ item })),
|
||||
edges: item.edges?.map((item) => storeEdgesRenderEdge({ edge: item })),
|
||||
title: item.versionName,
|
||||
chatConfig: item.chatConfig
|
||||
};
|
||||
|
||||
const res = await saveSnapshot({
|
||||
pastNodes: state.nodes,
|
||||
pastEdges: state.edges,
|
||||
chatConfig: state.chatConfig,
|
||||
customTitle: `${t('app:app.version_copy')}-${state.title}`
|
||||
});
|
||||
|
||||
if (!res) return;
|
||||
resetSnapshot(state);
|
||||
toast({
|
||||
title: t('workflow:workflow.Switch_success'),
|
||||
status: 'success'
|
||||
});
|
||||
}}
|
||||
>
|
||||
<MyPopover
|
||||
trigger="hover"
|
||||
placement={'bottom-end'}
|
||||
w={'208px'}
|
||||
h={'72px'}
|
||||
Trigger={
|
||||
<Box>
|
||||
<Avatar src={tmb?.avatar} borderRadius={'50%'} w={'24px'} h={'24px'} />
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
{({ onClose }) => (
|
||||
<Flex alignItems={'center'} h={'full'} pl={5} gap={3}>
|
||||
<Box>
|
||||
<Avatar src={tmb?.avatar} borderRadius={'50%'} w={'36px'} h={'36px'} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Box fontSize={'14px'} color={'myGray.900'}>
|
||||
{tmb?.memberName}
|
||||
</Box>
|
||||
<Box fontSize={'12px'} color={'myGray.500'}>
|
||||
{formatTime2YMDHMS(item.time)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</MyPopover>
|
||||
{editIndex !== index ? (
|
||||
<>
|
||||
<Box
|
||||
ml={3}
|
||||
flex={'1 0 0'}
|
||||
fontSize={'sm'}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
whiteSpace="nowrap"
|
||||
>
|
||||
<Box minWidth={0} overflow="hidden" textOverflow="ellipsis" whiteSpace="nowrap">
|
||||
<Box as={'span'} color={'myGray.900'}>
|
||||
{item.versionName || formatTime2YMDHMS(item.time)}
|
||||
</Box>
|
||||
</Box>
|
||||
{item.isPublish && (
|
||||
<Tag
|
||||
ml={3}
|
||||
flexShrink={0}
|
||||
type="borderSolid"
|
||||
colorSchema={index === firstPublishedIndex ? 'green' : 'blue'}
|
||||
>
|
||||
{index === firstPublishedIndex
|
||||
? t('app:app.version_current')
|
||||
: t('app:app.version_past')}
|
||||
</Tag>
|
||||
)}
|
||||
</Box>
|
||||
{hoveredIndex === index && (
|
||||
<MyIcon
|
||||
name="edit"
|
||||
w={'18px'}
|
||||
ml={2}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setEditIndex(index);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<MyBox ml={3} isLoading={isEditing} size={'md'}>
|
||||
<Input
|
||||
autoFocus
|
||||
h={8}
|
||||
defaultValue={item.versionName || formatTime2YMDHMS(item.time)}
|
||||
onBlur={async (e) => {
|
||||
setIsEditing(true);
|
||||
await updateAppVersion({
|
||||
appId: item.appId,
|
||||
versionName: e.target.value,
|
||||
versionId: item._id
|
||||
});
|
||||
await fetchData();
|
||||
setEditIndex(undefined);
|
||||
setIsEditing(false);
|
||||
}}
|
||||
/>
|
||||
</MyBox>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</ScrollList>
|
||||
);
|
||||
};
|
@@ -20,11 +20,11 @@ export const workflowBoxStyles: FlexProps = {
|
||||
export const publishStatusStyle = {
|
||||
unPublish: {
|
||||
colorSchema: 'adora' as any,
|
||||
text: i18nT('common:core.app.not_published')
|
||||
text: i18nT('common:core.app.not_saved')
|
||||
},
|
||||
published: {
|
||||
colorSchema: 'green' as any,
|
||||
text: i18nT('common:core.app.have_publish')
|
||||
text: i18nT('common:core.app.have_saved')
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -6,13 +6,12 @@ import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { AppChatConfigType, AppDetailType } from '@fastgpt/global/core/app/type';
|
||||
import { AppUpdateParams, PostPublishAppProps } from '@/global/core/app/api';
|
||||
import { postPublishApp } from '@/web/core/app/api/version';
|
||||
import { postPublishApp, getAppLatestVersion } from '@/web/core/app/api/version';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useDisclosure } from '@chakra-ui/react';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { getAppLatestVersion } from '@/web/core/app/api/version';
|
||||
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import type { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
|
||||
|
@@ -6,6 +6,7 @@ import type {
|
||||
getLatestVersionQuery,
|
||||
getLatestVersionResponse
|
||||
} from '@/pages/api/core/app/version/latest';
|
||||
import { UpdateAppVersionBody } from '@/pages/api/core/app/version/update';
|
||||
|
||||
export const getAppLatestVersion = (data: getLatestVersionQuery) =>
|
||||
GET<getLatestVersionResponse>('/core/app/version/latest', data);
|
||||
@@ -18,3 +19,6 @@ export const getPublishList = (data: PaginationProps<{ appId: string }>) =>
|
||||
|
||||
export const postRevertVersion = (appId: string, data: PostRevertAppProps) =>
|
||||
POST(`/core/app/version/revert?appId=${appId}`, data);
|
||||
|
||||
export const updateAppVersion = (data: UpdateAppVersionBody) =>
|
||||
POST(`/core/app/version/update`, data);
|
||||
|
@@ -512,3 +512,111 @@ export const compareWorkflow = (workflow1: WorkflowType, workflow2: WorkflowType
|
||||
|
||||
return isEqual(node1, node2);
|
||||
};
|
||||
|
||||
export const compareSnapshot = (
|
||||
snapshot1: {
|
||||
nodes: Node<FlowNodeItemType, string | undefined>[] | undefined;
|
||||
edges: Edge<any>[] | undefined;
|
||||
chatConfig: AppChatConfigType | undefined;
|
||||
},
|
||||
snapshot2: {
|
||||
nodes: Node<FlowNodeItemType, string | undefined>[];
|
||||
edges: Edge<any>[];
|
||||
chatConfig: AppChatConfigType;
|
||||
}
|
||||
) => {
|
||||
const clone1 = cloneDeep(snapshot1);
|
||||
const clone2 = cloneDeep(snapshot2);
|
||||
|
||||
if (!clone1.nodes || !clone2.nodes) return false;
|
||||
const formatEdge = (edges: Edge[] | undefined) => {
|
||||
if (!edges) return [];
|
||||
return edges.map((edge) => ({
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
sourceHandle: edge.sourceHandle,
|
||||
targetHandle: edge.targetHandle,
|
||||
type: edge.type
|
||||
}));
|
||||
};
|
||||
|
||||
if (!isEqual(formatEdge(clone1.edges), formatEdge(clone2.edges))) {
|
||||
console.log('Edge not equal');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
clone1.chatConfig &&
|
||||
clone2.chatConfig &&
|
||||
!isEqual(
|
||||
{
|
||||
welcomeText: clone1.chatConfig?.welcomeText || '',
|
||||
variables: clone1.chatConfig?.variables || [],
|
||||
questionGuide: clone1.chatConfig?.questionGuide || false,
|
||||
ttsConfig: clone1.chatConfig?.ttsConfig || undefined,
|
||||
whisperConfig: clone1.chatConfig?.whisperConfig || undefined,
|
||||
scheduledTriggerConfig: clone1.chatConfig?.scheduledTriggerConfig || undefined,
|
||||
chatInputGuide: clone1.chatConfig?.chatInputGuide || undefined,
|
||||
fileSelectConfig: clone1.chatConfig?.fileSelectConfig || undefined
|
||||
},
|
||||
{
|
||||
welcomeText: clone2.chatConfig?.welcomeText || '',
|
||||
variables: clone2.chatConfig?.variables || [],
|
||||
questionGuide: clone2.chatConfig?.questionGuide || false,
|
||||
ttsConfig: clone2.chatConfig?.ttsConfig || undefined,
|
||||
whisperConfig: clone2.chatConfig?.whisperConfig || undefined,
|
||||
scheduledTriggerConfig: clone2.chatConfig?.scheduledTriggerConfig || undefined,
|
||||
chatInputGuide: clone2.chatConfig?.chatInputGuide || undefined,
|
||||
fileSelectConfig: clone2.chatConfig?.fileSelectConfig || undefined
|
||||
}
|
||||
)
|
||||
) {
|
||||
console.log('chatConfig not equal');
|
||||
return false;
|
||||
}
|
||||
|
||||
const formatNodes = (nodes: Node[]) => {
|
||||
return nodes
|
||||
.filter((node) => {
|
||||
if (!node) return;
|
||||
if (FlowNodeTypeEnum.systemConfig === node.type) return;
|
||||
|
||||
return true;
|
||||
})
|
||||
.map((node) => ({
|
||||
id: node.id,
|
||||
type: node.type,
|
||||
position: node.position,
|
||||
data: {
|
||||
id: node.data.id,
|
||||
flowNodeType: node.data.flowNodeType,
|
||||
inputs: node.data.inputs.map((input: FlowNodeInputItemType) => ({
|
||||
key: input.key,
|
||||
selectedTypeIndex: input.selectedTypeIndex ?? 0,
|
||||
renderTypeLis: input.renderTypeList,
|
||||
valueType: input.valueType,
|
||||
value: input.value ?? undefined
|
||||
})),
|
||||
outputs: node.data.outputs.map((item: FlowNodeOutputItemType) => ({
|
||||
key: item.key,
|
||||
type: item.type,
|
||||
value: item.value ?? undefined
|
||||
})),
|
||||
name: node.data.name,
|
||||
intro: node.data.intro,
|
||||
avatar: node.data.avatar,
|
||||
version: node.data.version
|
||||
}
|
||||
}));
|
||||
};
|
||||
const node1 = formatNodes(clone1.nodes);
|
||||
const node2 = formatNodes(clone2.nodes);
|
||||
|
||||
node1.forEach((node, i) => {
|
||||
if (!isEqual(node, node2[i])) {
|
||||
console.log('node not equal');
|
||||
}
|
||||
});
|
||||
|
||||
return isEqual(node1, node2);
|
||||
};
|
||||
|