Commit 4e83840c by Archer Committed by GitHub

perf: tool call check (#4818)

* i18n

* tool call

* fix: mcp create permission;Plugin unauth tip

* fix: mcp create permission;Plugin unauth tip

* fix: Cite modal permission

* remove invalide cite

* perf: prompt

* filter fulltext search

* fix: ts

* fix: ts

* fix: ts
parent a6c80684
...@@ -11,11 +11,15 @@ weight: 791 ...@@ -11,11 +11,15 @@ weight: 791
## 🚀 新增内容 ## 🚀 新增内容
1. 切换 SessionId 来替代 JWT 实现登录鉴权,可控制最大登录客户端数量。 1. 切换 SessionId 来替代 JWT 实现登录鉴权,可控制最大登录客户端数量。
2. 新的商业版 License 管理模式。
## ⚙️ 优化 ## ⚙️ 优化
1. 优化工具调用,新工具的判断逻辑。
2. 调整 Cite 引用提示词。
## 🐛 修复 ## 🐛 修复
1. 无法正常获取应用历史保存/发布记录。
2. 成员创建 MCP 工具权限问题。
3. 来源引用展示,存在 ID 传递错误,导致提示无权操作该文件。
export const getDatasetSearchToolResponsePrompt = () => { export const getDatasetSearchToolResponsePrompt = () => {
return `## Role return `## Role
你是一个知识库回答助手,可以 "quotes" 中的内容作为本次对话的参考。为了使回答结果更加可信并且可追溯,你需要在每段话结尾添加引用标记 你是一个知识库回答助手,可以 "cites" 中的内容作为本次对话的参考。为了使回答结果更加可信并且可追溯,你需要在每段话结尾添加引用标记,标识参考了哪些内容
## Rules ## 追溯展示规则
- 使用 **[id](CITE)** 格式来引用 "cites" 中的知识,其中 CITE 是固定常量, id 为引文中的 id。
- 在 **每段话结尾** 自然地整合引用。例如: "Nginx是一款轻量级的Web服务器、反向代理服务器[67e517e74767063e882d6861](CITE)。"。
- 每段话**至少包含一个引用**,多个引用时按顺序排列,例如:"Nginx是一款轻量级的Web服务器、反向代理服务器[67e517e74767063e882d6861](CITE)[67e517e74767063e882d6862](CITE)。\n 它的特点是非常轻量[67e517e74767063e882d6863](CITE)。"
- 不要把示例作为知识点。
- 不要伪造 id,返回的 id 必须都存在 cites 中!
## 通用规则
- 如果你不清楚答案,你需要澄清。 - 如果你不清楚答案,你需要澄清。
- 避免提及你是从 "quotes" 获取的知识。 - 避免提及你是从 "cites" 获取的知识。
- 保持答案与 "quotes" 中描述的一致。 - 保持答案与 "cites" 中描述的一致。
- 使用 Markdown 语法优化回答格式。尤其是图片、表格、序列号等内容,需严格完整输出。 - 使用 Markdown 语法优化回答格式。尤其是图片、表格、序列号等内容,需严格完整输出。
- 使用与问题相同的语言回答。 - 使用与问题相同的语言回答。`;
- 使用 [id](CITE) 格式来引用 "quotes" 中的知识,其中 CITE 是固定常量, id 为引文中的 id。
- 在每段话结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](CITE)。"
- 每段话至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。`;
}; };
...@@ -9,6 +9,7 @@ import { type WorkflowTemplateBasicType } from '../workflow/type'; ...@@ -9,6 +9,7 @@ import { type WorkflowTemplateBasicType } from '../workflow/type';
import { AppTypeEnum } from './constants'; import { AppTypeEnum } from './constants';
import { AppErrEnum } from '../../common/error/code/app'; import { AppErrEnum } from '../../common/error/code/app';
import { PluginErrEnum } from '../../common/error/code/plugin'; import { PluginErrEnum } from '../../common/error/code/plugin';
import { i18nT } from '../../../web/i18n/utils';
export const getDefaultAppForm = (): AppSimpleEditFormType => { export const getDefaultAppForm = (): AppSimpleEditFormType => {
return { return {
...@@ -189,7 +190,7 @@ export const getAppType = (config?: WorkflowTemplateBasicType | AppSimpleEditFor ...@@ -189,7 +190,7 @@ export const getAppType = (config?: WorkflowTemplateBasicType | AppSimpleEditFor
return ''; return '';
}; };
export const checkAppUnExistError = (error?: string) => { export const formatToolError = (error?: string) => {
const unExistError: Array<string> = [ const unExistError: Array<string> = [
AppErrEnum.unAuthApp, AppErrEnum.unAuthApp,
AppErrEnum.unExist, AppErrEnum.unExist,
...@@ -197,9 +198,9 @@ export const checkAppUnExistError = (error?: string) => { ...@@ -197,9 +198,9 @@ export const checkAppUnExistError = (error?: string) => {
PluginErrEnum.unExist PluginErrEnum.unExist
]; ];
if (!!error && unExistError.includes(error)) { if (error && unExistError.includes(error)) {
return error; return i18nT('app:un_auth');
} else { } else {
return undefined; return error;
} }
}; };
...@@ -149,7 +149,7 @@ export const readRawContentByFileBuffer = async ({ ...@@ -149,7 +149,7 @@ export const readRawContentByFileBuffer = async ({
return await systemParse(); return await systemParse();
})(); })();
addLog.debug(`Parse file success, time: ${Date.now() - start}ms. Uploading file image.`); addLog.debug(`Parse file success, time: ${Date.now() - start}ms. `);
// markdown data format // markdown data format
if (imageList) { if (imageList) {
...@@ -185,7 +185,7 @@ export const readRawContentByFileBuffer = async ({ ...@@ -185,7 +185,7 @@ export const readRawContentByFileBuffer = async ({
} }
} }
addLog.debug(`Upload file image success, time: ${Date.now() - start}ms`); addLog.debug(`Upload file success, time: ${Date.now() - start}ms`);
return { rawText, formatText, imageList }; return { rawText, formatText, imageList };
}; };
...@@ -138,9 +138,11 @@ export const llmStreamResponseToAnswerText = async ( ...@@ -138,9 +138,11 @@ export const llmStreamResponseToAnswerText = async (
responseChoice.tool_calls.forEach((toolCall, i) => { responseChoice.tool_calls.forEach((toolCall, i) => {
const index = toolCall.index ?? i; const index = toolCall.index ?? i;
if (toolCall.id || callingTool) { // Call new tool
// 有 id,代表新 call 工具 const hasNewTool = toolCall?.function?.name || callingTool;
if (toolCall.id) { if (hasNewTool) {
// 有 function name,代表新 call 工具
if (toolCall?.function?.name) {
callingTool = { callingTool = {
name: toolCall.function?.name || '', name: toolCall.function?.name || '',
arguments: toolCall.function?.arguments || '' arguments: toolCall.function?.arguments || ''
...@@ -221,7 +223,9 @@ export const parseReasoningContent = (text: string): [string, string] => { ...@@ -221,7 +223,9 @@ export const parseReasoningContent = (text: string): [string, string] => {
}; };
export const removeDatasetCiteText = (text: string, retainDatasetCite: boolean) => { export const removeDatasetCiteText = (text: string, retainDatasetCite: boolean) => {
return retainDatasetCite ? text : text.replace(/\[([a-f0-9]{24})\](?:\([^\)]*\)?)?/g, ''); return retainDatasetCite
? text.replace(/\[id\]\(CITE\)/g, '')
: text.replace(/\[([a-f0-9]{24})\](?:\([^\)]*\)?)?/g, '').replace(/\[id\]\(CITE\)/g, '');
}; };
// Parse llm stream part // Parse llm stream part
......
...@@ -46,6 +46,7 @@ export async function rewriteAppWorkflowToDetail({ ...@@ -46,6 +46,7 @@ export async function rewriteAppWorkflowToDetail({
const versionIds = appNodes const versionIds = appNodes
.filter((node) => node.version && Types.ObjectId.isValid(node.version)) .filter((node) => node.version && Types.ObjectId.isValid(node.version))
.map((node) => node.version); .map((node) => node.version);
if (versionIds.length > 0) { if (versionIds.length > 0) {
const versionDataList = await MongoAppVersion.find( const versionDataList = await MongoAppVersion.find(
{ {
......
...@@ -474,7 +474,7 @@ export async function searchDatasetData( ...@@ -474,7 +474,7 @@ export async function searchDatasetData(
).lean() ).lean()
]); ]);
const set = new Map<string, number>(); const set = new Set<string>();
const formatResult = results const formatResult = results
.map((item, index) => { .map((item, index) => {
const collection = collections.find((col) => String(col._id) === String(item.collectionId)); const collection = collections.find((col) => String(col._id) === String(item.collectionId));
...@@ -507,7 +507,7 @@ export async function searchDatasetData( ...@@ -507,7 +507,7 @@ export async function searchDatasetData(
.filter((item) => { .filter((item) => {
if (!item) return false; if (!item) return false;
if (set.has(item.id)) return false; if (set.has(item.id)) return false;
set.set(item.id, 1); set.add(item.id);
return true; return true;
}) })
.map((item, index) => { .map((item, index) => {
...@@ -648,7 +648,17 @@ export async function searchDatasetData( ...@@ -648,7 +648,17 @@ export async function searchDatasetData(
] ]
}; };
}) })
.filter(Boolean) as SearchDataResponseItemType[], .filter((item) => {
if (!item) return false;
return true;
})
.map((item, index) => {
if (!item) return;
return {
...item,
score: item.score.map((item) => ({ ...item, index }))
};
}) as SearchDataResponseItemType[],
tokenLen: 0 tokenLen: 0
}; };
}; };
......
...@@ -727,9 +727,10 @@ async function streamResponse({ ...@@ -727,9 +727,10 @@ async function streamResponse({
const index = toolCall.index ?? i; const index = toolCall.index ?? i;
// Call new tool // Call new tool
if (toolCall.id || callingTool) { const hasNewTool = toolCall?.function?.name || callingTool;
// 有 id,代表新 call 工具 if (hasNewTool) {
if (toolCall.id) { // 有 function name,代表新 call 工具
if (toolCall?.function?.name) {
callingTool = { callingTool = {
name: toolCall.function?.name || '', name: toolCall.function?.name || '',
arguments: toolCall.function?.arguments || '' arguments: toolCall.function?.arguments || ''
......
...@@ -268,7 +268,7 @@ export async function dispatchDatasetSearch( ...@@ -268,7 +268,7 @@ export async function dispatchDatasetSearch(
nodeDispatchUsages, nodeDispatchUsages,
[DispatchNodeResponseKeyEnum.toolResponses]: { [DispatchNodeResponseKeyEnum.toolResponses]: {
prompt: getDatasetSearchToolResponsePrompt(), prompt: getDatasetSearchToolResponsePrompt(),
quotes: searchRes.map((item) => ({ cites: searchRes.map((item) => ({
id: item.id, id: item.id,
sourceName: item.sourceName, sourceName: item.sourceName,
updateTime: item.updateTime, updateTime: item.updateTime,
......
...@@ -6,7 +6,6 @@ import MyTooltip from '../MyTooltip'; ...@@ -6,7 +6,6 @@ import MyTooltip from '../MyTooltip';
type Props = FlexProps & { type Props = FlexProps & {
icon: string; icon: string;
size?: string; size?: string;
onClick?: () => void;
hoverColor?: string; hoverColor?: string;
hoverBg?: string; hoverBg?: string;
hoverBorderColor?: string; hoverBorderColor?: string;
...@@ -41,9 +40,9 @@ const MyIconButton = ({ ...@@ -41,9 +40,9 @@ const MyIconButton = ({
color: hoverColor, color: hoverColor,
borderColor: hoverBorderColor borderColor: hoverBorderColor
}} }}
onClick={() => { onClick={(e) => {
if (isLoading) return; if (isLoading) return;
onClick?.(); onClick?.(e);
}} }}
sx={{ userSelect: 'none' }} sx={{ userSelect: 'none' }}
{...props} {...props}
......
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
"owner": "owner", "owner": "owner",
"permission": "Permissions", "permission": "Permissions",
"permission_apikeyCreate": "Create API Key", "permission_apikeyCreate": "Create API Key",
"permission_apikeyCreate_Tip": "Can create global APIKeys", "permission_apikeyCreate_Tip": "You can create global APIKey and MCP services",
"permission_appCreate": "Create Application", "permission_appCreate": "Create Application",
"permission_appCreate_tip": "Can create applications in the root directory (creation permissions in folders are controlled by the folder)", "permission_appCreate_tip": "Can create applications in the root directory (creation permissions in folders are controlled by the folder)",
"permission_datasetCreate": "Create Knowledge Base", "permission_datasetCreate": "Create Knowledge Base",
......
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
"app.modules.click to update": "Click to Refresh", "app.modules.click to update": "Click to Refresh",
"app.modules.has new version": "New Version Available", "app.modules.has new version": "New Version Available",
"app.modules.not_found": "Not Found", "app.modules.not_found": "Not Found",
"app.modules.not_found_tips": "This component cannot be found in the system, please delete it, otherwise the process will not run normally",
"app.version_current": "Current Version", "app.version_current": "Current Version",
"app.version_initial": "Initial Version", "app.version_initial": "Initial Version",
"app.version_name_tips": "Version name cannot be empty", "app.version_name_tips": "Version name cannot be empty",
...@@ -193,6 +192,7 @@ ...@@ -193,6 +192,7 @@
"type.error.Workflow data is empty": "No workflow data was obtained", "type.error.Workflow data is empty": "No workflow data was obtained",
"type.error.workflowresponseempty": "Response content is empty", "type.error.workflowresponseempty": "Response content is empty",
"type_not_recognized": "App type not recognized", "type_not_recognized": "App type not recognized",
"un_auth": "No permission",
"upload_file_max_amount": "Maximum File Quantity", "upload_file_max_amount": "Maximum File Quantity",
"upload_file_max_amount_tip": "Maximum number of files uploaded in a single round of conversation", "upload_file_max_amount_tip": "Maximum number of files uploaded in a single round of conversation",
"variable.select type_desc": "You can define a global variable that does not need to be filled in by the user.\n\nThe value of this variable can come from the API interface, the Query of the shared link, or assigned through the [Variable Update] module.", "variable.select type_desc": "You can define a global variable that does not need to be filled in by the user.\n\nThe value of this variable can come from the API interface, the Query of the shared link, or assigned through the [Variable Update] module.",
......
...@@ -754,6 +754,7 @@ ...@@ -754,6 +754,7 @@
"data_index_image": "Image Index", "data_index_image": "Image Index",
"data_index_question": "Inferred question index", "data_index_question": "Inferred question index",
"data_index_summary": "Summary Index", "data_index_summary": "Summary Index",
"data_not_found": "Data can't be found",
"dataset.Confirm move the folder": "Confirm to Move to This Directory", "dataset.Confirm move the folder": "Confirm to Move to This Directory",
"dataset.Confirm to delete the data": "Confirm to Delete This Data?", "dataset.Confirm to delete the data": "Confirm to Delete This Data?",
"dataset.Confirm to delete the file": "Confirm to Delete This File and All Its Data?", "dataset.Confirm to delete the file": "Confirm to Delete This File and All Its Data?",
......
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
"owner": "所有者", "owner": "所有者",
"permission": "权限", "permission": "权限",
"permission_apikeyCreate": "创建 API 密钥", "permission_apikeyCreate": "创建 API 密钥",
"permission_apikeyCreate_Tip": "可以创建全局的 APIKey", "permission_apikeyCreate_Tip": "可以创建全局的 APIKey和 MCP 服务",
"permission_appCreate": "创建应用", "permission_appCreate": "创建应用",
"permission_appCreate_tip": "可以在根目录创建应用,(文件夹下的创建权限由文件夹控制)", "permission_appCreate_tip": "可以在根目录创建应用,(文件夹下的创建权限由文件夹控制)",
"permission_datasetCreate": "创建知识库", "permission_datasetCreate": "创建知识库",
......
...@@ -22,7 +22,6 @@ ...@@ -22,7 +22,6 @@
"app.modules.click to update": "点击更新", "app.modules.click to update": "点击更新",
"app.modules.has new version": "有新版本", "app.modules.has new version": "有新版本",
"app.modules.not_found": "组件缺失", "app.modules.not_found": "组件缺失",
"app.modules.not_found_tips": "系统内无法查找到该组件,请删除,否则流程无法正常运行",
"app.version_current": "当前版本", "app.version_current": "当前版本",
"app.version_initial": "初始版本", "app.version_initial": "初始版本",
"app.version_name_tips": "版本名称不能为空", "app.version_name_tips": "版本名称不能为空",
...@@ -202,6 +201,7 @@ ...@@ -202,6 +201,7 @@
"type.error.Workflow data is empty": "没有获取到工作流数据", "type.error.Workflow data is empty": "没有获取到工作流数据",
"type.error.workflowresponseempty": "响应内容为空", "type.error.workflowresponseempty": "响应内容为空",
"type_not_recognized": "未识别到应用类型", "type_not_recognized": "未识别到应用类型",
"un_auth": "无权限",
"upload_file_max_amount": "最大文件数量", "upload_file_max_amount": "最大文件数量",
"upload_file_max_amount_tip": "单轮对话中最大上传文件数量", "upload_file_max_amount_tip": "单轮对话中最大上传文件数量",
"variable.select type_desc": "可以为工作流定义全局变量,常用临时缓存。赋值的方式包括:\n1. 从对话页面的 query 参数获取。\n2. 通过 API 的 variables 对象传递。\n3. 通过【变量更新】节点进行赋值。", "variable.select type_desc": "可以为工作流定义全局变量,常用临时缓存。赋值的方式包括:\n1. 从对话页面的 query 参数获取。\n2. 通过 API 的 variables 对象传递。\n3. 通过【变量更新】节点进行赋值。",
......
...@@ -753,6 +753,7 @@ ...@@ -753,6 +753,7 @@
"data_index_image": "图片索引", "data_index_image": "图片索引",
"data_index_question": "推测问题索引", "data_index_question": "推测问题索引",
"data_index_summary": "摘要索引", "data_index_summary": "摘要索引",
"data_not_found": "数据找不到了",
"dataset.Confirm move the folder": "确认移动到该目录", "dataset.Confirm move the folder": "确认移动到该目录",
"dataset.Confirm to delete the data": "确认删除该数据?", "dataset.Confirm to delete the data": "确认删除该数据?",
"dataset.Confirm to delete the file": "确认删除该文件及其所有数据?", "dataset.Confirm to delete the file": "确认删除该文件及其所有数据?",
......
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
"owner": "擁有者", "owner": "擁有者",
"permission": "權限", "permission": "權限",
"permission_apikeyCreate": "建立 API 密鑰", "permission_apikeyCreate": "建立 API 密鑰",
"permission_apikeyCreate_Tip": "可以建立全域的 APIKey", "permission_apikeyCreate_Tip": "可以創建全局的 APIKey和 MCP 服務",
"permission_appCreate": "建立應用程式", "permission_appCreate": "建立應用程式",
"permission_appCreate_tip": "可以在根目錄建立應用程式,(資料夾下的建立權限由資料夾控制)", "permission_appCreate_tip": "可以在根目錄建立應用程式,(資料夾下的建立權限由資料夾控制)",
"permission_datasetCreate": "建立知識庫", "permission_datasetCreate": "建立知識庫",
......
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
"app.modules.click to update": "點選更新", "app.modules.click to update": "點選更新",
"app.modules.has new version": "有新版本", "app.modules.has new version": "有新版本",
"app.modules.not_found": "元件遺失", "app.modules.not_found": "元件遺失",
"app.modules.not_found_tips": "系統內無法查詢到該元件,請刪除,否則流程無法正常運作",
"app.version_current": "目前版本", "app.version_current": "目前版本",
"app.version_initial": "初始版本", "app.version_initial": "初始版本",
"app.version_name_tips": "版本名稱不能空白", "app.version_name_tips": "版本名稱不能空白",
...@@ -193,6 +192,7 @@ ...@@ -193,6 +192,7 @@
"type.error.Workflow data is empty": "沒有獲取到工作流數據", "type.error.Workflow data is empty": "沒有獲取到工作流數據",
"type.error.workflowresponseempty": "響應內容為空", "type.error.workflowresponseempty": "響應內容為空",
"type_not_recognized": "未識別到應用程式類型", "type_not_recognized": "未識別到應用程式類型",
"un_auth": "無權限",
"upload_file_max_amount": "最大檔案數量", "upload_file_max_amount": "最大檔案數量",
"upload_file_max_amount_tip": "單輪對話中最大上傳檔案數量", "upload_file_max_amount_tip": "單輪對話中最大上傳檔案數量",
"variable.select type_desc": "可以為工作流程定義全域變數,常用於暫存。賦值的方式包括:\n1. 從對話頁面的 query 參數取得。\n2. 透過 API 的 variables 物件傳遞。\n3. 透過【變數更新】節點進行賦值。", "variable.select type_desc": "可以為工作流程定義全域變數,常用於暫存。賦值的方式包括:\n1. 從對話頁面的 query 參數取得。\n2. 透過 API 的 variables 物件傳遞。\n3. 透過【變數更新】節點進行賦值。",
......
...@@ -753,6 +753,7 @@ ...@@ -753,6 +753,7 @@
"data_index_image": "圖片索引", "data_index_image": "圖片索引",
"data_index_question": "推測問題索引", "data_index_question": "推測問題索引",
"data_index_summary": "摘要索引", "data_index_summary": "摘要索引",
"data_not_found": "數據找不到了",
"dataset.Confirm move the folder": "確認移動到此目錄", "dataset.Confirm move the folder": "確認移動到此目錄",
"dataset.Confirm to delete the data": "確認刪除此資料?", "dataset.Confirm to delete the data": "確認刪除此資料?",
"dataset.Confirm to delete the file": "確認刪除此檔案及其所有資料?", "dataset.Confirm to delete the file": "確認刪除此檔案及其所有資料?",
......
...@@ -22,135 +22,184 @@ import { getCollectionSourceData } from '@fastgpt/global/core/dataset/collection ...@@ -22,135 +22,184 @@ import { getCollectionSourceData } from '@fastgpt/global/core/dataset/collection
import Markdown from '.'; import Markdown from '.';
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils'; import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { useCreation } from 'ahooks';
const A = ({ children, chatAuthData, showAnimation, ...props }: any) => { export type AProps = {
chatAuthData?: {
appId: string;
chatId: string;
chatItemDataId: string;
} & OutLinkChatAuthProps;
onOpenCiteModal?: (e?: {
collectionId?: string;
sourceId?: string;
sourceName?: string;
datasetId?: string;
quoteId?: string;
}) => void;
};
const EmptyHrefLink = function EmptyHrefLink({ content }: { content: string }) {
const { t } = useTranslation();
return (
<MyTooltip label={t('common:core.chat.markdown.Quick Question')}>
<Button
variant={'whitePrimary'}
size={'xs'}
borderRadius={'md'}
my={1}
onClick={() => eventBus.emit(EventNameEnum.sendQuestion, { text: content })}
>
{content}
</Button>
</MyTooltip>
);
};
const CiteLink = React.memo(function CiteLink({
id,
chatAuthData,
onOpenCiteModal,
showAnimation
}: { id: string; showAnimation?: boolean } & AProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const content = useMemo(() => String(children), [children]);
if (!Types.ObjectId.isValid(id)) {
return <></>;
}
const {
data: datasetCiteData,
loading,
runAsync: getQuoteDataById
} = useRequest2((id: string) => getQuoteData({ id, ...chatAuthData }), {
manual: true
});
const sourceData = useMemo(
() => getCollectionSourceData(datasetCiteData?.collection),
[datasetCiteData?.collection]
);
const icon = useMemo(
() => getSourceNameIcon({ sourceId: sourceData.sourceId, sourceName: sourceData.sourceName }),
[sourceData]
);
return (
<Popover
isLazy
direction="rtl"
placement="bottom"
strategy={'fixed'}
isOpen={isOpen}
onClose={onClose}
onOpen={() => {
onOpen();
if (showAnimation) return;
getQuoteDataById(id);
}}
trigger={'hover'}
gutter={4}
>
<PopoverTrigger>
<Button variant={'unstyled'} minH={0} minW={0} h={'auto'}>
<MyIcon
name={'core/chat/quoteSign'}
w={'1rem'}
color={'primary.700'}
cursor={'pointer'}
/>
</Button>
</PopoverTrigger>
<PopoverContent boxShadow={'lg'} w={'500px'} maxW={'90vw'} py={4}>
<MyBox isLoading={loading || showAnimation}>
<PopoverArrow />
<PopoverBody py={0} px={0} fontSize={'sm'}>
<Flex px={4} pb={1} justifyContent={'space-between'}>
<Box
alignItems={'center'}
fontSize={'xs'}
border={'sm'}
borderRadius={'sm'}
overflow={'hidden'}
display={'inline-flex'}
height={6}
mr={1}
>
<Flex px={1.5}>
<MyIcon name={icon as any} mr={1} flexShrink={0} w={'12px'} />
<Box
className={'textEllipsis'}
wordBreak={'break-all'}
flex={'1 0 0'}
fontSize={'mini'}
color={'myGray.900'}
>
{sourceData.sourceName}
</Box>
</Flex>
</Box>
<Button
variant={'ghost'}
color={'primary.600'}
size={'xs'}
onClick={() => {
onClose();
onOpenCiteModal?.({
quoteId: id,
sourceId: sourceData.sourceId,
sourceName: sourceData.sourceName,
datasetId: datasetCiteData?.collection.datasetId,
collectionId: datasetCiteData?.collection._id
});
}}
>
{t('common:all_quotes')}
</Button>
</Flex>
<Box h={'300px'} overflow={'auto'} px={4}>
<Markdown source={datasetCiteData?.q} />
{datasetCiteData?.a && <Markdown source={datasetCiteData?.a} />}
</Box>
</PopoverBody>
</MyBox>
</PopoverContent>
</Popover>
);
});
const A = ({
children,
chatAuthData,
onOpenCiteModal,
showAnimation,
...props
}: AProps & {
children: any;
showAnimation: boolean;
[key: string]: any;
}) => {
const content = useCreation(() => String(children), [children]);
// empty href link // empty href link
if (!props.href && typeof children?.[0] === 'string') { if (!props.href && typeof children?.[0] === 'string') {
return ( return <EmptyHrefLink content={content} />;
<MyTooltip label={t('common:core.chat.markdown.Quick Question')}>
<Button
variant={'whitePrimary'}
size={'xs'}
borderRadius={'md'}
my={1}
onClick={() => eventBus.emit(EventNameEnum.sendQuestion, { text: content })}
>
{content}
</Button>
</MyTooltip>
);
} }
// Cite // Cite
if ( if (
(props.href?.startsWith('CITE') || props.href?.startsWith('QUOTE')) && (props.href?.startsWith('CITE') || props.href?.startsWith('QUOTE')) &&
typeof children?.[0] === 'string' typeof content === 'string'
) { ) {
if (!Types.ObjectId.isValid(content)) {
return <></>;
}
const {
data: quoteData,
loading,
runAsync: getQuoteDataById
} = useRequest2((id: string) => getQuoteData({ id, ...chatAuthData }), {
manual: true
});
const sourceData = useMemo(
() => getCollectionSourceData(quoteData?.collection),
[quoteData?.collection]
);
const icon = useMemo(
() => getSourceNameIcon({ sourceId: sourceData.sourceId, sourceName: sourceData.sourceName }),
[sourceData]
);
return ( return (
<Popover <CiteLink
isLazy id={content}
direction="rtl" chatAuthData={chatAuthData}
placement="bottom" onOpenCiteModal={onOpenCiteModal}
strategy={'fixed'} showAnimation={showAnimation}
isOpen={isOpen} />
onClose={onClose}
onOpen={() => {
onOpen();
if (showAnimation) return;
getQuoteDataById(String(children));
}}
trigger={'hover'}
gutter={4}
>
<PopoverTrigger>
<Button variant={'unstyled'} minH={0} minW={0} h={'auto'}>
<MyIcon
name={'core/chat/quoteSign'}
w={'1rem'}
color={'primary.700'}
cursor={'pointer'}
/>
</Button>
</PopoverTrigger>
<PopoverContent boxShadow={'lg'} w={'500px'} maxW={'90vw'} py={4}>
<MyBox isLoading={loading || showAnimation}>
<PopoverArrow />
<PopoverBody py={0} px={0} fontSize={'sm'}>
<Flex px={4} pb={1} justifyContent={'space-between'}>
<Box
alignItems={'center'}
fontSize={'xs'}
border={'sm'}
borderRadius={'sm'}
overflow={'hidden'}
display={'inline-flex'}
height={6}
mr={1}
>
<Flex px={1.5}>
<MyIcon name={icon as any} mr={1} flexShrink={0} w={'12px'} />
<Box
className={'textEllipsis'}
wordBreak={'break-all'}
flex={'1 0 0'}
fontSize={'mini'}
color={'myGray.900'}
>
{sourceData.sourceName}
</Box>
</Flex>
</Box>
<Button
variant={'ghost'}
color={'primary.600'}
size={'xs'}
onClick={() => {
onClose();
eventBus.emit(EventNameEnum.openQuoteReader, {
quoteId: String(children),
sourceId: sourceData.sourceId,
sourceName: sourceData.sourceName,
datasetId: quoteData?.collection.datasetId,
collectionId: quoteData?.collection._id
});
}}
>
{t('common:all_quotes')}
</Button>
</Flex>
<Box h={'300px'} overflow={'auto'} px={4}>
<Markdown source={quoteData?.q} />
{quoteData?.a && <Markdown source={quoteData?.a} />}
</Box>
</PopoverBody>
</MyBox>
</PopoverContent>
</Popover>
); );
} }
......
...@@ -13,7 +13,7 @@ import dynamic from 'next/dynamic'; ...@@ -13,7 +13,7 @@ import dynamic from 'next/dynamic';
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import { CodeClassNameEnum, mdTextFormat } from './utils'; import { CodeClassNameEnum, mdTextFormat } from './utils';
import { useCreation } from 'ahooks'; import { useCreation } from 'ahooks';
import { type OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat'; import type { AProps } from './A';
const CodeLight = dynamic(() => import('./codeBlock/CodeLight'), { ssr: false }); const CodeLight = dynamic(() => import('./codeBlock/CodeLight'), { ssr: false });
const MermaidCodeBlock = dynamic(() => import('./img/MermaidCodeBlock'), { ssr: false }); const MermaidCodeBlock = dynamic(() => import('./img/MermaidCodeBlock'), { ssr: false });
...@@ -33,12 +33,7 @@ type Props = { ...@@ -33,12 +33,7 @@ type Props = {
showAnimation?: boolean; showAnimation?: boolean;
isDisabled?: boolean; isDisabled?: boolean;
forbidZhFormat?: boolean; forbidZhFormat?: boolean;
chatAuthData?: { } & AProps;
appId: string;
chatId: string;
chatItemDataId: string;
} & OutLinkChatAuthProps;
};
const Markdown = (props: Props) => { const Markdown = (props: Props) => {
const source = props.source || ''; const source = props.source || '';
...@@ -53,16 +48,25 @@ const MarkdownRender = ({ ...@@ -53,16 +48,25 @@ const MarkdownRender = ({
showAnimation, showAnimation,
isDisabled, isDisabled,
forbidZhFormat, forbidZhFormat,
chatAuthData
chatAuthData,
onOpenCiteModal
}: Props) => { }: Props) => {
const components = useCreation(() => { const components = useCreation(() => {
return { return {
img: Image, img: Image,
pre: RewritePre, pre: RewritePre,
code: Code, code: Code,
a: (props: any) => <A {...props} showAnimation={showAnimation} chatAuthData={chatAuthData} /> a: (props: any) => (
<A
{...props}
showAnimation={showAnimation}
chatAuthData={chatAuthData}
onOpenCiteModal={onOpenCiteModal}
/>
)
}; };
}, [chatAuthData, showAnimation]); }, [chatAuthData, onOpenCiteModal, showAnimation]);
const formatSource = useMemo(() => { const formatSource = useMemo(() => {
if (showAnimation || forbidZhFormat) return source; if (showAnimation || forbidZhFormat) return source;
......
...@@ -28,10 +28,15 @@ import { isEqual } from 'lodash'; ...@@ -28,10 +28,15 @@ import { isEqual } from 'lodash';
import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { formatTimeToChatItemTime } from '@fastgpt/global/common/string/time'; import { formatTimeToChatItemTime } from '@fastgpt/global/common/string/time';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext'; import {
import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus'; ChatItemContext,
type OnOpenCiteModalProps
} from '@/web/core/chat/context/chatItemContext';
import { addStatisticalDataToHistoryItem } from '@/global/core/chat/utils'; import { addStatisticalDataToHistoryItem } from '@/global/core/chat/utils';
import { useToast } from '@fastgpt/web/hooks/useToast'; import dynamic from 'next/dynamic';
import { useMemoizedFn } from 'ahooks';
const ResponseTags = dynamic(() => import('./ResponseTags'));
const colorMap = { const colorMap = {
[ChatStatusEnum.loading]: { [ChatStatusEnum.loading]: {
...@@ -88,13 +93,15 @@ const AIContentCard = React.memo(function AIContentCard({ ...@@ -88,13 +93,15 @@ const AIContentCard = React.memo(function AIContentCard({
dataId, dataId,
isLastChild, isLastChild,
isChatting, isChatting,
questionGuides questionGuides,
onOpenCiteModal
}: { }: {
dataId: string; dataId: string;
chatValue: ChatItemValueItemType[]; chatValue: ChatItemValueItemType[];
isLastChild: boolean; isLastChild: boolean;
isChatting: boolean; isChatting: boolean;
questionGuides: string[]; questionGuides: string[];
onOpenCiteModal: (e?: OnOpenCiteModalProps) => void;
}) { }) {
return ( return (
<Flex flexDirection={'column'} gap={2}> <Flex flexDirection={'column'} gap={2}>
...@@ -108,6 +115,7 @@ const AIContentCard = React.memo(function AIContentCard({ ...@@ -108,6 +115,7 @@ const AIContentCard = React.memo(function AIContentCard({
value={value} value={value}
isLastResponseValue={isLastChild && i === chatValue.length - 1} isLastResponseValue={isLastChild && i === chatValue.length - 1}
isChatting={isChatting} isChatting={isChatting}
onOpenCiteModal={onOpenCiteModal}
/> />
); );
})} })}
...@@ -122,7 +130,6 @@ const ChatItem = (props: Props) => { ...@@ -122,7 +130,6 @@ const ChatItem = (props: Props) => {
const { type, avatar, statusBoxData, children, isLastChild, questionGuides = [], chat } = props; const { type, avatar, statusBoxData, children, isLastChild, questionGuides = [], chat } = props;
const { isPc } = useSystem(); const { isPc } = useSystem();
const { toast } = useToast();
const styleMap: BoxProps = { const styleMap: BoxProps = {
...(type === ChatRoleEnum.Human ...(type === ChatRoleEnum.Human
...@@ -150,7 +157,6 @@ const ChatItem = (props: Props) => { ...@@ -150,7 +157,6 @@ const ChatItem = (props: Props) => {
const chatType = useContextSelector(ChatBoxContext, (v) => v.chatType); const chatType = useContextSelector(ChatBoxContext, (v) => v.chatType);
const showNodeStatus = useContextSelector(ChatItemContext, (v) => v.showNodeStatus); const showNodeStatus = useContextSelector(ChatItemContext, (v) => v.showNodeStatus);
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData);
const appId = useContextSelector(ChatBoxContext, (v) => v.appId); const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId); const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData); const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
...@@ -228,64 +234,48 @@ const ChatItem = (props: Props) => { ...@@ -228,64 +234,48 @@ const ChatItem = (props: Props) => {
return groupedValues; return groupedValues;
}, [chat.obj, chat.value, isChatting]); }, [chat.obj, chat.value, isChatting]);
const handleOpenQuoteReader = useCallback( const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData);
({ const onOpenCiteModal = useMemoizedFn(
collectionId, (item?: {
sourceId,
sourceName,
datasetId,
quoteId
}: {
collectionId?: string; collectionId?: string;
sourceId?: string; sourceId?: string;
sourceName?: string; sourceName?: string;
datasetId?: string; datasetId?: string;
quoteId?: string; quoteId?: string;
}) => { }) => {
if (!setQuoteData) return; const collectionIdList = item?.collectionId
? [item.collectionId]
const collectionIdList = collectionId
? [collectionId]
: [...new Set(quoteList.map((item) => item.collectionId))]; : [...new Set(quoteList.map((item) => item.collectionId))];
setQuoteData({ setCiteModalData({
rawSearch: quoteList, rawSearch: quoteList,
metadata: metadata:
collectionId && isShowReadRawSource item?.collectionId && isShowReadRawSource
? { ? {
appId: appId, appId: appId,
chatId: chatId, chatId: chatId,
chatItemDataId: chat.dataId, chatItemDataId: chat.dataId,
collectionId: collectionId, collectionId: item.collectionId,
collectionIdList, collectionIdList,
sourceId: sourceId || '', sourceId: item.sourceId || '',
sourceName: sourceName || '', sourceName: item.sourceName || '',
datasetId: datasetId || '', datasetId: item.datasetId || '',
outLinkAuthData, outLinkAuthData,
quoteId quoteId: item.quoteId
} }
: { : {
appId: appId, appId: appId,
chatId: chatId, chatId: chatId,
chatItemDataId: chat.dataId, chatItemDataId: chat.dataId,
collectionIdList, collectionIdList,
sourceId: sourceId, sourceId: item?.sourceId,
sourceName: sourceName, sourceName: item?.sourceName,
outLinkAuthData outLinkAuthData
} }
}); });
}, }
[setQuoteData, quoteList, isShowReadRawSource, appId, chatId, chat.dataId, outLinkAuthData]
); );
useEffect(() => {
if (chat.obj !== ChatRoleEnum.AI) return;
eventBus.on(EventNameEnum.openQuoteReader, handleOpenQuoteReader);
return () => {
eventBus.off(EventNameEnum.openQuoteReader);
};
}, [chat.obj, handleOpenQuoteReader]);
return ( return (
<Box <Box
_hover={{ _hover={{
...@@ -362,13 +352,23 @@ const ChatItem = (props: Props) => { ...@@ -362,13 +352,23 @@ const ChatItem = (props: Props) => {
> >
{type === ChatRoleEnum.Human && <HumanContentCard chatValue={value} />} {type === ChatRoleEnum.Human && <HumanContentCard chatValue={value} />}
{type === ChatRoleEnum.AI && ( {type === ChatRoleEnum.AI && (
<AIContentCard <>
chatValue={value} <AIContentCard
dataId={chat.dataId} chatValue={value}
isLastChild={isLastChild && i === splitAiResponseResults.length - 1} dataId={chat.dataId}
isChatting={isChatting} isLastChild={isLastChild && i === splitAiResponseResults.length - 1}
questionGuides={questionGuides} isChatting={isChatting}
/> questionGuides={questionGuides}
onOpenCiteModal={onOpenCiteModal}
/>
{i === splitAiResponseResults.length - 1 && (
<ResponseTags
showTags={!isLastChild || !isChatting}
historyItem={chat}
onOpenCiteModal={onOpenCiteModal}
/>
)}
</>
)} )}
{/* Example: Response tags. A set of dialogs only needs to be displayed once*/} {/* Example: Response tags. A set of dialogs only needs to be displayed once*/}
{i === splitAiResponseResults.length - 1 && <>{children}</>} {i === splitAiResponseResults.length - 1 && <>{children}</>}
......
...@@ -14,17 +14,24 @@ import { addStatisticalDataToHistoryItem } from '@/global/core/chat/utils'; ...@@ -14,17 +14,24 @@ import { addStatisticalDataToHistoryItem } from '@/global/core/chat/utils';
import { useSize } from 'ahooks'; import { useSize } from 'ahooks';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { ChatBoxContext } from '../Provider'; import { ChatBoxContext } from '../Provider';
import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
const ContextModal = dynamic(() => import('./ContextModal')); const ContextModal = dynamic(() => import('./ContextModal'));
const WholeResponseModal = dynamic(() => import('../../../components/WholeResponseModal')); const WholeResponseModal = dynamic(() => import('../../../components/WholeResponseModal'));
const ResponseTags = ({ const ResponseTags = ({
showTags, showTags,
historyItem historyItem,
onOpenCiteModal
}: { }: {
showTags: boolean; showTags: boolean;
historyItem: ChatSiteItemType; historyItem: ChatSiteItemType;
onOpenCiteModal: (e?: {
collectionId?: string;
sourceId?: string;
sourceName?: string;
datasetId?: string;
quoteId?: string;
}) => void;
}) => { }) => {
const { isPc } = useSystem(); const { isPc } = useSystem();
const { t } = useTranslation(); const { t } = useTranslation();
...@@ -80,15 +87,6 @@ const ResponseTags = ({ ...@@ -80,15 +87,6 @@ const ResponseTags = ({
})); }));
}, [quoteList]); }, [quoteList]);
const openQuoteReader = (item?: {
collectionId?: string;
sourceId?: string;
sourceName?: string;
datasetId?: string;
}) => {
eventBus.emit(EventNameEnum.openQuoteReader, item);
};
const notEmptyTags = const notEmptyTags =
quoteList.length > 0 || quoteList.length > 0 ||
(llmModuleAccount === 1 && notSharePage) || (llmModuleAccount === 1 && notSharePage) ||
...@@ -161,7 +159,7 @@ const ResponseTags = ({ ...@@ -161,7 +159,7 @@ const ResponseTags = ({
cursor={'pointer'} cursor={'pointer'}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
openQuoteReader(item); onOpenCiteModal(item);
}} }}
height={6} height={6}
> >
...@@ -216,7 +214,7 @@ const ResponseTags = ({ ...@@ -216,7 +214,7 @@ const ResponseTags = ({
cursor={'pointer'} cursor={'pointer'}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
openQuoteReader(); onOpenCiteModal();
}} }}
> >
{t('chat:citations', { num: quoteList.length })} {t('chat:citations', { num: quoteList.length })}
......
...@@ -68,7 +68,6 @@ import MyBox from '@fastgpt/web/components/common/MyBox'; ...@@ -68,7 +68,6 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants'; import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
import { valueTypeFormat } from '@fastgpt/global/core/workflow/runtime/utils'; import { valueTypeFormat } from '@fastgpt/global/core/workflow/runtime/utils';
const ResponseTags = dynamic(() => import('./components/ResponseTags'));
const FeedbackModal = dynamic(() => import('./components/FeedbackModal')); const FeedbackModal = dynamic(() => import('./components/FeedbackModal'));
const ReadFeedbackModal = dynamic(() => import('./components/ReadFeedbackModal')); const ReadFeedbackModal = dynamic(() => import('./components/ReadFeedbackModal'));
const SelectMarkCollection = dynamic(() => import('./components/SelectMarkCollection')); const SelectMarkCollection = dynamic(() => import('./components/SelectMarkCollection'));
...@@ -1014,10 +1013,6 @@ const ChatBox = ({ ...@@ -1014,10 +1013,6 @@ const ChatBox = ({
onReadUserDislike: onReadUserDislike(item) onReadUserDislike: onReadUserDislike(item)
}} }}
> >
<ResponseTags
showTags={index !== chatRecords.length - 1 || !isChatting}
historyItem={item}
/>
{/* custom feedback */} {/* custom feedback */}
{item.customFeedbacks && item.customFeedbacks.length > 0 && ( {item.customFeedbacks && item.customFeedbacks.length > 0 && (
<Box> <Box>
...@@ -1072,7 +1067,6 @@ const ChatBox = ({ ...@@ -1072,7 +1067,6 @@ const ChatBox = ({
chatType, chatType,
delOneMessage, delOneMessage,
externalVariableList?.length, externalVariableList?.length,
isChatting,
onAddUserDislike, onAddUserDislike,
onAddUserLike, onAddUserLike,
onCloseCustomFeedback, onCloseCustomFeedback,
......
...@@ -30,7 +30,7 @@ import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus'; ...@@ -30,7 +30,7 @@ import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
import { SelectOptionsComponent, FormInputComponent } from './Interactive/InteractiveComponents'; import { SelectOptionsComponent, FormInputComponent } from './Interactive/InteractiveComponents';
import { extractDeepestInteractive } from '@fastgpt/global/core/workflow/runtime/utils'; import { extractDeepestInteractive } from '@fastgpt/global/core/workflow/runtime/utils';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext'; import { type OnOpenCiteModalProps } from '@/web/core/chat/context/chatItemContext';
import { ChatBoxContext } from '../ChatContainer/ChatBox/Provider'; import { ChatBoxContext } from '../ChatContainer/ChatBox/Provider';
import { useCreation } from 'ahooks'; import { useCreation } from 'ahooks';
...@@ -90,11 +90,13 @@ const RenderResoningContent = React.memo(function RenderResoningContent({ ...@@ -90,11 +90,13 @@ const RenderResoningContent = React.memo(function RenderResoningContent({
const RenderText = React.memo(function RenderText({ const RenderText = React.memo(function RenderText({
showAnimation, showAnimation,
text, text,
chatItemDataId chatItemDataId,
onOpenCiteModal
}: { }: {
showAnimation: boolean; showAnimation: boolean;
text: string; text: string;
chatItemDataId: string; chatItemDataId: string;
onOpenCiteModal?: (e?: OnOpenCiteModalProps) => void;
}) { }) {
const appId = useContextSelector(ChatBoxContext, (v) => v.appId); const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId); const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
...@@ -111,7 +113,14 @@ const RenderText = React.memo(function RenderText({ ...@@ -111,7 +113,14 @@ const RenderText = React.memo(function RenderText({
return { appId, chatId, chatItemDataId, ...outLinkAuthData }; return { appId, chatId, chatItemDataId, ...outLinkAuthData };
}, [appId, chatId, chatItemDataId, outLinkAuthData]); }, [appId, chatId, chatItemDataId, outLinkAuthData]);
return <Markdown source={source} showAnimation={showAnimation} chatAuthData={chatAuthData} />; return (
<Markdown
source={source}
showAnimation={showAnimation}
chatAuthData={chatAuthData}
onOpenCiteModal={onOpenCiteModal}
/>
);
}); });
const RenderTool = React.memo( const RenderTool = React.memo(
...@@ -240,12 +249,14 @@ const AIResponseBox = ({ ...@@ -240,12 +249,14 @@ const AIResponseBox = ({
chatItemDataId, chatItemDataId,
value, value,
isLastResponseValue, isLastResponseValue,
isChatting isChatting,
onOpenCiteModal
}: { }: {
chatItemDataId: string; chatItemDataId: string;
value: UserChatItemValueItemType | AIChatItemValueItemType; value: UserChatItemValueItemType | AIChatItemValueItemType;
isLastResponseValue: boolean; isLastResponseValue: boolean;
isChatting: boolean; isChatting: boolean;
onOpenCiteModal?: (e?: OnOpenCiteModalProps) => void;
}) => { }) => {
if (value.type === ChatItemValueTypeEnum.text && value.text) { if (value.type === ChatItemValueTypeEnum.text && value.text) {
return ( return (
...@@ -253,6 +264,7 @@ const AIResponseBox = ({ ...@@ -253,6 +264,7 @@ const AIResponseBox = ({
chatItemDataId={chatItemDataId} chatItemDataId={chatItemDataId}
showAnimation={isChatting && isLastResponseValue} showAnimation={isChatting && isLastResponseValue}
text={value.text.content} text={value.text.content}
onOpenCiteModal={onOpenCiteModal}
/> />
); );
} }
......
...@@ -38,8 +38,8 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => { ...@@ -38,8 +38,8 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => {
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData); const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
const pluginRunTab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab); const pluginRunTab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab);
const setPluginRunTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab); const setPluginRunTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab);
const quoteData = useContextSelector(ChatItemContext, (v) => v.quoteData); const datasetCiteData = useContextSelector(ChatItemContext, (v) => v.datasetCiteData);
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData); const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData);
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords); const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount); const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
...@@ -81,7 +81,7 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => { ...@@ -81,7 +81,7 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => {
right={0} right={0}
h={['100%', '96%']} h={['100%', '96%']}
w={'100%'} w={'100%'}
maxW={quoteData ? ['100%', '1080px'] : ['100%', '600px']} maxW={datasetCiteData ? ['100%', '1080px'] : ['100%', '600px']}
bg={'white'} bg={'white'}
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'} boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
borderRadius={'md'} borderRadius={'md'}
...@@ -169,7 +169,7 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => { ...@@ -169,7 +169,7 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => {
)} )}
</Box> </Box>
{quoteData && ( {datasetCiteData && (
<Box <Box
flex={'1 0 0'} flex={'1 0 0'}
w={0} w={0}
...@@ -183,9 +183,9 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => { ...@@ -183,9 +183,9 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => {
borderRadius={'md'} borderRadius={'md'}
> >
<ChatQuoteList <ChatQuoteList
rawSearch={quoteData.rawSearch} rawSearch={datasetCiteData.rawSearch}
metadata={quoteData.metadata} metadata={datasetCiteData.metadata}
onClose={() => setQuoteData(undefined)} onClose={() => setCiteModalData(undefined)}
/> />
</Box> </Box>
)} )}
......
...@@ -109,6 +109,10 @@ const EditForm = ({ ...@@ -109,6 +109,10 @@ const EditForm = ({
boxShadow: boxShadow:
'0px 4px 4px 0px rgba(19, 51, 107, 0.05), 0px 0px 1px 0px rgba(19, 51, 107, 0.08)' '0px 4px 4px 0px rgba(19, 51, 107, 0.05), 0px 0px 1px 0px rgba(19, 51, 107, 0.08)'
}} }}
cursor={'pointer'}
onClick={() => {
setCurrentTool(tool);
}}
> >
<Flex alignItems={'center'} py={2} px={3}> <Flex alignItems={'center'} py={2} px={3}>
<Box w={'20px'} fontSize={'14px'} color={'myGray.500'} fontWeight={'medium'}> <Box w={'20px'} fontSize={'14px'} color={'myGray.500'} fontWeight={'medium'}>
...@@ -157,23 +161,11 @@ const EditForm = ({ ...@@ -157,23 +161,11 @@ const EditForm = ({
hoverBg={'rgba(51, 112, 255, 0.10)'} hoverBg={'rgba(51, 112, 255, 0.10)'}
hoverBorderColor={'primary.300'} hoverBorderColor={'primary.300'}
tip={t('app:MCP_tools_detail')} tip={t('app:MCP_tools_detail')}
onClick={() => { onClick={(e) => {
e.stopPropagation();
setToolDetail(tool); setToolDetail(tool);
}} }}
/> />
<MyIconButton
size={'16px'}
icon={'core/workflow/debug'}
p={2}
border={'1px solid'}
borderColor={'myGray.250'}
hoverBg={'rgba(51, 112, 255, 0.10)'}
hoverBorderColor={'primary.300'}
tip={t('app:MCP_tools_debug')}
onClick={() => {
setCurrentTool(tool);
}}
/>
</Flex> </Flex>
</MyBox> </MyBox>
); );
......
...@@ -26,8 +26,8 @@ const ChatTest = ({ appForm, setRenderEdit }: Props) => { ...@@ -26,8 +26,8 @@ const ChatTest = ({ appForm, setRenderEdit }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { appDetail } = useContextSelector(AppContext, (v) => v); const { appDetail } = useContextSelector(AppContext, (v) => v);
const quoteData = useContextSelector(ChatItemContext, (v) => v.quoteData); const datasetCiteData = useContextSelector(ChatItemContext, (v) => v.datasetCiteData);
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData); const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData);
// form2AppWorkflow dependent allDatasets // form2AppWorkflow dependent allDatasets
const isVariableVisible = useContextSelector(ChatItemContext, (v) => v.isVariableVisible); const isVariableVisible = useContextSelector(ChatItemContext, (v) => v.isVariableVisible);
...@@ -42,8 +42,8 @@ const ChatTest = ({ appForm, setRenderEdit }: Props) => { ...@@ -42,8 +42,8 @@ const ChatTest = ({ appForm, setRenderEdit }: Props) => {
}, [appForm, setWorkflowData, t]); }, [appForm, setWorkflowData, t]);
useEffect(() => { useEffect(() => {
setRenderEdit(!quoteData); setRenderEdit(!datasetCiteData);
}, [quoteData, setRenderEdit]); }, [datasetCiteData, setRenderEdit]);
const { ChatContainer, restartChat, loading } = useChatTest({ const { ChatContainer, restartChat, loading } = useChatTest({
...workflowData, ...workflowData,
...@@ -89,12 +89,12 @@ const ChatTest = ({ appForm, setRenderEdit }: Props) => { ...@@ -89,12 +89,12 @@ const ChatTest = ({ appForm, setRenderEdit }: Props) => {
<ChatContainer /> <ChatContainer />
</Box> </Box>
</MyBox> </MyBox>
{quoteData && ( {datasetCiteData && (
<Box flex={'1 0 0'} w={0} maxW={'560px'} {...cardStyles} boxShadow={'3'}> <Box flex={'1 0 0'} w={0} maxW={'560px'} {...cardStyles} boxShadow={'3'}>
<ChatQuoteList <ChatQuoteList
rawSearch={quoteData.rawSearch} rawSearch={datasetCiteData.rawSearch}
metadata={quoteData.metadata} metadata={datasetCiteData.metadata}
onClose={() => setQuoteData(undefined)} onClose={() => setCiteModalData(undefined)}
/> />
</Box> </Box>
)} )}
......
...@@ -17,7 +17,7 @@ import Avatar from '@fastgpt/web/components/common/Avatar'; ...@@ -17,7 +17,7 @@ import Avatar from '@fastgpt/web/components/common/Avatar';
import ConfigToolModal from './ConfigToolModal'; import ConfigToolModal from './ConfigToolModal';
import { getWebLLMModel } from '@/web/common/system/utils'; import { getWebLLMModel } from '@/web/common/system/utils';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import { checkAppUnExistError } from '@fastgpt/global/core/app/utils'; import { formatToolError } from '@fastgpt/global/core/app/utils';
const ToolSelect = ({ const ToolSelect = ({
appForm, appForm,
...@@ -65,7 +65,7 @@ const ToolSelect = ({ ...@@ -65,7 +65,7 @@ const ToolSelect = ({
gridGap={[2, 4]} gridGap={[2, 4]}
> >
{appForm.selectedTools.map((item) => { {appForm.selectedTools.map((item) => {
const hasError = checkAppUnExistError(item.pluginData?.error); const toolError = formatToolError(item.pluginData?.error);
return ( return (
<MyTooltip key={item.id} label={item.intro}> <MyTooltip key={item.id} label={item.intro}>
...@@ -77,10 +77,10 @@ const ToolSelect = ({ ...@@ -77,10 +77,10 @@ const ToolSelect = ({
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'} boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
borderRadius={'md'} borderRadius={'md'}
border={theme.borders.base} border={theme.borders.base}
borderColor={hasError ? 'red.600' : ''} borderColor={toolError ? 'red.600' : ''}
_hover={{ _hover={{
...hoverDeleteStyles, ...hoverDeleteStyles,
borderColor: hasError ? 'red.600' : 'primary.300' borderColor: toolError ? 'red.600' : 'primary.300'
}} }}
cursor={'pointer'} cursor={'pointer'}
onClick={() => { onClick={() => {
...@@ -93,7 +93,7 @@ const ToolSelect = ({ ...@@ -93,7 +93,7 @@ const ToolSelect = ({
input.renderTypeList.includes(FlowNodeInputTypeEnum.selectLLMModel) || input.renderTypeList.includes(FlowNodeInputTypeEnum.selectLLMModel) ||
input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect) input.renderTypeList.includes(FlowNodeInputTypeEnum.fileSelect)
) || ) ||
hasError || toolError ||
item.flowNodeType === FlowNodeTypeEnum.tool || item.flowNodeType === FlowNodeTypeEnum.tool ||
item.flowNodeType === FlowNodeTypeEnum.toolSet item.flowNodeType === FlowNodeTypeEnum.toolSet
) { ) {
...@@ -113,21 +113,19 @@ const ToolSelect = ({ ...@@ -113,21 +113,19 @@ const ToolSelect = ({
> >
{item.name} {item.name}
</Box> </Box>
{hasError && ( {toolError && (
<MyTooltip label={t('app:app.modules.not_found_tips')}> <Flex
<Flex bg={'red.50'}
bg={'red.50'} alignItems={'center'}
alignItems={'center'} h={6}
h={6} px={2}
px={2} rounded={'6px'}
rounded={'6px'} fontSize={'xs'}
fontSize={'xs'} fontWeight={'medium'}
fontWeight={'medium'} >
> <MyIcon name={'common/errorFill'} w={'14px'} mr={1} />
<MyIcon name={'common/errorFill'} w={'14px'} mr={1} /> <Box color={'red.600'}>{t(toolError as any)}</Box>
<Box color={'red.600'}>{t('app:app.modules.not_found')}</Box> </Flex>
</Flex>
</MyTooltip>
)} )}
<DeleteIcon <DeleteIcon
ml={2} ml={2}
......
...@@ -43,8 +43,8 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => { ...@@ -43,8 +43,8 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => {
}); });
const pluginRunTab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab); const pluginRunTab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab);
const setPluginRunTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab); const setPluginRunTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab);
const quoteData = useContextSelector(ChatItemContext, (v) => v.quoteData); const datasetCiteData = useContextSelector(ChatItemContext, (v) => v.datasetCiteData);
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData); const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData);
const isVariableVisible = useContextSelector(ChatItemContext, (v) => v.isVariableVisible); const isVariableVisible = useContextSelector(ChatItemContext, (v) => v.isVariableVisible);
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords); const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
...@@ -60,7 +60,7 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => { ...@@ -60,7 +60,7 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => {
bottom={0} bottom={0}
right={0} right={0}
onClick={() => { onClick={() => {
setQuoteData(undefined); setCiteModalData(undefined);
onClose(); onClose();
}} }}
/> />
...@@ -72,7 +72,7 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => { ...@@ -72,7 +72,7 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => {
top={5} top={5}
right={0} right={0}
h={isOpen ? '95%' : '0'} h={isOpen ? '95%' : '0'}
w={isOpen ? (quoteData ? ['100%', '960px'] : ['100%', '460px']) : '0'} w={isOpen ? (datasetCiteData ? ['100%', '960px'] : ['100%', '460px']) : '0'}
bg={'white'} bg={'white'}
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'} boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
borderRadius={'md'} borderRadius={'md'}
...@@ -152,7 +152,7 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => { ...@@ -152,7 +152,7 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => {
<ChatContainer /> <ChatContainer />
</Box> </Box>
{quoteData && ( {datasetCiteData && (
<Box <Box
flex={'1 0 0'} flex={'1 0 0'}
w={0} w={0}
...@@ -166,9 +166,9 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => { ...@@ -166,9 +166,9 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => {
borderRadius={'md'} borderRadius={'md'}
> >
<ChatQuoteList <ChatQuoteList
rawSearch={quoteData.rawSearch} rawSearch={datasetCiteData.rawSearch}
metadata={quoteData.metadata} metadata={datasetCiteData.metadata}
onClose={() => setQuoteData(undefined)} onClose={() => setCiteModalData(undefined)}
/> />
</Box> </Box>
)} )}
......
...@@ -35,6 +35,7 @@ import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; ...@@ -35,6 +35,7 @@ import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination';
import MyTag from '@fastgpt/web/components/common/Tag/index'; import MyTag from '@fastgpt/web/components/common/Tag/index';
import MySelect from '@fastgpt/web/components/common/MySelect'; import MySelect from '@fastgpt/web/components/common/MySelect';
import { useCreation } from 'ahooks'; import { useCreation } from 'ahooks';
import { formatToolError } from '@fastgpt/global/core/app/utils';
type Props = FlowNodeItemType & { type Props = FlowNodeItemType & {
children?: React.ReactNode | React.ReactNode[] | string; children?: React.ReactNode | React.ReactNode[] | string;
...@@ -144,6 +145,7 @@ const NodeCard = (props: Props) => { ...@@ -144,6 +145,7 @@ const NodeCard = (props: Props) => {
/* Node header */ /* Node header */
const Header = useMemo(() => { const Header = useMemo(() => {
const showHeader = node?.flowNodeType !== FlowNodeTypeEnum.comment; const showHeader = node?.flowNodeType !== FlowNodeTypeEnum.comment;
const error = formatToolError(node?.pluginData?.error);
return ( return (
<Box position={'relative'}> <Box position={'relative'}>
...@@ -254,23 +256,19 @@ const NodeCard = (props: Props) => { ...@@ -254,23 +256,19 @@ const NodeCard = (props: Props) => {
)} )}
</UseGuideModal> </UseGuideModal>
)} )}
{!!node?.pluginData?.error && ( {!!error && (
<MyTooltip label={node?.pluginData?.error || t('app:app.modules.not_found_tips')}> <Flex
<Flex bg={'red.50'}
bg={'red.50'} alignItems={'center'}
alignItems={'center'} h={8}
h={8} px={2}
px={2} rounded={'6px'}
rounded={'6px'} fontSize={'xs'}
fontSize={'xs'} fontWeight={'medium'}
fontWeight={'medium'} >
> <MyIcon name={'common/errorFill'} w={'14px'} mr={1} />
<MyIcon name={'common/errorFill'} w={'14px'} mr={1} /> <Box color={'red.600'}>{t(error as any)}</Box>
<Box color={'red.600'}> </Flex>
{node?.pluginData?.error || t('app:app.modules.not_found')}
</Box>
</Flex>
</MyTooltip>
)} )}
</Flex> </Flex>
<NodeIntro nodeId={nodeId} intro={intro} /> <NodeIntro nodeId={nodeId} intro={intro} />
......
...@@ -3,7 +3,6 @@ import { ...@@ -3,7 +3,6 @@ import {
type ReactNode, type ReactNode,
type SetStateAction, type SetStateAction,
useCallback, useCallback,
useEffect,
useMemo, useMemo,
useState useState
} from 'react'; } from 'react';
...@@ -22,7 +21,6 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; ...@@ -22,7 +21,6 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node'; import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import type { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; import type { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app'; import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { checkAppUnExistError } from '@fastgpt/global/core/app/utils';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
const InfoModal = dynamic(() => import('./InfoModal')); const InfoModal = dynamic(() => import('./InfoModal'));
...@@ -205,16 +203,6 @@ const AppContextProvider = ({ children }: { children: ReactNode }) => { ...@@ -205,16 +203,6 @@ const AppContextProvider = ({ children }: { children: ReactNode }) => {
[appDetail.name, deleteApp, openConfirmDel, t] [appDetail.name, deleteApp, openConfirmDel, t]
); );
// check app unExist error
useEffect(() => {
if (appDetail.modules.some((module) => checkAppUnExistError(module.pluginData?.error))) {
toast({
title: t('app:app.error.unExist_app'),
status: 'error'
});
}
}, [appDetail.modules, t, toast]);
const contextValue: AppContextType = useMemo( const contextValue: AppContextType = useMemo(
() => ({ () => ({
appId, appId,
......
...@@ -46,7 +46,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) = ...@@ -46,7 +46,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) =
const appName = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.name); const appName = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.name);
const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.avatar); const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.avatar);
const showRouteToAppDetail = useContextSelector(ChatItemContext, (v) => v.showRouteToAppDetail); const showRouteToAppDetail = useContextSelector(ChatItemContext, (v) => v.showRouteToAppDetail);
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData); const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData);
const concatHistory = useMemo(() => { const concatHistory = useMemo(() => {
const formatHistories: HistoryItemType[] = histories.map((item) => { const formatHistories: HistoryItemType[] = histories.map((item) => {
...@@ -146,7 +146,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) = ...@@ -146,7 +146,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) =
overflow={'hidden'} overflow={'hidden'}
onClick={() => { onClick={() => {
onChangeChatId(); onChangeChatId();
setQuoteData(undefined); setCiteModalData(undefined);
}} }}
> >
{t('common:core.chat.New Chat')} {t('common:core.chat.New Chat')}
...@@ -202,7 +202,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) = ...@@ -202,7 +202,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) =
: { : {
onClick: () => { onClick: () => {
onChangeChatId(item.id); onChangeChatId(item.id);
setQuoteData(undefined); setCiteModalData(undefined);
} }
})} })}
{...(i !== concatHistory.length - 1 && { {...(i !== concatHistory.length - 1 && {
...@@ -274,7 +274,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) = ...@@ -274,7 +274,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) =
onDelHistory(item.id); onDelHistory(item.id);
if (item.id === activeChatId) { if (item.id === activeChatId) {
onChangeChatId(); onChangeChatId();
setQuoteData(undefined); setCiteModalData(undefined);
} }
}, },
type: 'danger' type: 'danger'
......
...@@ -81,4 +81,5 @@ function App({ Component, pageProps }: AppPropsWithLayout) { ...@@ -81,4 +81,5 @@ function App({ Component, pageProps }: AppPropsWithLayout) {
); );
} }
// @ts-ignore
export default appWithTranslation(App); export default appWithTranslation(App);
...@@ -13,6 +13,7 @@ import { ...@@ -13,6 +13,7 @@ import {
} from '@fastgpt/global/core/app/mcpTools/utils'; } from '@fastgpt/global/core/app/mcpTools/utils';
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils'; import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
import { checkTeamAppLimit } from '@fastgpt/service/support/permission/teamLimit'; import { checkTeamAppLimit } from '@fastgpt/service/support/permission/teamLimit';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
export type createMCPToolsQuery = {}; export type createMCPToolsQuery = {};
...@@ -33,7 +34,7 @@ async function handler( ...@@ -33,7 +34,7 @@ async function handler(
const { name, avatar, toolList, url, parentId } = req.body; const { name, avatar, toolList, url, parentId } = req.body;
const { teamId, tmbId, userId } = parentId const { teamId, tmbId, userId } = parentId
? await authApp({ req, appId: parentId, per: TeamAppCreatePermissionVal, authToken: true }) ? await authApp({ req, appId: parentId, per: WritePermissionVal, authToken: true })
: await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal }); : await authUserPer({ req, authToken: true, per: TeamAppCreatePermissionVal });
await checkTeamAppLimit(teamId); await checkTeamAppLimit(teamId);
......
...@@ -56,10 +56,11 @@ async function handler( ...@@ -56,10 +56,11 @@ async function handler(
}); });
} }
await MongoApp.findByIdAndUpdate( await MongoApp.updateOne(
appId, { _id: appId },
{ {
modules: [getMCPToolSetRuntimeNode({ url, toolList, name: app.name, avatar: app.avatar })] modules: [getMCPToolSetRuntimeNode({ url, toolList, name: app.name, avatar: app.avatar })],
updateTime: new Date()
}, },
{ session } { session }
); );
...@@ -97,6 +98,7 @@ const updateMCPChildrenTool = async ({ ...@@ -97,6 +98,7 @@ const updateMCPChildrenTool = async ({
teamId teamId
}); });
// 删除 DB 里有,新的工具列表里没有的工具
for await (const tool of dbTools) { for await (const tool of dbTools) {
if (!toolSetData.toolList.find((t) => t.name === tool.name)) { if (!toolSetData.toolList.find((t) => t.name === tool.name)) {
await onDelOneApp({ await onDelOneApp({
...@@ -107,6 +109,7 @@ const updateMCPChildrenTool = async ({ ...@@ -107,6 +109,7 @@ const updateMCPChildrenTool = async ({
} }
} }
// 创建 DB 里没有,新的工具列表里有的工具
for await (const tool of toolSetData.toolList) { for await (const tool of toolSetData.toolList) {
if (!dbTools.find((t) => t.name === tool.name)) { if (!dbTools.find((t) => t.name === tool.name)) {
await onCreateApp({ await onCreateApp({
...@@ -123,11 +126,12 @@ const updateMCPChildrenTool = async ({ ...@@ -123,11 +126,12 @@ const updateMCPChildrenTool = async ({
} }
} }
// 更新 DB 里有的工具
for await (const tool of toolSetData.toolList) { for await (const tool of toolSetData.toolList) {
const dbTool = dbTools.find((t) => t.name === tool.name); const dbTool = dbTools.find((t) => t.name === tool.name);
if (dbTool) { if (dbTool) {
await MongoApp.findByIdAndUpdate( await MongoApp.updateOne(
dbTool._id, { _id: dbTool._id },
{ {
modules: [getMCPToolRuntimeNode({ tool, url: toolSetData.url })] modules: [getMCPToolRuntimeNode({ tool, url: toolSetData.url })]
}, },
......
...@@ -6,7 +6,7 @@ import { quoteDataFieldSelector, type QuoteDataItemType } from '@/service/core/c ...@@ -6,7 +6,7 @@ import { quoteDataFieldSelector, type QuoteDataItemType } from '@/service/core/c
import { processChatTimeFilter } from '@/service/core/chat/utils'; import { processChatTimeFilter } from '@/service/core/chat/utils';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat'; import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
export type GetQuoteDataProps = { export type GetQuoteProps = {
datasetDataIdList: string[]; datasetDataIdList: string[];
collectionIdList: string[]; collectionIdList: string[];
...@@ -19,9 +19,9 @@ export type GetQuoteDataProps = { ...@@ -19,9 +19,9 @@ export type GetQuoteDataProps = {
teamToken?: string; teamToken?: string;
}; };
export type GetQuoteDataRes = QuoteDataItemType[]; export type GetQuotesRes = QuoteDataItemType[];
async function handler(req: ApiRequestProps<GetQuoteDataProps>): Promise<GetQuoteDataRes> { async function handler(req: ApiRequestProps<GetQuoteProps>): Promise<GetQuotesRes> {
const { const {
appId, appId,
chatId, chatId,
......
...@@ -8,6 +8,7 @@ import { type OutLinkChatAuthProps } from '@fastgpt/global/support/permission/ch ...@@ -8,6 +8,7 @@ import { type OutLinkChatAuthProps } from '@fastgpt/global/support/permission/ch
import { type ApiRequestProps } from '@fastgpt/service/type/next'; import { type ApiRequestProps } from '@fastgpt/service/type/next';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat'; import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { i18nT } from '@fastgpt/web/i18n/utils';
export type GetQuoteDataResponse = { export type GetQuoteDataResponse = {
collection: DatasetCollectionSchemaType; collection: DatasetCollectionSchemaType;
...@@ -46,7 +47,7 @@ async function handler(req: ApiRequestProps<GetQuoteDataProps>): Promise<GetQuot ...@@ -46,7 +47,7 @@ async function handler(req: ApiRequestProps<GetQuoteDataProps>): Promise<GetQuot
const datasetData = await MongoDatasetData.findById(dataId); const datasetData = await MongoDatasetData.findById(dataId);
if (!datasetData) { if (!datasetData) {
return Promise.reject('Can not find the data'); return Promise.reject(i18nT('common:data_not_found'));
} }
const [collection, { responseDetail }] = await Promise.all([ const [collection, { responseDetail }] = await Promise.all([
......
...@@ -59,8 +59,8 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { ...@@ -59,8 +59,8 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
const isPlugin = useContextSelector(ChatItemContext, (v) => v.isPlugin); const isPlugin = useContextSelector(ChatItemContext, (v) => v.isPlugin);
const chatBoxData = useContextSelector(ChatItemContext, (v) => v.chatBoxData); const chatBoxData = useContextSelector(ChatItemContext, (v) => v.chatBoxData);
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData); const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
const quoteData = useContextSelector(ChatItemContext, (v) => v.quoteData); const datasetCiteData = useContextSelector(ChatItemContext, (v) => v.datasetCiteData);
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData); const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData);
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords); const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount); const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
...@@ -148,7 +148,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { ...@@ -148,7 +148,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
); );
return isPc || !appId ? ( return isPc || !appId ? (
<SideBar externalTrigger={!!quoteData}>{Children}</SideBar> <SideBar externalTrigger={!!datasetCiteData}>{Children}</SideBar>
) : ( ) : (
<Drawer <Drawer
isOpen={isOpenSlider} isOpen={isOpenSlider}
...@@ -161,7 +161,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { ...@@ -161,7 +161,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
<DrawerContent maxWidth={'75vw'}>{Children}</DrawerContent> <DrawerContent maxWidth={'75vw'}>{Children}</DrawerContent>
</Drawer> </Drawer>
); );
}, [t, isPc, appId, isOpenSlider, onCloseSlider, quoteData]); }, [t, isPc, appId, isOpenSlider, onCloseSlider, datasetCiteData]);
return ( return (
<Flex h={'100%'}> <Flex h={'100%'}>
...@@ -173,7 +173,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { ...@@ -173,7 +173,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
</Box> </Box>
)} )}
{(!quoteData || isPc) && ( {(!datasetCiteData || isPc) && (
<PageContainer flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}> <PageContainer flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<Flex h={'100%'} flexDirection={['column', 'row']}> <Flex h={'100%'} flexDirection={['column', 'row']}>
{/* pc always show history. */} {/* pc always show history. */}
...@@ -222,12 +222,12 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { ...@@ -222,12 +222,12 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
</PageContainer> </PageContainer>
)} )}
{quoteData && ( {datasetCiteData && (
<PageContainer flex={'1 0 0'} w={0} maxW={'560px'}> <PageContainer flex={'1 0 0'} w={0} maxW={'560px'}>
<ChatQuoteList <ChatQuoteList
rawSearch={quoteData.rawSearch} rawSearch={datasetCiteData.rawSearch}
metadata={quoteData.metadata} metadata={datasetCiteData.metadata}
onClose={() => setQuoteData(undefined)} onClose={() => setCiteModalData(undefined)}
/> />
</PageContainer> </PageContainer>
)} )}
......
...@@ -85,8 +85,8 @@ const OutLink = (props: Props) => { ...@@ -85,8 +85,8 @@ const OutLink = (props: Props) => {
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables); const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
const isPlugin = useContextSelector(ChatItemContext, (v) => v.isPlugin); const isPlugin = useContextSelector(ChatItemContext, (v) => v.isPlugin);
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData); const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
const quoteData = useContextSelector(ChatItemContext, (v) => v.quoteData); const datasetCiteData = useContextSelector(ChatItemContext, (v) => v.datasetCiteData);
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData); const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData);
const isResponseDetail = useContextSelector(ChatItemContext, (v) => v.isResponseDetail); const isResponseDetail = useContextSelector(ChatItemContext, (v) => v.isResponseDetail);
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords); const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
...@@ -226,7 +226,7 @@ const OutLink = (props: Props) => { ...@@ -226,7 +226,7 @@ const OutLink = (props: Props) => {
if (showHistory !== '1') return null; if (showHistory !== '1') return null;
return isPc ? ( return isPc ? (
<SideBar externalTrigger={!!quoteData}>{Children}</SideBar> <SideBar externalTrigger={!!datasetCiteData}>{Children}</SideBar>
) : ( ) : (
<Drawer <Drawer
isOpen={isOpenSlider} isOpen={isOpenSlider}
...@@ -241,7 +241,7 @@ const OutLink = (props: Props) => { ...@@ -241,7 +241,7 @@ const OutLink = (props: Props) => {
</DrawerContent> </DrawerContent>
</Drawer> </Drawer>
); );
}, [isOpenSlider, isPc, onCloseSlider, quoteData, showHistory, t]); }, [isOpenSlider, isPc, onCloseSlider, datasetCiteData, showHistory, t]);
return ( return (
<> <>
...@@ -255,7 +255,7 @@ const OutLink = (props: Props) => { ...@@ -255,7 +255,7 @@ const OutLink = (props: Props) => {
gap={4} gap={4}
{...(isEmbed ? { p: '0 !important', borderRadius: '0', boxShadow: 'none' } : { p: [0, 5] })} {...(isEmbed ? { p: '0 !important', borderRadius: '0', boxShadow: 'none' } : { p: [0, 5] })}
> >
{(!quoteData || isPc) && ( {(!datasetCiteData || isPc) && (
<PageContainer flex={'1 0 0'} w={0} p={'0 !important'}> <PageContainer flex={'1 0 0'} w={0} p={'0 !important'}>
<Flex h={'100%'} flexDirection={['column', 'row']}> <Flex h={'100%'} flexDirection={['column', 'row']}>
{RenderHistoryList} {RenderHistoryList}
...@@ -303,12 +303,12 @@ const OutLink = (props: Props) => { ...@@ -303,12 +303,12 @@ const OutLink = (props: Props) => {
</PageContainer> </PageContainer>
)} )}
{quoteData && ( {datasetCiteData && (
<PageContainer flex={'1 0 0'} w={0} maxW={'560px'} p={'0 !important'}> <PageContainer flex={'1 0 0'} w={0} maxW={'560px'} p={'0 !important'}>
<ChatQuoteList <ChatQuoteList
rawSearch={quoteData.rawSearch} rawSearch={datasetCiteData.rawSearch}
metadata={quoteData.metadata} metadata={datasetCiteData.metadata}
onClose={() => setQuoteData(undefined)} onClose={() => setCiteModalData(undefined)}
/> />
</PageContainer> </PageContainer>
)} )}
......
...@@ -64,8 +64,8 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { ...@@ -64,8 +64,8 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables); const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
const chatBoxData = useContextSelector(ChatItemContext, (v) => v.chatBoxData); const chatBoxData = useContextSelector(ChatItemContext, (v) => v.chatBoxData);
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData); const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
const quoteData = useContextSelector(ChatItemContext, (v) => v.quoteData); const datasetCiteData = useContextSelector(ChatItemContext, (v) => v.datasetCiteData);
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData); const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData);
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords); const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount); const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
...@@ -166,7 +166,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { ...@@ -166,7 +166,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
); );
return isPc || !appId ? ( return isPc || !appId ? (
<SideBar externalTrigger={!!quoteData}>{Children}</SideBar> <SideBar externalTrigger={!!datasetCiteData}>{Children}</SideBar>
) : ( ) : (
<Drawer <Drawer
isOpen={isOpenSlider} isOpen={isOpenSlider}
...@@ -179,7 +179,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { ...@@ -179,7 +179,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
<DrawerContent maxWidth={'75vw'}>{Children}</DrawerContent> <DrawerContent maxWidth={'75vw'}>{Children}</DrawerContent>
</Drawer> </Drawer>
); );
}, [appId, isOpenSlider, isPc, onCloseSlider, quoteData, t]); }, [appId, isOpenSlider, isPc, onCloseSlider, datasetCiteData, t]);
return ( return (
<Flex h={'100%'}> <Flex h={'100%'}>
...@@ -191,7 +191,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { ...@@ -191,7 +191,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
</Box> </Box>
)} )}
{(!quoteData || isPc) && ( {(!datasetCiteData || isPc) && (
<PageContainer flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}> <PageContainer flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}> <Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
{RenderHistoryList} {RenderHistoryList}
...@@ -236,12 +236,12 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { ...@@ -236,12 +236,12 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
</Flex> </Flex>
</PageContainer> </PageContainer>
)} )}
{quoteData && ( {datasetCiteData && (
<PageContainer flex={'1 0 0'} w={0} maxW={'560px'}> <PageContainer flex={'1 0 0'} w={0} maxW={'560px'}>
<ChatQuoteList <ChatQuoteList
rawSearch={quoteData.rawSearch} rawSearch={datasetCiteData.rawSearch}
metadata={quoteData.metadata} metadata={datasetCiteData.metadata}
onClose={() => setQuoteData(undefined)} onClose={() => setCiteModalData(undefined)}
/> />
</PageContainer> </PageContainer>
)} )}
......
...@@ -30,6 +30,7 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; ...@@ -30,6 +30,7 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { type McpKeyType } from '@fastgpt/global/support/mcp/type'; import { type McpKeyType } from '@fastgpt/global/support/mcp/type';
import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useUserStore } from '@/web/support/user/useUserStore';
const UsageWay = dynamic(() => import('@/pageComponents/dashboard/mcp/usageWay'), { const UsageWay = dynamic(() => import('@/pageComponents/dashboard/mcp/usageWay'), {
ssr: false ssr: false
...@@ -38,6 +39,7 @@ const UsageWay = dynamic(() => import('@/pageComponents/dashboard/mcp/usageWay') ...@@ -38,6 +39,7 @@ const UsageWay = dynamic(() => import('@/pageComponents/dashboard/mcp/usageWay')
const McpServer = () => { const McpServer = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { isPc } = useSystem(); const { isPc } = useSystem();
const { userInfo } = useUserStore();
const { const {
data: mcpServerList = [], data: mcpServerList = [],
...@@ -78,7 +80,10 @@ const McpServer = () => { ...@@ -78,7 +80,10 @@ const McpServer = () => {
{t('dashboard_mcp:mcp_server_description')} {t('dashboard_mcp:mcp_server_description')}
</Box> </Box>
</Box> </Box>
<Button onClick={() => setEditMcp(defaultForm)}> <Button
isDisabled={!userInfo?.permission.hasApikeyCreatePer}
onClick={() => setEditMcp(defaultForm)}
>
{t('dashboard_mcp:create_mcp_server')} {t('dashboard_mcp:create_mcp_server')}
</Button> </Button>
</Flex> </Flex>
...@@ -94,7 +99,10 @@ const McpServer = () => { ...@@ -94,7 +99,10 @@ const McpServer = () => {
{t('dashboard_mcp:mcp_server_description')} {t('dashboard_mcp:mcp_server_description')}
</Box> </Box>
<Flex mt={2} justifyContent={'flex-end'}> <Flex mt={2} justifyContent={'flex-end'}>
<Button onClick={() => setEditMcp(defaultForm)}> <Button
isDisabled={!userInfo?.permission.hasApikeyCreatePer}
onClick={() => setEditMcp(defaultForm)}
>
{t('dashboard_mcp:create_mcp_server')} {t('dashboard_mcp:create_mcp_server')}
</Button> </Button>
</Flex> </Flex>
......
...@@ -29,10 +29,8 @@ import { ...@@ -29,10 +29,8 @@ import {
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
import { authAppByTmbId } from '@fastgpt/service/support/permission/app/auth'; import { authAppByTmbId } from '@fastgpt/service/support/permission/app/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { import { type StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
type PluginDataType, import { getErrText } from '@fastgpt/global/common/error/utils';
type StoreNodeItemType
} from '@fastgpt/global/core/workflow/type/node';
export const getScheduleTriggerApp = async () => { export const getScheduleTriggerApp = async () => {
// 1. Find all the app // 1. Find all the app
...@@ -152,6 +150,7 @@ export const checkNode = async ({ ...@@ -152,6 +150,7 @@ export const checkNode = async ({
try { try {
const { source } = await splitCombinePluginId(pluginId); const { source } = await splitCombinePluginId(pluginId);
if (source === PluginSourceEnum.personal) { if (source === PluginSourceEnum.personal) {
await authAppByTmbId({ await authAppByTmbId({
tmbId: ownerTmbId, tmbId: ownerTmbId,
...@@ -176,8 +175,8 @@ export const checkNode = async ({ ...@@ -176,8 +175,8 @@ export const checkNode = async ({
return { return {
...node, ...node,
pluginData: { pluginData: {
error error: getErrText(error)
} as PluginDataType }
}; };
} }
}; };
...@@ -25,7 +25,7 @@ import type { ...@@ -25,7 +25,7 @@ import type {
getPaginationRecordsBody, getPaginationRecordsBody,
getPaginationRecordsResponse getPaginationRecordsResponse
} from '@/pages/api/core/chat/getPaginationRecords'; } from '@/pages/api/core/chat/getPaginationRecords';
import type { GetQuoteDataProps, GetQuoteDataRes } from '@/pages/api/core/chat/quote/getQuote'; import type { GetQuoteProps, GetQuotesRes } from '@/pages/api/core/chat/quote/getQuote';
import type { import type {
GetCollectionQuoteProps, GetCollectionQuoteProps,
GetCollectionQuoteRes GetCollectionQuoteRes
...@@ -101,8 +101,8 @@ export const getMyTokensApps = (data: AuthTeamTagTokenProps) => ...@@ -101,8 +101,8 @@ export const getMyTokensApps = (data: AuthTeamTagTokenProps) =>
export const getinitTeamChat = (data: { teamId: string; authToken: string; appId: string }) => export const getinitTeamChat = (data: { teamId: string; authToken: string; appId: string }) =>
GET(`/proApi/core/chat/initTeamChat`, data); GET(`/proApi/core/chat/initTeamChat`, data);
export const getQuoteDataList = (data: GetQuoteDataProps) => export const getQuoteDataList = (data: GetQuoteProps) =>
POST<GetQuoteDataRes>(`/core/chat/quote/getQuote`, data); POST<GetQuotesRes>(`/core/chat/quote/getQuote`, data);
export const getCollectionQuote = (data: GetCollectionQuoteProps) => export const getCollectionQuote = (data: GetCollectionQuoteProps) =>
POST<GetCollectionQuoteRes>(`/core/chat/quote/getCollectionQuote`, data); POST<GetCollectionQuoteRes>(`/core/chat/quote/getCollectionQuote`, data);
...@@ -34,13 +34,13 @@ type ChatBoxDataType = { ...@@ -34,13 +34,13 @@ type ChatBoxDataType = {
}; };
}; };
// 知识库引用相关 type
export type GetQuoteDataBasicProps = { export type GetQuoteDataBasicProps = {
appId: string; appId: string;
chatId: string; chatId: string;
chatItemDataId: string; chatItemDataId: string;
outLinkAuthData?: OutLinkChatAuthProps; outLinkAuthData?: OutLinkChatAuthProps;
}; };
// 获取单个集合引用
export type GetCollectionQuoteDataProps = GetQuoteDataBasicProps & { export type GetCollectionQuoteDataProps = GetQuoteDataBasicProps & {
quoteId?: string; quoteId?: string;
collectionId: string; collectionId: string;
...@@ -54,11 +54,17 @@ export type GetAllQuoteDataProps = GetQuoteDataBasicProps & { ...@@ -54,11 +54,17 @@ export type GetAllQuoteDataProps = GetQuoteDataBasicProps & {
sourceName?: string; sourceName?: string;
}; };
export type GetQuoteProps = GetAllQuoteDataProps | GetCollectionQuoteDataProps; export type GetQuoteProps = GetAllQuoteDataProps | GetCollectionQuoteDataProps;
export type QuoteDataType = { export type QuoteDataType = {
rawSearch: SearchDataResponseItemType[]; rawSearch: SearchDataResponseItemType[];
metadata: GetQuoteProps; metadata: GetQuoteProps;
}; };
export type OnOpenCiteModalProps = {
collectionId?: string;
sourceId?: string;
sourceName?: string;
datasetId?: string;
quoteId?: string;
};
type ChatItemContextType = { type ChatItemContextType = {
ChatBoxRef: React.RefObject<ChatComponentRef> | null; ChatBoxRef: React.RefObject<ChatComponentRef> | null;
...@@ -74,8 +80,8 @@ type ChatItemContextType = { ...@@ -74,8 +80,8 @@ type ChatItemContextType = {
setChatBoxData: React.Dispatch<React.SetStateAction<ChatBoxDataType>>; setChatBoxData: React.Dispatch<React.SetStateAction<ChatBoxDataType>>;
isPlugin: boolean; isPlugin: boolean;
quoteData?: QuoteDataType; datasetCiteData?: QuoteDataType;
setQuoteData: React.Dispatch<React.SetStateAction<QuoteDataType | undefined>>; setCiteModalData: React.Dispatch<React.SetStateAction<QuoteDataType | undefined>>;
isVariableVisible: boolean; isVariableVisible: boolean;
setIsVariableVisible: React.Dispatch<React.SetStateAction<boolean>>; setIsVariableVisible: React.Dispatch<React.SetStateAction<boolean>>;
} & ContextProps; } & ContextProps;
...@@ -98,8 +104,8 @@ export const ChatItemContext = createContext<ChatItemContextType>({ ...@@ -98,8 +104,8 @@ export const ChatItemContext = createContext<ChatItemContextType>({
throw new Error('Function not implemented.'); throw new Error('Function not implemented.');
}, },
quoteData: undefined, datasetCiteData: undefined,
setQuoteData: function (value: React.SetStateAction<QuoteDataType | undefined>): void { setCiteModalData: function (value: React.SetStateAction<QuoteDataType | undefined>): void {
throw new Error('Function not implemented.'); throw new Error('Function not implemented.');
}, },
isVariableVisible: true, isVariableVisible: true,
...@@ -124,7 +130,6 @@ const ChatItemContextProvider = ({ ...@@ -124,7 +130,6 @@ const ChatItemContextProvider = ({
} & ContextProps) => { } & ContextProps) => {
const ChatBoxRef = useRef<ChatComponentRef>(null); const ChatBoxRef = useRef<ChatComponentRef>(null);
const variablesForm = useForm<ChatBoxInputFormType>(); const variablesForm = useForm<ChatBoxInputFormType>();
const [quoteData, setQuoteData] = useState<QuoteDataType>();
const [isVariableVisible, setIsVariableVisible] = useState(true); const [isVariableVisible, setIsVariableVisible] = useState(true);
const [chatBoxData, setChatBoxData] = useState<ChatBoxDataType>({ const [chatBoxData, setChatBoxData] = useState<ChatBoxDataType>({
...@@ -162,6 +167,8 @@ const ChatItemContextProvider = ({ ...@@ -162,6 +167,8 @@ const ChatItemContextProvider = ({
ChatBoxRef.current?.restartChat?.(); ChatBoxRef.current?.restartChat?.();
}, [variablesForm]); }, [variablesForm]);
const [datasetCiteData, setCiteModalData] = useState<QuoteDataType>();
const contextValue = useMemo(() => { const contextValue = useMemo(() => {
return { return {
chatBoxData, chatBoxData,
...@@ -180,8 +187,8 @@ const ChatItemContextProvider = ({ ...@@ -180,8 +187,8 @@ const ChatItemContextProvider = ({
// isShowFullText, // isShowFullText,
showNodeStatus, showNodeStatus,
quoteData, datasetCiteData,
setQuoteData, setCiteModalData,
isVariableVisible, isVariableVisible,
setIsVariableVisible setIsVariableVisible
}; };
...@@ -198,8 +205,8 @@ const ChatItemContextProvider = ({ ...@@ -198,8 +205,8 @@ const ChatItemContextProvider = ({
isResponseDetail, isResponseDetail,
// isShowFullText, // isShowFullText,
showNodeStatus, showNodeStatus,
quoteData, datasetCiteData,
setQuoteData, setCiteModalData,
isVariableVisible, isVariableVisible,
setIsVariableVisible setIsVariableVisible
]); ]);
......
...@@ -72,7 +72,7 @@ import type { ...@@ -72,7 +72,7 @@ import type {
getTrainingErrorResponse getTrainingErrorResponse
} from '@/pages/api/core/dataset/training/getTrainingError'; } from '@/pages/api/core/dataset/training/getTrainingError';
import type { APIFileItem } from '@fastgpt/global/core/dataset/apiDataset'; import type { APIFileItem } from '@fastgpt/global/core/dataset/apiDataset';
import type { GetQuoteDataProps } from '@/pages/api/core/chat/quote/getQuote'; import type { GetQuoteDataProps } from '@/pages/api/core/dataset/data/getQuoteData';
import type { import type {
GetApiDatasetCataLogResponse, GetApiDatasetCataLogResponse,
GetApiDatasetCataLogProps GetApiDatasetCataLogProps
......
...@@ -384,6 +384,22 @@ describe('Parse dataset cite content test', async () => { ...@@ -384,6 +384,22 @@ describe('Parse dataset cite content test', async () => {
content: '知识库问答系统[67e517e7476861](CITE)[67e517e74767063e882d6861](CITE)', content: '知识库问答系统[67e517e7476861](CITE)[67e517e74767063e882d6861](CITE)',
responseContent: '知识库问答系统[67e517e7476861](CITE)' responseContent: '知识库问答系统[67e517e7476861](CITE)'
} }
},
{
// [id](CITE)
data: [
{ content: '知识库' },
{ content: '问答系统' },
{ content: '[i' },
{ content: 'd](CITE)' },
{ content: '[67e517e747' },
{ content: '67063e882d' },
{ content: '6861](CITE)' }
],
correct: {
content: '知识库问答系统[id](CITE)[67e517e74767063e882d6861](CITE)',
responseContent: '知识库问答系统'
}
} }
]; ];
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment