From 5892ded567f61cb435a63e5d0f905d2e029300f8 Mon Sep 17 00:00:00 2001 From: papapatrick <109422393+Patrickill@users.noreply.github.com> Date: Tue, 12 Nov 2024 10:09:02 +0800 Subject: [PATCH] feat: add more share config (#3120) * feat: add more share config * add i18n en --- packages/global/core/chat/utils.ts | 2 +- packages/global/support/outLink/api.d.ts | 4 + packages/global/support/outLink/type.d.ts | 6 + .../service/core/workflow/dispatch/utils.ts | 14 +- packages/service/support/outLink/schema.ts | 8 + packages/web/i18n/en/app.json | 1 + packages/web/i18n/en/chat.json | 1 + packages/web/i18n/en/common.json | 16 +- packages/web/i18n/en/dataset.json | 4 +- packages/web/i18n/en/publish.json | 6 +- packages/web/i18n/en/user.json | 31 ++- packages/web/i18n/en/workflow.json | 2 + packages/web/i18n/zh/common.json | 7 +- packages/web/i18n/zh/publish.json | 10 +- .../chat/ChatContainer/ChatBox/Provider.tsx | 2 +- .../ChatBox/components/ResponseTags.tsx | 110 +++++---- .../src/components/core/dataset/QuoteItem.tsx | 12 +- .../components/core/dataset/RawSourceBox.tsx | 12 +- .../pages/api/core/dataset/collection/read.ts | 19 +- .../src/pages/api/support/outLink/update.ts | 4 +- .../app/src/pages/api/v1/chat/completions.ts | 13 +- .../detail/components/Publish/Link/index.tsx | 210 +++++++++++------- projects/app/src/pages/chat/share.tsx | 11 +- projects/app/src/pages/chat/team.tsx | 1 + .../detail/components/MetaDataCard.tsx | 14 +- .../support/permission/auth/outLink.ts | 9 +- projects/app/src/web/core/app/constants.ts | 2 + .../src/web/core/chat/context/chatContext.tsx | 10 +- projects/app/src/web/core/dataset/api.ts | 6 +- .../dataset/hooks/readCollectionSource.ts | 15 +- 30 files changed, 389 insertions(+), 173 deletions(-) diff --git a/packages/global/core/chat/utils.ts b/packages/global/core/chat/utils.ts index d3b590633..3c252861c 100644 --- a/packages/global/core/chat/utils.ts +++ b/packages/global/core/chat/utils.ts @@ -82,7 +82,7 @@ export const filterPublicNodeResponseData = ({ }: { flowResponses?: ChatHistoryItemResType[]; }) => { - const filedList = ['quoteList', 'moduleType', 'pluginOutput']; + const filedList = ['quoteList', 'moduleType', 'pluginOutput', 'runningTime']; const filterModuleTypeList: any[] = [ FlowNodeTypeEnum.pluginModule, FlowNodeTypeEnum.datasetSearchNode, diff --git a/packages/global/support/outLink/api.d.ts b/packages/global/support/outLink/api.d.ts index 0088ae7d7..69f2fc540 100644 --- a/packages/global/support/outLink/api.d.ts +++ b/packages/global/support/outLink/api.d.ts @@ -10,3 +10,7 @@ export type AuthOutLinkLimitProps = AuthOutLinkChatProps & { outLink: OutLinkSch export type AuthOutLinkResponse = { uid: string; }; +export type AuthOutLinkProps = { + shareId?: string; + outLinkUid?: string; +}; diff --git a/packages/global/support/outLink/type.d.ts b/packages/global/support/outLink/type.d.ts index ec882c00b..77053f36a 100644 --- a/packages/global/support/outLink/type.d.ts +++ b/packages/global/support/outLink/type.d.ts @@ -51,6 +51,10 @@ export type OutLinkSchema = { // whether the response content is detailed responseDetail: boolean; + // whether to hide the node status + showNodeStatus: boolean; + // whether to show the complete quote + showCompleteQuote: boolean; // response when request immediateResponse?: string; @@ -79,6 +83,8 @@ export type OutLinkEditType = { _id?: string; name: string; responseDetail?: OutLinkSchema['responseDetail']; + showNodeStatus?: OutLinkSchema['showNodeStatus']; + showCompleteQuote?: OutLinkSchema['showCompleteQuote']; // response when request immediateResponse?: string; // response when error or other situation diff --git a/packages/service/core/workflow/dispatch/utils.ts b/packages/service/core/workflow/dispatch/utils.ts index 01ef6878e..4410d065f 100644 --- a/packages/service/core/workflow/dispatch/utils.ts +++ b/packages/service/core/workflow/dispatch/utils.ts @@ -18,12 +18,14 @@ export const getWorkflowResponseWrite = ({ res, detail, streamResponse, - id = getNanoid(24) + id = getNanoid(24), + showNodeStatus = true }: { res?: NextApiResponse; detail: boolean; streamResponse: boolean; id?: string; + showNodeStatus?: boolean; }) => { return ({ write, @@ -50,8 +52,18 @@ export const getWorkflowResponseWrite = ({ SseResponseEventEnum.toolResponse, SseResponseEventEnum.updateVariables ]; + if (!detail && detailEvent.includes(event)) return; + if ( + !showNodeStatus && + (event === SseResponseEventEnum.flowNodeStatus || + event === SseResponseEventEnum.toolCall || + event === SseResponseEventEnum.toolParams || + event === SseResponseEventEnum.toolResponse) + ) + return; + responseWrite({ res, write, diff --git a/packages/service/support/outLink/schema.ts b/packages/service/support/outLink/schema.ts index d5e6dc000..e12ac39fb 100644 --- a/packages/service/support/outLink/schema.ts +++ b/packages/service/support/outLink/schema.ts @@ -46,6 +46,14 @@ const OutLinkSchema = new Schema({ type: Boolean, default: false }, + showNodeStatus: { + type: Boolean, + default: false + }, + showCompleteQuote: { + type: Boolean, + default: false + }, limit: { maxUsagePoints: { type: Number, diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json index b0f370d3a..b8bdb7b98 100644 --- a/packages/web/i18n/en/app.json +++ b/packages/web/i18n/en/app.json @@ -74,6 +74,7 @@ "module.type": "\"{{type}}\" type\n{{description}}", "modules.Title is required": "Module name cannot be empty", "month.unit": "Day", + "move.hint": "After moving, the selected application/folder will inherit the permission settings of the new folder, and the original permission settings will become invalid.", "move_app": "Move Application", "not_json_file": "Please select a JSON file", "open_vision_function_tip": "Models with icon switches have image recognition capabilities. \nAfter being turned on, the model will parse the pictures in the file link and automatically parse the pictures in the user's question (user question ≤ 500 words).", diff --git a/packages/web/i18n/en/chat.json b/packages/web/i18n/en/chat.json index 27d940150..2f48e8371 100644 --- a/packages/web/i18n/en/chat.json +++ b/packages/web/i18n/en/chat.json @@ -35,6 +35,7 @@ "not_select_file": "No file selected", "plugins_output": "Plugin Output", "question_tip": "From top to bottom, the response order of each module", + "response.child total points": "Sub-workflow point consumption", "response.dataset_concat_length": "Combined total", "response.node_inputs": "Node Inputs", "select": "Select", diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index f51568158..a042562b9 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -53,6 +53,7 @@ "code_error.error_code.502": "Gateway Error", "code_error.error_code.503": "Server Overloaded or Under Maintenance", "code_error.error_code.504": "Gateway Timeout", + "code_error.error_code[429]": "Requests are too frequent", "code_error.error_message.403": "Credential Error", "code_error.error_message.510": "Insufficient Account Balance", "code_error.error_message.511": "Unauthorized to Operate This Model", @@ -79,6 +80,7 @@ "code_error.team_error.plugin_amount_not_enough": "Plugin Limit Reached", "code_error.team_error.re_rank_not_enough": "Unauthorized to Use Re-Rank", "code_error.team_error.un_auth": "Unauthorized to Operate This Team", + "code_error.team_error.user_not_active": "The user did not accept or has left the team", "code_error.team_error.website_sync_not_enough": "Unauthorized to Use Website Sync", "code_error.token_error_code.403": "Invalid Login Status, Please Re-login", "code_error.user_error.balance_not_enough": "Insufficient Account Balance", @@ -120,6 +122,7 @@ "common.Documents": "Documents", "common.Done": "Done", "common.Edit": "Edit", + "common.Error": "Error", "common.Exit": "Exit", "common.Exit Directly": "Exit Directly", "common.Expired Time": "Expiration Time", @@ -151,6 +154,7 @@ "common.Params": "Parameters", "common.Password inconsistency": "Passwords Do Not Match", "common.Permission": "Permission", + "common.Permission_tip": "Individual permissions are greater than group permissions", "common.Please Input Name": "Please Enter a Name", "common.Read document": "Read Document", "common.Read intro": "Read Introduction", @@ -188,7 +192,6 @@ "common.Update Successful": "Updated Successfully", "common.Username": "Username", "common.Waiting": "Waiting", - "common.Error": "Error", "common.Warning": "Warning", "common.Website": "Website", "common.all_result": "Full Results", @@ -546,6 +549,7 @@ "core.dataset.import.Chunk Range": "Range: {{min}}~{{max}}", "core.dataset.import.Chunk Split": "Direct Segmentation", "core.dataset.import.Chunk Split Tip": "Segment the text according to certain rules and convert it into a format that can be semantically searched. Suitable for most scenarios. No additional model processing is required, and the cost is low.", + "core.dataset.import.Continue upload": "Continue upload", "core.dataset.import.Custom process": "Custom Rules", "core.dataset.import.Custom process desc": "Customize segmentation and preprocessing rules", "core.dataset.import.Custom prompt": "Custom Prompt", @@ -574,11 +578,10 @@ "core.dataset.import.Select source": "Select Source", "core.dataset.import.Source name": "Source Name", "core.dataset.import.Sources list": "Source List", - "core.dataset.import.Continue upload": "Continue upload", - "core.dataset.import.Upload complete": "Upload complete", "core.dataset.import.Start upload": "Start Upload", "core.dataset.import.Total files": "Total {{total}} Files", "core.dataset.import.Training mode": "Training Mode", + "core.dataset.import.Upload complete": "Upload complete", "core.dataset.import.Upload data": "Confirm Upload", "core.dataset.import.Upload file progress": "File Upload Progress", "core.dataset.import.Upload status": "Status", @@ -889,7 +892,9 @@ "is_using": "In Use", "item_description": "Field Description", "item_name": "Field Name", + "just_now": "just", "key_repetition": "Key Repetition", + "move.confirm": "Confirm move", "navbar.Account": "Account", "navbar.Chat": "Chat", "navbar.Datasets": "Datasets", @@ -970,6 +975,9 @@ "support.outlink.Usage points": "Points Consumption", "support.outlink.share.Response Quote": "Return Quote", "support.outlink.share.Response Quote tips": "Return quoted content in the share link, but do not allow users to download the original document", + "support.outlink.share.running_node": "Running node", + "support.outlink.share.show_complete_quote": "View original source", + "support.outlink.share.show_complete_quote_tips": "View and download the complete citation document, or jump to the citation website", "support.permission.Permission": "Permission", "support.standard.AI Bonus Points": "AI Points", "support.standard.due_date": "Due Date", @@ -1200,6 +1208,8 @@ "user.team.member.waiting": "Pending Acceptance", "user.team.role.Admin": "Admin", "user.team.role.Owner": "Owner", + "user.team.role.Visitor": "visitor", + "user.team.role.writer": "writable member", "user.type": "Type", "verification": "Verification", "xx_search_result": "{{key}} Search Results", diff --git a/packages/web/i18n/en/dataset.json b/packages/web/i18n/en/dataset.json index 8f95e0ae8..7db8c2f9c 100644 --- a/packages/web/i18n/en/dataset.json +++ b/packages/web/i18n/en/dataset.json @@ -8,6 +8,7 @@ "confirm_to_rebuild_embedding_tip": "Are you sure you want to switch the index for the Dataset?\nSwitching the index is a significant operation that requires re-indexing all data in your Dataset, which may take a long time. Please ensure your account has sufficient remaining points.\n\nAdditionally, you need to update the applications that use this Dataset to avoid conflicts with other indexed model Datasets.", "custom_data_process_params": "Custom", "custom_data_process_params_desc": "Customize data processing rules", + "data.ideal_chunk_length": "ideal block length", "data_process_params": "Processing parameters", "data_process_setting": "Data processing configuration", "dataset.no_collections": "No datasets available", @@ -25,6 +26,7 @@ "ideal_chunk_length_tips": "Segment according to the end symbol and combine multiple segments into one block. This value determines the estimated size of the block, if there is any fluctuation.", "import.Auto mode Estimated Price Tips": "The text understanding model needs to be called, which requires more points: {{price}} points/1K tokens", "import.Embedding Estimated Price Tips": "Only use the index model and consume a small amount of AI points: {{price}} points/1K tokens", + "move.hint": "After moving, the selected knowledge base/folder will inherit the permission settings of the new folder, and the original permission settings will become invalid.", "permission.des.manage": "Can manage the entire knowledge base data and information", "permission.des.read": "View knowledge base content", "permission.des.write": "Ability to add and change knowledge base content", @@ -44,4 +46,4 @@ "training_mode": "Chunk mode", "website_dataset": "Website Sync", "website_dataset_desc": "Website sync allows you to build a Dataset directly using a web link." -} \ No newline at end of file +} diff --git a/packages/web/i18n/en/publish.json b/packages/web/i18n/en/publish.json index f0b2e00cf..4aebe4aef 100644 --- a/packages/web/i18n/en/publish.json +++ b/packages/web/i18n/en/publish.json @@ -1,6 +1,7 @@ { "app_key_tips": "These keys are already linked to the current application. Check the documentation for detailed usage.", "basic_info": "Basic Info", + "config": "Visibility configuration", "copy_link_hint": "Copy the link below to the specified location", "create_api_key": "Create New Key", "create_link": "Create Link", @@ -22,7 +23,10 @@ "publish_name": "Name", "qpm_is_empty": "QPM cannot be empty", "qpm_tips": "Maximum number of queries per minute per IP", + "quote_content": "Quote content", "request_address": "Request URL", + "show_node": "real-time running status", + "show_origin_content": "View original source", "show_share_link_modal_title": "Get Started", "token_auth": "Token Authentication", "token_auth_tips": "Token authentication server URL. If provided, a request will be sent to the specified server for authentication before each conversation.", @@ -33,4 +37,4 @@ "wecom.create_modal_title": "Create WeCom Bot", "wecom.edit_modal_title": "Edit WeCom Bot", "wecom.title": "Publish to WeCom Bot" -} \ No newline at end of file +} diff --git a/packages/web/i18n/en/user.json b/packages/web/i18n/en/user.json index 0af708fd7..6a59c1f96 100644 --- a/packages/web/i18n/en/user.json +++ b/packages/web/i18n/en/user.json @@ -22,6 +22,8 @@ "bind_inform_account_success": "Notification Account Bound Successfully", "delete.admin_failed": "Failed to Delete Admin", "delete.admin_success": "Admin Deleted Successfully", + "delete.failed": "Delete failed", + "delete.success": "Delete successfully", "has_chosen": "Selected", "individuation": "Individuation", "login.error": "Login Error", @@ -32,6 +34,7 @@ "notification.Bind Notification Pipe Hint": "Please bind a notification receiving account to ensure you receive notifications such as plan expiration reminders, ensuring your service runs smoothly.", "notification.remind_owner_bind": "Please remind the creator to bind a notification account", "operations": "Actions", + "owner": "owner", "password.code_required": "Verification Code Required", "password.code_send_error": "Failed to Send Verification Code", "password.code_sended": "Verification Code Sent", @@ -76,6 +79,32 @@ "synchronization.title": "Enter the sync tag link and click the sync button to synchronize", "team.Add manager": "Add Admin", "team.add_collaborator": "Add Collaborator", + "team.add_writer": "Add writable members", + "team.avatar_and_name": "avatar", + "team.belong_to_group": "Member group", + "team.group.avatar": "Group avatar", + "team.group.create": "Create group", + "team.group.create_failed": "Failed to create group", + "team.group.default_group": "Default group", + "team.group.delete_confirm": "Confirm to delete group?", + "team.group.edit": "Edit group", + "team.group.edit_info": "Edit information", + "team.group.group": "group", + "team.group.keep_admin": "Keep administrator rights", + "team.group.manage_member": "Managing members", + "team.group.manage_tip": "You can invite members, delete members, create groups, manage all groups, and assign permissions to groups and members", + "team.group.members": "member", + "team.group.name": "Group name", + "team.group.permission.manage": "administrator", + "team.group.permission.write": "Workbench/knowledge base creation", + "team.group.permission_tip": "Members with individually configured permissions will follow the individual permission configuration and will no longer be affected by group permissions.\n\nIf a member is in multiple permission groups, the member's permissions are combined.", + "team.group.role.admin": "administrator", + "team.group.role.member": "member", + "team.group.role.owner": "owner", + "team.group.search_placeholder": "Search member/group name", + "team.group.set_as_admin": "Set as administrator", + "team.group.toast.can_not_delete_owner": "Owner cannot be deleted, please transfer first", + "team.group.transfer_owner": "transfer owner", "team.manage_collaborators": "Manage Collaborators", "team.no_collaborators": "No Collaborators", "team.write_role_member": "", @@ -84,4 +113,4 @@ "usage.share": "Share Link", "usage.wecom": "WeCom", "usage_record": "Usages" -} \ No newline at end of file +} diff --git a/packages/web/i18n/en/workflow.json b/packages/web/i18n/en/workflow.json index 2f250675f..7940b4b44 100644 --- a/packages/web/i18n/en/workflow.json +++ b/packages/web/i18n/en/workflow.json @@ -29,6 +29,7 @@ "contains": "Contains", "content_to_retrieve": "Content to Retrieve", "content_to_search": "Content to Search", + "contextMenu.addComment": "Add comment", "context_menu.add_comment": "Add comment", "create_link_error": "Error creating link", "custom_feedback": "Custom Feedback", @@ -177,6 +178,7 @@ "tool_params.params_description_placeholder": "Name/Age/SQL statement..", "tool_params.params_name": "Name", "tool_params.params_name_placeholder": "name/age/sql", + "tool_params.tool_params_result": "Parameter configuration results", "trigger_after_application_completion": "Will be triggered after the application is fully completed", "update_link_error": "Error updating link", "update_specified_node_output_or_global_variable": "Can update the output value of a specified node or update global variables", diff --git a/packages/web/i18n/zh/common.json b/packages/web/i18n/zh/common.json index 9657b3661..d963a4198 100644 --- a/packages/web/i18n/zh/common.json +++ b/packages/web/i18n/zh/common.json @@ -974,8 +974,11 @@ "support.outlink.Max usage points": "积分上限", "support.outlink.Max usage points tip": "该链接最多允许使用多少积分,超出后将无法使用。-1 代表无限制。", "support.outlink.Usage points": "积分消耗", - "support.outlink.share.Response Quote": "返回引用", - "support.outlink.share.Response Quote tips": "在分享链接中返回引用内容,但不会允许用户下载原文档", + "support.outlink.share.Response Quote": "引用内容", + "support.outlink.share.Response Quote tips": "查看知识库搜索的引用内容,不可查看完整引用文档或跳转引用网站", + "support.outlink.share.running_node": "运行节点", + "support.outlink.share.show_complete_quote": "查看来源原文", + "support.outlink.share.show_complete_quote_tips": "查看及下载完整引用文档,或跳转引用网站", "support.permission.Permission": "权限", "support.standard.AI Bonus Points": "AI 积分", "support.standard.due_date": "到期时间", diff --git a/packages/web/i18n/zh/publish.json b/packages/web/i18n/zh/publish.json index e36bf6a37..bd1428711 100644 --- a/packages/web/i18n/zh/publish.json +++ b/packages/web/i18n/zh/publish.json @@ -1,6 +1,7 @@ { "app_key_tips": "这些 key 已有当前应用标识,具体使用可参考文档", "basic_info": "基本信息", + "config": "可见度配置", "copy_link_hint": "将下面链接复制到指定位置", "create_api_key": "创建新 key", "create_link": "创建链接", @@ -25,12 +26,15 @@ "request_address": "请求地址", "show_share_link_modal_title": "开始使用", "token_auth": "身份验证", - "token_auth_tips": "身份校验服务器地址,如填写该值,每次对话前都会向指定服务器发送一个请求,进行身份校验", + "token_auth_tips": "身份校验服务器地址", "token_auth_use_cases": "查看身份验证使用说明", "wecom.api": "企微 API", "wecom.bot": "企业微信机器人", "wecom.bot_desc": "通过 API 直接接入企业微信机器人", "wecom.create_modal_title": "创建企微机器人", "wecom.edit_modal_title": "编辑企微机器人", - "wecom.title": "发布到企业微信机器人" -} \ No newline at end of file + "wecom.title": "发布到企业微信机器人", + "show_node": "实时运行状态", + "quote_content": "引用内容", + "show_origin_content": "查看来源原文" +} diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/Provider.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/Provider.tsx index 57fbc6e0a..5e692ccc5 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/Provider.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/Provider.tsx @@ -34,7 +34,7 @@ export type ChatProviderProps = OutLinkChatAuthProps & { // not chat test params chatId?: string; - chatType?: 'log' | 'chat'; + chatType?: 'log' | 'chat' | 'share' | 'team'; }; type useChatStoreType = OutLinkChatAuthProps & diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx index 48e2cc5f6..31242e967 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx @@ -13,6 +13,9 @@ import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { ChatSiteItemType } from '@fastgpt/global/core/chat/type'; import { addStatisticalDataToHistoryItem } from '@/global/core/chat/utils'; import { useSize } from 'ahooks'; +import { ChatContext } from '@/web/core/chat/context/chatContext'; +import { useContextSelector } from 'use-context-selector'; +import { ChatBoxContext } from '../Provider'; const QuoteModal = dynamic(() => import('./QuoteModal')); const ContextModal = dynamic(() => import('./ContextModal')); @@ -37,6 +40,7 @@ const ResponseTags = ({ totalRunningTime: runningTime = 0, historyPreviewLength = 0 } = useMemo(() => addStatisticalDataToHistoryItem(historyItem), [historyItem]); + const [quoteModalData, setQuoteModalData] = useState<{ rawSearch: SearchDataResponseItemType[]; metadata?: { @@ -47,6 +51,13 @@ const ResponseTags = ({ }>(); const [quoteFolded, setQuoteFolded] = useState(true); + const showCompleteQuote = useContextSelector(ChatContext, (v) => v.showCompleteQuote); + const { chatType } = useContextSelector(ChatBoxContext, (v) => v); + + const showAllTag = useMemo(() => { + return chatType !== 'share' && chatType !== 'team'; + }, [chatType]); + const { isOpen: isOpenWholeModal, onOpen: onOpenWholeModal, @@ -77,10 +88,10 @@ const ResponseTags = ({ sourceName: item.sourceName, sourceId: item.sourceId, icon: getSourceNameIcon({ sourceId: item.sourceId, sourceName: item.sourceName }), - canReadQuote: showDetail || strIsLink(item.sourceId), + canReadQuote: showCompleteQuote || strIsLink(item.sourceId), collectionId: item.collectionId })); - }, [quoteList, showDetail]); + }, [quoteList, showCompleteQuote]); return !showTags ? null : ( <> @@ -176,49 +187,51 @@ const ResponseTags = ({ )} - {showDetail && ( - - {quoteList.length > 0 && ( - - setQuoteModalData({ rawSearch: quoteList })} - > - {t('chat:citations', { num: quoteList.length })} - - - )} - {llmModuleAccount === 1 && ( - <> - {historyPreviewLength > 0 && ( - - - {t('chat:contextual', { num: historyPreviewLength })} - - - )} - - )} - {llmModuleAccount > 1 && ( - - {t('chat:multiple_AI_conversations')} - - )} - {isPc && runningTime > 0 && ( - - - {runningTime}s - - - )} + + {quoteList.length > 0 && ( + + setQuoteModalData({ rawSearch: quoteList })} + > + {t('chat:citations', { num: quoteList.length })} + + + )} + {llmModuleAccount === 1 && showAllTag && ( + <> + {historyPreviewLength > 0 && ( + + + {t('chat:contextual', { num: historyPreviewLength })} + + + )} + + )} + {llmModuleAccount > 1 && showAllTag && ( + + {t('chat:multiple_AI_conversations')} + + )} + + {isPc && runningTime > 0 && ( + + + {runningTime}s + + + )} + + {showAllTag && ( - - )} + )} + + {!!quoteModalData && ( setQuoteModalData(undefined)} /> )} {isOpenContextModal && } {isOpenWholeModal && ( - + )} ); diff --git a/projects/app/src/components/core/dataset/QuoteItem.tsx b/projects/app/src/components/core/dataset/QuoteItem.tsx index 2a6b8f563..1c3fefaf8 100644 --- a/projects/app/src/components/core/dataset/QuoteItem.tsx +++ b/projects/app/src/components/core/dataset/QuoteItem.tsx @@ -9,6 +9,8 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import dynamic from 'next/dynamic'; import MyBox from '@fastgpt/web/components/common/MyBox'; import { SearchScoreTypeEnum, SearchScoreTypeMap } from '@fastgpt/global/core/dataset/constants'; +import { useContextSelector } from 'use-context-selector'; +import { ChatBoxContext } from '../chat/ChatContainer/ChatBox/Provider'; const InputDataModal = dynamic(() => import('@/pages/dataset/detail/components/InputDataModal')); @@ -54,6 +56,12 @@ const QuoteItem = ({ const { t } = useTranslation(); const [editInputData, setEditInputData] = useState<{ dataId: string; collectionId: string }>(); + const { chatType } = useContextSelector(ChatBoxContext, (v) => v); + + const canEdit = useMemo(() => { + return chatType !== 'share' && chatType !== 'team'; + }, [chatType]); + const score = useMemo(() => { if (!Array.isArray(quoteItem.score)) { return { @@ -224,7 +232,7 @@ const QuoteItem = ({ canView={canViewSource} /> - {quoteItem.id && ( + {quoteItem.id && canEdit && ( )} - {linkToDataset && ( + {linkToDataset && canEdit && ( { const { t } = useTranslation(); const { fileT } = useI18n(); + const { shareId, outLinkUid, chatType } = useContextSelector(ChatBoxContext, (v) => v); const canPreview = !!sourceId && canView; const icon = useMemo(() => getSourceNameIcon({ sourceId, sourceName }), [sourceId, sourceName]); - const read = getCollectionSourceAndOpen(collectionId); + const read = getCollectionSourceAndOpen({ + collectionId, + authProps: { + shareId, + outLinkUid + }, + isShare: chatType === 'share' + }); return ( ): Promise { + const { isShare, outLinkUid, shareId } = req.body; + + if (isShare) { + await authOutLink({ shareId, outLinkUid }); + } + const { collection, teamId, tmbId } = await authDatasetCollection({ req, authToken: true, authApiKey: true, - collectionId: req.query.collectionId, + collectionId: req.body.collectionId, per: ReadPermissionVal }); diff --git a/projects/app/src/pages/api/support/outLink/update.ts b/projects/app/src/pages/api/support/outLink/update.ts index 1cb9ed9da..9108a46a7 100644 --- a/projects/app/src/pages/api/support/outLink/update.ts +++ b/projects/app/src/pages/api/support/outLink/update.ts @@ -24,7 +24,7 @@ export type OutLinkUpdateResponse = {}; async function handler( req: ApiRequestProps ): Promise { - const { _id, name, responseDetail, limit, app } = req.body; + const { _id, name, responseDetail, limit, app, showCompleteQuote, showNodeStatus } = req.body; if (!_id) { return Promise.reject(CommonErrEnum.missingParams); @@ -35,6 +35,8 @@ async function handler( await MongoOutLink.findByIdAndUpdate(_id, { name, responseDetail, + showCompleteQuote, + showNodeStatus, limit, app }); diff --git a/projects/app/src/pages/api/v1/chat/completions.ts b/projects/app/src/pages/api/v1/chat/completions.ts index 12d417235..6d9e0fedd 100644 --- a/projects/app/src/pages/api/v1/chat/completions.ts +++ b/projects/app/src/pages/api/v1/chat/completions.ts @@ -83,6 +83,8 @@ type AuthResponseType = { user: UserModelSchema; app: AppSchema; responseDetail?: boolean; + showNodeStatus?: boolean; + showCompleteQuote?: boolean; authType: `${AuthUserTypeEnum}`; apikey?: string; canWrite: boolean; @@ -160,7 +162,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { sourceName, apikey, canWrite, - outLinkUserId = customUid + outLinkUserId = customUid, + showNodeStatus } = await (async () => { // share chat if (shareId && outLinkUid) { @@ -256,7 +259,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { res, detail, streamResponse: stream, - id: chatId + id: chatId, + showNodeStatus }); /* start flow controller */ @@ -461,7 +465,7 @@ const authShareChat = async ({ shareId: string; chatId?: string; }): Promise => { - const { teamId, tmbId, user, appId, authType, responseDetail, uid, sourceName } = + const { teamId, tmbId, user, appId, authType, responseDetail, showNodeStatus, uid, sourceName } = await authOutLinkChatStart(data); const app = await MongoApp.findById(appId).lean(); @@ -485,7 +489,8 @@ const authShareChat = async ({ apikey: '', authType, canWrite: false, - outLinkUserId: uid + outLinkUserId: uid, + showNodeStatus }; }; const authTeamSpaceChat = async ({ diff --git a/projects/app/src/pages/app/detail/components/Publish/Link/index.tsx b/projects/app/src/pages/app/detail/components/Publish/Link/index.tsx index 0635a5cba..4acdcb5d7 100644 --- a/projects/app/src/pages/app/detail/components/Publish/Link/index.tsx +++ b/projects/app/src/pages/app/detail/components/Publish/Link/index.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { Flex, Box, @@ -184,6 +184,8 @@ const Share = ({ appId }: { appId: string; type: PublishChannelEnum }) => { _id: item._id, name: item.name, responseDetail: item.responseDetail, + showCompleteQuote: item.showCompleteQuote, + showNodeStatus: item.showNodeStatus, limit: item.limit }) }, @@ -272,11 +274,27 @@ function EditLinkModal({ const { register, setValue, + watch, handleSubmit: submitShareChat } = useForm({ defaultValues: defaultData }); + const responseDetail = watch('responseDetail'); + const showCompleteQuote = watch('showCompleteQuote'); + + useEffect(() => { + if (!responseDetail) { + setValue('showCompleteQuote', false); + } + }, [responseDetail, setValue]); + + useEffect(() => { + if (showCompleteQuote) { + setValue('responseDetail', true); + } + }, [showCompleteQuote, setValue]); + const isEdit = useMemo(() => !!defaultData._id, [defaultData]); const { mutate: onclickCreate, isLoading: creating } = useRequest({ @@ -302,100 +320,130 @@ function EditLinkModal({ isOpen={true} iconSrc="/imgs/modal/shareFill.svg" title={isEdit ? publishT('edit_link') : publishT('create_link')} + w={'53.125rem'} > - - - {t('common:Name')} - - - {feConfigs?.isPlus && ( - <> + + + + + {t('publish:basic_info')} + - - {t('common:common.Expired Time')} - + {t('common:Name')} { - setValue('limit.expiredTime', new Date(e.target.value)); - }} - /> - - - - QPM - - - - - - {t('common:support.outlink.Max usage points')} + {feConfigs?.isPlus && ( + <> + + + {t('common:common.Expired Time')} + + { + setValue('limit.expiredTime', new Date(e.target.value)); + }} + /> + + + + QPM + + + + + + + {t('common:support.outlink.Max usage points')} + + + + + + + + {publishT('token_auth')} + + + + + + {publishT('token_auth_use_cases')} + + + )} + + + + {t('publish:config')} + + + + {t('publish:show_node')} + + + + + + + {t('common:support.outlink.share.Response Quote')} - + - - - {publishT('token_auth')} - + + + {t('common:support.outlink.share.show_complete_quote')} + - + - - {publishT('token_auth_use_cases')} - - - )} - - - - {t('common:support.outlink.share.Response Quote')} - - - + diff --git a/projects/app/src/pages/chat/share.tsx b/projects/app/src/pages/chat/share.tsx index d28734b33..34a933513 100644 --- a/projects/app/src/pages/chat/share.tsx +++ b/projects/app/src/pages/chat/share.tsx @@ -42,6 +42,7 @@ type Props = { shareId: string; authToken: string; customUid: string; + showCompleteQuote: boolean; }; const OutLink = ( @@ -364,6 +365,7 @@ const OutLink = ( chatId={chatId} shareId={shareId} outLinkUid={outLinkUid} + chatType="share" /> )} @@ -375,13 +377,13 @@ const OutLink = ( }; const Render = (props: Props) => { - const { shareId, authToken, customUid } = props; + const { shareId, authToken, customUid, showCompleteQuote } = props; const { localUId, loaded } = useShareChatStore(); const [isLoaded, setIsLoaded] = useState(false); const contextParams = useMemo(() => { - return { shareId, outLinkUid: authToken || customUid || localUId }; - }, [authToken, customUid, localUId, shareId]); + return { shareId, outLinkUid: authToken || localUId || customUid, showCompleteQuote }; + }, [authToken, customUid, localUId, shareId, showCompleteQuote]); useMount(() => { setIsLoaded(true); @@ -415,7 +417,7 @@ export async function getServerSideProps(context: any) { { shareId }, - 'appId' + 'appId showCompleteQuote' ) .populate('appId', 'name avatar intro') .lean()) as OutLinkWithAppType; @@ -431,6 +433,7 @@ export async function getServerSideProps(context: any) { appName: app?.appId?.name ?? 'AI', appAvatar: app?.appId?.avatar ?? '', appIntro: app?.appId?.intro ?? 'AI', + showCompleteQuote: app?.showCompleteQuote ?? false, shareId: shareId ?? '', authToken: authToken ?? '', customUid, diff --git a/projects/app/src/pages/chat/team.tsx b/projects/app/src/pages/chat/team.tsx index 59223e314..eea107f05 100644 --- a/projects/app/src/pages/chat/team.tsx +++ b/projects/app/src/pages/chat/team.tsx @@ -296,6 +296,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { chatId={chatId} teamId={teamId} teamToken={teamToken} + chatType="team" /> )} diff --git a/projects/app/src/pages/dataset/detail/components/MetaDataCard.tsx b/projects/app/src/pages/dataset/detail/components/MetaDataCard.tsx index e3022b37d..a17a92624 100644 --- a/projects/app/src/pages/dataset/detail/components/MetaDataCard.tsx +++ b/projects/app/src/pages/dataset/detail/components/MetaDataCard.tsx @@ -10,6 +10,8 @@ import { formatTime2YMDHM } from '@fastgpt/global/common/string/time'; import { DatasetCollectionTypeMap, TrainingTypeMap } from '@fastgpt/global/core/dataset/constants'; import { getCollectionSourceAndOpen } from '@/web/core/dataset/hooks/readCollectionSource'; import MyIcon from '@fastgpt/web/components/common/Icon'; +import { useContextSelector } from 'use-context-selector'; +import { ChatBoxContext } from '@/components/core/chat/ChatContainer/ChatBox/Provider'; const MetaDataCard = ({ datasetId }: { datasetId: string }) => { const { t } = useTranslation(); @@ -18,7 +20,17 @@ const MetaDataCard = ({ datasetId }: { datasetId: string }) => { collectionId: string; datasetId: string; }; - const readSource = getCollectionSourceAndOpen(collectionId); + + const { shareId, outLinkUid, chatType } = useContextSelector(ChatBoxContext, (v) => v); + + const readSource = getCollectionSourceAndOpen({ + collectionId, + authProps: { + shareId, + outLinkUid + }, + isShare: chatType === 'share' + }); const { data: collection, loading: isLoading } = useRequest2( () => getDatasetCollectionById(collectionId), { diff --git a/projects/app/src/service/support/permission/auth/outLink.ts b/projects/app/src/service/support/permission/auth/outLink.ts index 2a7a3c40b..7f0ab1a4e 100644 --- a/projects/app/src/service/support/permission/auth/outLink.ts +++ b/projects/app/src/service/support/permission/auth/outLink.ts @@ -3,7 +3,8 @@ import type { AuthOutLinkChatProps, AuthOutLinkLimitProps, AuthOutLinkInitProps, - AuthOutLinkResponse + AuthOutLinkResponse, + AuthOutLinkProps } from '@fastgpt/global/support/outLink/api.d'; import { authOutLinkValid } from '@fastgpt/service/support/permission/publish/authLink'; import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team'; @@ -23,10 +24,7 @@ export function authOutLinkChatLimit(data: AuthOutLinkLimitProps): Promise; + params: Record; }; type ChatContextType = { chatId: string; @@ -44,6 +44,7 @@ type ChatContextType = { isLoading: boolean; histories: ChatHistoryItemType[]; onUpdateHistoryTitle: ({ chatId, newTitle }: { chatId: string; newTitle: string }) => void; + showCompleteQuote: boolean; }; export const ChatContext = createContext({ @@ -85,7 +86,8 @@ export const ChatContext = createContext({ onChangeAppId: function (appId: string): void { throw new Error('Function not implemented.'); }, - isLoading: false + isLoading: false, + showCompleteQuote: true }); const ChatContextProvider = ({ @@ -94,6 +96,7 @@ const ChatContextProvider = ({ }: ChatContextValueType & { children: ReactNode }) => { const router = useRouter(); const { chatId = '' } = router.query as { chatId: string }; + const { showCompleteQuote }: { showCompleteQuote?: boolean } = params; const forbidLoadChat = useRef(false); @@ -225,7 +228,8 @@ const ChatContextProvider = ({ ScrollData, loadHistories, histories, - onUpdateHistoryTitle + onUpdateHistoryTitle, + showCompleteQuote: showCompleteQuote ?? true }; return {children}; }; diff --git a/projects/app/src/web/core/dataset/api.ts b/projects/app/src/web/core/dataset/api.ts index 08b192941..ddc283cc1 100644 --- a/projects/app/src/web/core/dataset/api.ts +++ b/projects/app/src/web/core/dataset/api.ts @@ -56,6 +56,7 @@ import type { UpdateDatasetDataProps } from '@fastgpt/global/core/dataset/contro import type { DatasetFolderCreateBody } from '@/pages/api/core/dataset/folder/create'; import type { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; import type { GetScrollCollectionsProps } from '@/pages/api/core/dataset/collection/scrollList'; +import { AuthOutLinkProps } from '@fastgpt/global/support/outLink/api'; /* ======================== dataset ======================= */ export const getDatasets = (data: GetDatasetListBody) => @@ -197,5 +198,6 @@ export const getPreviewChunks = (data: PostPreviewFilesChunksProps) => POST('/core/dataset/file/getPreviewChunks', data); /* ================== read source ======================== */ -export const getCollectionSource = (collectionId: string) => - GET('/core/dataset/collection/read', { collectionId }); +export const getCollectionSource = ( + data: { collectionId: string; isShare?: boolean } & AuthOutLinkProps +) => POST('/core/dataset/collection/read', data); diff --git a/projects/app/src/web/core/dataset/hooks/readCollectionSource.ts b/projects/app/src/web/core/dataset/hooks/readCollectionSource.ts index c9847be98..70f005dc2 100644 --- a/projects/app/src/web/core/dataset/hooks/readCollectionSource.ts +++ b/projects/app/src/web/core/dataset/hooks/readCollectionSource.ts @@ -1,10 +1,20 @@ +import { authOutLink } from '@/service/support/permission/auth/outLink'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { getCollectionSource } from '@/web/core/dataset/api'; import { getErrText } from '@fastgpt/global/common/error/utils'; +import { AuthOutLinkProps } from '@fastgpt/global/support/outLink/api'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { useTranslation } from 'next-i18next'; -export function getCollectionSourceAndOpen(collectionId: string) { +export function getCollectionSourceAndOpen({ + collectionId, + authProps, + isShare +}: { + collectionId: string; + authProps: AuthOutLinkProps; + isShare?: boolean; +}) { const { toast } = useToast(); const { t } = useTranslation(); const { setLoading } = useSystemStore(); @@ -12,7 +22,8 @@ export function getCollectionSourceAndOpen(collectionId: string) { return async () => { try { setLoading(true); - const { value: url } = await getCollectionSource(collectionId); + + const { value: url } = await getCollectionSource({ collectionId, isShare, ...authProps }); if (!url) { throw new Error('No file found');