Feat: App folder and permission (#1726)
* app folder * feat: app foldere * fix: run app param error * perf: select app ux * perf: folder rerender * fix: ts * fix: parentId * fix: permission * perf: loading ux * perf: per select ux * perf: clb context * perf: query extension tip * fix: ts * perf: app detail per * perf: default per
This commit is contained in:
parent
b20d075d35
commit
bc6864c3dc
6
.vscode/nextapi.code-snippets
vendored
6
.vscode/nextapi.code-snippets
vendored
@ -35,17 +35,19 @@
|
|||||||
"scope": "typescriptreact",
|
"scope": "typescriptreact",
|
||||||
"prefix": "context",
|
"prefix": "context",
|
||||||
"body": [
|
"body": [
|
||||||
"import { ReactNode } from 'react';",
|
"import React, { ReactNode } from 'react';",
|
||||||
"import { createContext } from 'use-context-selector';",
|
"import { createContext } from 'use-context-selector';",
|
||||||
"",
|
"",
|
||||||
"type ContextType = {$1};",
|
"type ContextType = {$1};",
|
||||||
"",
|
"",
|
||||||
"export const Context = createContext<ContextType>({});",
|
"export const Context = createContext<ContextType>({});",
|
||||||
"",
|
"",
|
||||||
"export const ContextProvider = ({ children }: { children: ReactNode }) => {",
|
"const ContextProvider = ({ children }: { children: ReactNode }) => {",
|
||||||
" const contextValue: ContextType = {};",
|
" const contextValue: ContextType = {};",
|
||||||
" return <Context.Provider value={contextValue}>{children}</Context.Provider>;",
|
" return <Context.Provider value={contextValue}>{children}</Context.Provider>;",
|
||||||
"};",
|
"};",
|
||||||
|
"",
|
||||||
|
"export default ContextProvider"
|
||||||
],
|
],
|
||||||
"description": "FastGPT usecontext template"
|
"description": "FastGPT usecontext template"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,9 @@ weight: 821
|
|||||||
## V4.8.4 更新说明
|
## V4.8.4 更新说明
|
||||||
|
|
||||||
1. 新增 - 应用使用新权限系统。
|
1. 新增 - 应用使用新权限系统。
|
||||||
2. 优化 - 文本分割增加连续换行、制表符清除,避免大文本性能问题。
|
2. 新增 - 应用支持文件夹。
|
||||||
3. 修复 - Debug 模式下,相同 source 和 target 内容,导致连线显示异常。
|
3. 优化 - 文本分割增加连续换行、制表符清除,避免大文本性能问题。
|
||||||
4. 修复 - 定时执行初始化错误。
|
4. 修复 - Debug 模式下,相同 source 和 target 内容,导致连线显示异常。
|
||||||
5. 调整组件库全局theme。
|
5. 修复 - 定时执行初始化错误。
|
||||||
|
6. 修复 - 应用调用传参异常。
|
||||||
|
7. 调整组件库全局theme。
|
||||||
14
packages/global/common/parentFolder/type.d.ts
vendored
14
packages/global/common/parentFolder/type.d.ts
vendored
@ -2,3 +2,17 @@ export type ParentTreePathItemType = {
|
|||||||
parentId: string;
|
parentId: string;
|
||||||
parentName: string;
|
parentName: string;
|
||||||
};
|
};
|
||||||
|
export type ParentIdType = string | null | undefined;
|
||||||
|
|
||||||
|
export type GetResourceFolderListProps = {
|
||||||
|
parentId: ParentIdType;
|
||||||
|
};
|
||||||
|
export type GetResourceFolderListItemResponse = {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetResourceListItemResponse = GetResourceFolderListItemResponse & {
|
||||||
|
avatar: string;
|
||||||
|
isFolder: boolean;
|
||||||
|
};
|
||||||
|
|||||||
17
packages/global/common/parentFolder/utils.ts
Normal file
17
packages/global/common/parentFolder/utils.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { ParentIdType } from './type';
|
||||||
|
|
||||||
|
export const parseParentIdInMongo = (parentId: ParentIdType) => {
|
||||||
|
if (parentId === undefined) return {};
|
||||||
|
|
||||||
|
if (parentId === null || parentId === '')
|
||||||
|
return {
|
||||||
|
parentId: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const pattern = /^[0-9a-fA-F]{24}$/;
|
||||||
|
if (pattern.test(parentId))
|
||||||
|
return {
|
||||||
|
parentId
|
||||||
|
};
|
||||||
|
return {};
|
||||||
|
};
|
||||||
@ -1,10 +1,14 @@
|
|||||||
import { AppTTSConfigType, AppWhisperConfigType } from './type';
|
import { AppTTSConfigType, AppWhisperConfigType } from './type';
|
||||||
|
|
||||||
export enum AppTypeEnum {
|
export enum AppTypeEnum {
|
||||||
|
folder = 'folder',
|
||||||
simple = 'simple',
|
simple = 'simple',
|
||||||
advanced = 'advanced'
|
advanced = 'advanced'
|
||||||
}
|
}
|
||||||
export const AppTypeMap = {
|
export const AppTypeMap = {
|
||||||
|
[AppTypeEnum.folder]: {
|
||||||
|
label: 'folder'
|
||||||
|
},
|
||||||
[AppTypeEnum.simple]: {
|
[AppTypeEnum.simple]: {
|
||||||
label: 'simple'
|
label: 'simple'
|
||||||
},
|
},
|
||||||
|
|||||||
9
packages/global/core/app/type.d.ts
vendored
9
packages/global/core/app/type.d.ts
vendored
@ -1,5 +1,4 @@
|
|||||||
import type { FlowNodeTemplateType, StoreNodeItemType } from '../workflow/type';
|
import type { FlowNodeTemplateType, StoreNodeItemType } from '../workflow/type';
|
||||||
|
|
||||||
import { AppTypeEnum } from './constants';
|
import { AppTypeEnum } from './constants';
|
||||||
import { PermissionTypeEnum } from '../../support/permission/constant';
|
import { PermissionTypeEnum } from '../../support/permission/constant';
|
||||||
import { VariableInputEnum } from '../workflow/constants';
|
import { VariableInputEnum } from '../workflow/constants';
|
||||||
@ -9,14 +8,17 @@ import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/use
|
|||||||
import { StoreEdgeItemType } from '../workflow/type/edge';
|
import { StoreEdgeItemType } from '../workflow/type/edge';
|
||||||
import { PermissionValueType } from '../../support/permission/type';
|
import { PermissionValueType } from '../../support/permission/type';
|
||||||
import { AppPermission } from '../../support/permission/app/controller';
|
import { AppPermission } from '../../support/permission/app/controller';
|
||||||
|
import { ParentIdType } from '../../common/parentFolder/type';
|
||||||
|
|
||||||
export type AppSchema = {
|
export type AppSchema = {
|
||||||
_id: string;
|
_id: string;
|
||||||
|
parentId?: ParentIdType;
|
||||||
teamId: string;
|
teamId: string;
|
||||||
tmbId: string;
|
tmbId: string;
|
||||||
name: string;
|
type: AppTypeEnum;
|
||||||
type: `${AppTypeEnum}`;
|
|
||||||
version?: 'v1' | 'v2';
|
version?: 'v1' | 'v2';
|
||||||
|
|
||||||
|
name: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
intro: string;
|
intro: string;
|
||||||
updateTime: number;
|
updateTime: number;
|
||||||
@ -39,6 +41,7 @@ export type AppListItemType = {
|
|||||||
name: string;
|
name: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
intro: string;
|
intro: string;
|
||||||
|
type: AppTypeEnum;
|
||||||
defaultPermission: PermissionValueType;
|
defaultPermission: PermissionValueType;
|
||||||
permission: AppPermission;
|
permission: AppPermission;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export const getDefaultAppForm = (): AppSimpleEditFormType => ({
|
|||||||
limit: 1500,
|
limit: 1500,
|
||||||
searchMode: DatasetSearchModeEnum.embedding,
|
searchMode: DatasetSearchModeEnum.embedding,
|
||||||
usingReRank: false,
|
usingReRank: false,
|
||||||
datasetSearchUsingExtensionQuery: true,
|
datasetSearchUsingExtensionQuery: false,
|
||||||
datasetSearchExtensionBg: ''
|
datasetSearchExtensionBg: ''
|
||||||
},
|
},
|
||||||
selectedTools: [],
|
selectedTools: [],
|
||||||
|
|||||||
@ -35,7 +35,10 @@ export const RunAppModule: FlowNodeTemplateType = {
|
|||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
Input_Template_History,
|
Input_Template_History,
|
||||||
Input_Template_UserChatInput
|
{
|
||||||
|
...Input_Template_UserChatInput,
|
||||||
|
toolDescription: '用户问题'
|
||||||
|
}
|
||||||
],
|
],
|
||||||
outputs: [
|
outputs: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -105,8 +105,8 @@ export type NodeSourceNodeItemType = {
|
|||||||
/* --------------- function type -------------------- */
|
/* --------------- function type -------------------- */
|
||||||
export type SelectAppItemType = {
|
export type SelectAppItemType = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
// name: string;
|
||||||
logo: string;
|
// logo?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* agent */
|
/* agent */
|
||||||
|
|||||||
@ -17,4 +17,4 @@ export const AppPermissionList: PermissionListType = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AppDefaultPermission = NullPermission;
|
export const AppDefaultPermissionVal = NullPermission;
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { PerConstructPros, Permission } from '../controller';
|
import { PerConstructPros, Permission } from '../controller';
|
||||||
import { AppDefaultPermission } from './constant';
|
import { AppDefaultPermissionVal } from './constant';
|
||||||
|
|
||||||
export class AppPermission extends Permission {
|
export class AppPermission extends Permission {
|
||||||
constructor(props?: PerConstructPros) {
|
constructor(props?: PerConstructPros) {
|
||||||
if (!props) {
|
if (!props) {
|
||||||
props = {
|
props = {
|
||||||
per: AppDefaultPermission
|
per: AppDefaultPermissionVal
|
||||||
};
|
};
|
||||||
} else if (!props?.per) {
|
} else if (!props?.per) {
|
||||||
props.per = AppDefaultPermission;
|
props.per = AppDefaultPermissionVal;
|
||||||
}
|
}
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
|
import { Permission } from './controller';
|
||||||
import { PermissionValueType } from './type';
|
import { PermissionValueType } from './type';
|
||||||
|
|
||||||
export type CollaboratorItemType = {
|
export type CollaboratorItemType = {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
tmbId: string;
|
tmbId: string;
|
||||||
permission: PermissionValueType;
|
permission: Permission;
|
||||||
name: string;
|
name: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export const TeamPermissionList = {
|
|||||||
},
|
},
|
||||||
[PermissionKeyEnum.manage]: {
|
[PermissionKeyEnum.manage]: {
|
||||||
...PermissionList[PermissionKeyEnum.manage],
|
...PermissionList[PermissionKeyEnum.manage],
|
||||||
description: '可邀请, 删除成员'
|
description: '可创建资源、邀请、删除成员'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
|||||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
import { getLLMModel } from '../ai/model';
|
import { getLLMModel } from '../ai/model';
|
||||||
import { MongoAppVersion } from './version/schema';
|
import { MongoAppVersion } from './version/schema';
|
||||||
|
import { MongoApp } from './schema';
|
||||||
|
|
||||||
export const beforeUpdateAppFormat = <T extends AppSchema['modules'] | undefined>({
|
export const beforeUpdateAppFormat = <T extends AppSchema['modules'] | undefined>({
|
||||||
nodes
|
nodes
|
||||||
@ -65,3 +66,40 @@ export const getAppLatestVersion = async (appId: string, app?: AppSchema) => {
|
|||||||
chatConfig: app?.chatConfig || {}
|
chatConfig: app?.chatConfig || {}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Get apps */
|
||||||
|
export async function findAppAndAllChildren({
|
||||||
|
teamId,
|
||||||
|
appId,
|
||||||
|
fields
|
||||||
|
}: {
|
||||||
|
teamId: string;
|
||||||
|
appId: string;
|
||||||
|
fields?: string;
|
||||||
|
}): Promise<AppSchema[]> {
|
||||||
|
const find = async (id: string) => {
|
||||||
|
const children = await MongoApp.find(
|
||||||
|
{
|
||||||
|
teamId,
|
||||||
|
parentId: id
|
||||||
|
},
|
||||||
|
fields
|
||||||
|
).lean();
|
||||||
|
|
||||||
|
let apps = children;
|
||||||
|
|
||||||
|
for (const child of children) {
|
||||||
|
const grandChildrenIds = await find(child._id);
|
||||||
|
apps = apps.concat(grandChildrenIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return apps;
|
||||||
|
};
|
||||||
|
const [app, childDatasets] = await Promise.all([MongoApp.findById(appId, fields), find(appId)]);
|
||||||
|
|
||||||
|
if (!app) {
|
||||||
|
return Promise.reject('Dataset not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [app, ...childDatasets];
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { AppTypeMap } from '@fastgpt/global/core/app/constants';
|
import { AppTypeEnum, AppTypeMap } from '@fastgpt/global/core/app/constants';
|
||||||
import { connectionMongo, type Model } from '../../common/mongo';
|
import { connectionMongo, type Model } from '../../common/mongo';
|
||||||
const { Schema, model, models } = connectionMongo;
|
const { Schema, model, models } = connectionMongo;
|
||||||
import type { AppSchema as AppType } from '@fastgpt/global/core/app/type.d';
|
import type { AppSchema as AppType } from '@fastgpt/global/core/app/type.d';
|
||||||
@ -7,7 +7,7 @@ import {
|
|||||||
TeamCollectionName,
|
TeamCollectionName,
|
||||||
TeamMemberCollectionName
|
TeamMemberCollectionName
|
||||||
} from '@fastgpt/global/support/user/team/constant';
|
} from '@fastgpt/global/support/user/team/constant';
|
||||||
import { AppDefaultPermission } from '@fastgpt/global/support/permission/app/constant';
|
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
|
||||||
|
|
||||||
export const AppCollectionName = 'apps';
|
export const AppCollectionName = 'apps';
|
||||||
|
|
||||||
@ -22,6 +22,11 @@ export const chatConfigType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const AppSchema = new Schema({
|
const AppSchema = new Schema({
|
||||||
|
parentId: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: AppCollectionName,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
teamId: {
|
teamId: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: TeamCollectionName,
|
ref: TeamCollectionName,
|
||||||
@ -38,8 +43,8 @@ const AppSchema = new Schema({
|
|||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'advanced',
|
default: AppTypeEnum.advanced,
|
||||||
enum: Object.keys(AppTypeMap)
|
enum: Object.values(AppTypeEnum)
|
||||||
},
|
},
|
||||||
version: {
|
version: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -104,13 +109,13 @@ const AppSchema = new Schema({
|
|||||||
// the default permission of a app
|
// the default permission of a app
|
||||||
defaultPermission: {
|
defaultPermission: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: AppDefaultPermission
|
default: AppDefaultPermissionVal
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
AppSchema.index({ updateTime: -1 });
|
AppSchema.index({ updateTime: -1 });
|
||||||
AppSchema.index({ teamId: 1 });
|
AppSchema.index({ teamId: 1, type: 1 });
|
||||||
AppSchema.index({ scheduledTriggerConfig: 1, intervalNextTime: -1 });
|
AppSchema.index({ scheduledTriggerConfig: 1, intervalNextTime: -1 });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
@ -17,6 +17,8 @@ import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runti
|
|||||||
import { getHistories } from '../utils';
|
import { getHistories } from '../utils';
|
||||||
import { chatValue2RuntimePrompt, runtimePrompt2ChatsValue } from '@fastgpt/global/core/chat/adapt';
|
import { chatValue2RuntimePrompt, runtimePrompt2ChatsValue } from '@fastgpt/global/core/chat/adapt';
|
||||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||||
|
import { authAppByTmbId } from '../../../../support/permission/app/auth';
|
||||||
|
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
|
|
||||||
type Props = ModuleDispatchProps<{
|
type Props = ModuleDispatchProps<{
|
||||||
[NodeInputKeyEnum.userChatInput]: string;
|
[NodeInputKeyEnum.userChatInput]: string;
|
||||||
@ -32,27 +34,25 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
|
|||||||
const {
|
const {
|
||||||
res,
|
res,
|
||||||
teamId,
|
teamId,
|
||||||
|
tmbId,
|
||||||
stream,
|
stream,
|
||||||
detail,
|
detail,
|
||||||
histories,
|
histories,
|
||||||
query,
|
query,
|
||||||
params: { userChatInput, history, app }
|
params: { userChatInput, history, app }
|
||||||
} = props;
|
} = props;
|
||||||
let start = Date.now();
|
|
||||||
|
|
||||||
if (!userChatInput) {
|
if (!userChatInput) {
|
||||||
return Promise.reject('Input is empty');
|
return Promise.reject('Input is empty');
|
||||||
}
|
}
|
||||||
|
|
||||||
const appData = await MongoApp.findOne({
|
const { app: appData } = await authAppByTmbId({
|
||||||
_id: app.id,
|
appId: app.id,
|
||||||
teamId
|
teamId,
|
||||||
|
tmbId,
|
||||||
|
per: ReadPermissionVal
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!appData) {
|
|
||||||
return Promise.reject('App not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res && stream) {
|
if (res && stream) {
|
||||||
responseWrite({
|
responseWrite({
|
||||||
res,
|
res,
|
||||||
@ -64,6 +64,7 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const chatHistories = getHistories(history, histories);
|
const chatHistories = getHistories(history, histories);
|
||||||
|
const { files } = chatValue2RuntimePrompt(query);
|
||||||
|
|
||||||
const { flowResponses, flowUsages, assistantResponses } = await dispatchWorkFlow({
|
const { flowResponses, flowUsages, assistantResponses } = await dispatchWorkFlow({
|
||||||
...props,
|
...props,
|
||||||
@ -71,11 +72,11 @@ export const dispatchAppRequest = async (props: Props): Promise<Response> => {
|
|||||||
runtimeNodes: storeNodes2RuntimeNodes(appData.modules, getDefaultEntryNodeIds(appData.modules)),
|
runtimeNodes: storeNodes2RuntimeNodes(appData.modules, getDefaultEntryNodeIds(appData.modules)),
|
||||||
runtimeEdges: initWorkflowEdgeStatus(appData.edges),
|
runtimeEdges: initWorkflowEdgeStatus(appData.edges),
|
||||||
histories: chatHistories,
|
histories: chatHistories,
|
||||||
query,
|
query: runtimePrompt2ChatsValue({
|
||||||
variables: {
|
files,
|
||||||
...props.variables,
|
text: userChatInput
|
||||||
userChatInput
|
}),
|
||||||
}
|
variables: props.variables
|
||||||
});
|
});
|
||||||
|
|
||||||
const completeMessages = chatHistories.concat([
|
const completeMessages = chatHistories.concat([
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { MongoDataset } from '../../core/dataset/schema';
|
|||||||
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||||
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
|
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
|
||||||
import { SystemErrEnum } from '@fastgpt/global/common/error/code/system';
|
import { SystemErrEnum } from '@fastgpt/global/common/error/code/system';
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
|
||||||
export const checkDatasetLimit = async ({
|
export const checkDatasetLimit = async ({
|
||||||
teamId,
|
teamId,
|
||||||
@ -66,7 +67,7 @@ export const checkTeamDatasetLimit = async (teamId: string) => {
|
|||||||
export const checkTeamAppLimit = async (teamId: string) => {
|
export const checkTeamAppLimit = async (teamId: string) => {
|
||||||
const [{ standardConstants }, appCount] = await Promise.all([
|
const [{ standardConstants }, appCount] = await Promise.all([
|
||||||
getTeamStandPlan({ teamId }),
|
getTeamStandPlan({ teamId }),
|
||||||
MongoApp.count({ teamId })
|
MongoApp.count({ teamId, type: { $in: [AppTypeEnum.advanced, AppTypeEnum.simple] } })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (standardConstants && appCount >= standardConstants.maxAppAmount) {
|
if (standardConstants && appCount >= standardConstants.maxAppAmount) {
|
||||||
|
|||||||
@ -46,6 +46,7 @@ export const iconPaths = {
|
|||||||
'common/refreshLight': () => import('./icons/common/refreshLight.svg'),
|
'common/refreshLight': () => import('./icons/common/refreshLight.svg'),
|
||||||
'common/resultLight': () => import('./icons/common/resultLight.svg'),
|
'common/resultLight': () => import('./icons/common/resultLight.svg'),
|
||||||
'common/retryLight': () => import('./icons/common/retryLight.svg'),
|
'common/retryLight': () => import('./icons/common/retryLight.svg'),
|
||||||
|
'common/rightArrowFill': () => import('./icons/common/rightArrowFill.svg'),
|
||||||
'common/rightArrowLight': () => import('./icons/common/rightArrowLight.svg'),
|
'common/rightArrowLight': () => import('./icons/common/rightArrowLight.svg'),
|
||||||
'common/routePushLight': () => import('./icons/common/routePushLight.svg'),
|
'common/routePushLight': () => import('./icons/common/routePushLight.svg'),
|
||||||
'common/saveFill': () => import('./icons/common/saveFill.svg'),
|
'common/saveFill': () => import('./icons/common/saveFill.svg'),
|
||||||
@ -71,6 +72,7 @@ export const iconPaths = {
|
|||||||
'core/app/publish/lark': () => import('./icons/core/app/publish/lark.svg'),
|
'core/app/publish/lark': () => import('./icons/core/app/publish/lark.svg'),
|
||||||
'core/app/questionGuide': () => import('./icons/core/app/questionGuide.svg'),
|
'core/app/questionGuide': () => import('./icons/core/app/questionGuide.svg'),
|
||||||
'core/app/schedulePlan': () => import('./icons/core/app/schedulePlan.svg'),
|
'core/app/schedulePlan': () => import('./icons/core/app/schedulePlan.svg'),
|
||||||
|
'core/app/simpleBot': () => import('./icons/core/app/simpleBot.svg'),
|
||||||
'core/app/simpleMode/ai': () => import('./icons/core/app/simpleMode/ai.svg'),
|
'core/app/simpleMode/ai': () => import('./icons/core/app/simpleMode/ai.svg'),
|
||||||
'core/app/simpleMode/chat': () => import('./icons/core/app/simpleMode/chat.svg'),
|
'core/app/simpleMode/chat': () => import('./icons/core/app/simpleMode/chat.svg'),
|
||||||
'core/app/simpleMode/dataset': () => import('./icons/core/app/simpleMode/dataset.svg'),
|
'core/app/simpleMode/dataset': () => import('./icons/core/app/simpleMode/dataset.svg'),
|
||||||
|
|||||||
@ -1,52 +1,78 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" width="330" height="330"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style=" background: rgb(255, 255, 255); display: block; shape-rendering: auto;" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
style="shape-rendering: auto; display: block; background: transparent;" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g>
|
||||||
<g transform="rotate(0 50 50)">
|
<g transform="rotate(0 50 50)">
|
||||||
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
|
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite"></animate>
|
<animate repeatCount="indefinite" begin="-0.9166666666666666s" dur="1s" keyTimes="0;1" values="1;0"
|
||||||
</rect>
|
attributeName="opacity"></animate>
|
||||||
</g><g transform="rotate(30 50 50)">
|
|
||||||
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
|
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite"></animate>
|
|
||||||
</rect>
|
|
||||||
</g><g transform="rotate(60 50 50)">
|
|
||||||
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
|
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.75s" repeatCount="indefinite"></animate>
|
|
||||||
</rect>
|
|
||||||
</g><g transform="rotate(90 50 50)">
|
|
||||||
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
|
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.6666666666666666s" repeatCount="indefinite"></animate>
|
|
||||||
</rect>
|
|
||||||
</g><g transform="rotate(120 50 50)">
|
|
||||||
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
|
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5833333333333334s" repeatCount="indefinite"></animate>
|
|
||||||
</rect>
|
|
||||||
</g><g transform="rotate(150 50 50)">
|
|
||||||
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
|
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5s" repeatCount="indefinite"></animate>
|
|
||||||
</rect>
|
|
||||||
</g><g transform="rotate(180 50 50)">
|
|
||||||
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
|
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.4166666666666667s" repeatCount="indefinite"></animate>
|
|
||||||
</rect>
|
|
||||||
</g><g transform="rotate(210 50 50)">
|
|
||||||
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
|
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.3333333333333333s" repeatCount="indefinite"></animate>
|
|
||||||
</rect>
|
|
||||||
</g><g transform="rotate(240 50 50)">
|
|
||||||
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
|
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.25s" repeatCount="indefinite"></animate>
|
|
||||||
</rect>
|
|
||||||
</g><g transform="rotate(270 50 50)">
|
|
||||||
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
|
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.16666666666666666s" repeatCount="indefinite"></animate>
|
|
||||||
</rect>
|
|
||||||
</g><g transform="rotate(300 50 50)">
|
|
||||||
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
|
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.08333333333333333s" repeatCount="indefinite"></animate>
|
|
||||||
</rect>
|
|
||||||
</g><g transform="rotate(330 50 50)">
|
|
||||||
<rect x="47.5" y="24" rx="2.5" ry="6" width="5" height="12" fill="#3370ff">
|
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"></animate>
|
|
||||||
</rect>
|
</rect>
|
||||||
</g>
|
</g>
|
||||||
<!-- [ldio] generated by https://loading.io/ --></svg>
|
<g transform="rotate(30 50 50)">
|
||||||
|
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
|
||||||
|
<animate repeatCount="indefinite" begin="-0.8333333333333334s" dur="1s" keyTimes="0;1" values="1;0"
|
||||||
|
attributeName="opacity"></animate>
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(60 50 50)">
|
||||||
|
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
|
||||||
|
<animate repeatCount="indefinite" begin="-0.75s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity">
|
||||||
|
</animate>
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(90 50 50)">
|
||||||
|
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
|
||||||
|
<animate repeatCount="indefinite" begin="-0.6666666666666666s" dur="1s" keyTimes="0;1" values="1;0"
|
||||||
|
attributeName="opacity"></animate>
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(120 50 50)">
|
||||||
|
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
|
||||||
|
<animate repeatCount="indefinite" begin="-0.5833333333333334s" dur="1s" keyTimes="0;1" values="1;0"
|
||||||
|
attributeName="opacity"></animate>
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(150 50 50)">
|
||||||
|
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
|
||||||
|
<animate repeatCount="indefinite" begin="-0.5s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity">
|
||||||
|
</animate>
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(180 50 50)">
|
||||||
|
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
|
||||||
|
<animate repeatCount="indefinite" begin="-0.4166666666666667s" dur="1s" keyTimes="0;1" values="1;0"
|
||||||
|
attributeName="opacity"></animate>
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(210 50 50)">
|
||||||
|
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
|
||||||
|
<animate repeatCount="indefinite" begin="-0.3333333333333333s" dur="1s" keyTimes="0;1" values="1;0"
|
||||||
|
attributeName="opacity"></animate>
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(240 50 50)">
|
||||||
|
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
|
||||||
|
<animate repeatCount="indefinite" begin="-0.25s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity">
|
||||||
|
</animate>
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(270 50 50)">
|
||||||
|
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
|
||||||
|
<animate repeatCount="indefinite" begin="-0.16666666666666666s" dur="1s" keyTimes="0;1" values="1;0"
|
||||||
|
attributeName="opacity"></animate>
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(300 50 50)">
|
||||||
|
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
|
||||||
|
<animate repeatCount="indefinite" begin="-0.08333333333333333s" dur="1s" keyTimes="0;1" values="1;0"
|
||||||
|
attributeName="opacity"></animate>
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g transform="rotate(330 50 50)">
|
||||||
|
<rect fill="#3370ff" height="12" width="6" ry="6" rx="3" y="24" x="47">
|
||||||
|
<animate repeatCount="indefinite" begin="0s" dur="1s" keyTimes="0;1" values="1;0" attributeName="opacity">
|
||||||
|
</animate>
|
||||||
|
</rect>
|
||||||
|
</g>
|
||||||
|
<g></g>
|
||||||
|
</g><!-- [ldio] generated by https://loading.io -->
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.7 KiB |
@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
|
||||||
|
<path
|
||||||
|
d="M5.56201 4.59992C5.56201 3.41205 6.9982 2.81716 7.83815 3.65711L11.2382 7.0572C11.7589 7.5779 11.7589 8.42212 11.2382 8.94282L7.83815 12.3429C6.9982 13.1829 5.56201 12.588 5.56201 11.4001V4.59992Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 301 B |
@ -0,0 +1,19 @@
|
|||||||
|
<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="32" height="32" rx="5.9541" fill="url(#paint0_linear_5533_28322)" />
|
||||||
|
<path
|
||||||
|
d="M15.1466 6.22388C15.593 5.96615 16.3168 5.96615 16.7632 6.22388L23.5846 10.1622C24.4774 10.6776 24.4774 11.5134 23.5846 12.0288L16.853 15.9153C16.4066 16.1731 15.6828 16.1731 15.2364 15.9153L8.41507 11.977C7.52225 11.4616 7.52225 10.6258 8.41506 10.1104L15.1466 6.22388Z"
|
||||||
|
fill="white" />
|
||||||
|
<path
|
||||||
|
d="M7.18506 14.3387C7.18506 13.617 7.6917 13.3245 8.31667 13.6853L14.6222 17.3258C14.9793 17.532 15.2688 18.0335 15.2688 18.4458V25.1119C15.2688 25.8336 14.7622 26.1261 14.1372 25.7653L7.83169 22.1248C7.47457 21.9186 7.18506 21.4172 7.18506 21.0048V14.3387Z"
|
||||||
|
fill="white" />
|
||||||
|
<path
|
||||||
|
d="M16.7302 18.4962C16.7302 18.0838 17.0197 17.5823 17.3768 17.3762L23.6833 13.7351C24.3083 13.3742 24.8149 13.6668 24.8149 14.3884V21.0537C24.8149 21.4661 24.5254 21.9675 24.1683 22.1737L17.8618 25.8148C17.2368 26.1756 16.7302 25.8831 16.7302 25.1614V18.4962Z"
|
||||||
|
fill="white" />
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear_5533_28322" x1="16" y1="0" x2="4.88889" y2="29.3333"
|
||||||
|
gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#67BFFF" />
|
||||||
|
<stop offset="1" stop-color="#5BA6FF" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -3,7 +3,14 @@ import { Divider, type DividerProps } from '@chakra-ui/react';
|
|||||||
|
|
||||||
const MyDivider = (props: DividerProps) => {
|
const MyDivider = (props: DividerProps) => {
|
||||||
const { h } = props;
|
const { h } = props;
|
||||||
return <Divider my={4} borderBottomWidth={h || '1x'} {...props}></Divider>;
|
return (
|
||||||
|
<Divider
|
||||||
|
my={4}
|
||||||
|
borderBottomWidth={h || '2px'}
|
||||||
|
borderColor={props.color || 'myGray.200'}
|
||||||
|
{...props}
|
||||||
|
></Divider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MyDivider;
|
export default MyDivider;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ const Loading = ({
|
|||||||
text = '',
|
text = '',
|
||||||
bg = 'rgba(255,255,255,0.5)',
|
bg = 'rgba(255,255,255,0.5)',
|
||||||
zIndex = 1000,
|
zIndex = 1000,
|
||||||
size = 'xl'
|
size = 'lg'
|
||||||
}: {
|
}: {
|
||||||
fixed?: boolean;
|
fixed?: boolean;
|
||||||
text?: string;
|
text?: string;
|
||||||
@ -17,7 +17,7 @@ const Loading = ({
|
|||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
position={fixed ? 'fixed' : 'absolute'}
|
position={fixed ? 'fixed' : 'absolute'}
|
||||||
zIndex={zIndex}
|
zIndex={fixed ? zIndex : 10}
|
||||||
bg={bg}
|
bg={bg}
|
||||||
borderRadius={'md'}
|
borderRadius={'md'}
|
||||||
top={0}
|
top={0}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useRef, useState } from 'react';
|
import React, { useMemo, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Menu,
|
Menu,
|
||||||
MenuList,
|
MenuList,
|
||||||
@ -10,21 +10,24 @@ import {
|
|||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import MyIcon from '../Icon';
|
import MyIcon from '../Icon';
|
||||||
import MyDivider from '../MyDivider';
|
import MyDivider from '../MyDivider';
|
||||||
|
import type { IconNameType } from '../Icon/type';
|
||||||
|
|
||||||
type MenuItemType = 'primary' | 'danger';
|
export type MenuItemType = 'primary' | 'danger';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
width?: number | string;
|
width?: number | string;
|
||||||
offset?: [number, number];
|
offset?: [number, number];
|
||||||
Button: React.ReactNode;
|
Button: React.ReactNode;
|
||||||
trigger?: 'hover' | 'click';
|
trigger?: 'hover' | 'click';
|
||||||
|
iconSize?: string;
|
||||||
menuList: {
|
menuList: {
|
||||||
label?: string;
|
label?: string;
|
||||||
children: {
|
children: {
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
type?: MenuItemType;
|
type?: MenuItemType;
|
||||||
icon?: string;
|
icon?: IconNameType | string;
|
||||||
label: string | React.ReactNode;
|
label: string | React.ReactNode;
|
||||||
|
description?: string;
|
||||||
onClick: () => any;
|
onClick: () => any;
|
||||||
}[];
|
}[];
|
||||||
}[];
|
}[];
|
||||||
@ -33,7 +36,8 @@ export type Props = {
|
|||||||
const MyMenu = ({
|
const MyMenu = ({
|
||||||
width = 'auto',
|
width = 'auto',
|
||||||
trigger = 'hover',
|
trigger = 'hover',
|
||||||
offset = [0, 5],
|
offset,
|
||||||
|
iconSize = '1rem',
|
||||||
Button,
|
Button,
|
||||||
menuList
|
menuList
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
@ -45,8 +49,8 @@ const MyMenu = ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
danger: {
|
danger: {
|
||||||
_hover: {
|
|
||||||
color: 'red.600',
|
color: 'red.600',
|
||||||
|
_hover: {
|
||||||
background: 'red.1'
|
background: 'red.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,8 +74,14 @@ const MyMenu = ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const computeOffset = useMemo<[number, number]>(() => {
|
||||||
|
if (offset) return offset;
|
||||||
|
if (typeof width === 'number') return [-width / 2, 5];
|
||||||
|
return [0, 5];
|
||||||
|
}, [offset]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu offset={offset} isOpen={isOpen} autoSelect={false} direction={'ltr'} isLazy>
|
<Menu offset={computeOffset} isOpen={isOpen} autoSelect={false} direction={'ltr'} isLazy>
|
||||||
<Box
|
<Box
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
@ -90,7 +100,8 @@ const MyMenu = ({
|
|||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
position={'relative'}
|
position={'relative'}
|
||||||
onClickCapture={() => {
|
onClickCapture={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
if (trigger === 'click') {
|
if (trigger === 'click') {
|
||||||
setIsOpen(!isOpen);
|
setIsOpen(!isOpen);
|
||||||
}
|
}
|
||||||
@ -124,8 +135,7 @@ const MyMenu = ({
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
key={index}
|
key={index}
|
||||||
{...menuItemStyles}
|
{...menuItemStyles}
|
||||||
{...typeMapStyle[child.type || 'primary']}
|
onClickCapture={(e) => {
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
child.onClick && child.onClick();
|
child.onClick && child.onClick();
|
||||||
@ -133,9 +143,19 @@ const MyMenu = ({
|
|||||||
color={child.isActive ? 'primary.700' : 'myGray.600'}
|
color={child.isActive ? 'primary.700' : 'myGray.600'}
|
||||||
whiteSpace={'pre-wrap'}
|
whiteSpace={'pre-wrap'}
|
||||||
_notLast={{ mb: 0.5 }}
|
_notLast={{ mb: 0.5 }}
|
||||||
|
{...typeMapStyle[child.type || 'primary']}
|
||||||
>
|
>
|
||||||
{!!child.icon && <MyIcon name={child.icon as any} w={'16px'} mr={2} />}
|
{!!child.icon && <MyIcon name={child.icon as any} w={iconSize} mr={3} />}
|
||||||
<Box>{child.label}</Box>
|
<Box>
|
||||||
|
<Box color={child.description ? 'myGray.900' : 'inherit'} fontSize={'sm'}>
|
||||||
|
{child.label}
|
||||||
|
</Box>
|
||||||
|
{child.description && (
|
||||||
|
<Box color={'myGray.500'} fontSize={'mini'}>
|
||||||
|
{child.description}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
92
packages/web/components/common/MyModal/EditFolderModal.tsx
Normal file
92
packages/web/components/common/MyModal/EditFolderModal.tsx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { ModalFooter, ModalBody, Input, Button, Box, Textarea } from '@chakra-ui/react';
|
||||||
|
import MyModal from './index';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { useRequest2 } from '../../../hooks/useRequest';
|
||||||
|
import FormLabel from '../MyBox/FormLabel';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
|
||||||
|
export type EditFolderFormType = {
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
intro?: string;
|
||||||
|
};
|
||||||
|
type CommitType = {
|
||||||
|
name: string;
|
||||||
|
intro?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EditFolderModal = ({
|
||||||
|
onClose,
|
||||||
|
onCreate,
|
||||||
|
onEdit,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
intro
|
||||||
|
}: EditFolderFormType & {
|
||||||
|
onClose: () => void;
|
||||||
|
onCreate: (data: CommitType) => any;
|
||||||
|
onEdit: (data: CommitType & { id: string }) => any;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const isEdit = !!id;
|
||||||
|
const { register, handleSubmit } = useForm<EditFolderFormType>({
|
||||||
|
defaultValues: {
|
||||||
|
name,
|
||||||
|
intro
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const typeMap = useMemo(
|
||||||
|
() =>
|
||||||
|
isEdit
|
||||||
|
? {
|
||||||
|
title: t('dataset.Edit Folder')
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
title: t('dataset.Create Folder')
|
||||||
|
},
|
||||||
|
[isEdit, t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { run: onSave, loading } = useRequest2(
|
||||||
|
({ name = '', intro }: EditFolderFormType) => {
|
||||||
|
if (!name) return;
|
||||||
|
|
||||||
|
if (isEdit) return onEdit({ id, name, intro });
|
||||||
|
return onCreate({ name, intro });
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: (res) => {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MyModal isOpen onClose={onClose} iconSrc="common/folderFill" title={typeMap.title}>
|
||||||
|
<ModalBody>
|
||||||
|
<Box>
|
||||||
|
<FormLabel mb={1}>{t('common.Input name')}</FormLabel>
|
||||||
|
<Input
|
||||||
|
{...register('name', { required: true })}
|
||||||
|
bg={'myGray.50'}
|
||||||
|
autoFocus
|
||||||
|
maxLength={20}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box mt={4}>
|
||||||
|
<FormLabel mb={1}>{t('common.Input folder description')}</FormLabel>
|
||||||
|
<Textarea {...register('intro')} bg={'myGray.50'} maxLength={200} />
|
||||||
|
</Box>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button isLoading={loading} onClick={handleSubmit(onSave)}>
|
||||||
|
{t('common.Confirm')}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</MyModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditFolderModal;
|
||||||
@ -78,7 +78,7 @@ const MyModal = ({
|
|||||||
{title}
|
{title}
|
||||||
<Box flex={1} />
|
<Box flex={1} />
|
||||||
{onClose && (
|
{onClose && (
|
||||||
<ModalCloseButton position={'relative'} fontSize={'sm'} top={0} right={0} />
|
<ModalCloseButton position={'relative'} fontSize={'xs'} top={0} right={0} />
|
||||||
)}
|
)}
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Flex, type FlexProps } from '@chakra-ui/react';
|
import { Flex, type FlexProps } from '@chakra-ui/react';
|
||||||
|
|
||||||
type ColorSchemaType = 'blue' | 'green' | 'red' | 'yellow' | 'gray' | 'purple' | 'adora';
|
type ColorSchemaType = 'white' | 'blue' | 'green' | 'red' | 'yellow' | 'gray' | 'purple' | 'adora';
|
||||||
|
|
||||||
interface Props extends FlexProps {
|
export type TagProps = FlexProps & {
|
||||||
children: React.ReactNode | React.ReactNode[];
|
children: React.ReactNode | React.ReactNode[];
|
||||||
colorSchema?: ColorSchemaType;
|
colorSchema?: ColorSchemaType;
|
||||||
type?: 'fill' | 'borderFill' | 'borderSolid';
|
type?: 'fill' | 'borderFill' | 'borderSolid';
|
||||||
}
|
};
|
||||||
|
|
||||||
const colorMap: Record<
|
const colorMap: Record<
|
||||||
ColorSchemaType,
|
ColorSchemaType,
|
||||||
@ -17,6 +17,11 @@ const colorMap: Record<
|
|||||||
color: string;
|
color: string;
|
||||||
}
|
}
|
||||||
> = {
|
> = {
|
||||||
|
white: {
|
||||||
|
borderColor: 'myGray.400',
|
||||||
|
bg: 'white',
|
||||||
|
color: 'myGray.700'
|
||||||
|
},
|
||||||
yellow: {
|
yellow: {
|
||||||
borderColor: 'yellow.200',
|
borderColor: 'yellow.200',
|
||||||
bg: 'yellow.50',
|
bg: 'yellow.50',
|
||||||
@ -54,7 +59,7 @@ const colorMap: Record<
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const MyTag = ({ children, colorSchema = 'blue', type = 'fill', ...props }: Props) => {
|
const MyTag = ({ children, colorSchema = 'blue', type = 'fill', ...props }: TagProps) => {
|
||||||
const theme = useMemo(() => {
|
const theme = useMemo(() => {
|
||||||
return colorMap[colorSchema];
|
return colorMap[colorSchema];
|
||||||
}, [colorSchema]);
|
}, [colorSchema]);
|
||||||
|
|||||||
@ -72,6 +72,7 @@ export const useConfirm = (props?: {
|
|||||||
}) => {
|
}) => {
|
||||||
const timer = useRef<any>();
|
const timer = useRef<any>();
|
||||||
const [countDownAmount, setCountDownAmount] = useState(countDown);
|
const [countDownAmount, setCountDownAmount] = useState(countDown);
|
||||||
|
const [requesting, setRequesting] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
timer.current = setInterval(() => {
|
timer.current = setInterval(() => {
|
||||||
@ -85,14 +86,15 @@ export const useConfirm = (props?: {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MyModal isOpen={isOpen} iconSrc={iconSrc} title={title} maxW={['90vw', '500px']}>
|
<MyModal isOpen={isOpen} iconSrc={iconSrc} title={title} maxW={['90vw', '400px']}>
|
||||||
<ModalBody pt={5} whiteSpace={'pre-wrap'}>
|
<ModalBody pt={5} whiteSpace={'pre-wrap'} fontSize={'sm'}>
|
||||||
{customContent}
|
{customContent}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
{!hideFooter && (
|
{!hideFooter && (
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
{showCancel && (
|
{showCancel && (
|
||||||
<Button
|
<Button
|
||||||
|
size={'sm'}
|
||||||
variant={'whiteBase'}
|
variant={'whiteBase'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onClose();
|
onClose();
|
||||||
@ -104,13 +106,18 @@ export const useConfirm = (props?: {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
size={'sm'}
|
||||||
bg={bg ? bg : map.bg}
|
bg={bg ? bg : map.bg}
|
||||||
isDisabled={countDownAmount > 0}
|
isDisabled={countDownAmount > 0}
|
||||||
ml={4}
|
ml={4}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading || requesting}
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
|
setRequesting(true);
|
||||||
|
try {
|
||||||
|
typeof confirmCb.current === 'function' && (await confirmCb.current());
|
||||||
onClose();
|
onClose();
|
||||||
typeof confirmCb.current === 'function' && confirmCb.current();
|
} catch (error) {}
|
||||||
|
setRequesting(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{countDownAmount > 0 ? `${countDownAmount}s` : confirmText}
|
{countDownAmount > 0 ? `${countDownAmount}s` : confirmText}
|
||||||
|
|||||||
@ -53,12 +53,13 @@ export const useRequest2 = <TData, TParams extends any[]>(
|
|||||||
plugin?: UseRequestFunProps<TData, TParams>[2]
|
plugin?: UseRequestFunProps<TData, TParams>[2]
|
||||||
) => {
|
) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { errorToast, successToast, ...rest } = options || {};
|
const { errorToast = 'Error', successToast, ...rest } = options || {};
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const res = ahooksUseRequest<TData, TParams>(
|
const res = ahooksUseRequest<TData, TParams>(
|
||||||
server,
|
server,
|
||||||
{
|
{
|
||||||
|
manual: true,
|
||||||
...rest,
|
...rest,
|
||||||
onError: (err, params) => {
|
onError: (err, params) => {
|
||||||
rest?.onError?.(err, params);
|
rest?.onError?.(err, params);
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
"@lexical/selection": "^0.14.5",
|
"@lexical/selection": "^0.14.5",
|
||||||
"@lexical/text": "0.12.6",
|
"@lexical/text": "0.12.6",
|
||||||
"@lexical/utils": "0.12.6",
|
"@lexical/utils": "0.12.6",
|
||||||
|
"react-hook-form": "7.43.1",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@tanstack/react-query": "^4.24.10",
|
"@tanstack/react-query": "^4.24.10",
|
||||||
"ahooks": "^3.7.11",
|
"ahooks": "^3.7.11",
|
||||||
|
|||||||
@ -37,7 +37,7 @@ const Button = defineStyleConfig({
|
|||||||
sizes: {
|
sizes: {
|
||||||
xs: {
|
xs: {
|
||||||
fontSize: 'xs',
|
fontSize: 'xs',
|
||||||
px: '8px',
|
px: '2',
|
||||||
py: '0',
|
py: '0',
|
||||||
h: '24px',
|
h: '24px',
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
@ -54,7 +54,7 @@ const Button = defineStyleConfig({
|
|||||||
},
|
},
|
||||||
sm: {
|
sm: {
|
||||||
fontSize: 'sm',
|
fontSize: 'sm',
|
||||||
px: '14px',
|
px: '3',
|
||||||
py: 0,
|
py: 0,
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
h: '30px',
|
h: '30px',
|
||||||
@ -71,9 +71,9 @@ const Button = defineStyleConfig({
|
|||||||
},
|
},
|
||||||
md: {
|
md: {
|
||||||
fontSize: 'sm',
|
fontSize: 'sm',
|
||||||
px: '20px',
|
px: '4',
|
||||||
py: 0,
|
py: 0,
|
||||||
h: '36px',
|
h: '34px',
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
borderRadius: '8px'
|
borderRadius: '8px'
|
||||||
},
|
},
|
||||||
@ -81,14 +81,14 @@ const Button = defineStyleConfig({
|
|||||||
fontSize: 'sm',
|
fontSize: 'sm',
|
||||||
px: '0',
|
px: '0',
|
||||||
py: 0,
|
py: 0,
|
||||||
h: '36px',
|
h: '34px',
|
||||||
w: '36px',
|
w: '34px',
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
borderRadius: '6px'
|
borderRadius: '6px'
|
||||||
},
|
},
|
||||||
lg: {
|
lg: {
|
||||||
fontSize: 'md',
|
fontSize: 'md',
|
||||||
px: '20px',
|
px: '4',
|
||||||
py: 0,
|
py: 0,
|
||||||
h: '40px',
|
h: '40px',
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
@ -252,7 +252,7 @@ const Button = defineStyleConfig({
|
|||||||
transparentBase: {
|
transparentBase: {
|
||||||
color: 'myGray.800',
|
color: 'myGray.800',
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
bg: 'white',
|
bg: 'transparent',
|
||||||
transition: 'background 0.1s',
|
transition: 'background 0.1s',
|
||||||
_hover: {
|
_hover: {
|
||||||
bg: 'myGray.150'
|
bg: 'myGray.150'
|
||||||
@ -263,6 +263,22 @@ const Button = defineStyleConfig({
|
|||||||
_disabled: {
|
_disabled: {
|
||||||
color: 'myGray.800 !important'
|
color: 'myGray.800 !important'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
transparentDanger: {
|
||||||
|
color: 'myGray.800',
|
||||||
|
fontWeight: '500',
|
||||||
|
bg: 'transparent',
|
||||||
|
transition: 'background 0.1s',
|
||||||
|
_hover: {
|
||||||
|
bg: 'myGray.150',
|
||||||
|
color: 'red.600'
|
||||||
|
},
|
||||||
|
_active: {
|
||||||
|
bg: 'myGray.150'
|
||||||
|
},
|
||||||
|
_disabled: {
|
||||||
|
color: 'myGray.800 !important'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
@ -459,8 +475,8 @@ const Checkbox = checkBoxMultiStyle({
|
|||||||
const Modal = modalMultiStyle({
|
const Modal = modalMultiStyle({
|
||||||
baseStyle: modalPart({
|
baseStyle: modalPart({
|
||||||
body: {
|
body: {
|
||||||
py: [2, 4],
|
py: 4,
|
||||||
px: [5, 7]
|
px: 7
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
pt: 2
|
pt: 2
|
||||||
|
|||||||
@ -16,6 +16,6 @@
|
|||||||
"incremental": true,
|
"incremental": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts", "../**/*.d.ts"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.d.ts", "../**/*.d.ts", "../../projects/app/src/components/common/Modal/EditResourceModal.tsx"],
|
||||||
"exclude": ["node_modules","./components/common/Icon/constants.ts"]
|
"exclude": ["node_modules","./components/common/Icon/constants.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@ -307,6 +307,9 @@ importers:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: 18.3.1
|
specifier: 18.3.1
|
||||||
version: 18.3.1(react@18.3.1)
|
version: 18.3.1(react@18.3.1)
|
||||||
|
react-hook-form:
|
||||||
|
specifier: 7.43.1
|
||||||
|
version: 7.43.1(react@18.3.1)
|
||||||
react-i18next:
|
react-i18next:
|
||||||
specifier: 13.5.0
|
specifier: 13.5.0
|
||||||
version: 13.5.0(i18next@23.10.0)(react-dom@18.3.1)(react@18.3.1)
|
version: 13.5.0(i18next@23.10.0)(react-dom@18.3.1)(react@18.3.1)
|
||||||
|
|||||||
@ -9,13 +9,18 @@
|
|||||||
"Chat Logs Tips": "Logs will record online, shared and API (chatId required) conversation records for this app",
|
"Chat Logs Tips": "Logs will record online, shared and API (chatId required) conversation records for this app",
|
||||||
"Chat logs": "Chat Logs",
|
"Chat logs": "Chat Logs",
|
||||||
"Confirm Del App Tip": "Confirm to delete this app and all its chat records?",
|
"Confirm Del App Tip": "Confirm to delete this app and all its chat records?",
|
||||||
|
"Confirm delete folder tip": "Are you sure to delete this folder? All the following applications and corresponding chat records will be deleted, please confirm!",
|
||||||
"Connection is invalid": "Connection is invalid",
|
"Connection is invalid": "Connection is invalid",
|
||||||
"Connection type is different": "Connection type is different",
|
"Connection type is different": "Connection type is different",
|
||||||
"Copy Module Config": "Copy Config",
|
"Copy Module Config": "Copy Config",
|
||||||
|
"Create bot": "App",
|
||||||
|
"Create one ai app": "Create AI app",
|
||||||
"Dataset Quote Template": "Knowledge Base QA Mode",
|
"Dataset Quote Template": "Knowledge Base QA Mode",
|
||||||
|
"Edit app": "Edit app",
|
||||||
"Export Config Successful": "Config copied, please check for important data",
|
"Export Config Successful": "Config copied, please check for important data",
|
||||||
"Export Configs": "Export Configs",
|
"Export Configs": "Export Configs",
|
||||||
"Feedback Count": "User Feedback",
|
"Feedback Count": "User Feedback",
|
||||||
|
"Go to chat": "To chat",
|
||||||
"Import Configs": "Import Configs",
|
"Import Configs": "Import Configs",
|
||||||
"Import Configs Failed": "Failed to import configs, please ensure configs are valid!",
|
"Import Configs Failed": "Failed to import configs, please ensure configs are valid!",
|
||||||
"Input Field Settings": "Input Field Settings",
|
"Input Field Settings": "Input Field Settings",
|
||||||
@ -25,6 +30,7 @@
|
|||||||
"Logs Time": "Time",
|
"Logs Time": "Time",
|
||||||
"Logs Title": "Title",
|
"Logs Title": "Title",
|
||||||
"Mark Count": "Marked Answer Count",
|
"Mark Count": "Marked Answer Count",
|
||||||
|
"Move app": "Move app",
|
||||||
"My Apps": "My Apps",
|
"My Apps": "My Apps",
|
||||||
"Output Field Settings": "Output Field Settings",
|
"Output Field Settings": "Output Field Settings",
|
||||||
"Paste Config": "Paste Config",
|
"Paste Config": "Paste Config",
|
||||||
|
|||||||
@ -49,6 +49,7 @@
|
|||||||
"Delete Success": "Delete Success",
|
"Delete Success": "Delete Success",
|
||||||
"Delete Tip": "Delete Tip",
|
"Delete Tip": "Delete Tip",
|
||||||
"Delete Warning": "Delete Warning",
|
"Delete Warning": "Delete Warning",
|
||||||
|
"Delete folder": "Delete",
|
||||||
"Detail": "Detail",
|
"Detail": "Detail",
|
||||||
"Documents": "Documents",
|
"Documents": "Documents",
|
||||||
"Done": "Done",
|
"Done": "Done",
|
||||||
@ -64,6 +65,8 @@
|
|||||||
"Import failed": "Import failed",
|
"Import failed": "Import failed",
|
||||||
"Import success": "Import success",
|
"Import success": "Import success",
|
||||||
"Input": "Input",
|
"Input": "Input",
|
||||||
|
"Input folder description": "Folder description",
|
||||||
|
"Input name": "Folder name",
|
||||||
"Intro": "Intro",
|
"Intro": "Intro",
|
||||||
"Invalid Json": "Invalid JSON format, please check.",
|
"Invalid Json": "Invalid JSON format, please check.",
|
||||||
"Last Step": "Last Step",
|
"Last Step": "Last Step",
|
||||||
@ -71,6 +74,7 @@
|
|||||||
"Load Failed": "Load Failed",
|
"Load Failed": "Load Failed",
|
||||||
"Loading": "Loading...",
|
"Loading": "Loading...",
|
||||||
"More settings": "More settings",
|
"More settings": "More settings",
|
||||||
|
"Move": "Move",
|
||||||
"MultipleRowSelect": {
|
"MultipleRowSelect": {
|
||||||
"No data": "No data available"
|
"No data": "No data available"
|
||||||
},
|
},
|
||||||
@ -84,6 +88,7 @@
|
|||||||
"OK": "OK",
|
"OK": "OK",
|
||||||
"Open": "Open",
|
"Open": "Open",
|
||||||
"Opened": "Opened",
|
"Opened": "Opened",
|
||||||
|
"Operation": "Operation",
|
||||||
"Other": "Other",
|
"Other": "Other",
|
||||||
"Output": "Output",
|
"Output": "Output",
|
||||||
"Params": "Params",
|
"Params": "Params",
|
||||||
@ -161,7 +166,9 @@
|
|||||||
"folder": {
|
"folder": {
|
||||||
"Drag Tip": "Drag me",
|
"Drag Tip": "Drag me",
|
||||||
"Move Success": "Move successful",
|
"Move Success": "Move successful",
|
||||||
|
"Move to": "Move to",
|
||||||
"No Folder": "No subdirectories, place here",
|
"No Folder": "No subdirectories, place here",
|
||||||
|
"Open folder": "Open folder",
|
||||||
"Root Path": "Root directory",
|
"Root Path": "Root directory",
|
||||||
"empty": "This directory has nothing selectable~"
|
"empty": "This directory has nothing selectable~"
|
||||||
},
|
},
|
||||||
@ -1212,8 +1219,12 @@
|
|||||||
"Tools": "Tools"
|
"Tools": "Tools"
|
||||||
},
|
},
|
||||||
"permission": {
|
"permission": {
|
||||||
|
"Collaborator": "",
|
||||||
"Default permission": "Default permission",
|
"Default permission": "Default permission",
|
||||||
"Manage": "Manage",
|
"Manage": "Manage",
|
||||||
|
"Not collaborator": "Not collaborator",
|
||||||
|
"Permission": "Permission",
|
||||||
|
"Permission config": "Permission config",
|
||||||
"Private": "Private",
|
"Private": "Private",
|
||||||
"Private Tip": "Only available to oneself",
|
"Private Tip": "Only available to oneself",
|
||||||
"Public": "Team",
|
"Public": "Team",
|
||||||
@ -1292,6 +1303,9 @@
|
|||||||
"Response Quote tips": "Return quote content in the share link, but will not allow users to download the original document"
|
"Response Quote tips": "Return quote content in the share link, but will not allow users to download the original document"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"permission": {
|
||||||
|
"Permission": "Permission"
|
||||||
|
},
|
||||||
"standard": {
|
"standard": {
|
||||||
"AI Bonus Points": "AI points",
|
"AI Bonus Points": "AI points",
|
||||||
"Expired Time": "End time",
|
"Expired Time": "End time",
|
||||||
|
|||||||
@ -8,13 +8,18 @@
|
|||||||
"Chat Logs Tips": "日志会记录该应用的在线、分享和 API(需填写 chatId) 对话记录",
|
"Chat Logs Tips": "日志会记录该应用的在线、分享和 API(需填写 chatId) 对话记录",
|
||||||
"Chat logs": "对话日志",
|
"Chat logs": "对话日志",
|
||||||
"Confirm Del App Tip": "确认删除该应用及其所有聊天记录?",
|
"Confirm Del App Tip": "确认删除该应用及其所有聊天记录?",
|
||||||
|
"Confirm delete folder tip": "确认删除该文件夹?将会删除它下面所有应用及对应的聊天记录,请确认!",
|
||||||
"Connection is invalid": "连接无效",
|
"Connection is invalid": "连接无效",
|
||||||
"Connection type is different": "连接的类型不一致",
|
"Connection type is different": "连接的类型不一致",
|
||||||
"Copy Module Config": "复制配置",
|
"Copy Module Config": "复制配置",
|
||||||
|
"Create bot": "应用",
|
||||||
|
"Create one ai app": "创建一个AI应用",
|
||||||
"Dataset Quote Template": "知识库问答模式",
|
"Dataset Quote Template": "知识库问答模式",
|
||||||
|
"Edit app": "编辑应用",
|
||||||
"Export Config Successful": "已复制配置,自动过滤部分敏感信息,请注意检查是否仍有敏感数据",
|
"Export Config Successful": "已复制配置,自动过滤部分敏感信息,请注意检查是否仍有敏感数据",
|
||||||
"Export Configs": "导出配置",
|
"Export Configs": "导出配置",
|
||||||
"Feedback Count": "用户反馈",
|
"Feedback Count": "用户反馈",
|
||||||
|
"Go to chat": "去对话",
|
||||||
"Import Configs": "导入配置",
|
"Import Configs": "导入配置",
|
||||||
"Import Configs Failed": "导入配置失败,请确保配置正常!",
|
"Import Configs Failed": "导入配置失败,请确保配置正常!",
|
||||||
"Input Field Settings": "输入字段编辑",
|
"Input Field Settings": "输入字段编辑",
|
||||||
@ -24,6 +29,7 @@
|
|||||||
"Logs Time": "时间",
|
"Logs Time": "时间",
|
||||||
"Logs Title": "标题",
|
"Logs Title": "标题",
|
||||||
"Mark Count": "标注答案数量",
|
"Mark Count": "标注答案数量",
|
||||||
|
"Move app": "移动应用",
|
||||||
"My Apps": "我的应用",
|
"My Apps": "我的应用",
|
||||||
"Output Field Settings": "输出字段编辑",
|
"Output Field Settings": "输出字段编辑",
|
||||||
"Paste Config": "粘贴配置",
|
"Paste Config": "粘贴配置",
|
||||||
|
|||||||
@ -49,6 +49,7 @@
|
|||||||
"Delete Success": "删除成功",
|
"Delete Success": "删除成功",
|
||||||
"Delete Tip": "删除提示",
|
"Delete Tip": "删除提示",
|
||||||
"Delete Warning": "删除警告",
|
"Delete Warning": "删除警告",
|
||||||
|
"Delete folder": "删除文件夹",
|
||||||
"Detail": "详情",
|
"Detail": "详情",
|
||||||
"Documents": "文档",
|
"Documents": "文档",
|
||||||
"Done": "完成",
|
"Done": "完成",
|
||||||
@ -64,6 +65,8 @@
|
|||||||
"Import failed": "导入失败",
|
"Import failed": "导入失败",
|
||||||
"Import success": "导入成功",
|
"Import success": "导入成功",
|
||||||
"Input": "输入",
|
"Input": "输入",
|
||||||
|
"Input folder description": "文件夹描述",
|
||||||
|
"Input name": "取个名字",
|
||||||
"Intro": "介绍",
|
"Intro": "介绍",
|
||||||
"Invalid Json": "无效的JSON格式,请注意检查。",
|
"Invalid Json": "无效的JSON格式,请注意检查。",
|
||||||
"Last Step": "上一步",
|
"Last Step": "上一步",
|
||||||
@ -71,6 +74,7 @@
|
|||||||
"Load Failed": "加载失败",
|
"Load Failed": "加载失败",
|
||||||
"Loading": "加载中...",
|
"Loading": "加载中...",
|
||||||
"More settings": "更多设置",
|
"More settings": "更多设置",
|
||||||
|
"Move": "移动",
|
||||||
"MultipleRowSelect": {
|
"MultipleRowSelect": {
|
||||||
"No data": "没有可选值"
|
"No data": "没有可选值"
|
||||||
},
|
},
|
||||||
@ -84,6 +88,7 @@
|
|||||||
"OK": "好的",
|
"OK": "好的",
|
||||||
"Open": "打开",
|
"Open": "打开",
|
||||||
"Opened": "已开启",
|
"Opened": "已开启",
|
||||||
|
"Operation": "操作",
|
||||||
"Other": "其他",
|
"Other": "其他",
|
||||||
"Output": "输出",
|
"Output": "输出",
|
||||||
"Params": "参数",
|
"Params": "参数",
|
||||||
@ -162,7 +167,9 @@
|
|||||||
"folder": {
|
"folder": {
|
||||||
"Drag Tip": "点我可拖动",
|
"Drag Tip": "点我可拖动",
|
||||||
"Move Success": "移动成功",
|
"Move Success": "移动成功",
|
||||||
|
"Move to": "移动到",
|
||||||
"No Folder": "没有子目录了,就放这里吧",
|
"No Folder": "没有子目录了,就放这里吧",
|
||||||
|
"Open folder": "打开文件夹",
|
||||||
"Root Path": "根目录",
|
"Root Path": "根目录",
|
||||||
"empty": "这个目录已经没东西可选了~"
|
"empty": "这个目录已经没东西可选了~"
|
||||||
},
|
},
|
||||||
@ -532,7 +539,6 @@
|
|||||||
"Go Dataset": "前往知识库",
|
"Go Dataset": "前往知识库",
|
||||||
"Intro Placeholder": "这个知识库还没有介绍~",
|
"Intro Placeholder": "这个知识库还没有介绍~",
|
||||||
"Manual collection": "手动数据集",
|
"Manual collection": "手动数据集",
|
||||||
"externalFile": "外部文件库",
|
|
||||||
"My Dataset": "我的知识库",
|
"My Dataset": "我的知识库",
|
||||||
"Name": "知识库名称",
|
"Name": "知识库名称",
|
||||||
"Query extension intro": "开启问题优化功能,可以提高提高连续对话时,知识库搜索的精度。开启该功能后,在进行知识库搜索时,会根据对话记录,利用 AI 补全问题缺失的信息。",
|
"Query extension intro": "开启问题优化功能,可以提高提高连续对话时,知识库搜索的精度。开启该功能后,在进行知识库搜索时,会根据对话记录,利用 AI 补全问题缺失的信息。",
|
||||||
@ -586,7 +592,8 @@
|
|||||||
"success": "开始同步"
|
"success": "开始同步"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"training": {}
|
"training": {
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"Auxiliary Data": "辅助数据",
|
"Auxiliary Data": "辅助数据",
|
||||||
@ -619,6 +626,7 @@
|
|||||||
"unCreateCollection": "无权操作该数据",
|
"unCreateCollection": "无权操作该数据",
|
||||||
"unLinkCollection": "不是网络链接集合"
|
"unLinkCollection": "不是网络链接集合"
|
||||||
},
|
},
|
||||||
|
"externalFile": "外部文件库",
|
||||||
"file": "文件",
|
"file": "文件",
|
||||||
"folder": "目录",
|
"folder": "目录",
|
||||||
"import": {
|
"import": {
|
||||||
@ -1219,8 +1227,12 @@
|
|||||||
"Tools": "工具"
|
"Tools": "工具"
|
||||||
},
|
},
|
||||||
"permission": {
|
"permission": {
|
||||||
|
"Collaborator": "协作者",
|
||||||
"Default permission": "默认权限",
|
"Default permission": "默认权限",
|
||||||
"Manage": "管理",
|
"Manage": "管理",
|
||||||
|
"Not collaborator": "暂无协作者",
|
||||||
|
"Permission": "权限",
|
||||||
|
"Permission config": "权限配置",
|
||||||
"Private": "私有",
|
"Private": "私有",
|
||||||
"Private Tip": "仅自己可用",
|
"Private Tip": "仅自己可用",
|
||||||
"Public": "团队",
|
"Public": "团队",
|
||||||
@ -1299,6 +1311,9 @@
|
|||||||
"Response Quote tips": "在分享链接中返回引用内容,但不会允许用户下载原文档"
|
"Response Quote tips": "在分享链接中返回引用内容,但不会允许用户下载原文档"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"permission": {
|
||||||
|
"Permission": "权限"
|
||||||
|
},
|
||||||
"standard": {
|
"standard": {
|
||||||
"AI Bonus Points": "AI 积分",
|
"AI Bonus Points": "AI 积分",
|
||||||
"Expired Time": "结束时间",
|
"Expired Time": "结束时间",
|
||||||
|
|||||||
@ -1 +1,14 @@
|
|||||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1700746780241" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="67557" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M768 320v128H576V256h128L512 64 320 256h128v192H256V320L64 512l64 64 128 128V576h192v192H320l192 192 64-64 128-128H576V576h192v128l192-192-192-192z" p-id="67558" fill="#13227a"></path></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||||
|
<path
|
||||||
|
d="M10.5898 1.90814C10.4269 1.74528 10.2134 1.66392 9.99998 1.66406C9.78652 1.66392 9.57302 1.74528 9.41016 1.90814C9.40148 1.91682 9.39303 1.92565 9.38481 1.93461L7.64351 3.67591C7.31807 4.00134 7.31807 4.52898 7.64351 4.85442C7.96895 5.17986 8.49659 5.17986 8.82202 4.85442L9.1672 4.50924V7.49791C9.1672 7.95815 9.5403 8.33125 10.0005 8.33125C10.4608 8.33125 10.8339 7.95815 10.8339 7.49791V4.51036L11.1779 4.85442C11.5034 5.17986 12.031 5.17986 12.3564 4.85442C12.6819 4.52898 12.6819 4.00134 12.3564 3.67591L10.6151 1.93461C10.6069 1.92565 10.5985 1.91682 10.5898 1.90814Z"
|
||||||
|
fill="#3370FF" />
|
||||||
|
<path
|
||||||
|
d="M9.1672 15.4907V12.5048C9.1672 12.0446 9.5403 11.6715 10.0005 11.6715C10.4608 11.6715 10.8339 12.0446 10.8339 12.5048V15.4896L11.1779 15.1455C11.5034 14.8201 12.031 14.8201 12.3564 15.1455C12.6819 15.471 12.6819 15.9986 12.3564 16.324L10.6151 18.0653C10.6069 18.0743 10.5985 18.0831 10.5898 18.0918C10.4269 18.2547 10.2134 18.336 9.99998 18.3359C9.78652 18.336 9.57302 18.2547 9.41016 18.0918C9.40148 18.0831 9.39303 18.0743 9.38481 18.0653L7.64351 16.324C7.31807 15.9986 7.31807 15.471 7.64351 15.1455C7.96895 14.8201 8.49659 14.8201 8.82202 15.1455L9.1672 15.4907Z"
|
||||||
|
fill="#3370FF" />
|
||||||
|
<path
|
||||||
|
d="M7.58068 9.1672C8.04092 9.1672 8.41402 9.5403 8.41402 10.0005C8.41402 10.4608 8.04092 10.8339 7.58068 10.8339H4.51036L4.85442 11.1779C5.17986 11.5034 5.17986 12.031 4.85442 12.3564C4.52898 12.6819 4.00134 12.6819 3.67591 12.3564L1.93461 10.6151C1.92565 10.6069 1.91682 10.5985 1.90814 10.5898C1.74527 10.4269 1.66391 10.2134 1.66406 9.99995C1.66393 9.7865 1.74529 9.57301 1.90814 9.41016C1.91682 9.40148 1.92565 9.39303 1.93461 9.38481L3.67591 7.64351C4.00134 7.31807 4.52898 7.31807 4.85442 7.64351C5.17986 7.96895 5.17986 8.49659 4.85442 8.82202L4.50924 9.1672H7.58068Z"
|
||||||
|
fill="#3370FF" />
|
||||||
|
<path
|
||||||
|
d="M16.3241 7.64351L18.0654 9.38481C18.0743 9.39303 18.0832 9.40148 18.0918 9.41016C18.2547 9.57302 18.3361 9.78652 18.3359 9.99998C18.3361 10.2134 18.2547 10.4269 18.0918 10.5898C18.0832 10.5985 18.0743 10.6069 18.0654 10.6151L16.3241 12.3564C15.9986 12.6819 15.471 12.6819 15.1456 12.3564C14.8201 12.031 14.8201 11.5034 15.1456 11.1779L15.4888 10.8347H12.4119C11.9517 10.8347 11.5786 10.4616 11.5786 10.0014C11.5786 9.54114 11.9517 9.16804 12.4119 9.16804H15.4916L15.1456 8.82202C14.8201 8.49659 14.8201 7.96895 15.1456 7.64351C15.471 7.31807 15.9986 7.31807 16.3241 7.64351Z"
|
||||||
|
fill="#3370FF" />
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 524 B After Width: | Height: | Size: 2.5 KiB |
@ -39,6 +39,7 @@ export default function InputGuideBox({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
manual: false,
|
||||||
refreshDeps: [text],
|
refreshDeps: [text],
|
||||||
throttleWait: 300
|
throttleWait: 300
|
||||||
}
|
}
|
||||||
|
|||||||
@ -155,12 +155,13 @@ ${JSON.stringify(questionGuides)}`;
|
|||||||
borderColor={'myGray.200'}
|
borderColor={'myGray.200'}
|
||||||
boxShadow={'1'}
|
boxShadow={'1'}
|
||||||
_hover={{
|
_hover={{
|
||||||
bg: 'auto',
|
bg: 'auto'
|
||||||
color: 'primary.600'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar src={tool.toolAvatar} borderRadius={'md'} w={'14px'} mr={2} />
|
<Avatar src={tool.toolAvatar} borderRadius={'md'} w={'1rem'} mr={2} />
|
||||||
<Box mr={1}>{tool.toolName}</Box>
|
<Box mr={1} fontSize={'sm'}>
|
||||||
|
{tool.toolName}
|
||||||
|
</Box>
|
||||||
{isChatting && !tool.response && (
|
{isChatting && !tool.response && (
|
||||||
<MyIcon name={'common/loading'} w={'14px'} />
|
<MyIcon name={'common/loading'} w={'14px'} />
|
||||||
)}
|
)}
|
||||||
@ -219,7 +220,14 @@ ${toolResponse}`}
|
|||||||
<ChatAvatar src={avatar} type={type} />
|
<ChatAvatar src={avatar} type={type} />
|
||||||
|
|
||||||
{!!chatStatusMap && statusBoxData && isLastChild && (
|
{!!chatStatusMap && statusBoxData && isLastChild && (
|
||||||
<Flex alignItems={'center'} px={3} py={'1.5px'} borderRadius="md" bg={chatStatusMap.bg}>
|
<Flex
|
||||||
|
alignItems={'center'}
|
||||||
|
px={3}
|
||||||
|
py={'1.5px'}
|
||||||
|
borderRadius="md"
|
||||||
|
bg={chatStatusMap.bg}
|
||||||
|
fontSize={'sm'}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
className={styles.statusAnimation}
|
className={styles.statusAnimation}
|
||||||
bg={chatStatusMap.color}
|
bg={chatStatusMap.color}
|
||||||
|
|||||||
116
projects/app/src/components/common/Modal/EditResourceModal.tsx
Normal file
116
projects/app/src/components/common/Modal/EditResourceModal.tsx
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { ModalFooter, ModalBody, Input, Button, Box, Textarea, HStack } from '@chakra-ui/react';
|
||||||
|
import MyModal from '@fastgpt/web/components/common/MyModal/index';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||||
|
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||||
|
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||||
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||||
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
|
|
||||||
|
export type EditResourceInfoFormType = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
avatar?: string;
|
||||||
|
intro?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EditResourceModal = ({
|
||||||
|
onClose,
|
||||||
|
onEdit,
|
||||||
|
title,
|
||||||
|
...defaultForm
|
||||||
|
}: EditResourceInfoFormType & {
|
||||||
|
title: string;
|
||||||
|
onClose: () => void;
|
||||||
|
onEdit: (data: EditResourceInfoFormType) => any;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const { register, watch, setValue, handleSubmit } = useForm<EditResourceInfoFormType>({
|
||||||
|
defaultValues: defaultForm
|
||||||
|
});
|
||||||
|
const avatar = watch('avatar');
|
||||||
|
|
||||||
|
const { runAsync: onSave, loading } = useRequest2(
|
||||||
|
(data: EditResourceInfoFormType) => onEdit(data),
|
||||||
|
{
|
||||||
|
onSuccess: (res) => {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||||
|
fileType: '.jpg,.png',
|
||||||
|
multiple: false
|
||||||
|
});
|
||||||
|
const onSelectFile = useCallback(
|
||||||
|
async (e: File[]) => {
|
||||||
|
const file = e[0];
|
||||||
|
if (!file) return;
|
||||||
|
try {
|
||||||
|
const src = await compressImgFileAndUpload({
|
||||||
|
type: MongoImageTypeEnum.appAvatar,
|
||||||
|
file,
|
||||||
|
maxW: 300,
|
||||||
|
maxH: 300
|
||||||
|
});
|
||||||
|
setValue('avatar', src);
|
||||||
|
} catch (err: any) {
|
||||||
|
toast({
|
||||||
|
title: getErrText(err, t('common.error.Select avatar failed')),
|
||||||
|
status: 'warning'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setValue, t, toast]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MyModal isOpen onClose={onClose} iconSrc={avatar} title={title}>
|
||||||
|
<ModalBody>
|
||||||
|
<Box>
|
||||||
|
<FormLabel mb={1}>{t('core.app.Name and avatar')}</FormLabel>
|
||||||
|
<HStack spacing={4}>
|
||||||
|
<MyTooltip label={t('common.Set Avatar')}>
|
||||||
|
<Avatar
|
||||||
|
flexShrink={0}
|
||||||
|
src={avatar}
|
||||||
|
w={['28px', '32px']}
|
||||||
|
h={['28px', '32px']}
|
||||||
|
cursor={'pointer'}
|
||||||
|
borderRadius={'md'}
|
||||||
|
onClick={onOpenSelectFile}
|
||||||
|
/>
|
||||||
|
</MyTooltip>
|
||||||
|
<Input
|
||||||
|
{...register('name', { required: true })}
|
||||||
|
bg={'myGray.50'}
|
||||||
|
autoFocus
|
||||||
|
maxLength={20}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
</Box>
|
||||||
|
<Box mt={4}>
|
||||||
|
<FormLabel mb={1}>{t('common.Intro')}</FormLabel>
|
||||||
|
<Textarea {...register('intro')} bg={'myGray.50'} maxLength={200} />
|
||||||
|
</Box>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button isLoading={loading} onClick={handleSubmit(onSave)}>
|
||||||
|
{t('common.Confirm')}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
|
||||||
|
<File onSelect={onSelectFile} />
|
||||||
|
</MyModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditResourceModal;
|
||||||
@ -1,4 +1,3 @@
|
|||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
|
||||||
import { Box, Flex } from '@chakra-ui/react';
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
|
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
@ -37,16 +36,19 @@ const ParentPaths = (props: {
|
|||||||
{concatPaths.map((item, i) => (
|
{concatPaths.map((item, i) => (
|
||||||
<Flex key={item.parentId || i} alignItems={'center'}>
|
<Flex key={item.parentId || i} alignItems={'center'}>
|
||||||
<Box
|
<Box
|
||||||
fontSize={['sm', fontSize || 'md']}
|
fontSize={['sm', fontSize || 'sm']}
|
||||||
py={1}
|
py={0.5}
|
||||||
px={[1, 2]}
|
px={1.5}
|
||||||
borderRadius={'md'}
|
borderRadius={'md'}
|
||||||
{...(i === concatPaths.length - 1
|
{...(i === concatPaths.length - 1
|
||||||
? {
|
? {
|
||||||
cursor: 'default'
|
cursor: 'default',
|
||||||
|
color: 'myGray.700',
|
||||||
|
fontWeight: 'bold'
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
color: 'myGray.600',
|
||||||
_hover: {
|
_hover: {
|
||||||
bg: 'myGray.100'
|
bg: 'myGray.100'
|
||||||
},
|
},
|
||||||
@ -58,7 +60,9 @@ const ParentPaths = (props: {
|
|||||||
{item.parentName}
|
{item.parentName}
|
||||||
</Box>
|
</Box>
|
||||||
{i !== concatPaths.length - 1 && (
|
{i !== concatPaths.length - 1 && (
|
||||||
<MyIcon name={'common/rightArrowLight'} color={'myGray.500'} w={'14px'} />
|
<Box mx={1.5} color={'myGray.500'}>
|
||||||
|
/
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
))}
|
))}
|
||||||
|
|||||||
184
projects/app/src/components/common/folder/MoveModal.tsx
Normal file
184
projects/app/src/components/common/folder/MoveModal.tsx
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { Box, Button, Flex, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
GetResourceFolderListProps,
|
||||||
|
GetResourceFolderListItemResponse,
|
||||||
|
ParentIdType
|
||||||
|
} from '@fastgpt/global/common/parentFolder/type';
|
||||||
|
import { useMemoizedFn, useMount } from 'ahooks';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import { FolderIcon } from '@fastgpt/global/common/file/image/constants';
|
||||||
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
|
||||||
|
type FolderItemType = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
open: boolean;
|
||||||
|
children?: FolderItemType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const rootId = 'root';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
moveResourceId: string;
|
||||||
|
title: string;
|
||||||
|
server: (e: GetResourceFolderListProps) => Promise<GetResourceFolderListItemResponse[]>;
|
||||||
|
onConfirm: (id: ParentIdType) => Promise<any>;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const MoveModal = ({ moveResourceId, title, server, onConfirm, onClose }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [selectedId, setSelectedId] = React.useState<string>();
|
||||||
|
const [requestingIdList, setRequestingIdList] = useState<ParentIdType[]>([]);
|
||||||
|
const [folderList, setFolderList] = useState<FolderItemType[]>([]);
|
||||||
|
|
||||||
|
const { runAsync: requestServer } = useRequest2((e: GetResourceFolderListProps) => {
|
||||||
|
if (requestingIdList.includes(e.parentId)) return Promise.reject(null);
|
||||||
|
|
||||||
|
setRequestingIdList((state) => [...state, e.parentId]);
|
||||||
|
return server(e).finally(() =>
|
||||||
|
setRequestingIdList((state) => state.filter((id) => id !== e.parentId))
|
||||||
|
);
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
useMount(async () => {
|
||||||
|
const data = await requestServer({ parentId: null });
|
||||||
|
setFolderList([
|
||||||
|
{
|
||||||
|
id: rootId,
|
||||||
|
name: t('common.folder.Root Path'),
|
||||||
|
open: true,
|
||||||
|
children: data.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
open: false
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
const RenderList = useMemoizedFn(
|
||||||
|
({ list, index = 0 }: { list: FolderItemType[]; index?: number }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{list
|
||||||
|
// can not move to itself
|
||||||
|
.filter((item) => moveResourceId !== item.id)
|
||||||
|
.map((item) => (
|
||||||
|
<Box key={item.id} _notLast={{ mb: 0.5 }} userSelect={'none'}>
|
||||||
|
<Flex
|
||||||
|
alignItems={'center'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
py={1}
|
||||||
|
pl={index === 0 ? '0.5rem' : `${1.75 * (index - 1) + 0.5}rem`}
|
||||||
|
pr={2}
|
||||||
|
borderRadius={'md'}
|
||||||
|
_hover={{
|
||||||
|
bg: 'myGray.100'
|
||||||
|
}}
|
||||||
|
{...(item.id === selectedId
|
||||||
|
? {
|
||||||
|
bg: 'primary.50 !important',
|
||||||
|
onClick: () => setSelectedId(undefined)
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
onClick: () => setSelectedId(item.id)
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{index !== 0 && (
|
||||||
|
<Flex
|
||||||
|
alignItems={'center'}
|
||||||
|
justifyContent={'center'}
|
||||||
|
visibility={!item.children || item.children.length > 0 ? 'visible' : 'hidden'}
|
||||||
|
w={'1.25rem'}
|
||||||
|
h={'1.25rem'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
borderRadius={'xs'}
|
||||||
|
_hover={{
|
||||||
|
bg: 'rgba(31, 35, 41, 0.08)'
|
||||||
|
}}
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (requestingIdList.includes(item.id)) return;
|
||||||
|
|
||||||
|
if (!item.children) {
|
||||||
|
const data = await requestServer({ parentId: item.id });
|
||||||
|
item.children = data.map((item) => ({
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
open: false
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
item.open = !item.open;
|
||||||
|
setFolderList([...folderList]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MyIcon
|
||||||
|
name={
|
||||||
|
requestingIdList.includes(item.id)
|
||||||
|
? 'common/loading'
|
||||||
|
: 'common/rightArrowFill'
|
||||||
|
}
|
||||||
|
w={'1.25rem'}
|
||||||
|
color={'myGray.500'}
|
||||||
|
transform={item.open ? 'rotate(90deg)' : 'none'}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
<MyIcon ml={index !== 0 ? '0.5rem' : 0} name={FolderIcon} w={'1.25rem'} />
|
||||||
|
<Box fontSize={'sm'} ml={2}>
|
||||||
|
{item.name}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
{item.children && item.open && (
|
||||||
|
<Box mt={0.5}>
|
||||||
|
<RenderList list={item.children} index={index + 1} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { runAsync: onConfirmSelect, loading: confirming } = useRequest2(
|
||||||
|
() => {
|
||||||
|
if (selectedId) {
|
||||||
|
return onConfirm(selectedId === rootId ? null : selectedId);
|
||||||
|
}
|
||||||
|
return Promise.reject('');
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
successToast: t('common.folder.Move Success')
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MyModal
|
||||||
|
isLoading={folderList.length === 0}
|
||||||
|
iconSrc="/imgs/modal/move.svg"
|
||||||
|
isOpen
|
||||||
|
w={'30rem'}
|
||||||
|
title={title}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
<ModalBody flex={'1 0 0'} overflow={'auto'} minH={'400px'}>
|
||||||
|
<RenderList list={folderList} />
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button isLoading={confirming} isDisabled={!selectedId} onClick={onConfirmSelect}>
|
||||||
|
{t('common.Confirm')}
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</MyModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MoveModal;
|
||||||
68
projects/app/src/components/common/folder/Path.tsx
Normal file
68
projects/app/src/components/common/folder/Path.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
|
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
|
const FolderPath = (props: {
|
||||||
|
paths: ParentTreePathItemType[];
|
||||||
|
rootName?: string;
|
||||||
|
FirstPathDom?: React.ReactNode;
|
||||||
|
onClick: (parentId: string) => void;
|
||||||
|
fontSize?: string;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { paths, rootName = t('common.folder.Root Path'), FirstPathDom, onClick, fontSize } = props;
|
||||||
|
|
||||||
|
const concatPaths = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
parentId: '',
|
||||||
|
parentName: rootName
|
||||||
|
},
|
||||||
|
...paths
|
||||||
|
],
|
||||||
|
[rootName, paths]
|
||||||
|
);
|
||||||
|
|
||||||
|
return paths.length === 0 && !!FirstPathDom ? (
|
||||||
|
<>{FirstPathDom}</>
|
||||||
|
) : (
|
||||||
|
<Flex flex={1} ml={-1.5}>
|
||||||
|
{concatPaths.map((item, i) => (
|
||||||
|
<Flex key={item.parentId || i} alignItems={'center'}>
|
||||||
|
<Box
|
||||||
|
fontSize={['sm', fontSize || 'sm']}
|
||||||
|
py={0.5}
|
||||||
|
px={1.5}
|
||||||
|
borderRadius={'md'}
|
||||||
|
{...(i === concatPaths.length - 1
|
||||||
|
? {
|
||||||
|
cursor: 'default',
|
||||||
|
color: 'myGray.700',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: 'myGray.600',
|
||||||
|
_hover: {
|
||||||
|
bg: 'myGray.100'
|
||||||
|
},
|
||||||
|
onClick: () => {
|
||||||
|
onClick(item.parentId);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{item.parentName}
|
||||||
|
</Box>
|
||||||
|
{i !== concatPaths.length - 1 && (
|
||||||
|
<Box mx={1.5} color={'myGray.500'}>
|
||||||
|
/
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(FolderPath);
|
||||||
140
projects/app/src/components/common/folder/SelectOneResource.tsx
Normal file
140
projects/app/src/components/common/folder/SelectOneResource.tsx
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
GetResourceFolderListProps,
|
||||||
|
GetResourceListItemResponse,
|
||||||
|
ParentIdType
|
||||||
|
} from '@fastgpt/global/common/parentFolder/type';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import Loading from '@fastgpt/web/components/common/MyLoading';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
import { useMemoizedFn } from 'ahooks';
|
||||||
|
|
||||||
|
type ResourceItemType = GetResourceListItemResponse & {
|
||||||
|
open: boolean;
|
||||||
|
children?: ResourceItemType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const SelectOneResource = ({
|
||||||
|
server,
|
||||||
|
value,
|
||||||
|
onSelect
|
||||||
|
}: {
|
||||||
|
server: (e: GetResourceFolderListProps) => Promise<GetResourceListItemResponse[]>;
|
||||||
|
value?: ParentIdType;
|
||||||
|
onSelect: (e?: string) => any;
|
||||||
|
}) => {
|
||||||
|
const [dataList, setDataList] = useState<ResourceItemType[]>([]);
|
||||||
|
const [requestingIdList, setRequestingIdList] = useState<ParentIdType[]>([]);
|
||||||
|
|
||||||
|
const { runAsync: requestServer } = useRequest2((e: GetResourceFolderListProps) => {
|
||||||
|
if (requestingIdList.includes(e.parentId)) return Promise.reject(null);
|
||||||
|
|
||||||
|
setRequestingIdList((state) => [...state, e.parentId]);
|
||||||
|
return server(e).finally(() =>
|
||||||
|
setRequestingIdList((state) => state.filter((id) => id !== e.parentId))
|
||||||
|
);
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const { loading } = useRequest2(() => requestServer({ parentId: null }), {
|
||||||
|
manual: false,
|
||||||
|
onSuccess: (data) => {
|
||||||
|
setDataList(
|
||||||
|
data.map((item) => ({
|
||||||
|
...item,
|
||||||
|
open: false
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Render = useMemoizedFn(
|
||||||
|
({ list, index = 0 }: { list: ResourceItemType[]; index?: number }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{list.map((item) => (
|
||||||
|
<Box key={item.id} _notLast={{ mb: 0.5 }} userSelect={'none'}>
|
||||||
|
<Flex
|
||||||
|
alignItems={'center'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
py={1}
|
||||||
|
pl={`${1.25 * index + 0.5}rem`}
|
||||||
|
pr={2}
|
||||||
|
borderRadius={'md'}
|
||||||
|
_hover={{
|
||||||
|
bg: 'myGray.100'
|
||||||
|
}}
|
||||||
|
{...(item.id === value
|
||||||
|
? {
|
||||||
|
bg: 'primary.50 !important',
|
||||||
|
onClick: () => onSelect(undefined)
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
onClick: async () => {
|
||||||
|
// folder => open(request children) or close
|
||||||
|
if (item.isFolder) {
|
||||||
|
if (!item.children) {
|
||||||
|
const data = await requestServer({ parentId: item.id });
|
||||||
|
item.children = data.map((item) => ({
|
||||||
|
...item,
|
||||||
|
open: false
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
item.open = !item.open;
|
||||||
|
setDataList([...dataList]);
|
||||||
|
} else {
|
||||||
|
onSelect(item.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
alignItems={'center'}
|
||||||
|
justifyContent={'center'}
|
||||||
|
visibility={
|
||||||
|
item.isFolder && (!item.children || item.children.length > 0)
|
||||||
|
? 'visible'
|
||||||
|
: 'hidden'
|
||||||
|
}
|
||||||
|
w={'1.25rem'}
|
||||||
|
h={'1.25rem'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
borderRadius={'xs'}
|
||||||
|
_hover={{
|
||||||
|
bg: 'rgba(31, 35, 41, 0.08)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MyIcon
|
||||||
|
name={
|
||||||
|
requestingIdList.includes(item.id)
|
||||||
|
? 'common/loading'
|
||||||
|
: 'common/rightArrowFill'
|
||||||
|
}
|
||||||
|
w={'14px'}
|
||||||
|
color={'myGray.500'}
|
||||||
|
transform={item.open ? 'rotate(90deg)' : 'none'}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Avatar ml={index !== 0 ? '0.5rem' : 0} src={item.avatar} w={'1.25rem'} />
|
||||||
|
<Box fontSize={'sm'} ml={2}>
|
||||||
|
{item.name}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
{item.children && item.open && (
|
||||||
|
<Box mt={0.5}>
|
||||||
|
<Render list={item.children} index={index + 1} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return loading ? <Loading fixed={false} /> : <Render list={dataList} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectOneResource;
|
||||||
178
projects/app/src/components/common/folder/SlideCard.tsx
Normal file
178
projects/app/src/components/common/folder/SlideCard.tsx
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import { Box, Button, Flex, HStack } from '@chakra-ui/react';
|
||||||
|
import React from 'react';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import { FolderIcon } from '@fastgpt/global/common/file/image/constants';
|
||||||
|
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||||
|
import MyDivider from '@fastgpt/web/components/common/MyDivider';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||||
|
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||||
|
import DefaultPermissionList from '@/components/support/permission/DefaultPerList';
|
||||||
|
import {
|
||||||
|
CollaboratorContextProvider,
|
||||||
|
MemberManagerInputPropsType
|
||||||
|
} from '../../support/permission/MemberManager/context';
|
||||||
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||||
|
|
||||||
|
const FolderSlideCard = ({
|
||||||
|
name,
|
||||||
|
intro,
|
||||||
|
onEdit,
|
||||||
|
onMove,
|
||||||
|
deleteTip,
|
||||||
|
onDelete,
|
||||||
|
|
||||||
|
defaultPer,
|
||||||
|
managePer
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
intro?: string;
|
||||||
|
onEdit: () => void;
|
||||||
|
onMove: () => void;
|
||||||
|
deleteTip: string;
|
||||||
|
onDelete: () => void;
|
||||||
|
|
||||||
|
defaultPer: {
|
||||||
|
value: PermissionValueType;
|
||||||
|
defaultValue: PermissionValueType;
|
||||||
|
onChange: (v: PermissionValueType) => Promise<any>;
|
||||||
|
};
|
||||||
|
managePer: MemberManagerInputPropsType;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { ConfirmModal, openConfirm } = useConfirm({
|
||||||
|
type: 'delete',
|
||||||
|
content: deleteTip
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box w={'13rem'}>
|
||||||
|
<Box>
|
||||||
|
<HStack>
|
||||||
|
<MyIcon name={FolderIcon} w={'1.5rem'} />
|
||||||
|
<Box color={'myGray.900'}>{name}</Box>
|
||||||
|
<MyIcon
|
||||||
|
name={'edit'}
|
||||||
|
_hover={{ color: 'primary.600' }}
|
||||||
|
w={'0.875rem'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
onClick={onEdit}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
<Box mt={3} fontSize={'sm'} color={'myGray.500'} cursor={'pointer'} onClick={onEdit}>
|
||||||
|
{intro || '暂无介绍'}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{managePer.permission.hasManagePer && (
|
||||||
|
<>
|
||||||
|
<MyDivider my={6} />
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<FormLabel>{t('common.Operation')}</FormLabel>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant={'transparentBase'}
|
||||||
|
pl={1}
|
||||||
|
leftIcon={<MyIcon name={'common/file/move'} w={'1rem'} />}
|
||||||
|
transform={'none !important'}
|
||||||
|
w={'100%'}
|
||||||
|
justifyContent={'flex-start'}
|
||||||
|
size={'sm'}
|
||||||
|
fontSize={'mini'}
|
||||||
|
mt={4}
|
||||||
|
onClick={onMove}
|
||||||
|
>
|
||||||
|
{t('common.Move')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={'transparentDanger'}
|
||||||
|
pl={1}
|
||||||
|
leftIcon={<MyIcon name={'delete'} w={'1rem'} />}
|
||||||
|
transform={'none !important'}
|
||||||
|
w={'100%'}
|
||||||
|
justifyContent={'flex-start'}
|
||||||
|
size={'sm'}
|
||||||
|
fontSize={'mini'}
|
||||||
|
mt={3}
|
||||||
|
onClick={() => {
|
||||||
|
openConfirm(onDelete)();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('common.Delete folder')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<MyDivider my={6} />
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<FormLabel>{t('support.permission.Permission')}</FormLabel>
|
||||||
|
|
||||||
|
{managePer.permission.hasManagePer && (
|
||||||
|
<Box mt={5}>
|
||||||
|
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||||
|
{t('permission.Default permission')}
|
||||||
|
</Box>
|
||||||
|
<DefaultPermissionList
|
||||||
|
mt="1"
|
||||||
|
per={defaultPer.value}
|
||||||
|
defaultPer={defaultPer.defaultValue}
|
||||||
|
onChange={defaultPer.onChange}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box mt={6}>
|
||||||
|
<CollaboratorContextProvider {...managePer}>
|
||||||
|
{({ MemberListCard, onOpenManageModal, onOpenAddMember }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex alignItems="center" justifyContent="space-between">
|
||||||
|
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||||
|
{t('permission.Collaborator')}
|
||||||
|
</Box>
|
||||||
|
{managePer.permission.hasManagePer && (
|
||||||
|
<HStack spacing={3}>
|
||||||
|
<MyTooltip label={t('permission.Manage')}>
|
||||||
|
<MyIcon
|
||||||
|
w="1rem"
|
||||||
|
name="common/settingLight"
|
||||||
|
cursor={'pointer'}
|
||||||
|
_hover={{ color: 'primary.600' }}
|
||||||
|
onClick={onOpenManageModal}
|
||||||
|
/>
|
||||||
|
</MyTooltip>
|
||||||
|
<MyTooltip label={t('common.Add')}>
|
||||||
|
<MyIcon
|
||||||
|
w="1rem"
|
||||||
|
name="support/permission/collaborator"
|
||||||
|
cursor={'pointer'}
|
||||||
|
_hover={{ color: 'primary.600' }}
|
||||||
|
onClick={onOpenAddMember}
|
||||||
|
/>
|
||||||
|
</MyTooltip>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<MemberListCard
|
||||||
|
mt={2}
|
||||||
|
tagStyle={{
|
||||||
|
type: 'borderSolid',
|
||||||
|
colorSchema: 'gray'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</CollaboratorContextProvider>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<ConfirmModal />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FolderSlideCard;
|
||||||
54
projects/app/src/components/common/folder/useFolderDrag.tsx
Normal file
54
projects/app/src/components/common/folder/useFolderDrag.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import React, { useState, DragEvent, useCallback } from 'react';
|
||||||
|
import type { BoxProps } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export const useFolderDrag = ({
|
||||||
|
onDrop,
|
||||||
|
activeStyles
|
||||||
|
}: {
|
||||||
|
onDrop: (dragId: string, targetId: string) => any;
|
||||||
|
activeStyles: BoxProps;
|
||||||
|
}) => {
|
||||||
|
const [dragId, setDragId] = useState<string>();
|
||||||
|
const [targetId, setTargetId] = useState<string>();
|
||||||
|
|
||||||
|
const getBoxProps = useCallback(
|
||||||
|
({ dataId, isFolder }: { dataId: string; isFolder: boolean }) => {
|
||||||
|
return {
|
||||||
|
draggable: true,
|
||||||
|
'data-drag-id': isFolder ? dataId : undefined,
|
||||||
|
onDragStart: (e: DragEvent<HTMLDivElement>) => {
|
||||||
|
setDragId(dataId);
|
||||||
|
},
|
||||||
|
onDragOver: (e: DragEvent<HTMLDivElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const targetId = e.currentTarget.getAttribute('data-drag-id');
|
||||||
|
if (!targetId) return;
|
||||||
|
setTargetId(targetId);
|
||||||
|
},
|
||||||
|
onDragLeave: (e: DragEvent<HTMLDivElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setTargetId(undefined);
|
||||||
|
},
|
||||||
|
onDrop: (e: DragEvent<HTMLDivElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (targetId && dragId && targetId !== dragId) {
|
||||||
|
onDrop(dragId, targetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTargetId(undefined);
|
||||||
|
setDragId(undefined);
|
||||||
|
},
|
||||||
|
...(activeStyles &&
|
||||||
|
targetId === dataId && {
|
||||||
|
...activeStyles
|
||||||
|
})
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[activeStyles, dragId, onDrop, targetId]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
getBoxProps
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
@ -68,6 +68,14 @@ const DatasetParamsModal = ({
|
|||||||
const [refresh, setRefresh] = useState(false);
|
const [refresh, setRefresh] = useState(false);
|
||||||
const [currentTabType, setCurrentTabType] = useState(SearchSettingTabEnum.searchMode);
|
const [currentTabType, setCurrentTabType] = useState(SearchSettingTabEnum.searchMode);
|
||||||
|
|
||||||
|
const chatModelSelectList = (() =>
|
||||||
|
llmModelList
|
||||||
|
.filter((model) => model.usedInQueryExtension)
|
||||||
|
.map((item) => ({
|
||||||
|
value: item.model,
|
||||||
|
label: item.name
|
||||||
|
})))();
|
||||||
|
|
||||||
const { register, setValue, getValues, handleSubmit, watch } = useForm<DatasetParamsProps>({
|
const { register, setValue, getValues, handleSubmit, watch } = useForm<DatasetParamsProps>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
limit,
|
limit,
|
||||||
@ -75,7 +83,7 @@ const DatasetParamsModal = ({
|
|||||||
searchMode,
|
searchMode,
|
||||||
usingReRank: !!usingReRank && teamPlanStatus?.standardConstants?.permissionReRank !== false,
|
usingReRank: !!usingReRank && teamPlanStatus?.standardConstants?.permissionReRank !== false,
|
||||||
datasetSearchUsingExtensionQuery,
|
datasetSearchUsingExtensionQuery,
|
||||||
datasetSearchExtensionModel: datasetSearchExtensionModel ?? llmModelList[0]?.model,
|
datasetSearchExtensionModel: datasetSearchExtensionModel || chatModelSelectList[0]?.value,
|
||||||
datasetSearchExtensionBg
|
datasetSearchExtensionBg
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -85,14 +93,6 @@ const DatasetParamsModal = ({
|
|||||||
const usingReRankWatch = watch('usingReRank');
|
const usingReRankWatch = watch('usingReRank');
|
||||||
const searchModeWatch = watch('searchMode');
|
const searchModeWatch = watch('searchMode');
|
||||||
|
|
||||||
const chatModelSelectList = (() =>
|
|
||||||
llmModelList
|
|
||||||
.filter((model) => model.usedInQueryExtension)
|
|
||||||
.map((item) => ({
|
|
||||||
value: item.model,
|
|
||||||
label: item.name
|
|
||||||
})))();
|
|
||||||
|
|
||||||
const searchModeList = useMemo(() => {
|
const searchModeList = useMemo(() => {
|
||||||
const list = Object.values(DatasetSearchModeMap);
|
const list = Object.values(DatasetSearchModeMap);
|
||||||
return list;
|
return list;
|
||||||
@ -109,6 +109,15 @@ const DatasetParamsModal = ({
|
|||||||
return usingReRank !== undefined && reRankModelList.length > 0;
|
return usingReRank !== undefined && reRankModelList.length > 0;
|
||||||
}, [reRankModelList.length, usingReRank]);
|
}, [reRankModelList.length, usingReRank]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (datasetSearchUsingCfrForm) {
|
||||||
|
!queryExtensionModel &&
|
||||||
|
setValue('datasetSearchExtensionModel', chatModelSelectList[0]?.value);
|
||||||
|
} else {
|
||||||
|
setValue('datasetSearchExtensionModel', '');
|
||||||
|
}
|
||||||
|
}, [chatModelSelectList, datasetSearchUsingCfrForm, queryExtensionModel, setValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MyModal
|
<MyModal
|
||||||
isOpen={true}
|
isOpen={true}
|
||||||
@ -270,7 +279,7 @@ const DatasetParamsModal = ({
|
|||||||
{t('core.dataset.Query extension intro')}
|
{t('core.dataset.Query extension intro')}
|
||||||
</Box>
|
</Box>
|
||||||
<Flex mt={3} alignItems={'center'}>
|
<Flex mt={3} alignItems={'center'}>
|
||||||
<Box flex={'1 0 0'}>{t('core.dataset.search.Using query extension')}</Box>
|
<FormLabel flex={'1 0 0'}>{t('core.dataset.search.Using query extension')}</FormLabel>
|
||||||
<Switch {...register('datasetSearchUsingExtensionQuery')} />
|
<Switch {...register('datasetSearchUsingExtensionQuery')} />
|
||||||
</Flex>
|
</Flex>
|
||||||
{datasetSearchUsingCfrForm === true && (
|
{datasetSearchUsingCfrForm === true && (
|
||||||
|
|||||||
@ -237,7 +237,6 @@ const LexiconConfigModal = ({ appId, onClose }: { appId: string; onClose: () =>
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
manual: true,
|
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
setNewData(undefined);
|
setNewData(undefined);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -14,22 +14,31 @@ const SearchParamsTip = ({
|
|||||||
limit = 1500,
|
limit = 1500,
|
||||||
responseEmptyText,
|
responseEmptyText,
|
||||||
usingReRank = false,
|
usingReRank = false,
|
||||||
usingQueryExtension = false
|
queryExtensionModel
|
||||||
}: {
|
}: {
|
||||||
searchMode: `${DatasetSearchModeEnum}`;
|
searchMode: `${DatasetSearchModeEnum}`;
|
||||||
similarity?: number;
|
similarity?: number;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
responseEmptyText?: string;
|
responseEmptyText?: string;
|
||||||
usingReRank?: boolean;
|
usingReRank?: boolean;
|
||||||
usingQueryExtension?: boolean;
|
queryExtensionModel?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { reRankModelList } = useSystemStore();
|
const { reRankModelList, llmModelList } = useSystemStore();
|
||||||
|
|
||||||
const hasReRankModel = reRankModelList.length > 0;
|
const hasReRankModel = reRankModelList.length > 0;
|
||||||
const hasEmptyResponseMode = responseEmptyText !== undefined;
|
const hasEmptyResponseMode = responseEmptyText !== undefined;
|
||||||
const hasSimilarityMode = usingReRank || searchMode === DatasetSearchModeEnum.embedding;
|
const hasSimilarityMode = usingReRank || searchMode === DatasetSearchModeEnum.embedding;
|
||||||
|
|
||||||
|
const extensionModelName = useMemo(
|
||||||
|
() =>
|
||||||
|
queryExtensionModel
|
||||||
|
? llmModelList.find((item) => item.model === queryExtensionModel)?.name ??
|
||||||
|
llmModelList[0]?.name
|
||||||
|
: undefined,
|
||||||
|
[llmModelList, queryExtensionModel]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer
|
<TableContainer
|
||||||
bg={'primary.50'}
|
bg={'primary.50'}
|
||||||
@ -73,8 +82,8 @@ const SearchParamsTip = ({
|
|||||||
{usingReRank ? '✅' : '❌'}
|
{usingReRank ? '✅' : '❌'}
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
<Td pt={0} pb={2}>
|
<Td pt={0} pb={2} fontSize={'mini'}>
|
||||||
{usingQueryExtension ? '✅' : '❌'}
|
{extensionModelName ? extensionModelName : '❌'}
|
||||||
</Td>
|
</Td>
|
||||||
{hasEmptyResponseMode && <Th>{responseEmptyText !== '' ? '✅' : '❌'}</Th>}
|
{hasEmptyResponseMode && <Th>{responseEmptyText !== '' ? '✅' : '❌'}</Th>}
|
||||||
</Tr>
|
</Tr>
|
||||||
|
|||||||
@ -1,108 +1,78 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { ModalBody, Flex, Box, useTheme, ModalFooter, Button } from '@chakra-ui/react';
|
import { ModalBody, ModalFooter, Button } from '@chakra-ui/react';
|
||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import type { SelectAppItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
import type { SelectAppItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||||
import Avatar from '@/components/Avatar';
|
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
import SelectOneResource from '@/components/common/folder/SelectOneResource';
|
||||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
import {
|
||||||
|
GetResourceFolderListProps,
|
||||||
|
GetResourceListItemResponse
|
||||||
|
} from '@fastgpt/global/common/parentFolder/type';
|
||||||
|
import { getMyApps } from '@/web/core/app/api';
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
|
||||||
const SelectAppModal = ({
|
const SelectAppModal = ({
|
||||||
defaultApps = [],
|
value,
|
||||||
filterAppIds = [],
|
filterAppIds = [],
|
||||||
max = 1,
|
|
||||||
onClose,
|
onClose,
|
||||||
onSuccess
|
onSuccess
|
||||||
}: {
|
}: {
|
||||||
defaultApps: string[];
|
value?: SelectAppItemType;
|
||||||
filterAppIds?: string[];
|
filterAppIds?: string[];
|
||||||
max?: number;
|
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSuccess: (e: SelectAppItemType[]) => void;
|
onSuccess: (e: SelectAppItemType) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { Loading } = useLoading();
|
const [selectedApp, setSelectedApp] = useState<SelectAppItemType | undefined>(value);
|
||||||
const theme = useTheme();
|
|
||||||
const [selectedApps, setSelectedApps] = React.useState<string[]>(defaultApps);
|
|
||||||
/* 加载模型 */
|
|
||||||
const { myApps, loadMyApps } = useAppStore();
|
|
||||||
const { isLoading } = useQuery(['loadMyApos'], () => loadMyApps());
|
|
||||||
|
|
||||||
const apps = useMemo(
|
const getAppList = useCallback(
|
||||||
() => myApps.filter((app) => !filterAppIds.includes(app._id)),
|
async ({ parentId }: GetResourceFolderListProps) => {
|
||||||
[myApps, filterAppIds]
|
return getMyApps({ parentId }).then((res) =>
|
||||||
|
res
|
||||||
|
.filter((item) => !filterAppIds.includes(item._id))
|
||||||
|
.map<GetResourceListItemResponse>((item) => ({
|
||||||
|
id: item._id,
|
||||||
|
name: item.name,
|
||||||
|
avatar: item.avatar,
|
||||||
|
isFolder: item.type === AppTypeEnum.folder
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[filterAppIds]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MyModal
|
<MyModal
|
||||||
isOpen
|
isOpen
|
||||||
title={`选择应用${max > 1 ? `(${selectedApps.length}/${max})` : ''}`}
|
title={`选择应用`}
|
||||||
iconSrc="/imgs/workflow/ai.svg"
|
iconSrc="/imgs/workflow/ai.svg"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
position={'relative'}
|
position={'relative'}
|
||||||
w={'600px'}
|
w={'600px'}
|
||||||
>
|
>
|
||||||
<ModalBody
|
<ModalBody flex={'1 0 0'} overflow={'auto'} minH={'400px'} position={'relative'}>
|
||||||
display={'grid'}
|
<SelectOneResource
|
||||||
gridTemplateColumns={['1fr', 'repeat(3, minmax(0, 1fr))']}
|
value={selectedApp?.id}
|
||||||
gridGap={4}
|
onSelect={(id) => setSelectedApp(id ? { id } : undefined)}
|
||||||
>
|
server={getAppList}
|
||||||
{apps.map((app) => (
|
/>
|
||||||
<Flex
|
|
||||||
key={app._id}
|
|
||||||
alignItems={'center'}
|
|
||||||
border={theme.borders.base}
|
|
||||||
borderRadius={'md'}
|
|
||||||
p={2}
|
|
||||||
cursor={'pointer'}
|
|
||||||
{...(selectedApps.includes(app._id)
|
|
||||||
? {
|
|
||||||
bg: 'primary.100',
|
|
||||||
onClick: () => {
|
|
||||||
setSelectedApps(selectedApps.filter((e) => e !== app._id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
onClick: () => {
|
|
||||||
if (max === 1) {
|
|
||||||
setSelectedApps([app._id]);
|
|
||||||
} else if (selectedApps.length < max) {
|
|
||||||
setSelectedApps([...selectedApps, app._id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Avatar src={app.avatar} w={['16px', '22px']} />
|
|
||||||
<Box fontSize={'sm'} color={'myGray.900'} ml={1}>
|
|
||||||
{app.name}
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
))}
|
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button variant={'whiteBase'} onClick={onClose}>
|
<Button variant={'whiteBase'} onClick={onClose}>
|
||||||
{t('common.Close')}
|
{t('common.Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
ml={2}
|
ml={2}
|
||||||
|
isDisabled={!selectedApp}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onSuccess(
|
if (!selectedApp) return;
|
||||||
apps
|
onSuccess(selectedApp);
|
||||||
.filter((app) => selectedApps.includes(app._id))
|
|
||||||
.map((app) => ({
|
|
||||||
id: app._id,
|
|
||||||
name: app.name,
|
|
||||||
logo: app.avatar
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('common.Confirm')}
|
{t('common.Confirm')}
|
||||||
</Button>
|
</Button>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
<Loading loading={isLoading} fixed={false} />
|
|
||||||
</MyModal>
|
</MyModal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,16 +1,17 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import type { RenderInputProps } from '../type';
|
import type { RenderInputProps } from '../type';
|
||||||
import { Box, Button, Flex, useDisclosure, useTheme } from '@chakra-ui/react';
|
import { Box, Button, useDisclosure } from '@chakra-ui/react';
|
||||||
import { SelectAppItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
import { SelectAppItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
import SelectAppModal from '../../../../SelectAppModal';
|
import SelectAppModal from '../../../../SelectAppModal';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { WorkflowContext } from '@/components/core/workflow/context';
|
import { WorkflowContext } from '@/components/core/workflow/context';
|
||||||
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
import { getAppDetailById } from '@/web/core/app/api';
|
||||||
|
|
||||||
const SelectAppRender = ({ item, nodeId }: RenderInputProps) => {
|
const SelectAppRender = ({ item, nodeId }: RenderInputProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
|
||||||
const filterAppIds = useContextSelector(WorkflowContext, (ctx) => ctx.filterAppIds);
|
const filterAppIds = useContextSelector(WorkflowContext, (ctx) => ctx.filterAppIds);
|
||||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||||
|
|
||||||
@ -21,8 +22,28 @@ const SelectAppRender = ({ item, nodeId }: RenderInputProps) => {
|
|||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
|
|
||||||
const value = item.value as SelectAppItemType | undefined;
|
const value = item.value as SelectAppItemType | undefined;
|
||||||
|
const { data: appDetail, loading } = useRequest2(
|
||||||
const filterAppString = useMemo(() => filterAppIds?.join(',') || '', [filterAppIds]);
|
() => {
|
||||||
|
if (value?.id) return getAppDetailById(value.id);
|
||||||
|
return Promise.resolve(null);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
manual: false,
|
||||||
|
refreshDeps: [value?.id],
|
||||||
|
errorToast: 'Error',
|
||||||
|
onError() {
|
||||||
|
onChangeNode({
|
||||||
|
nodeId,
|
||||||
|
type: 'updateInput',
|
||||||
|
key: 'app',
|
||||||
|
value: {
|
||||||
|
...item,
|
||||||
|
value: undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const Render = useMemo(() => {
|
const Render = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
@ -33,26 +54,22 @@ const SelectAppRender = ({ item, nodeId }: RenderInputProps) => {
|
|||||||
{t('core.module.Select app')}
|
{t('core.module.Select app')}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Flex
|
<Button
|
||||||
alignItems={'center'}
|
isLoading={loading}
|
||||||
border={theme.borders.base}
|
w={'100%'}
|
||||||
borderRadius={'md'}
|
justifyContent={loading ? 'center' : 'flex-start'}
|
||||||
bg={'white'}
|
variant={'whiteFlow'}
|
||||||
px={3}
|
leftIcon={<Avatar src={appDetail?.avatar} w={6} />}
|
||||||
py={2}
|
|
||||||
>
|
>
|
||||||
<Avatar src={value?.logo} w={6} />
|
{appDetail?.name}
|
||||||
<Box fontWeight={'medium'} ml={2}>
|
</Button>
|
||||||
{value?.name}
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{isOpenSelectApp && (
|
{isOpenSelectApp && (
|
||||||
<SelectAppModal
|
<SelectAppModal
|
||||||
defaultApps={item.value?.id ? [item.value.id] : []}
|
value={item.value}
|
||||||
filterAppIds={filterAppString.split(',')}
|
filterAppIds={filterAppIds}
|
||||||
onClose={onCloseSelectApp}
|
onClose={onCloseSelectApp}
|
||||||
onSuccess={(e) => {
|
onSuccess={(e) => {
|
||||||
onChangeNode({
|
onChangeNode({
|
||||||
@ -61,7 +78,7 @@ const SelectAppRender = ({ item, nodeId }: RenderInputProps) => {
|
|||||||
key: 'app',
|
key: 'app',
|
||||||
value: {
|
value: {
|
||||||
...item,
|
...item,
|
||||||
value: e[0]
|
value: e
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -70,15 +87,17 @@ const SelectAppRender = ({ item, nodeId }: RenderInputProps) => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
filterAppString,
|
appDetail?.avatar,
|
||||||
|
appDetail?.name,
|
||||||
|
filterAppIds,
|
||||||
isOpenSelectApp,
|
isOpenSelectApp,
|
||||||
item,
|
item,
|
||||||
|
loading,
|
||||||
nodeId,
|
nodeId,
|
||||||
onChangeNode,
|
onChangeNode,
|
||||||
onCloseSelectApp,
|
onCloseSelectApp,
|
||||||
onOpenSelectApp,
|
onOpenSelectApp,
|
||||||
t,
|
t,
|
||||||
theme.borders.base,
|
|
||||||
value
|
value
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@ -82,7 +82,7 @@ const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => {
|
|||||||
similarity={data.similarity}
|
similarity={data.similarity}
|
||||||
limit={data.limit}
|
limit={data.limit}
|
||||||
usingReRank={data.usingReRank}
|
usingReRank={data.usingReRank}
|
||||||
usingQueryExtension={data.datasetSearchUsingExtensionQuery}
|
queryExtensionModel={data.datasetSearchExtensionModel}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,96 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||||
|
import { MemberManagerInputPropsType, CollaboratorContextProvider } from '../MemberManager/context';
|
||||||
|
import { Box, Button, Flex, HStack, ModalBody } from '@chakra-ui/react';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
import DefaultPermissionList from '../DefaultPerList';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
|
||||||
|
export type ConfigPerModalProps = {
|
||||||
|
avatar?: string;
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
defaultPer: {
|
||||||
|
value: PermissionValueType;
|
||||||
|
defaultValue: PermissionValueType;
|
||||||
|
onChange: (v: PermissionValueType) => Promise<any>;
|
||||||
|
};
|
||||||
|
managePer: MemberManagerInputPropsType;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConfigPerModal = ({
|
||||||
|
avatar,
|
||||||
|
name,
|
||||||
|
defaultPer,
|
||||||
|
managePer,
|
||||||
|
onClose
|
||||||
|
}: ConfigPerModalProps & {
|
||||||
|
onClose: () => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<MyModal
|
||||||
|
isOpen
|
||||||
|
iconSrc="/imgs/modal/key.svg"
|
||||||
|
onClose={onClose}
|
||||||
|
title={t('permission.Permission config')}
|
||||||
|
>
|
||||||
|
<ModalBody>
|
||||||
|
<HStack>
|
||||||
|
<Avatar src={avatar} w={'1.75rem'} />
|
||||||
|
<Box fontSize={'lg'}>{name}</Box>
|
||||||
|
</HStack>
|
||||||
|
<Box mt={6}>
|
||||||
|
<Box fontSize={'sm'}>{t('permission.Default permission')}</Box>
|
||||||
|
<DefaultPermissionList
|
||||||
|
mt="1"
|
||||||
|
per={defaultPer.value}
|
||||||
|
defaultPer={defaultPer.defaultValue}
|
||||||
|
onChange={defaultPer.onChange}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box mt={4}>
|
||||||
|
<CollaboratorContextProvider {...managePer}>
|
||||||
|
{({ MemberListCard, onOpenManageModal, onOpenAddMember }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex
|
||||||
|
alignItems="center"
|
||||||
|
flexDirection="row"
|
||||||
|
justifyContent="space-between"
|
||||||
|
w="full"
|
||||||
|
>
|
||||||
|
<Box fontSize={'sm'}>{t('permission.Collaborator')}</Box>
|
||||||
|
<Flex flexDirection="row" gap="2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="whitePrimary"
|
||||||
|
leftIcon={<MyIcon w="4" name="common/settingLight" />}
|
||||||
|
onClick={onOpenManageModal}
|
||||||
|
>
|
||||||
|
{t('permission.Manage')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="whitePrimary"
|
||||||
|
leftIcon={<MyIcon w="4" name="support/permission/collaborator" />}
|
||||||
|
onClick={onOpenAddMember}
|
||||||
|
>
|
||||||
|
{t('common.Add')}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<MemberListCard mt={2} p={1.5} bg="myGray.100" borderRadius="md" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</CollaboratorContextProvider>
|
||||||
|
</Box>
|
||||||
|
</ModalBody>
|
||||||
|
</MyModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfigPerModal;
|
||||||
@ -3,6 +3,8 @@ import MySelect from '@fastgpt/web/components/common/MySelect';
|
|||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||||
|
import { ReadPermissionVal, WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
|
||||||
export enum defaultPermissionEnum {
|
export enum defaultPermissionEnum {
|
||||||
private = 'private',
|
private = 'private',
|
||||||
@ -13,16 +15,16 @@ export enum defaultPermissionEnum {
|
|||||||
type Props = Omit<BoxProps, 'onChange'> & {
|
type Props = Omit<BoxProps, 'onChange'> & {
|
||||||
per: PermissionValueType;
|
per: PermissionValueType;
|
||||||
defaultPer: PermissionValueType;
|
defaultPer: PermissionValueType;
|
||||||
readPer: PermissionValueType;
|
readPer?: PermissionValueType;
|
||||||
writePer: PermissionValueType;
|
writePer?: PermissionValueType;
|
||||||
onChange: (v: PermissionValueType) => void;
|
onChange: (v: PermissionValueType) => Promise<any> | any;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DefaultPermissionList = ({
|
const DefaultPermissionList = ({
|
||||||
per,
|
per,
|
||||||
defaultPer,
|
defaultPer,
|
||||||
readPer,
|
readPer = ReadPermissionVal,
|
||||||
writePer,
|
writePer = WritePermissionVal,
|
||||||
onChange,
|
onChange,
|
||||||
...styles
|
...styles
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
@ -33,14 +35,17 @@ const DefaultPermissionList = ({
|
|||||||
{ label: '团队可编辑', value: writePer }
|
{ label: '团队可编辑', value: writePer }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const { runAsync: onRequestChange, loading } = useRequest2(async (v: PermissionValueType) =>
|
||||||
|
onChange(v)
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box {...styles}>
|
<Box {...styles}>
|
||||||
<MySelect
|
<MySelect
|
||||||
|
isLoading={loading}
|
||||||
list={defaultPermissionSelectList}
|
list={defaultPermissionSelectList}
|
||||||
value={per}
|
value={per}
|
||||||
onchange={(v) => {
|
onchange={onRequestChange}
|
||||||
onChange(v);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
import { getTeamMembers } from '@/web/support/user/team/api';
|
import { getTeamMembers } from '@/web/support/user/team/api';
|
||||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
|
||||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||||
@ -32,11 +31,10 @@ export type AddModalPropsType = {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function AddMemberModal({ onClose }: AddModalPropsType) {
|
function AddMemberModal({ onClose }: AddModalPropsType) {
|
||||||
const toast = useToast();
|
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
|
|
||||||
const { permissionList, collaboratorList, onUpdateCollaborators, getPreLabelList } =
|
const { permissionList, collaboratorList, onUpdateCollaborators, getPerLabelList } =
|
||||||
useContextSelector(CollaboratorContext, (v) => v);
|
useContextSelector(CollaboratorContext, (v) => v);
|
||||||
const [searchText, setSearchText] = useState<string>('');
|
const [searchText, setSearchText] = useState<string>('');
|
||||||
const {
|
const {
|
||||||
@ -50,7 +48,7 @@ export function AddMemberModal({ onClose }: AddModalPropsType) {
|
|||||||
});
|
});
|
||||||
const filterMembers = useMemo(() => {
|
const filterMembers = useMemo(() => {
|
||||||
return members.filter((item) => {
|
return members.filter((item) => {
|
||||||
if (item.permission.isOwner) return false;
|
// if (item.permission.isOwner) return false;
|
||||||
if (item.tmbId === userInfo?.team?.tmbId) return false;
|
if (item.tmbId === userInfo?.team?.tmbId) return false;
|
||||||
if (!searchText) return true;
|
if (!searchText) return true;
|
||||||
return item.memberName.includes(searchText);
|
return item.memberName.includes(searchText);
|
||||||
@ -60,8 +58,8 @@ export function AddMemberModal({ onClose }: AddModalPropsType) {
|
|||||||
const [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]);
|
const [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]);
|
||||||
const [selectedPermission, setSelectedPermission] = useState(permissionList['read'].value);
|
const [selectedPermission, setSelectedPermission] = useState(permissionList['read'].value);
|
||||||
const perLabel = useMemo(() => {
|
const perLabel = useMemo(() => {
|
||||||
return getPreLabelList(selectedPermission).join('、');
|
return getPerLabelList(selectedPermission).join('、');
|
||||||
}, [getPreLabelList, selectedPermission]);
|
}, [getPerLabelList, selectedPermission]);
|
||||||
|
|
||||||
const { mutate: onConfirm, isLoading: isUpdating } = useRequest({
|
const { mutate: onConfirm, isLoading: isUpdating } = useRequest({
|
||||||
mutationFn: () => {
|
mutationFn: () => {
|
||||||
@ -85,6 +83,7 @@ export function AddMemberModal({ onClose }: AddModalPropsType) {
|
|||||||
borderColor="myGray.200"
|
borderColor="myGray.200"
|
||||||
borderRadius="0.5rem"
|
borderRadius="0.5rem"
|
||||||
gridTemplateColumns="55% 45%"
|
gridTemplateColumns="55% 45%"
|
||||||
|
fontSize={'sm'}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
@ -141,7 +140,9 @@ export function AddMemberModal({ onClose }: AddModalPropsType) {
|
|||||||
<MyAvatar src={member.avatar} w="32px" />
|
<MyAvatar src={member.avatar} w="32px" />
|
||||||
<Box ml="2">{member.memberName}</Box>
|
<Box ml="2">{member.memberName}</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
{!!collaborator && <PermissionTags permission={collaborator.permission} />}
|
{!!collaborator && (
|
||||||
|
<PermissionTags permission={collaborator.permission.value} />
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
@ -210,3 +211,5 @@ export function AddMemberModal({ onClose }: AddModalPropsType) {
|
|||||||
</MyModal>
|
</MyModal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default AddMemberModal;
|
||||||
|
|||||||
@ -18,10 +18,11 @@ import PermissionTags from './PermissionTags';
|
|||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
import { CollaboratorContext } from './context';
|
import { CollaboratorContext } from './context';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||||
|
import Loading from '@fastgpt/web/components/common/MyLoading';
|
||||||
|
|
||||||
export type ManageModalProps = {
|
export type ManageModalProps = {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@ -29,14 +30,12 @@ export type ManageModalProps = {
|
|||||||
|
|
||||||
function ManageModal({ onClose }: ManageModalProps) {
|
function ManageModal({ onClose }: ManageModalProps) {
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
const { collaboratorList, onUpdateCollaborators, onDelOneCollaborator } = useContextSelector(
|
const { permission, collaboratorList, onUpdateCollaborators, onDelOneCollaborator } =
|
||||||
CollaboratorContext,
|
useContextSelector(CollaboratorContext, (v) => v);
|
||||||
(v) => v
|
|
||||||
);
|
|
||||||
|
|
||||||
const { mutate: onDelete, isLoading: isDeleting } = useRequest({
|
const { runAsync: onDelete, loading: isDeleting } = useRequest2((tmbId: string) =>
|
||||||
mutationFn: (tmbId: string) => onDelOneCollaborator(tmbId)
|
onDelOneCollaborator(tmbId)
|
||||||
});
|
);
|
||||||
|
|
||||||
const { mutate: onUpdate, isLoading: isUpdating } = useRequest({
|
const { mutate: onUpdate, isLoading: isUpdating } = useRequest({
|
||||||
mutationFn: ({ tmbId, per }: { tmbId: string; per: PermissionValueType }) => {
|
mutationFn: ({ tmbId, per }: { tmbId: string; per: PermissionValueType }) => {
|
||||||
@ -49,14 +48,7 @@ function ManageModal({ onClose }: ManageModalProps) {
|
|||||||
const loading = isDeleting || isUpdating;
|
const loading = isDeleting || isUpdating;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MyModal
|
<MyModal isOpen onClose={onClose} minW="600px" title="管理协作者" iconSrc="common/settingLight">
|
||||||
isLoading={loading}
|
|
||||||
isOpen
|
|
||||||
onClose={onClose}
|
|
||||||
minW="600px"
|
|
||||||
title="管理协作者"
|
|
||||||
iconSrc="common/settingLight"
|
|
||||||
>
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<TableContainer borderRadius="md" minH="400px">
|
<TableContainer borderRadius="md" minH="400px">
|
||||||
<Table>
|
<Table>
|
||||||
@ -86,15 +78,17 @@ function ManageModal({ onClose }: ManageModalProps) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</Td>
|
</Td>
|
||||||
<Td border="none">
|
<Td border="none">
|
||||||
<PermissionTags permission={item.permission} />
|
<PermissionTags permission={item.permission.value} />
|
||||||
</Td>
|
</Td>
|
||||||
<Td border="none">
|
<Td border="none">
|
||||||
{item.tmbId !== userInfo?.team?.tmbId && (
|
{/* Not self; Not owner and other manager */}
|
||||||
|
{item.tmbId !== userInfo?.team?.tmbId &&
|
||||||
|
(permission.isOwner || !item.permission.hasManagePer) && (
|
||||||
<PermissionSelect
|
<PermissionSelect
|
||||||
Button={
|
Button={
|
||||||
<MyIcon name={'edit'} w={'16px'} _hover={{ color: 'primary.600' }} />
|
<MyIcon name={'edit'} w={'16px'} _hover={{ color: 'primary.600' }} />
|
||||||
}
|
}
|
||||||
value={item.permission}
|
value={item.permission.value}
|
||||||
onChange={(per) => {
|
onChange={(per) => {
|
||||||
onUpdate({
|
onUpdate({
|
||||||
tmbId: item.tmbId,
|
tmbId: item.tmbId,
|
||||||
@ -114,6 +108,7 @@ function ManageModal({ onClose }: ManageModalProps) {
|
|||||||
</Table>
|
</Table>
|
||||||
{collaboratorList?.length === 0 && <EmptyTip text={'暂无协作者'} />}
|
{collaboratorList?.length === 0 && <EmptyTip text={'暂无协作者'} />}
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
{loading && <Loading fixed={false} />}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</MyModal>
|
</MyModal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,42 @@
|
|||||||
|
import { Box, BoxProps, Flex } from '@chakra-ui/react';
|
||||||
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||||
|
import React from 'react';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { CollaboratorContext } from './context';
|
||||||
|
import Tag, { TagProps } from '@fastgpt/web/components/common/Tag';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
|
export type MemberListCardProps = BoxProps & { tagStyle?: Omit<TagProps, 'children'> };
|
||||||
|
|
||||||
|
const MemberListCard = ({ tagStyle, ...props }: MemberListCardProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { collaboratorList, isFetchingCollaborator } = useContextSelector(
|
||||||
|
CollaboratorContext,
|
||||||
|
(v) => v
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MyBox isLoading={isFetchingCollaborator} userSelect={'none'} {...props}>
|
||||||
|
{collaboratorList?.length === 0 ? (
|
||||||
|
<Box p={3} color="myGray.600" fontSize={'xs'} textAlign={'center'}>
|
||||||
|
{t('permission.Not collaborator')}
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Flex gap="2" flexWrap={'wrap'}>
|
||||||
|
{collaboratorList?.map((member) => {
|
||||||
|
return (
|
||||||
|
<Tag key={member.tmbId} type={'fill'} colorSchema="white" {...tagStyle}>
|
||||||
|
<Avatar src={member.avatar} w="1.25rem" />
|
||||||
|
<Box fontSize={'sm'}>{member.name}</Box>
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</MyBox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MemberListCard;
|
||||||
@ -49,7 +49,7 @@ function PermissionSelect({
|
|||||||
...props
|
...props
|
||||||
}: PermissionSelectProps) {
|
}: PermissionSelectProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { permissionList } = useContextSelector(CollaboratorContext, (v) => v);
|
const { permission, permissionList } = useContextSelector(CollaboratorContext, (v) => v);
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const closeTimer = useRef<any>();
|
const closeTimer = useRef<any>();
|
||||||
|
|
||||||
@ -66,10 +66,16 @@ function PermissionSelect({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
singleCheckBoxList: list.filter((item) => item.checkBoxType === 'single'),
|
singleCheckBoxList: list
|
||||||
|
.filter((item) => item.checkBoxType === 'single')
|
||||||
|
.filter((item) => {
|
||||||
|
if (permission.isOwner) return true;
|
||||||
|
if (item.value === permissionList['manage'].value) return false;
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
multipleCheckBoxList: list.filter((item) => item.checkBoxType === 'multiple')
|
multipleCheckBoxList: list.filter((item) => item.checkBoxType === 'multiple')
|
||||||
};
|
};
|
||||||
}, [permissionList]);
|
}, [permission.isOwner, permissionList]);
|
||||||
const selectedSingleValue = useMemo(() => {
|
const selectedSingleValue = useMemo(() => {
|
||||||
const per = new Permission({ per: value });
|
const per = new Permission({ per: value });
|
||||||
|
|
||||||
@ -88,6 +94,12 @@ function PermissionSelect({
|
|||||||
.map((item) => item.value);
|
.map((item) => item.value);
|
||||||
}, [permissionSelectList.multipleCheckBoxList, value]);
|
}, [permissionSelectList.multipleCheckBoxList, value]);
|
||||||
|
|
||||||
|
const onSelectPer = (per: PermissionValueType) => {
|
||||||
|
if (per === value) return;
|
||||||
|
onChange(per);
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
useOutsideClick({
|
useOutsideClick({
|
||||||
ref: ref,
|
ref: ref,
|
||||||
handler: () => {
|
handler: () => {
|
||||||
@ -151,8 +163,7 @@ function PermissionSelect({
|
|||||||
const per = new Permission({ per: value });
|
const per = new Permission({ per: value });
|
||||||
per.removePer(selectedSingleValue);
|
per.removePer(selectedSingleValue);
|
||||||
per.addPer(item.value);
|
per.addPer(item.value);
|
||||||
onChange(per.value);
|
onSelectPer(per.value);
|
||||||
setIsOpen(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -10,9 +10,9 @@ export type PermissionTagsProp = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function PermissionTags({ permission }: PermissionTagsProp) {
|
function PermissionTags({ permission }: PermissionTagsProp) {
|
||||||
const { getPreLabelList } = useContextSelector(CollaboratorContext, (v) => v);
|
const { getPerLabelList } = useContextSelector(CollaboratorContext, (v) => v);
|
||||||
|
|
||||||
const perTagList = getPreLabelList(permission);
|
const perTagList = getPerLabelList(permission);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex gap="2" alignItems="center">
|
<Flex gap="2" alignItems="center">
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { BoxProps, useDisclosure } from '@chakra-ui/react';
|
||||||
import { CollaboratorItemType } from '@fastgpt/global/support/permission/collaborator';
|
import { CollaboratorItemType } from '@fastgpt/global/support/permission/collaborator';
|
||||||
import { PermissionList } from '@fastgpt/global/support/permission/constant';
|
import { PermissionList } from '@fastgpt/global/support/permission/constant';
|
||||||
import { Permission } from '@fastgpt/global/support/permission/controller';
|
import { Permission } from '@fastgpt/global/support/permission/controller';
|
||||||
@ -5,8 +6,14 @@ import { PermissionListType, PermissionValueType } from '@fastgpt/global/support
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { ReactNode, useCallback } from 'react';
|
import { ReactNode, useCallback } from 'react';
|
||||||
import { createContext } from 'use-context-selector';
|
import { createContext } from 'use-context-selector';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
|
import MemberListCard, { MemberListCardProps } from './MemberListCard';
|
||||||
|
const AddMemberModal = dynamic(() => import('./AddMemberModal'));
|
||||||
|
const ManageModal = dynamic(() => import('./ManageModal'));
|
||||||
|
|
||||||
export type MemberManagerInputPropsType = {
|
export type MemberManagerInputPropsType = {
|
||||||
|
permission: Permission;
|
||||||
onGetCollaboratorList: () => Promise<CollaboratorItemType[]>;
|
onGetCollaboratorList: () => Promise<CollaboratorItemType[]>;
|
||||||
permissionList: PermissionListType;
|
permissionList: PermissionListType;
|
||||||
onUpdateCollaborators: (tmbIds: string[], permission: PermissionValueType) => any;
|
onUpdateCollaborators: (tmbIds: string[], permission: PermissionValueType) => any;
|
||||||
@ -16,7 +23,12 @@ export type MemberManagerPropsType = MemberManagerInputPropsType & {
|
|||||||
collaboratorList: CollaboratorItemType[];
|
collaboratorList: CollaboratorItemType[];
|
||||||
refetchCollaboratorList: () => void;
|
refetchCollaboratorList: () => void;
|
||||||
isFetchingCollaborator: boolean;
|
isFetchingCollaborator: boolean;
|
||||||
getPreLabelList: (per: PermissionValueType) => string[];
|
getPerLabelList: (per: PermissionValueType) => string[];
|
||||||
|
};
|
||||||
|
export type ChildrenProps = {
|
||||||
|
onOpenAddMember: () => void;
|
||||||
|
onOpenManageModal: () => void;
|
||||||
|
MemberListCard: (props: MemberListCardProps) => JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CollaboratorContextType = MemberManagerPropsType & {};
|
type CollaboratorContextType = MemberManagerPropsType & {};
|
||||||
@ -30,7 +42,7 @@ export const CollaboratorContext = createContext<CollaboratorContextType>({
|
|||||||
onDelOneCollaborator: function () {
|
onDelOneCollaborator: function () {
|
||||||
throw new Error('Function not implemented.');
|
throw new Error('Function not implemented.');
|
||||||
},
|
},
|
||||||
getPreLabelList: function (): string[] {
|
getPerLabelList: function (): string[] {
|
||||||
throw new Error('Function not implemented.');
|
throw new Error('Function not implemented.');
|
||||||
},
|
},
|
||||||
refetchCollaboratorList: function (): void {
|
refetchCollaboratorList: function (): void {
|
||||||
@ -39,33 +51,36 @@ export const CollaboratorContext = createContext<CollaboratorContextType>({
|
|||||||
onGetCollaboratorList: function (): Promise<CollaboratorItemType[]> {
|
onGetCollaboratorList: function (): Promise<CollaboratorItemType[]> {
|
||||||
throw new Error('Function not implemented.');
|
throw new Error('Function not implemented.');
|
||||||
},
|
},
|
||||||
isFetchingCollaborator: false
|
isFetchingCollaborator: false,
|
||||||
|
permission: new Permission()
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CollaboratorContextProvider = ({
|
export const CollaboratorContextProvider = ({
|
||||||
|
permission,
|
||||||
onGetCollaboratorList,
|
onGetCollaboratorList,
|
||||||
permissionList,
|
permissionList,
|
||||||
onUpdateCollaborators,
|
onUpdateCollaborators,
|
||||||
onDelOneCollaborator,
|
onDelOneCollaborator,
|
||||||
children
|
children
|
||||||
}: MemberManagerInputPropsType & {
|
}: MemberManagerInputPropsType & {
|
||||||
children: ReactNode;
|
children: (props: ChildrenProps) => ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
data: collaboratorList = [],
|
data: collaboratorList = [],
|
||||||
refetch: refetchCollaboratorList,
|
refetch: refetchCollaboratorList,
|
||||||
isLoading: isFetchingCollaborator
|
isLoading: isFetchingCollaborator
|
||||||
} = useQuery(['collaboratorList'], onGetCollaboratorList);
|
} = useQuery(['collaboratorList'], onGetCollaboratorList);
|
||||||
|
|
||||||
const onUpdateCollaboratorsThen = async (tmbIds: string[], permission: PermissionValueType) => {
|
const onUpdateCollaboratorsThen = async (tmbIds: string[], permission: PermissionValueType) => {
|
||||||
await onUpdateCollaborators(tmbIds, permission);
|
await onUpdateCollaborators(tmbIds, permission);
|
||||||
refetchCollaboratorList();
|
refetchCollaboratorList();
|
||||||
};
|
};
|
||||||
const onDelOneCollaboratorThem = async (tmbId: string) => {
|
const onDelOneCollaboratorThen = async (tmbId: string) => {
|
||||||
await onDelOneCollaborator(tmbId);
|
await onDelOneCollaborator(tmbId);
|
||||||
refetchCollaboratorList();
|
refetchCollaboratorList();
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPreLabelList = useCallback(
|
const getPerLabelList = useCallback(
|
||||||
(per: PermissionValueType) => {
|
(per: PermissionValueType) => {
|
||||||
const Per = new Permission({ per });
|
const Per = new Permission({ per });
|
||||||
const labels: string[] = [];
|
const labels: string[] = [];
|
||||||
@ -91,17 +106,33 @@ export const CollaboratorContextProvider = ({
|
|||||||
[permissionList]
|
[permissionList]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
isOpen: isOpenAddMember,
|
||||||
|
onOpen: onOpenAddMember,
|
||||||
|
onClose: onCloseAddMember
|
||||||
|
} = useDisclosure();
|
||||||
|
const {
|
||||||
|
isOpen: isOpenManageModal,
|
||||||
|
onOpen: onOpenManageModal,
|
||||||
|
onClose: onCloseManageModal
|
||||||
|
} = useDisclosure();
|
||||||
|
|
||||||
const contextValue = {
|
const contextValue = {
|
||||||
|
permission,
|
||||||
onGetCollaboratorList,
|
onGetCollaboratorList,
|
||||||
collaboratorList,
|
collaboratorList,
|
||||||
refetchCollaboratorList,
|
refetchCollaboratorList,
|
||||||
isFetchingCollaborator,
|
isFetchingCollaborator,
|
||||||
permissionList,
|
permissionList,
|
||||||
onUpdateCollaborators: onUpdateCollaboratorsThen,
|
onUpdateCollaborators: onUpdateCollaboratorsThen,
|
||||||
onDelOneCollaborator: onDelOneCollaboratorThem,
|
onDelOneCollaborator: onDelOneCollaboratorThen,
|
||||||
getPreLabelList
|
getPerLabelList
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<CollaboratorContext.Provider value={contextValue}>{children}</CollaboratorContext.Provider>
|
<CollaboratorContext.Provider value={contextValue}>
|
||||||
|
{children({ onOpenAddMember, onOpenManageModal, MemberListCard })}
|
||||||
|
{isOpenAddMember && <AddMemberModal onClose={onCloseAddMember} />}
|
||||||
|
{isOpenManageModal && <ManageModal onClose={onCloseManageModal} />}
|
||||||
|
</CollaboratorContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,99 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { Flex, Box, Button, Tag, TagLabel, useDisclosure } from '@chakra-ui/react';
|
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
|
||||||
import Avatar from '@/components/Avatar';
|
|
||||||
import { AddMemberModal } from './AddMemberModal';
|
|
||||||
import { useContextSelector } from 'use-context-selector';
|
|
||||||
import ManageModal from './ManageModal';
|
|
||||||
import {
|
|
||||||
CollaboratorContext,
|
|
||||||
CollaboratorContextProvider,
|
|
||||||
MemberManagerInputPropsType
|
|
||||||
} from './context';
|
|
||||||
import { useTranslation } from 'next-i18next';
|
|
||||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
|
||||||
|
|
||||||
function MemberManger() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const {
|
|
||||||
isOpen: isOpenAddMember,
|
|
||||||
onOpen: onOpenAddMember,
|
|
||||||
onClose: onCloseAddMember
|
|
||||||
} = useDisclosure();
|
|
||||||
const {
|
|
||||||
isOpen: isOpenManageModal,
|
|
||||||
onOpen: onOpenManageModal,
|
|
||||||
onClose: onCloseManageModal
|
|
||||||
} = useDisclosure();
|
|
||||||
|
|
||||||
const { collaboratorList, isFetchingCollaborator } = useContextSelector(
|
|
||||||
CollaboratorContext,
|
|
||||||
(v) => v
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Flex alignItems="center" flexDirection="row" justifyContent="space-between" w="full">
|
|
||||||
<Box fontSize={'sm'}>协作者</Box>
|
|
||||||
<Flex flexDirection="row" gap="2">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="whitePrimary"
|
|
||||||
leftIcon={<MyIcon w="4" name="common/settingLight" />}
|
|
||||||
onClick={onOpenManageModal}
|
|
||||||
>
|
|
||||||
{t('permission.Manage')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="whitePrimary"
|
|
||||||
leftIcon={<MyIcon w="4" name="support/permission/collaborator" />}
|
|
||||||
onClick={onOpenAddMember}
|
|
||||||
>
|
|
||||||
{t('common.Add')}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{/* member list */}
|
|
||||||
<MyBox
|
|
||||||
isLoading={isFetchingCollaborator}
|
|
||||||
mt={2}
|
|
||||||
bg="myGray.100"
|
|
||||||
borderRadius="md"
|
|
||||||
size={'md'}
|
|
||||||
>
|
|
||||||
{collaboratorList?.length === 0 ? (
|
|
||||||
<Box p={3} color="myGray.600" fontSize={'xs'} textAlign={'center'}>
|
|
||||||
暂无协作者
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<Flex gap="2" p={1.5}>
|
|
||||||
{collaboratorList?.map((member) => {
|
|
||||||
return (
|
|
||||||
<Tag px="4" py="1.5" bgColor="white" key={member.tmbId} width="fit-content">
|
|
||||||
<Flex alignItems="center">
|
|
||||||
<Avatar src={member.avatar} w="24px" />
|
|
||||||
<TagLabel mx="2">{member.name}</TagLabel>
|
|
||||||
</Flex>
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</MyBox>
|
|
||||||
{isOpenAddMember && <AddMemberModal onClose={onCloseAddMember} />}
|
|
||||||
{isOpenManageModal && <ManageModal onClose={onCloseManageModal} />}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Render(props: MemberManagerInputPropsType) {
|
|
||||||
return (
|
|
||||||
<CollaboratorContextProvider {...props}>
|
|
||||||
<MemberManger />
|
|
||||||
</CollaboratorContextProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default React.memo(Render);
|
|
||||||
@ -6,6 +6,7 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
|||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
|
||||||
const TeamManageModal = dynamic(() => import('../TeamManageModal'));
|
const TeamManageModal = dynamic(() => import('../TeamManageModal'));
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ const TeamMenu = () => {
|
|||||||
<Flex w={'100%'} alignItems={'center'}>
|
<Flex w={'100%'} alignItems={'center'}>
|
||||||
{userInfo?.team ? (
|
{userInfo?.team ? (
|
||||||
<>
|
<>
|
||||||
<Image src={userInfo.team.avatar} alt={''} w={'16px'} />
|
<Avatar src={userInfo.team.avatar} w={'1rem'} />
|
||||||
<Box ml={2}>{userInfo.team.teamName}</Box>
|
<Box ml={2}>{userInfo.team.teamName}</Box>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
14
projects/app/src/global/core/app/api.d.ts
vendored
14
projects/app/src/global/core/app/api.d.ts
vendored
@ -1,17 +1,11 @@
|
|||||||
|
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
import { AppSchema } from '@fastgpt/global/core/app/type';
|
import { AppSchema } from '@fastgpt/global/core/app/type';
|
||||||
|
|
||||||
export type CreateAppParams = {
|
|
||||||
name?: string;
|
|
||||||
avatar?: string;
|
|
||||||
type?: `${AppTypeEnum}`;
|
|
||||||
modules: AppSchema['modules'];
|
|
||||||
edges?: AppSchema['edges'];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type AppUpdateParams = {
|
export type AppUpdateParams = {
|
||||||
|
parentId?: ParentIdType;
|
||||||
name?: string;
|
name?: string;
|
||||||
type?: `${AppTypeEnum}`;
|
type?: AppTypeEnum;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
intro?: string;
|
intro?: string;
|
||||||
nodes?: AppSchema['modules'];
|
nodes?: AppSchema['modules'];
|
||||||
@ -23,7 +17,7 @@ export type AppUpdateParams = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type PostPublishAppProps = {
|
export type PostPublishAppProps = {
|
||||||
type: `${AppTypeEnum}`;
|
type: AppTypeEnum;
|
||||||
nodes: AppSchema['modules'];
|
nodes: AppSchema['modules'];
|
||||||
edges: AppSchema['edges'];
|
edges: AppSchema['edges'];
|
||||||
chatConfig: AppSchema['chatConfig'];
|
chatConfig: AppSchema['chatConfig'];
|
||||||
|
|||||||
@ -72,7 +72,7 @@ export type SearchTestResponse = {
|
|||||||
searchMode: `${DatasetSearchModeEnum}`;
|
searchMode: `${DatasetSearchModeEnum}`;
|
||||||
usingReRank: boolean;
|
usingReRank: boolean;
|
||||||
similarity: number;
|
similarity: number;
|
||||||
usingQueryExtension: boolean;
|
queryExtensionModel?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* =========== training =========== */
|
/* =========== training =========== */
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import type { UsageItemType } from '@fastgpt/global/support/wallet/usage/type';
|
|||||||
import { usePagination } from '@fastgpt/web/hooks/usePagination';
|
import { usePagination } from '@fastgpt/web/hooks/usePagination';
|
||||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
|
||||||
import DateRangePicker, {
|
import DateRangePicker, {
|
||||||
type DateRangeType
|
type DateRangeType
|
||||||
} from '@fastgpt/web/components/common/DateRangePicker';
|
} from '@fastgpt/web/components/common/DateRangePicker';
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiResponse } from 'next';
|
||||||
import { jsonRes } from '@fastgpt/service/common/response';
|
import { jsonRes } from '@fastgpt/service/common/response';
|
||||||
import type { CreateAppParams } from '@/global/core/app/api.d';
|
|
||||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||||
@ -9,17 +8,24 @@ import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
|||||||
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||||
import { NextAPI } from '@/service/middleware/entry';
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
|
import type { AppSchema } from '@fastgpt/global/core/app/type';
|
||||||
|
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||||
|
import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||||
|
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||||
|
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
export type CreateAppBody = {
|
||||||
const {
|
parentId?: ParentIdType;
|
||||||
name = 'APP',
|
name?: string;
|
||||||
avatar,
|
avatar?: string;
|
||||||
type = AppTypeEnum.advanced,
|
type?: AppTypeEnum;
|
||||||
modules,
|
modules: AppSchema['modules'];
|
||||||
edges
|
edges?: AppSchema['edges'];
|
||||||
} = req.body as CreateAppParams;
|
};
|
||||||
|
|
||||||
if (!name || !Array.isArray(modules)) {
|
async function handler(req: ApiRequestProps<CreateAppBody>, res: NextApiResponse<any>) {
|
||||||
|
const { parentId, name, avatar, type, modules, edges } = req.body;
|
||||||
|
|
||||||
|
if (!name || !type || !Array.isArray(modules)) {
|
||||||
throw new Error('缺少参数');
|
throw new Error('缺少参数');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,6 +40,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
|||||||
const [{ _id: appId }] = await MongoApp.create(
|
const [{ _id: appId }] = await MongoApp.create(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
...parseParentIdInMongo(parentId),
|
||||||
avatar,
|
avatar,
|
||||||
name,
|
name,
|
||||||
teamId,
|
teamId,
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
|||||||
import { NextAPI } from '@/service/middleware/entry';
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
|
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
|
||||||
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
|
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
|
import { findAppAndAllChildren } from '@fastgpt/service/core/app/controller';
|
||||||
|
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
const { appId } = req.query as { appId: string };
|
const { appId } = req.query as { appId: string };
|
||||||
@ -17,11 +18,21 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
|||||||
throw new Error('参数错误');
|
throw new Error('参数错误');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 凭证校验
|
// Auth owner (folder owner, can delete all apps in the folder)
|
||||||
await authApp({ req, authToken: true, appId, per: OwnerPermissionVal });
|
const { teamId } = await authApp({ req, authToken: true, appId, per: OwnerPermissionVal });
|
||||||
|
|
||||||
|
const apps = await findAppAndAllChildren({
|
||||||
|
teamId,
|
||||||
|
appId,
|
||||||
|
fields: '_id'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(apps);
|
||||||
|
|
||||||
// 删除对应的聊天
|
|
||||||
await mongoSessionRun(async (session) => {
|
await mongoSessionRun(async (session) => {
|
||||||
|
for await (const app of apps) {
|
||||||
|
const appId = app._id;
|
||||||
|
// Chats
|
||||||
await MongoChatItem.deleteMany(
|
await MongoChatItem.deleteMany(
|
||||||
{
|
{
|
||||||
appId
|
appId
|
||||||
@ -61,6 +72,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
|||||||
},
|
},
|
||||||
{ session }
|
{ session }
|
||||||
);
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||||
import { NextAPI } from '@/service/middleware/entry';
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
import { ReadPermissionVal, WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
|
|
||||||
/* 获取我的模型 */
|
/* 获取我的模型 */
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
@ -11,7 +11,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
|||||||
throw new Error('参数错误');
|
throw new Error('参数错误');
|
||||||
}
|
}
|
||||||
// 凭证校验
|
// 凭证校验
|
||||||
const { app } = await authApp({ req, authToken: true, appId, per: WritePermissionVal });
|
const { app } = await authApp({ req, authToken: true, appId, per: ReadPermissionVal });
|
||||||
|
|
||||||
|
if (!app.permission.hasWritePer) {
|
||||||
|
app.modules = [];
|
||||||
|
app.edges = [];
|
||||||
|
}
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|||||||
40
projects/app/src/pages/api/core/app/folder/create.ts
Normal file
40
projects/app/src/pages/api/core/app/folder/create.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import type { NextApiResponse } from 'next';
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||||
|
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||||
|
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
|
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||||
|
import { FolderImgUrl } from '@fastgpt/global/common/file/image/constants';
|
||||||
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
|
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||||
|
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||||
|
|
||||||
|
export type CreateAppFolderBody = {
|
||||||
|
parentId?: ParentIdType;
|
||||||
|
name: string;
|
||||||
|
intro?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handler(req: ApiRequestProps<CreateAppFolderBody>, res: NextApiResponse<any>) {
|
||||||
|
const { name, intro, parentId } = req.body;
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
throw new Error('缺少参数');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 凭证校验
|
||||||
|
const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal });
|
||||||
|
|
||||||
|
// Create app
|
||||||
|
await MongoApp.create({
|
||||||
|
...parseParentIdInMongo(parentId),
|
||||||
|
avatar: FolderImgUrl,
|
||||||
|
name,
|
||||||
|
intro,
|
||||||
|
teamId,
|
||||||
|
tmbId,
|
||||||
|
type: AppTypeEnum.folder
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NextAPI(handler);
|
||||||
41
projects/app/src/pages/api/core/app/folder/path.ts
Normal file
41
projects/app/src/pages/api/core/app/folder/path.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import type {
|
||||||
|
ParentIdType,
|
||||||
|
ParentTreePathItemType
|
||||||
|
} from '@fastgpt/global/common/parentFolder/type.d';
|
||||||
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
|
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||||
|
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
|
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||||
|
|
||||||
|
async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<any>
|
||||||
|
): Promise<ParentTreePathItemType[]> {
|
||||||
|
const { parentId } = req.query as { parentId: string };
|
||||||
|
|
||||||
|
if (!parentId) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
await authApp({ req, authToken: true, appId: parentId, per: ReadPermissionVal });
|
||||||
|
|
||||||
|
return await getParents(parentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NextAPI(handler);
|
||||||
|
|
||||||
|
async function getParents(parentId: ParentIdType): Promise<ParentTreePathItemType[]> {
|
||||||
|
if (!parentId) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = await MongoApp.findById(parentId, 'name parentId');
|
||||||
|
|
||||||
|
if (!parent) return [];
|
||||||
|
|
||||||
|
const paths = await getParents(parent.parentId);
|
||||||
|
paths.push({ parentId, parentName: parent.name });
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiResponse } from 'next';
|
||||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||||
import { AppListItemType } from '@fastgpt/global/core/app/type';
|
import { AppListItemType } from '@fastgpt/global/core/app/type';
|
||||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||||
@ -9,8 +9,21 @@ import {
|
|||||||
ReadPermissionVal
|
ReadPermissionVal
|
||||||
} from '@fastgpt/global/support/permission/constant';
|
} from '@fastgpt/global/support/permission/constant';
|
||||||
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
|
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
|
||||||
|
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||||
|
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||||
|
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
|
||||||
|
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<AppListItemType[]> {
|
export type ListAppBody = {
|
||||||
|
parentId: ParentIdType;
|
||||||
|
type?: AppTypeEnum;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handler(
|
||||||
|
req: ApiRequestProps<ListAppBody>,
|
||||||
|
res: NextApiResponse<any>
|
||||||
|
): Promise<AppListItemType[]> {
|
||||||
// 凭证校验
|
// 凭证校验
|
||||||
const {
|
const {
|
||||||
teamId,
|
teamId,
|
||||||
@ -22,9 +35,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
|
|||||||
per: ReadPermissionVal
|
per: ReadPermissionVal
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { parentId, type } = req.body;
|
||||||
|
|
||||||
/* temp: get all apps and per */
|
/* temp: get all apps and per */
|
||||||
const [myApps, rpList] = await Promise.all([
|
const [myApps, rpList] = await Promise.all([
|
||||||
MongoApp.find({ teamId }, '_id avatar name intro tmbId defaultPermission')
|
MongoApp.find(
|
||||||
|
{ teamId, ...(type && { type }), ...parseParentIdInMongo(parentId) },
|
||||||
|
'_id avatar type name intro tmbId defaultPermission'
|
||||||
|
)
|
||||||
.sort({
|
.sort({
|
||||||
updateTime: -1
|
updateTime: -1
|
||||||
})
|
})
|
||||||
@ -54,10 +72,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
|
|||||||
return filterApps.map((app) => ({
|
return filterApps.map((app) => ({
|
||||||
_id: app._id,
|
_id: app._id,
|
||||||
avatar: app.avatar,
|
avatar: app.avatar,
|
||||||
|
type: app.type,
|
||||||
name: app.name,
|
name: app.name,
|
||||||
intro: app.intro,
|
intro: app.intro,
|
||||||
permission: app.permission,
|
permission: app.permission,
|
||||||
defaultPermission: app.defaultPermission
|
defaultPermission: app.defaultPermission || AppDefaultPermissionVal
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,10 +9,12 @@ import {
|
|||||||
WritePermissionVal,
|
WritePermissionVal,
|
||||||
OwnerPermissionVal
|
OwnerPermissionVal
|
||||||
} from '@fastgpt/global/support/permission/constant';
|
} from '@fastgpt/global/support/permission/constant';
|
||||||
|
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||||
|
|
||||||
/* 获取我的模型 */
|
/* 获取我的模型 */
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
const {
|
const {
|
||||||
|
parentId,
|
||||||
name,
|
name,
|
||||||
avatar,
|
avatar,
|
||||||
type,
|
type,
|
||||||
@ -49,6 +51,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
|||||||
_id: appId
|
_id: appId
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
...parseParentIdInMongo(parentId),
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
avatar,
|
avatar,
|
||||||
|
|||||||
@ -92,7 +92,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
|||||||
data: {
|
data: {
|
||||||
list: searchRes,
|
list: searchRes,
|
||||||
duration: `${((Date.now() - start) / 1000).toFixed(3)}s`,
|
duration: `${((Date.now() - start) / 1000).toFixed(3)}s`,
|
||||||
usingQueryExtension: !!aiExtensionResult,
|
queryExtensionModel: aiExtensionResult?.model,
|
||||||
...result
|
...result
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import Avatar from '@/components/Avatar';
|
|||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||||
import MemberManager from '@/components/support/permission/MemberManager';
|
import { CollaboratorContextProvider } from '@/components/support/permission/MemberManager/context';
|
||||||
import {
|
import {
|
||||||
postUpdateAppCollaborators,
|
postUpdateAppCollaborators,
|
||||||
deleteAppCollaborators,
|
deleteAppCollaborators,
|
||||||
@ -29,12 +29,12 @@ import {
|
|||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { AppContext } from '@/web/core/app/context/appContext';
|
import { AppContext } from '@/web/core/app/context/appContext';
|
||||||
import {
|
import {
|
||||||
AppDefaultPermission,
|
AppDefaultPermissionVal,
|
||||||
AppPermissionList
|
AppPermissionList
|
||||||
} from '@fastgpt/global/support/permission/app/constant';
|
} from '@fastgpt/global/support/permission/app/constant';
|
||||||
import { ReadPermissionVal, WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
|
||||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||||
import DefaultPermissionList from '@/components/support/permission/DefaultPerList';
|
import DefaultPermissionList from '@/components/support/permission/DefaultPerList';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
|
||||||
const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -181,25 +181,57 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
|||||||
{/* role */}
|
{/* role */}
|
||||||
{appDetail.permission.hasManagePer && (
|
{appDetail.permission.hasManagePer && (
|
||||||
<>
|
<>
|
||||||
{' '}
|
|
||||||
<Box mt="4">
|
<Box mt="4">
|
||||||
<Box fontSize={'sm'}>{t('permission.Default permission')}</Box>
|
<Box fontSize={'sm'}>{t('permission.Default permission')}</Box>
|
||||||
<DefaultPermissionList
|
<DefaultPermissionList
|
||||||
mt="2"
|
mt="2"
|
||||||
per={defaultPermission}
|
per={defaultPermission}
|
||||||
defaultPer={AppDefaultPermission}
|
defaultPer={AppDefaultPermissionVal}
|
||||||
readPer={ReadPermissionVal}
|
|
||||||
writePer={WritePermissionVal}
|
|
||||||
onChange={(v) => setValue('defaultPermission', v)}
|
onChange={(v) => setValue('defaultPermission', v)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box mt={6}>
|
<Box mt={6}>
|
||||||
<MemberManager
|
<CollaboratorContextProvider
|
||||||
|
permission={appDetail.permission}
|
||||||
onGetCollaboratorList={() => getCollaboratorList(appDetail._id)}
|
onGetCollaboratorList={() => getCollaboratorList(appDetail._id)}
|
||||||
permissionList={AppPermissionList}
|
permissionList={AppPermissionList}
|
||||||
onUpdateCollaborators={onUpdateCollaborators}
|
onUpdateCollaborators={onUpdateCollaborators}
|
||||||
onDelOneCollaborator={onDelCollaborator}
|
onDelOneCollaborator={onDelCollaborator}
|
||||||
/>
|
>
|
||||||
|
{({ MemberListCard, onOpenManageModal, onOpenAddMember }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex
|
||||||
|
alignItems="center"
|
||||||
|
flexDirection="row"
|
||||||
|
justifyContent="space-between"
|
||||||
|
w="full"
|
||||||
|
>
|
||||||
|
<Box fontSize={'sm'}>协作者</Box>
|
||||||
|
<Flex flexDirection="row" gap="2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="whitePrimary"
|
||||||
|
leftIcon={<MyIcon w="4" name="common/settingLight" />}
|
||||||
|
onClick={onOpenManageModal}
|
||||||
|
>
|
||||||
|
{t('permission.Manage')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="whitePrimary"
|
||||||
|
leftIcon={<MyIcon w="4" name="support/permission/collaborator" />}
|
||||||
|
onClick={onOpenAddMember}
|
||||||
|
>
|
||||||
|
{t('common.Add')}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<MemberListCard mt={2} p={1.5} bg="myGray.100" borderRadius="md" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</CollaboratorContextProvider>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||||
import { delModelById } from '@/web/core/app/api';
|
import { delAppById } from '@/web/core/app/api';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import PermissionIconText from '@/components/support/permission/IconText';
|
import PermissionIconText from '@/components/support/permission/IconText';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
@ -45,7 +45,7 @@ const AppCard = () => {
|
|||||||
const { mutate: handleDelModel, isLoading } = useRequest({
|
const { mutate: handleDelModel, isLoading } = useRequest({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
if (!appDetail) return null;
|
if (!appDetail) return null;
|
||||||
await delModelById(appDetail._id);
|
await delAppById(appDetail._id);
|
||||||
return 'success';
|
return 'success';
|
||||||
},
|
},
|
||||||
onSuccess(res) {
|
onSuccess(res) {
|
||||||
|
|||||||
@ -297,7 +297,7 @@ const EditForm = ({
|
|||||||
similarity={getValues('dataset.similarity')}
|
similarity={getValues('dataset.similarity')}
|
||||||
limit={getValues('dataset.limit')}
|
limit={getValues('dataset.limit')}
|
||||||
usingReRank={getValues('dataset.usingReRank')}
|
usingReRank={getValues('dataset.usingReRank')}
|
||||||
usingQueryExtension={getValues('dataset.datasetSearchUsingExtensionQuery')}
|
queryExtensionModel={getValues('dataset.datasetSearchExtensionModel')}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -25,6 +25,8 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
|||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { AppListContext } from './context';
|
||||||
|
|
||||||
type FormType = {
|
type FormType = {
|
||||||
avatar: string;
|
avatar: string;
|
||||||
@ -32,12 +34,15 @@ type FormType = {
|
|||||||
templateId: string;
|
templateId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: () => void }) => {
|
const CreateModal = ({ onClose }: { onClose: () => void }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { parentId, loadMyApps } = useContextSelector(AppListContext, (v) => v);
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { isPc, feConfigs } = useSystemStore();
|
const { isPc } = useSystemStore();
|
||||||
|
|
||||||
const { register, setValue, watch, handleSubmit } = useForm<FormType>({
|
const { register, setValue, watch, handleSubmit } = useForm<FormType>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
avatar: '',
|
avatar: '',
|
||||||
@ -82,6 +87,7 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
|
|||||||
return Promise.reject(t('core.dataset.error.Template does not exist'));
|
return Promise.reject(t('core.dataset.error.Template does not exist'));
|
||||||
}
|
}
|
||||||
return postCreateApp({
|
return postCreateApp({
|
||||||
|
parentId,
|
||||||
avatar: data.avatar || template.avatar,
|
avatar: data.avatar || template.avatar,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
type: template.type,
|
type: template.type,
|
||||||
@ -91,7 +97,7 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
|
|||||||
},
|
},
|
||||||
onSuccess(id: string) {
|
onSuccess(id: string) {
|
||||||
router.push(`/app/detail?appId=${id}`);
|
router.push(`/app/detail?appId=${id}`);
|
||||||
onSuccess();
|
loadMyApps();
|
||||||
onClose();
|
onClose();
|
||||||
},
|
},
|
||||||
successToast: t('common.Create Success'),
|
successToast: t('common.Create Success'),
|
||||||
|
|||||||
294
projects/app/src/pages/app/list/component/List.tsx
Normal file
294
projects/app/src/pages/app/list/component/List.tsx
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import { Box, Grid, Flex, IconButton } from '@chakra-ui/react';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { delAppById, putAppById } from '@/web/core/app/api';
|
||||||
|
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
import PermissionIconText from '@/components/support/permission/IconText';
|
||||||
|
import { useI18n } from '@/web/context/I18n';
|
||||||
|
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||||
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { AppListContext } from './context';
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
import { useFolderDrag } from '@/components/common/folder/useFolderDrag';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import type { EditResourceInfoFormType } from '@/components/common/Modal/EditResourceModal';
|
||||||
|
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||||
|
import {
|
||||||
|
AppDefaultPermissionVal,
|
||||||
|
AppPermissionList
|
||||||
|
} from '@fastgpt/global/support/permission/app/constant';
|
||||||
|
import {
|
||||||
|
deleteAppCollaborators,
|
||||||
|
getCollaboratorList,
|
||||||
|
postUpdateAppCollaborators
|
||||||
|
} from '@/web/core/app/api/collaborator';
|
||||||
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||||
|
|
||||||
|
const EditResourceModal = dynamic(() => import('@/components/common/Modal/EditResourceModal'));
|
||||||
|
const ConfigPerModal = dynamic(() => import('@/components/support/permission/ConfigPerModal'));
|
||||||
|
|
||||||
|
const ListItem = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { appT } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
|
const { myApps, loadMyApps, onUpdateApp, setMoveAppId } = useContextSelector(
|
||||||
|
AppListContext,
|
||||||
|
(v) => v
|
||||||
|
);
|
||||||
|
const [loadingAppId, setLoadingAppId] = useState<string>();
|
||||||
|
|
||||||
|
const [editedApp, setEditedApp] = useState<EditResourceInfoFormType>();
|
||||||
|
const [editPerAppIndex, setEditPerAppIndex] = useState<number>();
|
||||||
|
const editPerApp = useMemo(
|
||||||
|
() => (editPerAppIndex !== undefined ? myApps[editPerAppIndex] : undefined),
|
||||||
|
[editPerAppIndex, myApps]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getBoxProps } = useFolderDrag({
|
||||||
|
activeStyles: {
|
||||||
|
borderColor: 'primary.600'
|
||||||
|
},
|
||||||
|
onDrop: async (dragId: string, targetId: string) => {
|
||||||
|
setLoadingAppId(dragId);
|
||||||
|
try {
|
||||||
|
await putAppById(dragId, { parentId: targetId });
|
||||||
|
loadMyApps();
|
||||||
|
} catch (error) {}
|
||||||
|
setLoadingAppId(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { openConfirm, ConfirmModal } = useConfirm({
|
||||||
|
type: 'delete'
|
||||||
|
});
|
||||||
|
|
||||||
|
const { run: onclickDelApp } = useRequest2(
|
||||||
|
(id: string) => {
|
||||||
|
setLoadingAppId(id);
|
||||||
|
return delAppById(id);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess() {
|
||||||
|
loadMyApps();
|
||||||
|
},
|
||||||
|
onFinally() {
|
||||||
|
setLoadingAppId(undefined);
|
||||||
|
},
|
||||||
|
successToast: t('common.Delete Success'),
|
||||||
|
errorToast: t('common.Delete Failed')
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid
|
||||||
|
py={[4, 6]}
|
||||||
|
gridTemplateColumns={['1fr', 'repeat(2,1fr)', 'repeat(3,1fr)', 'repeat(4,1fr)']}
|
||||||
|
gridGap={5}
|
||||||
|
>
|
||||||
|
{myApps.map((app, index) => (
|
||||||
|
<MyTooltip
|
||||||
|
key={app._id}
|
||||||
|
label={
|
||||||
|
app.type === AppTypeEnum.folder
|
||||||
|
? t('common.folder.Open folder')
|
||||||
|
: app.permission.hasWritePer
|
||||||
|
? appT('Edit app')
|
||||||
|
: appT('Go to chat')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MyBox
|
||||||
|
isLoading={loadingAppId === app._id}
|
||||||
|
lineHeight={1.5}
|
||||||
|
h={'100%'}
|
||||||
|
py={3}
|
||||||
|
px={5}
|
||||||
|
cursor={'pointer'}
|
||||||
|
borderWidth={'1.5px'}
|
||||||
|
borderColor={'borderColor.low'}
|
||||||
|
bg={'white'}
|
||||||
|
borderRadius={'md'}
|
||||||
|
userSelect={'none'}
|
||||||
|
position={'relative'}
|
||||||
|
display={'flex'}
|
||||||
|
flexDirection={'column'}
|
||||||
|
_hover={{
|
||||||
|
borderColor: 'primary.300',
|
||||||
|
boxShadow: '1.5',
|
||||||
|
'& .more': {
|
||||||
|
display: 'flex'
|
||||||
|
},
|
||||||
|
'& .chat': {
|
||||||
|
display: 'flex'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (app.type === AppTypeEnum.folder) {
|
||||||
|
router.push({
|
||||||
|
query: {
|
||||||
|
parentId: app._id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (app.permission.hasWritePer) {
|
||||||
|
router.push(`/app/detail?appId=${app._id}`);
|
||||||
|
} else {
|
||||||
|
router.push(`/chat?appId=${app._id}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{...getBoxProps({
|
||||||
|
dataId: app._id,
|
||||||
|
isFolder: app.type === AppTypeEnum.folder
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Flex alignItems={'center'} h={'38px'}>
|
||||||
|
<Avatar src={app.avatar} borderRadius={'md'} w={'28px'} />
|
||||||
|
<Box ml={3}>{app.name}</Box>
|
||||||
|
{app.permission.hasManagePer && (
|
||||||
|
<Box
|
||||||
|
className="more"
|
||||||
|
position={'absolute'}
|
||||||
|
top={3.5}
|
||||||
|
right={4}
|
||||||
|
display={['', 'none']}
|
||||||
|
>
|
||||||
|
<MyMenu
|
||||||
|
Button={
|
||||||
|
<IconButton
|
||||||
|
size={'xsSquare'}
|
||||||
|
variant={'transparentBase'}
|
||||||
|
icon={<MyIcon name={'more'} w={'1rem'} />}
|
||||||
|
aria-label={''}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
menuList={[
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
icon: 'edit',
|
||||||
|
label: '编辑信息',
|
||||||
|
onClick: () =>
|
||||||
|
setEditedApp({
|
||||||
|
id: app._id,
|
||||||
|
avatar: app.avatar,
|
||||||
|
name: app.name,
|
||||||
|
intro: app.intro
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'common/file/move',
|
||||||
|
label: t('common.folder.Move to'),
|
||||||
|
onClick: () => setMoveAppId(app._id)
|
||||||
|
},
|
||||||
|
...(app.permission.hasManagePer
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
icon: 'support/team/key',
|
||||||
|
label: t('permission.Permission'),
|
||||||
|
onClick: () => setEditPerAppIndex(index)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [])
|
||||||
|
]
|
||||||
|
},
|
||||||
|
...(app.permission.isOwner
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'danger' as 'danger',
|
||||||
|
icon: 'delete',
|
||||||
|
label: t('common.Delete'),
|
||||||
|
onClick: () =>
|
||||||
|
openConfirm(
|
||||||
|
() => onclickDelApp(app._id),
|
||||||
|
undefined,
|
||||||
|
app.type === AppTypeEnum.folder
|
||||||
|
? appT('Confirm delete folder tip')
|
||||||
|
: appT('Confirm Del App Tip')
|
||||||
|
)()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [])
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<Box
|
||||||
|
flex={1}
|
||||||
|
className={'textEllipsis3'}
|
||||||
|
py={2}
|
||||||
|
wordBreak={'break-all'}
|
||||||
|
fontSize={'mini'}
|
||||||
|
color={'myGray.600'}
|
||||||
|
>
|
||||||
|
{app.intro || '还没写介绍~'}
|
||||||
|
</Box>
|
||||||
|
<Flex h={'34px'} alignItems={'flex-end'}>
|
||||||
|
<Box flex={1}>
|
||||||
|
<PermissionIconText
|
||||||
|
defaultPermission={app.defaultPermission}
|
||||||
|
color={'myGray.600'}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</MyBox>
|
||||||
|
</MyTooltip>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{myApps.length === 0 && <EmptyTip text={'还没有应用,快去创建一个吧!'} pt={'30vh'} />}
|
||||||
|
<ConfirmModal />
|
||||||
|
{!!editedApp && (
|
||||||
|
<EditResourceModal
|
||||||
|
{...editedApp}
|
||||||
|
title="应用信息编辑"
|
||||||
|
onClose={() => {
|
||||||
|
setEditedApp(undefined);
|
||||||
|
}}
|
||||||
|
onEdit={({ id, ...data }) => onUpdateApp(id, data)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!!editPerApp && (
|
||||||
|
<ConfigPerModal
|
||||||
|
avatar={editPerApp.avatar}
|
||||||
|
name={editPerApp.name}
|
||||||
|
defaultPer={{
|
||||||
|
value: editPerApp.defaultPermission,
|
||||||
|
defaultValue: AppDefaultPermissionVal,
|
||||||
|
onChange: (e) => {
|
||||||
|
return onUpdateApp(editPerApp._id, { defaultPermission: e });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
managePer={{
|
||||||
|
permission: editPerApp.permission,
|
||||||
|
onGetCollaboratorList: () => getCollaboratorList(editPerApp._id),
|
||||||
|
permissionList: AppPermissionList,
|
||||||
|
onUpdateCollaborators: (tmbIds: string[], permission: number) => {
|
||||||
|
return postUpdateAppCollaborators({
|
||||||
|
tmbIds,
|
||||||
|
permission,
|
||||||
|
appId: editPerApp._id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onDelOneCollaborator: (tmbId: string) =>
|
||||||
|
deleteAppCollaborators({
|
||||||
|
appId: editPerApp._id,
|
||||||
|
tmbId
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
onClose={() => setEditPerAppIndex(undefined)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListItem;
|
||||||
138
projects/app/src/pages/app/list/component/context.tsx
Normal file
138
projects/app/src/pages/app/list/component/context.tsx
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import React, { ReactNode, useCallback, useState } from 'react';
|
||||||
|
import { createContext } from 'use-context-selector';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
import { getAppDetailById, getMyApps, putAppById } from '@/web/core/app/api';
|
||||||
|
import { AppDetailType, AppListItemType } from '@fastgpt/global/core/app/type';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getAppFolderPath } from '@/web/core/app/api/app';
|
||||||
|
import {
|
||||||
|
GetResourceFolderListProps,
|
||||||
|
ParentIdType,
|
||||||
|
ParentTreePathItemType
|
||||||
|
} from '@fastgpt/global/common/parentFolder/type';
|
||||||
|
import { AppUpdateParams } from '@/global/core/app/api';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import { useI18n } from '@/web/context/I18n';
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
import { delay } from '@fastgpt/global/common/system/utils';
|
||||||
|
|
||||||
|
type AppListContextType = {
|
||||||
|
parentId?: string | null;
|
||||||
|
myApps: AppListItemType[];
|
||||||
|
loadMyApps: () => void;
|
||||||
|
isFetchingApps: boolean;
|
||||||
|
folderDetail: AppDetailType | undefined | null;
|
||||||
|
paths: ParentTreePathItemType[];
|
||||||
|
onUpdateApp: (id: string, data: AppUpdateParams) => Promise<any>;
|
||||||
|
setMoveAppId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AppListContext = createContext<AppListContextType>({
|
||||||
|
parentId: undefined,
|
||||||
|
myApps: [],
|
||||||
|
loadMyApps: function (): void {
|
||||||
|
throw new Error('Function not implemented.');
|
||||||
|
},
|
||||||
|
isFetchingApps: false,
|
||||||
|
folderDetail: undefined,
|
||||||
|
paths: [],
|
||||||
|
onUpdateApp: function (id: string, data: AppUpdateParams): Promise<any> {
|
||||||
|
throw new Error('Function not implemented.');
|
||||||
|
},
|
||||||
|
setMoveAppId: function (value: React.SetStateAction<string | undefined>): void {
|
||||||
|
throw new Error('Function not implemented.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const MoveModal = dynamic(() => import('@/components/common/folder/MoveModal'));
|
||||||
|
|
||||||
|
const AppListContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
|
const { appT } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
|
const { parentId = null } = router.query as { parentId?: string | null };
|
||||||
|
|
||||||
|
const {
|
||||||
|
data = [],
|
||||||
|
runAsync: loadMyApps,
|
||||||
|
loading: isFetchingApps
|
||||||
|
} = useRequest2(() => getMyApps({ parentId }), {
|
||||||
|
manual: false,
|
||||||
|
refreshOnWindowFocus: true,
|
||||||
|
refreshDeps: [parentId]
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: paths = [], runAsync: refetchPaths } = useRequest2(
|
||||||
|
() => getAppFolderPath(parentId),
|
||||||
|
{
|
||||||
|
manual: false,
|
||||||
|
refreshDeps: [parentId]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: folderDetail, runAsync: refetchFolderDetail } = useRequest2(
|
||||||
|
() => {
|
||||||
|
if (parentId) return getAppDetailById(parentId);
|
||||||
|
return Promise.resolve(null);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
manual: false,
|
||||||
|
refreshDeps: [parentId]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { runAsync: onUpdateApp } = useRequest2((id: string, data: AppUpdateParams) =>
|
||||||
|
putAppById(id, data).then(async (res) => {
|
||||||
|
await Promise.all([refetchFolderDetail(), refetchPaths(), loadMyApps()]);
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const [moveAppId, setMoveAppId] = useState<string>();
|
||||||
|
const onMoveApp = useCallback(
|
||||||
|
async (parentId: ParentIdType) => {
|
||||||
|
if (!moveAppId) return;
|
||||||
|
await onUpdateApp(moveAppId, { parentId });
|
||||||
|
},
|
||||||
|
[moveAppId, onUpdateApp]
|
||||||
|
);
|
||||||
|
|
||||||
|
const getAppFolderList = useCallback(({ parentId }: GetResourceFolderListProps) => {
|
||||||
|
return getMyApps({
|
||||||
|
parentId,
|
||||||
|
type: AppTypeEnum.folder
|
||||||
|
}).then((res) =>
|
||||||
|
res.map((item) => ({
|
||||||
|
id: item._id,
|
||||||
|
name: item.name
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const contextValue: AppListContextType = {
|
||||||
|
parentId,
|
||||||
|
myApps: data,
|
||||||
|
loadMyApps,
|
||||||
|
isFetchingApps,
|
||||||
|
folderDetail,
|
||||||
|
paths,
|
||||||
|
onUpdateApp,
|
||||||
|
setMoveAppId
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<AppListContext.Provider value={contextValue}>
|
||||||
|
{children}
|
||||||
|
{!!moveAppId && (
|
||||||
|
<MoveModal
|
||||||
|
moveResourceId={moveAppId}
|
||||||
|
server={getAppFolderList}
|
||||||
|
title={appT('Move app')}
|
||||||
|
onClose={() => setMoveAppId(undefined)}
|
||||||
|
onConfirm={onMoveApp}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AppListContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppListContextProvider;
|
||||||
@ -1,192 +1,209 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { Box, Grid, Flex, IconButton, Button, useDisclosure } from '@chakra-ui/react';
|
import { Box, Flex, Button, useDisclosure } from '@chakra-ui/react';
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { AddIcon } from '@chakra-ui/icons';
|
import { AddIcon } from '@chakra-ui/icons';
|
||||||
import { delModelById } from '@/web/core/app/api';
|
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
|
||||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
|
||||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
|
||||||
import PageContainer from '@/components/PageContainer';
|
import PageContainer from '@/components/PageContainer';
|
||||||
import Avatar from '@/components/Avatar';
|
|
||||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
|
||||||
import CreateModal from './component/CreateModal';
|
import CreateModal from './component/CreateModal';
|
||||||
import { useAppStore } from '@/web/core/app/store/useAppStore';
|
|
||||||
import PermissionIconText from '@/components/support/permission/IconText';
|
|
||||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
import { useI18n } from '@/web/context/I18n';
|
import { useI18n } from '@/web/context/I18n';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
|
import List from './component/List';
|
||||||
|
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||||
|
import { FolderIcon } from '@fastgpt/global/common/file/image/constants';
|
||||||
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
import { postCreateAppFolder } from '@/web/core/app/api/app';
|
||||||
|
import type { EditFolderFormType } from '@fastgpt/web/components/common/MyModal/EditFolderModal';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import AppListContextProvider, { AppListContext } from './component/context';
|
||||||
|
import FolderPath from '@/components/common/folder/Path';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import FolderSlideCard from '@/components/common/folder/SlideCard';
|
||||||
|
import { delAppById } from '@/web/core/app/api';
|
||||||
|
import {
|
||||||
|
AppDefaultPermissionVal,
|
||||||
|
AppPermissionList
|
||||||
|
} from '@fastgpt/global/support/permission/app/constant';
|
||||||
|
import {
|
||||||
|
deleteAppCollaborators,
|
||||||
|
getCollaboratorList,
|
||||||
|
postUpdateAppCollaborators
|
||||||
|
} from '@/web/core/app/api/collaborator';
|
||||||
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
|
|
||||||
|
const EditFolderModal = dynamic(
|
||||||
|
() => import('@fastgpt/web/components/common/MyModal/EditFolderModal')
|
||||||
|
);
|
||||||
|
|
||||||
const MyApps = () => {
|
const MyApps = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { toast } = useToast();
|
const { appT } = useI18n();
|
||||||
const { appT, commonT } = useI18n();
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { isPc } = useSystemStore();
|
||||||
|
const {
|
||||||
|
paths,
|
||||||
|
parentId,
|
||||||
|
myApps,
|
||||||
|
loadMyApps,
|
||||||
|
onUpdateApp,
|
||||||
|
setMoveAppId,
|
||||||
|
isFetchingApps,
|
||||||
|
folderDetail
|
||||||
|
} = useContextSelector(AppListContext, (v) => v);
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
const { myApps, loadMyApps } = useAppStore();
|
|
||||||
const { openConfirm, ConfirmModal } = useConfirm({
|
|
||||||
type: 'delete',
|
|
||||||
content: '确认删除该应用所有信息?'
|
|
||||||
});
|
|
||||||
const {
|
const {
|
||||||
isOpen: isOpenCreateModal,
|
isOpen: isOpenCreateModal,
|
||||||
onOpen: onOpenCreateModal,
|
onOpen: onOpenCreateModal,
|
||||||
onClose: onCloseCreateModal
|
onClose: onCloseCreateModal
|
||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
|
const [editFolder, setEditFolder] = useState<EditFolderFormType>();
|
||||||
|
|
||||||
/* 点击删除 */
|
const { runAsync: onCreateFolder } = useRequest2(postCreateAppFolder, {
|
||||||
const onclickDelApp = useCallback(
|
onSuccess() {
|
||||||
async (id: string) => {
|
|
||||||
try {
|
|
||||||
await delModelById(id);
|
|
||||||
toast({
|
|
||||||
title: '删除成功',
|
|
||||||
status: 'success'
|
|
||||||
});
|
|
||||||
loadMyApps();
|
loadMyApps();
|
||||||
} catch (err: any) {
|
|
||||||
toast({
|
|
||||||
title: err?.message || t('common.Delete Failed'),
|
|
||||||
status: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[toast, loadMyApps, t]
|
errorToast: 'Error'
|
||||||
);
|
});
|
||||||
|
const { runAsync: onDeleFolder } = useRequest2(delAppById, {
|
||||||
/* 加载模型 */
|
onSuccess() {
|
||||||
const { isFetching } = useQuery(['loadApps'], () => loadMyApps(), {
|
router.replace({
|
||||||
refetchOnMount: true
|
query: {
|
||||||
|
parentId: folderDetail?.parentId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
errorToast: 'Error'
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer isLoading={isFetching} insertProps={{ px: [5, '48px'] }}>
|
<PageContainer
|
||||||
<Flex pt={[4, '30px']} alignItems={'center'} justifyContent={'space-between'}>
|
isLoading={myApps.length === 0 && isFetchingApps}
|
||||||
<Box letterSpacing={1} fontSize={['20px', '24px']} color={'myGray.900'}>
|
insertProps={{ px: folderDetail ? [4, 6] : [4, 10] }}
|
||||||
|
>
|
||||||
|
<Flex gap={5}>
|
||||||
|
<Box flex={'1 0 0'}>
|
||||||
|
<Flex pt={[4, 6]} alignItems={'center'} justifyContent={'space-between'}>
|
||||||
|
<FolderPath
|
||||||
|
paths={paths}
|
||||||
|
FirstPathDom={
|
||||||
|
<Box letterSpacing={1} fontSize={['md', 'lg']} color={'myGray.900'}>
|
||||||
{appT('My Apps')}
|
{appT('My Apps')}
|
||||||
</Box>
|
</Box>
|
||||||
{userInfo?.team.permission.hasWritePer && (
|
|
||||||
<Button leftIcon={<AddIcon />} variant={'primaryOutline'} onClick={onOpenCreateModal}>
|
|
||||||
{commonT('New Create')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
<Grid
|
|
||||||
py={[4, 6]}
|
|
||||||
gridTemplateColumns={['1fr', 'repeat(2,1fr)', 'repeat(3,1fr)', 'repeat(4,1fr)']}
|
|
||||||
gridGap={5}
|
|
||||||
>
|
|
||||||
{myApps.map((app) => (
|
|
||||||
<MyTooltip
|
|
||||||
key={app._id}
|
|
||||||
label={app.permission.hasWritePer ? appT('To Settings') : appT('To Chat')}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
lineHeight={1.5}
|
|
||||||
h={'100%'}
|
|
||||||
py={3}
|
|
||||||
px={5}
|
|
||||||
cursor={'pointer'}
|
|
||||||
borderWidth={'1.5px'}
|
|
||||||
borderColor={'borderColor.low'}
|
|
||||||
bg={'white'}
|
|
||||||
borderRadius={'md'}
|
|
||||||
userSelect={'none'}
|
|
||||||
position={'relative'}
|
|
||||||
display={'flex'}
|
|
||||||
flexDirection={'column'}
|
|
||||||
_hover={{
|
|
||||||
borderColor: 'primary.300',
|
|
||||||
boxShadow: '1.5',
|
|
||||||
'& .delete': {
|
|
||||||
display: 'flex'
|
|
||||||
},
|
|
||||||
'& .chat': {
|
|
||||||
display: 'flex'
|
|
||||||
}
|
}
|
||||||
}}
|
onClick={(parentId) => {
|
||||||
onClick={() => {
|
router.push({
|
||||||
if (app.permission.hasWritePer) {
|
query: {
|
||||||
router.push(`/app/detail?appId=${app._id}`);
|
parentId
|
||||||
} else {
|
|
||||||
router.push(`/chat?appId=${app._id}`);
|
|
||||||
}
|
}
|
||||||
}}
|
});
|
||||||
>
|
|
||||||
<Flex alignItems={'center'} h={'38px'}>
|
|
||||||
<Avatar src={app.avatar} borderRadius={'md'} w={'28px'} />
|
|
||||||
<Box ml={3}>{app.name}</Box>
|
|
||||||
{app.permission.isOwner && (
|
|
||||||
<IconButton
|
|
||||||
className="delete"
|
|
||||||
position={'absolute'}
|
|
||||||
top={4}
|
|
||||||
right={4}
|
|
||||||
size={'xsSquare'}
|
|
||||||
variant={'whiteDanger'}
|
|
||||||
icon={<MyIcon name={'delete'} w={'14px'} />}
|
|
||||||
aria-label={'delete'}
|
|
||||||
display={['', 'none']}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
openConfirm(() => onclickDelApp(app._id))();
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
<Box
|
|
||||||
flex={1}
|
|
||||||
className={'textEllipsis3'}
|
|
||||||
py={2}
|
|
||||||
wordBreak={'break-all'}
|
|
||||||
fontSize={'xs'}
|
|
||||||
color={'myGray.600'}
|
|
||||||
>
|
|
||||||
{app.intro || '这个应用还没写介绍~'}
|
|
||||||
</Box>
|
|
||||||
<Flex h={'34px'} alignItems={'flex-end'}>
|
|
||||||
<Box flex={1}>
|
|
||||||
<PermissionIconText
|
|
||||||
defaultPermission={app.defaultPermission}
|
|
||||||
color={'myGray.600'}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
{app.permission.hasWritePer && (
|
|
||||||
<IconButton
|
|
||||||
className="chat"
|
|
||||||
size={'xsSquare'}
|
|
||||||
variant={'whitePrimary'}
|
|
||||||
icon={
|
|
||||||
<MyTooltip label={'去聊天'}>
|
|
||||||
<MyIcon name={'core/chat/chatLight'} w={'14px'} />
|
|
||||||
</MyTooltip>
|
|
||||||
}
|
|
||||||
aria-label={'chat'}
|
|
||||||
display={['', 'none']}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
router.push(`/chat?appId=${app._id}`);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
</MyTooltip>
|
|
||||||
))}
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
{myApps.length === 0 && <EmptyTip text={'还没有应用,快去创建一个吧!'} pt={'30vh'} />}
|
{userInfo?.team.permission.hasWritePer && (
|
||||||
<ConfirmModal />
|
<MyMenu
|
||||||
{isOpenCreateModal && (
|
width={150}
|
||||||
<CreateModal onClose={onCloseCreateModal} onSuccess={() => loadMyApps()} />
|
iconSize="1.5rem"
|
||||||
|
Button={
|
||||||
|
<Button variant={'primary'} leftIcon={<AddIcon />}>
|
||||||
|
<Box>{t('common.Create New')}</Box>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
menuList={[
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
icon: 'core/app/simpleBot',
|
||||||
|
label: appT('Create bot'),
|
||||||
|
description: appT('Create one ai app'),
|
||||||
|
onClick: onOpenCreateModal
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
icon: FolderIcon,
|
||||||
|
label: t('Folder'),
|
||||||
|
onClick: () => setEditFolder({})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<List />
|
||||||
|
</Box>
|
||||||
|
{!!folderDetail && isPc && (
|
||||||
|
<Box pt={[4, 6]}>
|
||||||
|
<FolderSlideCard
|
||||||
|
name={folderDetail.name}
|
||||||
|
intro={folderDetail.intro}
|
||||||
|
onEdit={() => {
|
||||||
|
setEditFolder({
|
||||||
|
id: folderDetail._id,
|
||||||
|
name: folderDetail.name,
|
||||||
|
intro: folderDetail.intro
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onMove={() => setMoveAppId(folderDetail._id)}
|
||||||
|
deleteTip={appT('Confirm delete folder tip')}
|
||||||
|
onDelete={() => onDeleFolder(folderDetail._id)}
|
||||||
|
defaultPer={{
|
||||||
|
value: folderDetail.defaultPermission,
|
||||||
|
defaultValue: AppDefaultPermissionVal,
|
||||||
|
onChange: (e) => {
|
||||||
|
return onUpdateApp(folderDetail._id, { defaultPermission: e });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
managePer={{
|
||||||
|
permission: folderDetail.permission,
|
||||||
|
onGetCollaboratorList: () => getCollaboratorList(folderDetail._id),
|
||||||
|
permissionList: AppPermissionList,
|
||||||
|
onUpdateCollaborators: (tmbIds: string[], permission: number) => {
|
||||||
|
return postUpdateAppCollaborators({
|
||||||
|
tmbIds,
|
||||||
|
permission,
|
||||||
|
appId: folderDetail._id
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onDelOneCollaborator: (tmbId: string) =>
|
||||||
|
deleteAppCollaborators({
|
||||||
|
appId: folderDetail._id,
|
||||||
|
tmbId
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{!!editFolder && (
|
||||||
|
<EditFolderModal
|
||||||
|
{...editFolder}
|
||||||
|
onClose={() => setEditFolder(undefined)}
|
||||||
|
onCreate={(data) => onCreateFolder({ ...data, parentId })}
|
||||||
|
onEdit={({ id, ...data }) => onUpdateApp(id, data)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isOpenCreateModal && <CreateModal onClose={onCloseCreateModal} />}
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function ContextRender() {
|
||||||
|
return (
|
||||||
|
<AppListContextProvider>
|
||||||
|
<MyApps />
|
||||||
|
</AppListContextProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContextRender;
|
||||||
|
|
||||||
export async function getServerSideProps(content: any) {
|
export async function getServerSideProps(content: any) {
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
@ -194,5 +211,3 @@ export async function getServerSideProps(content: any) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MyApps;
|
|
||||||
|
|||||||
@ -108,7 +108,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
|
|||||||
usingReRank: res.usingReRank,
|
usingReRank: res.usingReRank,
|
||||||
limit: res.limit,
|
limit: res.limit,
|
||||||
similarity: res.similarity,
|
similarity: res.similarity,
|
||||||
usingQueryExtension: res.usingQueryExtension
|
queryExtensionModel: res.queryExtensionModel
|
||||||
};
|
};
|
||||||
pushDatasetTestItem(testItem);
|
pushDatasetTestItem(testItem);
|
||||||
setDatasetTestItem(testItem);
|
setDatasetTestItem(testItem);
|
||||||
@ -430,7 +430,7 @@ const TestResults = React.memo(function TestResults({
|
|||||||
similarity={datasetTestItem.similarity}
|
similarity={datasetTestItem.similarity}
|
||||||
limit={datasetTestItem.limit}
|
limit={datasetTestItem.limit}
|
||||||
usingReRank={datasetTestItem.usingReRank}
|
usingReRank={datasetTestItem.usingReRank}
|
||||||
usingQueryExtension={datasetTestItem.usingQueryExtension}
|
queryExtensionModel={datasetTestItem.queryExtensionModel}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@ -143,7 +143,7 @@ function request(
|
|||||||
): any {
|
): any {
|
||||||
/* 去空 */
|
/* 去空 */
|
||||||
for (const key in data) {
|
for (const key in data) {
|
||||||
if (data[key] === null || data[key] === undefined) {
|
if (data[key] === undefined) {
|
||||||
delete data[key];
|
delete data[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,25 @@
|
|||||||
import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
|
import { GET, POST, DELETE, PUT } from '@/web/common/api/request';
|
||||||
import type {
|
import type { AppDetailType, AppListItemType } from '@fastgpt/global/core/app/type.d';
|
||||||
AppDetailType,
|
|
||||||
AppListItemType,
|
|
||||||
ChatInputGuideConfigType
|
|
||||||
} from '@fastgpt/global/core/app/type.d';
|
|
||||||
import type { GetAppChatLogsParams } from '@/global/core/api/appReq.d';
|
import type { GetAppChatLogsParams } from '@/global/core/api/appReq.d';
|
||||||
import { AppUpdateParams, CreateAppParams } from '@/global/core/app/api';
|
import { AppUpdateParams } from '@/global/core/app/api';
|
||||||
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
import type { CreateAppBody } from '@/pages/api/core/app/create';
|
||||||
|
import type { ListAppBody } from '@/pages/api/core/app/list';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取模型列表
|
* 获取模型列表
|
||||||
*/
|
*/
|
||||||
export const getMyApps = () => GET<AppListItemType[]>('/core/app/list');
|
export const getMyApps = (data?: ListAppBody) => POST<AppListItemType[]>('/core/app/list', data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建一个模型
|
* 创建一个模型
|
||||||
*/
|
*/
|
||||||
export const postCreateApp = (data: CreateAppParams) => POST<string>('/core/app/create', data);
|
export const postCreateApp = (data: CreateAppBody) => POST<string>('/core/app/create', data);
|
||||||
|
|
||||||
export const getMyAppsByTags = (data: {}) => POST(`/proApi/core/chat/team/getApps`, data);
|
export const getMyAppsByTags = (data: {}) => POST(`/proApi/core/chat/team/getApps`, data);
|
||||||
/**
|
/**
|
||||||
* 根据 ID 删除模型
|
* 根据 ID 删除模型
|
||||||
*/
|
*/
|
||||||
export const delModelById = (id: string) => DELETE(`/core/app/del?appId=${id}`);
|
export const delAppById = (id: string) => DELETE(`/core/app/del?appId=${id}`);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据 ID 获取模型
|
* 根据 ID 获取模型
|
||||||
|
|||||||
11
projects/app/src/web/core/app/api/app.ts
Normal file
11
projects/app/src/web/core/app/api/app.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { DELETE, GET, POST } from '@/web/common/api/request';
|
||||||
|
import type { CreateAppFolderBody } from '@/pages/api/core/app/folder/create';
|
||||||
|
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
|
||||||
|
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||||
|
|
||||||
|
/* folder */
|
||||||
|
export const postCreateAppFolder = (data: CreateAppFolderBody) =>
|
||||||
|
POST('/core/app/folder/create', data);
|
||||||
|
|
||||||
|
export const getAppFolderPath = (parentId: ParentIdType) =>
|
||||||
|
GET<ParentTreePathItemType[]>(`/core/app/folder/path`, { parentId });
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
import { AppDetailType } from '@fastgpt/global/core/app/type.d';
|
import { AppDetailType } from '@fastgpt/global/core/app/type.d';
|
||||||
import type { FeishuType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
|
import type { FeishuType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
|
||||||
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
|
import { AppPermission } from '@fastgpt/global/support/permission/app/controller';
|
||||||
@ -6,7 +7,7 @@ import { NullPermission } from '@fastgpt/global/support/permission/constant';
|
|||||||
export const defaultApp: AppDetailType = {
|
export const defaultApp: AppDetailType = {
|
||||||
_id: '',
|
_id: '',
|
||||||
name: '应用加载中',
|
name: '应用加载中',
|
||||||
type: 'simple',
|
type: AppTypeEnum.simple,
|
||||||
avatar: '/icon/logo.svg',
|
avatar: '/icon/logo.svg',
|
||||||
intro: '',
|
intro: '',
|
||||||
updateTime: Date.now(),
|
updateTime: Date.now(),
|
||||||
|
|||||||
@ -1,31 +1,25 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { devtools, persist } from 'zustand/middleware';
|
import { devtools } from 'zustand/middleware';
|
||||||
import { immer } from 'zustand/middleware/immer';
|
import { immer } from 'zustand/middleware/immer';
|
||||||
import { getMyApps } from '@/web/core/app/api';
|
import { getMyApps } from '@/web/core/app/api';
|
||||||
import { AppListItemType } from '@fastgpt/global/core/app/type';
|
import { AppListItemType } from '@fastgpt/global/core/app/type';
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
myApps: AppListItemType[];
|
myApps: AppListItemType[];
|
||||||
loadMyApps: () => Promise<AppListItemType[]>;
|
loadMyApps: (...arg: Parameters<typeof getMyApps>) => Promise<AppListItemType[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAppStore = create<State>()(
|
export const useAppStore = create<State>()(
|
||||||
devtools(
|
devtools(
|
||||||
persist(
|
|
||||||
immer((set, get) => ({
|
immer((set, get) => ({
|
||||||
myApps: [],
|
myApps: [],
|
||||||
async loadMyApps() {
|
async loadMyApps(data) {
|
||||||
const res = await getMyApps();
|
const res = await getMyApps(data);
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.myApps = res;
|
state.myApps = res;
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
})),
|
}))
|
||||||
{
|
|
||||||
name: 'appStore',
|
|
||||||
partialize: (state) => ({})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
export const appTemplates: (AppItemType & {
|
export const appTemplates: (AppItemType & {
|
||||||
avatar: string;
|
avatar: string;
|
||||||
intro: string;
|
intro: string;
|
||||||
type: `${AppTypeEnum}`;
|
type: AppTypeEnum;
|
||||||
})[] = [
|
})[] = [
|
||||||
{
|
{
|
||||||
id: 'simpleChat',
|
id: 'simpleChat',
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export type SearchTestStoreItemType = {
|
|||||||
limit: number;
|
limit: number;
|
||||||
usingReRank: boolean;
|
usingReRank: boolean;
|
||||||
similarity: number;
|
similarity: number;
|
||||||
usingQueryExtension: boolean;
|
queryExtensionModel?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user