V4.8.14 dev (#3234)

* feat: rewrite chat context (#3176)

* feat: add app auto execute (#3115)

* feat: add app auto execute

* auto exec configtion

* chatting animation

* change icon

* fix

* fix

* fix link

* feat: add chat context to all chatbox

* perf: loading ui

---------

Co-authored-by: heheer <heheer@sealos.io>

* app auto exec (#3179)

* add chat records loaded state (#3184)

* perf: chat store reset storage (#3186)

* perf: chat store reset storage

* perf: auto exec code

* chore: workflow ui (#3175)

* chore: workflow ui

* fix

* change icon color config

* change popover to mymenu

* 4.8.14 test (#3189)

* update doc

* fix: token check

* perf: icon button

* update doc

* feat: share page support configuration Whether to allow the original view (#3194)

* update doc

* perf: fix index (#3206)

* perf: i18n

* perf: Add service entry (#3226)

* 4.8.14 test (#3228)

* fix: ai log

* fix: text splitter

* fix: reference unselect & user form description & simple to advance (#3229)

* fix: reference unselect & user form description & simple to advance

* change abort position

* perf

* perf: code (#3232)

* perf: code

* update doc

* fix: create btn permission (#3233)

* update doc

* fix: refresh chatbox listener

* perf: check invalid reference

* perf: check invalid reference

* update doc

* fix: ui props

---------

Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
Archer
2024-11-26 12:02:58 +08:00
committed by GitHub
parent 7e1d31b5a9
commit 8aa6b53760
221 changed files with 3831 additions and 2737 deletions

View File

@@ -16,8 +16,8 @@
"i18n-ally.keystyle": "flat",
"i18n-ally.sortKeys": true,
"i18n-ally.keepFulfilled": false,
"i18n-ally.sourceLanguage": "zh", // 根据此语言文件翻译其他语言文件的变量和内容
"i18n-ally.displayLanguage": "zh", // 显示语言
"i18n-ally.sourceLanguage": "zh-CN", // 根据此语言文件翻译其他语言文件的变量和内容
"i18n-ally.displayLanguage": "zh-CN", // 显示语言
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.json",
"i18n-ally.extract.targetPickingStrategy": "most-similar-by-key",

View File

@@ -7,13 +7,19 @@ toc: true
weight: 810
---
## 更新预告
## 更新内容
1.
2. 新增 - 工作流支持进入聊天框/点击开始对话后,自动触发一轮对话。
3. 新增 - 重写 chatContext对话测试也会有日志并且刷新后不会丢失对话
4. 新增 - 分享链接支持配置是否允许查看原文
5. 优化 - 工作流 ui 细节
6. 优化 - 应用编辑记录采用 diff 存储,避免浏览器溢出
7. 修复 - 分块策略,四级标题会被丢失。 同时新增了五级标题的支持
8. 修复 - MongoDB 知识库集合唯一索引
1. 新增 - 工作流支持进入聊天框/点击开始对话后,自动触发一轮对话。
2. 新增 - 重写 chatContext对话测试也会有日志并且刷新后不会丢失对话。
3. 新增 - 分享链接支持配置是否允许查看原文
4. 新增 - 新的 doc2x 插件
5. 新增 - 繁体中文-台湾
6. 优化 - 工作流 ui 细节
7. 优化 - 应用编辑记录采用 diff 存储,避免浏览器溢出
8. 优化 - 代码入口,增加 register 入口,无需等待首次访问才执行
9. 优化 - 工作流检查,增加更多缺失值检查。
10. 修复 - 分块策略,四级标题会被丢失。 同时新增了五级标题的支持。
11. 修复 - MongoDB 知识库集合唯一索引。
12. 修复 - 反选知识库引用后可能会报错。
13. 修复 - 简易模式转工作流,不是使用最新编辑记录进行转移。
14. 修复 - 表单输入的说明文字不显示。

View File

@@ -12,5 +12,7 @@ export const fileImgs = [
];
export function getFileIcon(name = '', defaultImg = 'file/fill/file') {
return fileImgs.find((item) => new RegExp(item.suffix, 'gi').test(name))?.src || defaultImg;
return (
fileImgs.find((item) => new RegExp(`\.${item.suffix}`, 'gi').test(name))?.src || defaultImg
);
}

View File

@@ -3,6 +3,7 @@ import { BucketNameEnum } from './constants';
export type FileTokenQuery = {
bucketName: `${BucketNameEnum}`;
teamId: string;
tmbId: string;
uid: string; // tmbId/ share uid/ teamChat uid
fileId: string;
customExpireMinutes?: number;
};

View File

@@ -140,9 +140,10 @@ const commonSplit = (props: SplitProps): SplitResponse => {
const customRegLen = customReg.length;
const checkIsCustomStep = (step: number) => step < customRegLen;
const checkIsMarkdownSplit = (step: number) => step >= customRegLen && step <= markdownIndex;
const checkIsMarkdownSplit = (step: number) =>
step >= customRegLen && step <= markdownIndex + customRegLen;
+customReg.length;
const checkForbidOverlap = (step: number) => step <= forbidOverlapIndex + customReg.length;
const checkForbidOverlap = (step: number) => step <= forbidOverlapIndex + customRegLen;
// if use markdown title split, Separate record title
const getSplitTexts = ({ text, step }: { text: string; step: number }) => {

View File

@@ -49,6 +49,7 @@ export type ChatCompletionMessageParam = (
| CustomChatCompletionAssistantMessageParam
) & {
dataId?: string;
hideInUI?: boolean;
};
export type SdkChatCompletionMessageParam = SdkChatCompletionMessageParam;

View File

@@ -1,4 +1,9 @@
import { AppTTSConfigType, AppFileSelectConfigType, AppWhisperConfigType } from './type';
import {
AppTTSConfigType,
AppFileSelectConfigType,
AppWhisperConfigType,
AppAutoExecuteConfigType
} from './type';
export enum AppTypeEnum {
folder = 'folder',
@@ -12,6 +17,11 @@ export const AppFolderTypeList = [AppTypeEnum.folder, AppTypeEnum.httpPlugin];
export const defaultTTSConfig: AppTTSConfigType = { type: 'web' };
export const defaultAutoExecuteConfig: AppAutoExecuteConfigType = {
open: false,
defaultPrompt: ''
};
export const defaultWhisperConfig: AppWhisperConfigType = {
open: false,
autoSend: false,

View File

@@ -96,6 +96,7 @@ export type AppSimpleEditFormType = {
export type AppChatConfigType = {
welcomeText?: string;
variables?: VariableItemType[];
autoExecute?: AppAutoExecuteConfigType;
questionGuide?: boolean;
ttsConfig?: AppTTSConfigType;
whisperConfig?: AppWhisperConfigType;
@@ -158,6 +159,11 @@ export type AppScheduledTriggerConfigType = {
timezone: string;
defaultPrompt: string;
};
// auto execute
export type AppAutoExecuteConfigType = {
open: boolean;
defaultPrompt: string;
};
// File
export type AppFileSelectConfigType = {
canSelectFile: boolean;

View File

@@ -76,6 +76,7 @@ export const chats2GPTMessages = ({
results.push({
dataId,
hideInUI: item.hideInUI,
role: ChatCompletionRequestMessageRoleEnum.User,
content: simpleUserContentPart(value)
});
@@ -318,6 +319,7 @@ export const GPTMessages2Chats = (
return {
dataId: item.dataId,
obj,
hideInUI: item.hideInUI,
value
} as ChatItemType;
})

View File

@@ -56,6 +56,7 @@ export type UserChatItemValueItemType = {
export type UserChatItemType = {
obj: ChatRoleEnum.Human;
value: UserChatItemValueItemType[];
hideInUI?: boolean;
};
export type SystemChatItemValueItemType = {
type: ChatItemValueTypeEnum.text;

View File

@@ -101,7 +101,7 @@ export const filterPublicNodeResponseData = ({
for (let key in item) {
if (key === 'toolDetail' || key === 'pluginDetail') {
// @ts-ignore
obj[key] = filterPublicNodeResponseData({ flowResponses: item[key] });
obj[key] = filterPublicNodeResponseData({ flowResponses: item[key], responseDetail });
} else if (filedList.includes(key)) {
// @ts-ignore
obj[key] = item[key];

View File

@@ -204,7 +204,8 @@ export type DatasetFileSchema = {
contentType: string;
metadata: {
teamId: string;
tmbId: string;
tmbId?: string;
uid: string;
encoding?: string;
};
};

View File

@@ -106,6 +106,7 @@ export enum NodeInputKeyEnum {
variables = 'variables',
scheduleTrigger = 'scheduleTrigger',
chatInputGuide = 'chatInputGuide',
autoExecute = 'autoExecute',
// plugin config
instruction = 'instruction',

View File

@@ -25,10 +25,12 @@ import type {
AppWhisperConfigType,
AppScheduledTriggerConfigType,
ChatInputGuideConfigType,
AppChatConfigType
AppChatConfigType,
AppAutoExecuteConfigType
} from '../app/type';
import { EditorVariablePickerType } from '../../../web/components/common/Textarea/PromptEditor/type';
import {
defaultAutoExecuteConfig,
defaultChatInputGuideConfig,
defaultTTSConfig,
defaultWhisperConfig
@@ -69,34 +71,37 @@ export const getGuideModule = (modules: StoreNodeItemType[]) =>
);
export const splitGuideModule = (guideModules?: StoreNodeItemType) => {
const welcomeText: string =
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.welcomeText)?.value || '';
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.welcomeText)?.value ?? '';
const variables: VariableItemType[] =
guideModules?.inputs.find((item) => item.key === NodeInputKeyEnum.variables)?.value || [];
guideModules?.inputs.find((item) => item.key === NodeInputKeyEnum.variables)?.value ?? [];
const questionGuide: boolean =
!!guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.questionGuide)?.value ||
!!guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.questionGuide)?.value ??
false;
const ttsConfig: AppTTSConfigType =
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.tts)?.value ||
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.tts)?.value ??
defaultTTSConfig;
const whisperConfig: AppWhisperConfigType =
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.whisper)?.value ||
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.whisper)?.value ??
defaultWhisperConfig;
const scheduledTriggerConfig: AppScheduledTriggerConfigType = guideModules?.inputs?.find(
(item) => item.key === NodeInputKeyEnum.scheduleTrigger
)?.value;
const scheduledTriggerConfig: AppScheduledTriggerConfigType =
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.scheduleTrigger)?.value ??
undefined;
const chatInputGuide: ChatInputGuideConfigType =
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.chatInputGuide)?.value ||
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.chatInputGuide)?.value ??
defaultChatInputGuideConfig;
// plugin
const instruction: string =
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.instruction)?.value || '';
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.instruction)?.value ?? '';
const autoExecute: AppAutoExecuteConfigType =
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.autoExecute)?.value ??
defaultAutoExecuteConfig;
return {
welcomeText,
@@ -106,7 +111,8 @@ export const splitGuideModule = (guideModules?: StoreNodeItemType) => {
whisperConfig,
scheduledTriggerConfig,
chatInputGuide,
instruction
instruction,
autoExecute
};
};
@@ -132,7 +138,8 @@ export const getAppChatConfig = ({
whisperConfig,
scheduledTriggerConfig,
chatInputGuide,
instruction
instruction,
autoExecute
} = splitGuideModule(systemConfigNode);
const config: AppChatConfigType = {
@@ -142,6 +149,7 @@ export const getAppChatConfig = ({
scheduledTriggerConfig,
chatInputGuide,
instruction,
autoExecute,
...chatConfig,
variables: storeVariables ?? chatConfig?.variables ?? variables,
welcomeText: storeWelcomeText ?? chatConfig?.welcomeText ?? welcomeText

View File

@@ -32,7 +32,7 @@ export function getGridBucket(bucket: `${BucketNameEnum}`) {
export async function uploadFile({
bucketName,
teamId,
tmbId,
uid,
path,
filename,
contentType,
@@ -41,7 +41,7 @@ export async function uploadFile({
}: {
bucketName: `${BucketNameEnum}`;
teamId: string;
tmbId: string;
uid: string; // tmbId / outLinkUId
path: string;
filename: string;
contentType?: string;
@@ -58,7 +58,7 @@ export async function uploadFile({
// Add default metadata
metadata.teamId = teamId;
metadata.tmbId = tmbId;
metadata.uid = uid;
metadata.encoding = encoding;
// create a gridfs bucket

View File

@@ -4,7 +4,7 @@ import path from 'path';
import { BucketNameEnum, bucketNameMap } from '@fastgpt/global/common/file/constants';
import { getNanoid } from '@fastgpt/global/common/string/tools';
type FileType = {
export type FileType = {
fieldname: string;
originalname: string;
encoding: string;
@@ -41,7 +41,7 @@ export const getUploadModel = ({ maxSize = 500 }: { maxSize?: number }) => {
})
}).single('file');
async doUpload<T = Record<string, any>>(
async doUpload<T = any>(
req: NextApiRequest,
res: NextApiResponse,
originBucketName?: `${BucketNameEnum}`

View File

@@ -6,6 +6,7 @@ import {
} from '@fastgpt/global/core/ai/type';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { addLog } from '../../common/system/log';
import { i18nT } from '../../../web/i18n/utils';
export const openaiBaseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';
@@ -62,6 +63,7 @@ export const createChatCompletion = async <T extends CompletionsBodyType>({
}): Promise<{
response: InferResponseType<T>;
isStreamResponse: boolean;
getEmptyResponseTip: () => string;
}> => {
try {
const formatTimeout = timeout ? timeout : body.stream ? 60000 : 600000;
@@ -76,9 +78,21 @@ export const createChatCompletion = async <T extends CompletionsBodyType>({
response !== null &&
('iterator' in response || 'controller' in response);
const getEmptyResponseTip = () => {
addLog.warn(`LLM response empty`, {
baseUrl: userKey?.baseUrl,
requestBody: body
});
if (userKey?.baseUrl) {
return `您的 OpenAI key 没有响应: ${JSON.stringify(body)}`;
}
return i18nT('chat:LLM_model_response_empty');
};
return {
response: response as InferResponseType<T>,
isStreamResponse
isStreamResponse,
getEmptyResponseTip
};
} catch (error) {
addLog.error(`LLM response error`, error);

View File

@@ -17,7 +17,8 @@ export const chatConfigType = {
scheduledTriggerConfig: Object,
chatInputGuide: Object,
fileSelectConfig: Object,
instruction: String
instruction: String,
autoExecute: Object
};
// schema

View File

@@ -46,6 +46,10 @@ const ChatItemSchema = new Schema({
type: Date,
default: () => new Date()
},
hideInUI: {
type: Boolean,
default: false
},
obj: {
// chat role
type: String,

View File

@@ -1,15 +1,6 @@
import type {
AIChatItemType,
ChatItemType,
UserChatItemType
} from '@fastgpt/global/core/chat/type.d';
import axios from 'axios';
import type { AIChatItemType, UserChatItemType } from '@fastgpt/global/core/chat/type.d';
import { MongoApp } from '../app/schema';
import {
ChatItemValueTypeEnum,
ChatRoleEnum,
ChatSourceEnum
} from '@fastgpt/global/core/chat/constants';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { MongoChatItem } from './chatItemSchema';
import { MongoChat } from './chatSchema';
import { addLog } from '../../common/system/log';
@@ -133,21 +124,15 @@ export async function saveChat({
export const updateInteractiveChat = async ({
chatId,
appId,
teamId,
tmbId,
userInteractiveVal,
aiResponse,
newVariables,
newTitle
newVariables
}: {
chatId: string;
appId: string;
teamId: string;
tmbId: string;
userInteractiveVal: string;
aiResponse: AIChatItemType & { dataId?: string };
newVariables?: Record<string, any>;
newTitle: string;
}) => {
if (!chatId) return;
@@ -232,7 +217,6 @@ export const updateInteractiveChat = async ({
{
$set: {
variables: newVariables,
title: newTitle,
updateTime: new Date()
}
},

View File

@@ -67,7 +67,7 @@ export async function createOneCollection({
fileId,
rawLink,
externalFileId,
...(externalFileId ? { externalFileId } : {}),
externalFileUrl,
rawTextLength,

View File

@@ -118,7 +118,7 @@ try {
{
unique: true,
partialFilterExpression: {
externalFileId: { $exists: true, $ne: '' }
externalFileId: { $exists: true }
}
}
);

View File

@@ -77,7 +77,7 @@ export async function pushDataListToTrainingQueue({
if (trainingMode === TrainingModeEnum.chunk) {
return {
maxToken: vectorModelData.maxToken * 1.3,
maxToken: vectorModelData.maxToken * 1.5,
model: vectorModelData.model,
weight: vectorModelData.weight
};
@@ -125,10 +125,7 @@ export async function pushDataListToTrainingQueue({
const text = item.q + item.a;
// count q token
const token = item.q.length;
if (token > maxToken) {
if (text.length > maxToken) {
filterResult.overToken.push(item);
return;
}

View File

@@ -27,7 +27,6 @@ import { computedMaxToken, llmCompletionsBodyFormat } from '../../../../ai/utils
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
import { i18nT } from '../../../../../../web/i18n/utils';
type FunctionRunResponseType = {
toolRunResponse: DispatchFlowResponse;
@@ -216,7 +215,11 @@ export const runToolWithFunctionCall = async (
// console.log(JSON.stringify(requestMessages, null, 2));
/* Run llm */
const { response: aiResponse, isStreamResponse } = await createChatCompletion({
const {
response: aiResponse,
isStreamResponse,
getEmptyResponseTip
} = await createChatCompletion({
body: requestBody,
userKey: user.openaiAccount,
options: {
@@ -256,6 +259,9 @@ export const runToolWithFunctionCall = async (
};
}
})();
if (!answer && functionCalls.length === 0) {
return Promise.reject(getEmptyResponseTip());
}
// Run the selected tool.
const toolsRunResponse = (
@@ -549,9 +555,5 @@ async function streamResponse({
}
}
if (!textAnswer && functionCalls.length === 0) {
return Promise.reject(i18nT('chat:LLM_model_response_empty'));
}
return { answer: textAnswer, functionCalls };
}

View File

@@ -29,7 +29,6 @@ import { WorkflowResponseType } from '../../type';
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
import { i18nT } from '../../../../../../web/i18n/utils';
type FunctionCallCompletion = {
id: string;
@@ -225,7 +224,11 @@ export const runToolWithPromptCall = async (
// console.log(JSON.stringify(requestMessages, null, 2));
/* Run llm */
const { response: aiResponse, isStreamResponse } = await createChatCompletion({
const {
response: aiResponse,
isStreamResponse,
getEmptyResponseTip
} = await createChatCompletion({
body: requestBody,
userKey: user.openaiAccount,
options: {
@@ -251,8 +254,11 @@ export const runToolWithPromptCall = async (
return result.choices?.[0]?.message?.content || '';
}
})();
const { answer: replaceAnswer, toolJson } = parseAnswer(answer);
if (!answer && !toolJson) {
return Promise.reject(getEmptyResponseTip());
}
// No tools
if (!toolJson) {
if (replaceAnswer === ERROR_TEXT) {
@@ -534,9 +540,6 @@ async function streamResponse({
}
}
if (!textAnswer) {
return Promise.reject(i18nT('chat:LLM_model_response_empty'));
}
return { answer: textAnswer.trim() };
}

View File

@@ -272,7 +272,11 @@ export const runToolWithToolChoice = async (
);
// console.log(JSON.stringify(requestBody, null, 2), '==requestBody');
/* Run llm */
const { response: aiResponse, isStreamResponse } = await createChatCompletion({
const {
response: aiResponse,
isStreamResponse,
getEmptyResponseTip
} = await createChatCompletion({
body: requestBody,
userKey: user.openaiAccount,
options: {
@@ -336,6 +340,9 @@ export const runToolWithToolChoice = async (
};
}
})();
if (!answer && toolCalls.length === 0) {
return Promise.reject(getEmptyResponseTip());
}
// Run the selected tool by LLM.
const toolsRunResponse = (
@@ -645,9 +652,5 @@ async function streamResponse({
}
}
if (!textAnswer && toolCalls.length === 0) {
return Promise.reject(i18nT('chat:LLM_model_response_empty'));
}
return { answer: textAnswer, toolCalls };
}

View File

@@ -33,15 +33,13 @@ import { getLLMModel, ModelTypeEnum } from '../../../ai/model';
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { getHistories } from '../utils';
import { checkQuoteQAValue, getHistories } from '../utils';
import { filterSearchResultsByMaxChars } from '../../utils';
import { getHistoryPreview } from '@fastgpt/global/core/chat/utils';
import { addLog } from '../../../../common/system/log';
import { computedMaxToken, llmCompletionsBodyFormat } from '../../../ai/utils';
import { WorkflowResponseType } from '../type';
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
import { AiChatQuoteRoleType } from '@fastgpt/global/core/workflow/template/system/aiChat/type';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { getFileContentFromLinks, getHistoryFileLinks } from '../tools/readFiles';
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
import { i18nT } from '../../../../../web/i18n/utils';
@@ -93,6 +91,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
stream = stream && isResponseAnswerText;
const chatHistories = getHistories(history, histories);
quoteQA = checkQuoteQAValue(quoteQA);
const modelConstantsData = getLLMModel(model);
if (!modelConstantsData) {
@@ -169,99 +168,91 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
modelConstantsData
);
// console.log(JSON.stringify(requestBody, null, 2), '===');
try {
const { response, isStreamResponse } = await createChatCompletion({
body: requestBody,
userKey: user.openaiAccount,
options: {
headers: {
Accept: 'application/json, text/plain, */*'
}
const { response, isStreamResponse, getEmptyResponseTip } = await createChatCompletion({
body: requestBody,
userKey: user.openaiAccount,
options: {
headers: {
Accept: 'application/json, text/plain, */*'
}
});
}
});
const { answerText } = await (async () => {
if (res && isStreamResponse) {
// sse response
const { answer } = await streamResponse({
res,
stream: response,
workflowStreamResponse
const { answerText } = await (async () => {
if (res && isStreamResponse) {
// sse response
const { answer } = await streamResponse({
res,
stream: response,
workflowStreamResponse
});
return {
answerText: answer
};
} else {
const unStreamResponse = response as ChatCompletion;
const answer = unStreamResponse.choices?.[0]?.message?.content || '';
if (stream) {
// Some models do not support streaming
workflowStreamResponse?.({
event: SseResponseEventEnum.fastAnswer,
data: textAdaptGptResponse({
text: answer
})
});
if (!answer) {
return Promise.reject(i18nT('chat:LLM_model_response_empty'));
}
return {
answerText: answer
};
} else {
const unStreamResponse = response as ChatCompletion;
const answer = unStreamResponse.choices?.[0]?.message?.content || '';
if (stream) {
// Some models do not support streaming
workflowStreamResponse?.({
event: SseResponseEventEnum.fastAnswer,
data: textAdaptGptResponse({
text: answer
})
});
}
return {
answerText: answer
};
}
})();
const completeMessages = requestMessages.concat({
role: ChatCompletionRequestMessageRoleEnum.Assistant,
content: answerText
});
const chatCompleteMessages = GPTMessages2Chats(completeMessages);
return {
answerText: answer
};
}
})();
const tokens = await countMessagesTokens(chatCompleteMessages);
const { totalPoints, modelName } = formatModelChars2Points({
model,
if (!answerText) {
return Promise.reject(getEmptyResponseTip());
}
const completeMessages = requestMessages.concat({
role: ChatCompletionRequestMessageRoleEnum.Assistant,
content: answerText
});
const chatCompleteMessages = GPTMessages2Chats(completeMessages);
const tokens = await countMessagesTokens(chatCompleteMessages);
const { totalPoints, modelName } = formatModelChars2Points({
model,
tokens,
modelType: ModelTypeEnum.llm
});
return {
answerText,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
model: modelName,
tokens,
modelType: ModelTypeEnum.llm
});
return {
answerText,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
query: `${userChatInput}`,
maxToken: max_tokens,
historyPreview: getHistoryPreview(
chatCompleteMessages,
10000,
modelConstantsData.vision && aiChatVision
),
contextTotalLen: completeMessages.length
},
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
{
moduleName: name,
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
model: modelName,
tokens,
query: `${userChatInput}`,
maxToken: max_tokens,
historyPreview: getHistoryPreview(
chatCompleteMessages,
10000,
modelConstantsData.vision && aiChatVision
),
contextTotalLen: completeMessages.length
},
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
{
moduleName: name,
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
model: modelName,
tokens
}
],
[DispatchNodeResponseKeyEnum.toolResponses]: answerText,
history: chatCompleteMessages
};
} catch (error) {
if (user.openaiAccount?.baseUrl) {
return Promise.reject(`您的 OpenAI key 出错了: ${getErrText(error)}`);
}
return Promise.reject(error);
}
tokens
}
],
[DispatchNodeResponseKeyEnum.toolResponses]: answerText,
history: chatCompleteMessages
};
};
async function filterDatasetQuote({

View File

@@ -209,7 +209,6 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
try {
const { formatResponse, rawResponse } = await (async () => {
const systemPluginCb = global.systemPluginCb;
console.log(systemPluginCb, '-=', httpReqUrl);
if (systemPluginCb[httpReqUrl]) {
const pluginResult = await replaceSystemPluginResponse({
response: await systemPluginCb[httpReqUrl](requestBody),
@@ -395,7 +394,7 @@ async function replaceSystemPluginResponse({
response[key] = `${ReadFileBaseUrl}/${filename}?token=${await createFileToken({
bucketName: 'chat',
teamId,
tmbId,
uid: tmbId,
fileId
})}`;
} catch (error) {}

View File

@@ -13,6 +13,7 @@ import { responseWrite } from '../../../common/response';
import { NextApiResponse } from 'next';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
export const getWorkflowResponseWrite = ({
res,
@@ -87,27 +88,6 @@ export const filterToolNodeIdByEdges = ({
.map((edge) => edge.target);
};
// export const checkTheModuleConnectedByTool = (
// modules: StoreNodeItemType[],
// node: StoreNodeItemType
// ) => {
// let sign = false;
// const toolModules = modules.filter((item) => item.flowNodeType === FlowNodeTypeEnum.tools);
// toolModules.forEach((item) => {
// const toolOutput = item.outputs.find(
// (output) => output.key === NodeOutputKeyEnum.selectedTools
// );
// toolOutput?.targets.forEach((target) => {
// if (target.moduleId === node.moduleId) {
// sign = true;
// }
// });
// });
// return sign;
// };
export const getHistories = (history?: ChatItemType[] | number, histories: ChatItemType[] = []) => {
if (!history) return [];
@@ -149,6 +129,13 @@ export const valueTypeFormat = (value: any, type?: WorkflowIOValueTypeEnum) => {
return value;
};
export const checkQuoteQAValue = (quoteQA: SearchDataResponseItemType[] = []) => {
if (quoteQA.some((item) => !item.q || !item.datasetId)) {
return undefined;
}
return quoteQA;
};
/* remove system variable */
export const removeSystemVariable = (variables: Record<string, any>) => {
const copyVariables = { ...variables };

View File

@@ -48,7 +48,8 @@ const OutLinkSchema = new Schema({
default: false
},
showNodeStatus: {
type: Boolean
type: Boolean,
default: true
},
showRawSource: {
type: Boolean

View File

@@ -7,7 +7,7 @@ import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { OwnerPermissionVal, ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { Permission } from '@fastgpt/global/support/permission/controller';
export async function authFile({
export const authCollectionFile = async ({
fileId,
per = OwnerPermissionVal,
...props
@@ -17,7 +17,7 @@ export async function authFile({
AuthResponseType & {
file: DatasetFileSchema;
}
> {
> => {
const authRes = await parseHeaderCert(props);
const { teamId, tmbId } = authRes;
@@ -33,7 +33,7 @@ export async function authFile({
const permission = new Permission({
per: ReadPermissionVal,
isOwner: file.metadata?.tmbId === tmbId
isOwner: file.metadata?.uid === tmbId || file.metadata?.tmbId === tmbId
});
if (!permission.checkPer(per)) {
@@ -45,4 +45,4 @@ export async function authFile({
permission,
file
};
}
};

View File

@@ -413,7 +413,8 @@ export const createFileToken = (data: FileTokenQuery) => {
return Promise.reject('System unset FILE_TOKEN_KEY');
}
const expireMinutes = bucketNameMap[data.bucketName].previewExpireMinutes;
const expireMinutes =
data.customExpireMinutes ?? bucketNameMap[data.bucketName].previewExpireMinutes;
const expiredTime = Math.floor(addMinutes(new Date(), expireMinutes).getTime() / 1000);
const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken';
@@ -435,14 +436,14 @@ export const authFileToken = (token?: string) =>
const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken';
jwt.verify(token, key, function (err, decoded: any) {
if (err || !decoded.bucketName || !decoded?.teamId || !decoded?.tmbId || !decoded?.fileId) {
if (err || !decoded.bucketName || !decoded?.teamId || !decoded?.fileId) {
reject(ERROR_ENUM.unAuthFile);
return;
}
resolve({
bucketName: decoded.bucketName,
teamId: decoded.teamId,
tmbId: decoded.tmbId,
uid: decoded.uid,
fileId: decoded.fileId
});
});

View File

@@ -62,14 +62,14 @@ export async function authOutLinkValid<T extends OutlinkAppType = undefined>({
if (!shareId) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
}
const shareChat = (await MongoOutLink.findOne({ shareId }).lean()) as OutLinkSchema<T>;
const outLinkConfig = (await MongoOutLink.findOne({ shareId }).lean()) as OutLinkSchema<T>;
if (!shareChat) {
if (!outLinkConfig) {
return Promise.reject(OutLinkErrEnum.linkUnInvalid);
}
return {
appId: shareChat.appId,
shareChat
appId: outLinkConfig.appId,
outLinkConfig
};
}

View File

@@ -2,6 +2,7 @@ import { Permission } from '@fastgpt/global/support/permission/controller';
import { ApiRequestProps } from '../../type/next';
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
import { RequireAtLeastOne } from '@fastgpt/global/common/type/utils';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
export type ReqHeaderAuthType = {
cookie?: string;

View File

@@ -23,9 +23,6 @@ declare global {
var whisperModel: WhisperModelType;
var reRankModels: ReRankModelItemType[];
var systemLoadedGlobalVariables: boolean;
var systemLoadedGlobalConfig: boolean;
var workerPoll: Record<WorkerNameEnum, WorkerPool>;
var appMarketTemplates: TemplateMarketItemType[];
}

View File

@@ -10,6 +10,8 @@ export const hasHttps = () => {
return window.location.protocol === 'https:';
};
export const subRoute = process.env.NEXT_PUBLIC_BASE_URL;
export const getWebReqUrl = (url: string = '') => {
if (!url) return '/';
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL;

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { Flex, FlexProps } from '@chakra-ui/react';
import MyIcon from './index';
type Props = FlexProps & {
icon: string;
size?: string;
onClick?: () => void;
hoverColor?: string;
};
const MyIconButton = ({
icon,
onClick,
hoverColor = 'primary.600',
size = '1rem',
...props
}: Props) => {
return (
<Flex
mr={1}
p={1}
color={'myGray.500'}
rounded={'sm'}
alignItems={'center'}
bg={'transparent'}
transition={'background 0.1s'}
cursor={'pointer'}
_hover={{
bg: 'myGray.05',
color: hoverColor
}}
onClick={onClick}
{...props}
>
<MyIcon name={icon as any} w={size} />
</Flex>
);
};
export default MyIconButton;

View File

@@ -5,6 +5,7 @@ export const iconPaths = {
change: () => import('./icons/change.svg'),
chatSend: () => import('./icons/chatSend.svg'),
check: () => import('./icons/check.svg'),
checkCircle: () => import('./icons/checkCircle.svg'),
closeSolid: () => import('./icons/closeSolid.svg'),
collectionLight: () => import('./icons/collectionLight.svg'),
collectionSolid: () => import('./icons/collectionSolid.svg'),
@@ -101,6 +102,7 @@ export const iconPaths = {
'core/app/schedulePlan': () => import('./icons/core/app/schedulePlan.svg'),
'core/app/simpleBot': () => import('./icons/core/app/simpleBot.svg'),
'core/app/simpleMode/ai': () => import('./icons/core/app/simpleMode/ai.svg'),
'core/app/simpleMode/autoExec': () => import('./icons/core/app/simpleMode/autoExec.svg'),
'core/app/simpleMode/chat': () => import('./icons/core/app/simpleMode/chat.svg'),
'core/app/simpleMode/dataset': () => import('./icons/core/app/simpleMode/dataset.svg'),
'core/app/simpleMode/file': () => import('./icons/core/app/simpleMode/file.svg'),
@@ -183,6 +185,7 @@ export const iconPaths = {
'core/workflow/debugNext': () => import('./icons/core/workflow/debugNext.svg'),
'core/workflow/debugResult': () => import('./icons/core/workflow/debugResult.svg'),
'core/workflow/edgeArrow': () => import('./icons/core/workflow/edgeArrow.svg'),
'core/workflow/edgeArrowBold': () => import('./icons/core/workflow/edgeArrowBold.svg'),
'core/workflow/grout': () => import('./icons/core/workflow/grout.svg'),
'core/workflow/inputType/array': () => import('./icons/core/workflow/inputType/array.svg'),
'core/workflow/inputType/customVariable': () =>

View File

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17 17" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.5587 3.69438C13.9492 3.30386 14.5824 3.30386 14.9729 3.69438C15.3634 4.08491 15.3634 4.71807 14.9729 5.1086L7.63956 12.4419C7.24904 12.8325 6.61587 12.8325 6.22535 12.4419L2.89201 9.1086C2.50149 8.71807 2.50149 8.08491 2.89201 7.69438C3.28254 7.30386 3.9157 7.30386 4.30623 7.69438L6.93245 10.3206L13.5587 3.69438Z"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.8043 3.52859C14.0646 3.78894 14.0646 4.21105 13.8043 4.4714L6.47092 11.8047C6.21057 12.0651 5.78846 12.0651 5.52811 11.8047L2.19477 8.4714C1.93442 8.21105 1.93442 7.78894 2.19477 7.52859C2.45512 7.26824 2.87723 7.26824 3.13758 7.52859L5.99951 10.3905L12.8614 3.52859C13.1218 3.26824 13.5439 3.26824 13.8043 3.52859Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 452 B

After

Width:  |  Height:  |  Size: 453 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 17" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.1848 3.90471C9.12756 3.43362 7.94633 3.31691 6.81731 3.572C5.6883 3.82708 4.67198 4.44029 3.91995 5.32018C3.16791 6.20006 2.72044 7.29947 2.64429 8.45443C2.56813 9.6094 2.86736 10.758 3.49735 11.7291C4.12734 12.7001 5.05433 13.4414 6.14008 13.8426C7.22582 14.2437 8.41215 14.2831 9.52213 13.9549C10.6321 13.6267 11.6063 12.9485 12.2993 12.0215C12.9923 11.0944 13.3671 9.96815 13.3678 8.81067V8.2559C13.3678 7.88771 13.6663 7.58924 14.0345 7.58924C14.4027 7.58924 14.7011 7.88771 14.7011 8.2559V8.81105C14.7003 10.2561 14.2324 11.6625 13.3672 12.8198C12.502 13.9771 11.2859 14.8238 9.90017 15.2335C8.51447 15.6432 7.03346 15.594 5.67801 15.0933C4.32255 14.5925 3.16529 13.667 2.37881 12.4548C1.59232 11.2425 1.21877 9.80857 1.31384 8.3667C1.40892 6.92484 1.96754 5.55233 2.90638 4.45388C3.84523 3.35543 5.114 2.5899 6.52347 2.27144C7.93294 1.95299 9.4076 2.09869 10.7275 2.6868C11.0638 2.83666 11.215 3.23077 11.0651 3.56709C10.9153 3.9034 10.5211 4.05456 10.1848 3.90471ZM14.5056 3.50856C14.7661 3.76878 14.7663 4.19089 14.5061 4.45137L8.47187 10.4917C8.34686 10.6168 8.17726 10.6871 8.00039 10.6871C7.82352 10.6872 7.65389 10.617 7.52882 10.4919L5.71855 8.68161C5.4582 8.42126 5.4582 7.99915 5.71855 7.7388C5.9789 7.47846 6.40101 7.47846 6.66136 7.7388L7.99999 9.07744L13.5628 3.50904C13.823 3.24856 14.2452 3.24835 14.5056 3.50856Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1 +1,6 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694403033666" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4053" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M1016.32 494.08l-143.872-143.36c-9.728-9.728-26.112-9.728-35.84 0-9.728 9.728-9.728 26.112 0 35.84L936.96 486.4h-399.36V87.04l99.84 100.352c9.728 9.728 26.112 9.728 35.84 0 9.728-9.728 9.728-26.112 0-35.84l-143.36-143.872c-9.728-9.728-26.112-9.728-35.84 0l-143.36 143.872c-9.728 9.728-9.728 26.112 0 35.84 9.728 9.728 26.112 9.728 35.84 0L486.4 87.04v399.36H87.04l100.352-99.84c9.728-9.728 9.728-26.112 0-35.84-9.728-9.728-26.112-9.728-35.84 0l-143.872 143.36c-9.728 9.728-9.728 26.112 0 35.84l143.872 143.36c9.728 9.728 26.112 9.728 35.84 0 9.728-9.728 9.728-26.112 0-35.84L87.04 537.6h399.36v399.36l-99.84-100.352c-9.728-9.728-26.112-9.728-35.84 0-9.728 9.728-9.728 26.112 0 35.84l143.36 143.872c9.728 9.728 26.112 9.728 35.84 0l143.36-143.872c9.728-9.728 9.728-26.112 0-35.84-9.728-9.728-26.112-9.728-35.84 0L537.6 936.96v-399.36h399.36l-100.352 99.84c-9.728 9.728-9.728 26.112 0 35.84 9.728 9.728 26.112 9.728 35.84 0l143.872-143.36c10.24-9.728 10.24-26.112 0-35.84z" p-id="4054"></path></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
<path d="M8.47188 1.52653C8.34159 1.39624 8.17079 1.33115 8.00003 1.33127C7.82926 1.33115 7.65847 1.39624 7.52818 1.52653C7.52123 1.53348 7.51447 1.54054 7.5079 1.54771L6.11486 2.94074C5.85451 3.20109 5.85451 3.6232 6.11486 3.88355C6.37521 4.1439 6.79732 4.1439 7.05767 3.88355L7.33381 3.60741V5.99835C7.33381 6.36654 7.63229 6.66502 8.00048 6.66502C8.36867 6.66502 8.66714 6.36654 8.66714 5.99835V3.60831L8.94239 3.88355C9.20274 4.1439 9.62485 4.1439 9.8852 3.88355C10.1455 3.6232 10.1455 3.20109 9.8852 2.94074L8.49216 1.54771C8.48559 1.54054 8.47883 1.53348 8.47188 1.52653Z" />
<path d="M7.33381 12.3926V10.0039C7.33381 9.6357 7.63229 9.33723 8.00048 9.33723C8.36867 9.33723 8.66714 9.6357 8.66714 10.0039V12.3917L8.94239 12.1164C9.20274 11.8561 9.62485 11.8561 9.8852 12.1164C10.1455 12.3768 10.1455 12.7989 9.8852 13.0593L8.49216 14.4523C8.48559 14.4595 8.47883 14.4665 8.47188 14.4735C8.34159 14.6038 8.17079 14.6688 8.00003 14.6687C7.82926 14.6688 7.65847 14.6038 7.52818 14.4735C7.52123 14.4665 7.51447 14.4595 7.5079 14.4523L6.11486 13.0593C5.85451 12.7989 5.85451 12.3768 6.11486 12.1164C6.37521 11.8561 6.79732 11.8561 7.05767 12.1164L7.33381 12.3926Z" />
<path d="M6.0646 7.33378C6.43279 7.33378 6.73126 7.63226 6.73126 8.00045C6.73126 8.36864 6.43279 8.66711 6.0646 8.66711H3.60834L3.88358 8.94236C4.14393 9.20271 4.14393 9.62482 3.88358 9.88517C3.62323 10.1455 3.20112 10.1455 2.94077 9.88517L1.54774 8.49213C1.54057 8.48556 1.53351 8.4788 1.52656 8.47185C1.39627 8.34156 1.33118 8.17075 1.3313 7.99998C1.33119 7.82922 1.39628 7.65843 1.52656 7.52815C1.53351 7.5212 1.54057 7.51444 1.54774 7.50786L2.94077 6.11483C3.20112 5.85448 3.62323 5.85448 3.88358 6.11483C4.14393 6.37518 4.14393 6.79729 3.88358 7.05764L3.60744 7.33378H6.0646Z" />
<path d="M13.0593 6.11483L14.4523 7.50786C14.4595 7.51444 14.4666 7.5212 14.4735 7.52815C14.6038 7.65843 14.6689 7.82923 14.6688 8C14.6689 8.17076 14.6038 8.34156 14.4735 8.47185C14.4666 8.4788 14.4595 8.48556 14.4523 8.49213L13.0593 9.88517C12.799 10.1455 12.3768 10.1455 12.1165 9.88517C11.8562 9.62482 11.8562 9.20271 12.1165 8.94236L12.3911 8.66779H9.92959C9.5614 8.66779 9.26292 8.36931 9.26292 8.00112C9.26292 7.63293 9.5614 7.33445 9.92959 7.33445H12.3933L12.1165 7.05764C11.8562 6.79729 11.8562 6.37518 12.1165 6.11483C12.3769 5.85448 12.799 5.85448 13.0593 6.11483Z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21" fill="none">
<path d="M7.56249 11.1348L9.12024 7.33546C9.17155 7.21031 9.2934 7.12858 9.42866 7.12858H11.6153C11.857 7.12858 12.0184 7.37785 11.9194 7.5984L10.9629 9.72956C10.9134 9.83984 10.9941 9.96447 11.1149 9.96447H12.7714C13.0487 9.96447 13.2048 10.2834 13.0346 10.5024L9.93738 14.4874C9.73038 14.7537 9.30408 14.5793 9.34309 14.2443L9.62999 11.7805C9.64152 11.6815 9.56413 11.5946 9.46444 11.5946H7.87091C7.63386 11.5946 7.47257 11.3541 7.56249 11.1348Z" fill="#8774EE"/>
<path d="M3.68233 14.3773C3.28604 14.6067 2.77464 14.4728 2.58571 14.0557C1.82751 12.3817 1.64076 10.4957 2.06882 8.69266C2.561 6.61955 3.82859 4.81371 5.61106 3.64631C7.39354 2.47891 9.55548 2.03863 11.6525 2.41596C13.4764 2.74413 15.1306 3.66906 16.3621 5.03305C16.6689 5.37293 16.5873 5.89525 16.2187 6.16683C15.85 6.43841 15.3344 6.3554 15.0192 6.02321C14.0456 4.99697 12.7652 4.301 11.3589 4.04796C9.67911 3.74571 7.94736 4.09838 6.51957 5.03349C5.09178 5.9686 4.07642 7.4151 3.68218 9.07569C3.35212 10.4659 3.47839 11.9178 4.03001 13.2204C4.20857 13.6421 4.07861 14.1479 3.68233 14.3773Z" fill="#8774EE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.04324 13.2533C4.0589 13.2943 4.07172 13.336 4.08171 13.3781C4.17284 13.7615 4.0302 14.1759 3.68233 14.3773C3.28604 14.6067 2.77464 14.4728 2.58571 14.0557C2.55736 13.9931 2.52981 13.9302 2.50307 13.867C2.48986 13.8358 2.47685 13.8046 2.46403 13.7733C1.80857 12.1713 1.66467 10.395 2.06882 8.69266C2.561 6.61955 3.82859 4.81371 5.61106 3.64631C7.39354 2.47891 9.55548 2.03863 11.6525 2.41596C13.3745 2.7258 14.9453 3.5676 16.1518 4.80865C16.1754 4.83292 16.1989 4.85734 16.2222 4.88192C16.2694 4.93168 16.316 4.98206 16.3621 5.03305C16.6689 5.37293 16.5873 5.89525 16.2187 6.16683C15.895 6.40524 15.4582 6.37039 15.1431 6.1336C15.1085 6.10762 15.0754 6.07921 15.0441 6.04846C15.0357 6.04021 15.0274 6.03179 15.0192 6.02321C14.0456 4.99697 12.7652 4.301 11.3589 4.04796C9.67911 3.74571 7.94736 4.09838 6.51957 5.03349C5.09178 5.9686 4.07642 7.4151 3.68218 9.07569C3.35212 10.4659 3.47839 11.9178 4.03001 13.2204C4.03463 13.2313 4.03904 13.2423 4.04324 13.2533Z" fill="#8774EE"/>
<path d="M17.123 7.77804C17.5469 7.60478 18.0353 7.80718 18.1656 8.24615C18.6407 9.84662 18.6258 11.5599 18.1122 13.1618C17.5139 15.028 16.277 16.6236 14.6189 17.6682C12.9608 18.7129 10.9876 19.1397 9.04598 18.8738C7.37931 18.6455 5.82746 17.9193 4.58891 16.7998C4.24921 16.4928 4.27748 15.9649 4.61675 15.6574C4.95601 15.3498 5.47737 15.3802 5.82456 15.6788C6.79537 16.5136 7.9909 17.0556 9.27099 17.2309C10.8262 17.444 12.4068 17.102 13.735 16.2652C15.0631 15.4285 16.0539 14.1504 16.5332 12.6556C16.9276 11.4252 16.9548 10.1128 16.6209 8.87674C16.5015 8.43469 16.6992 7.95131 17.123 7.77804Z" fill="#8774EE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.6123 8.8424C16.615 8.85387 16.6178 8.86532 16.6209 8.87674C16.9548 10.1128 16.9276 11.4252 16.5332 12.6556C16.0539 14.1504 15.0631 15.4285 13.735 16.2652C12.4068 17.102 10.8262 17.444 9.27099 17.2309C7.9909 17.0556 6.79537 16.5136 5.82456 15.6788C5.81558 15.671 5.8065 15.6635 5.7973 15.6561C5.76301 15.6287 5.72719 15.6038 5.69016 15.5815C5.3527 15.3778 4.91457 15.3874 4.61675 15.6574C4.27748 15.9649 4.24921 16.4928 4.58891 16.7998C4.63988 16.8459 4.69138 16.8913 4.74339 16.936C4.76907 16.9581 4.79489 16.98 4.82082 17.0018C6.01868 18.0067 7.48176 18.6596 9.04598 18.8738C10.9876 19.1397 12.9608 18.7129 14.6189 17.6682C16.277 16.6236 17.5139 15.028 18.1122 13.1618C18.5942 11.6584 18.637 10.0568 18.2476 8.54252C18.2392 8.50973 18.2306 8.47699 18.2217 8.44429C18.2038 8.37806 18.1851 8.31201 18.1656 8.24615C18.0353 7.80718 17.5469 7.60478 17.123 7.77804C16.751 7.93014 16.5532 8.32121 16.5912 8.71352C16.5953 8.75657 16.6024 8.79963 16.6123 8.8424Z" fill="#8774EE"/>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 16" fill="none">
<path d="M3.07373 0.535309C2.48828 -0.0505066 1.53809 -0.0505066 0.952637 0.535309C0.366699 1.12106 0.366699 2.07083 0.952637 2.65665L6.26123 7.96536L0.952637 13.2737C0.367188 13.8595 0.367188 14.8092 0.952637 15.395C1.53857 15.9807 2.48828 15.9807 3.07422 15.395L9.43799 9.03104C9.66016 8.80875 9.7959 8.53372 9.84961 8.24643C9.90283 7.97018 9.87842 7.68179 9.77588 7.41672C9.70264 7.22818 9.58984 7.05142 9.43799 6.89926L3.07373 0.535309Z"/>
</svg>

After

Width:  |  Height:  |  Size: 525 B

View File

@@ -1,7 +1,7 @@
import React from 'react';
import React, { ForwardedRef, forwardRef } from 'react';
import { Image, ImageProps } from '@chakra-ui/react';
import { getWebReqUrl } from '../../../common/system/utils';
const MyImage = (props: ImageProps) => {
const MyImage = (props: ImageProps, ref?: ForwardedRef<any>) => {
return <Image {...props} src={getWebReqUrl(props.src)} alt={props.alt || ''} />;
};
export default React.memo(MyImage);
export default forwardRef(MyImage);

View File

@@ -7,22 +7,26 @@ import {
useOutsideClick,
MenuButton,
MenuItemProps,
PlacementWithLogical
PlacementWithLogical,
AvatarProps,
BoxProps,
DividerProps
} from '@chakra-ui/react';
import MyDivider from '../MyDivider';
import type { IconNameType } from '../Icon/type';
import { useSystem } from '../../../hooks/useSystem';
import Avatar from '../Avatar';
export type MenuItemType = 'primary' | 'danger';
export type MenuItemType = 'primary' | 'danger' | 'gray' | 'grayBg';
export type MenuSizeType = 'sm' | 'md' | 'xs' | 'mini';
export type Props = {
width?: number | string;
offset?: [number, number];
Button: React.ReactNode;
trigger?: 'hover' | 'click';
iconSize?: string;
iconRadius?: string;
size?: MenuSizeType;
placement?: PlacementWithLogical;
menuList: {
@@ -39,18 +43,9 @@ export type Props = {
}[];
};
const MyMenu = ({
width = 'auto',
trigger = 'hover',
offset,
iconSize = '1rem',
Button,
menuList,
iconRadius,
placement = 'bottom-start'
}: Props) => {
const typeMapStyle: Record<MenuItemType, MenuItemProps> = {
primary: {
const typeMapStyle: Record<MenuItemType, { styles: MenuItemProps; iconColor?: string }> = {
primary: {
styles: {
_hover: {
backgroundColor: 'primary.50',
color: 'primary.600'
@@ -64,7 +59,44 @@ const MyMenu = ({
color: 'primary.600'
}
},
danger: {
iconColor: 'myGray.600'
},
gray: {
styles: {
_hover: {
backgroundColor: 'myGray.05',
color: 'primary.600'
},
_focus: {
backgroundColor: 'myGray.05',
color: 'primary.600'
},
_active: {
backgroundColor: 'myGray.05',
color: 'primary.600'
}
},
iconColor: 'myGray.400'
},
grayBg: {
styles: {
_hover: {
backgroundColor: 'myGray.05',
color: 'primary.600'
},
_focus: {
backgroundColor: 'myGray.05',
color: 'primary.600'
},
_active: {
backgroundColor: 'myGray.05',
color: 'primary.600'
}
},
iconColor: 'myGray.600'
},
danger: {
styles: {
color: 'red.600',
_hover: {
background: 'red.1'
@@ -75,9 +107,97 @@ const MyMenu = ({
_active: {
background: 'red.1'
}
},
iconColor: 'red.600'
}
};
const sizeMapStyle: Record<
MenuSizeType,
{
iconStyle: AvatarProps;
labelStyle: BoxProps;
dividerStyle: DividerProps;
menuItemStyle: MenuItemProps;
}
> = {
mini: {
iconStyle: {
w: '14px'
},
labelStyle: {
fontSize: 'mini'
},
dividerStyle: {
my: 0.5
},
menuItemStyle: {
py: 1.5,
px: 2
}
};
},
xs: {
iconStyle: {
w: '14px'
},
labelStyle: {
fontSize: 'sm'
},
dividerStyle: {
my: 0.5
},
menuItemStyle: {
py: 1.5,
px: 2
}
},
sm: {
iconStyle: {
w: '1rem'
},
labelStyle: {
fontSize: 'sm'
},
dividerStyle: {
my: 1
},
menuItemStyle: {
py: 2,
px: 3,
_notLast: {
mb: 0.5
}
}
},
md: {
iconStyle: {
w: '2rem',
borderRadius: '6px'
},
labelStyle: {
fontSize: 'sm'
},
dividerStyle: {
my: 1
},
menuItemStyle: {
py: 2,
px: 3,
_notLast: {
mb: 0.5
}
}
}
};
const MyMenu = ({
width = 'auto',
trigger = 'hover',
size = 'sm',
offset,
Button,
menuList,
placement = 'bottom-start'
}: Props) => {
const { isPc } = useSystem();
const ref = useRef<HTMLDivElement>(null);
const closeTimer = useRef<any>();
@@ -165,7 +285,7 @@ const MyMenu = ({
return (
<Box key={i}>
{item.label && <Box fontSize={'sm'}>{item.label}</Box>}
{i !== 0 && <MyDivider h={'1.5px'} my={1} />}
{i !== 0 && <MyDivider h={'1.5px'} {...sizeMapStyle[size].dividerStyle} />}
{item.children.map((child, index) => (
<MenuItem
key={index}
@@ -177,29 +297,36 @@ const MyMenu = ({
child.onClick();
}
}}
py={2}
px={3}
alignItems={'center'}
fontSize={'sm'}
color={child.isActive ? 'primary.700' : 'myGray.600'}
whiteSpace={'pre-wrap'}
_notLast={{ mb: 0.5 }}
{...typeMapStyle[child.type || 'primary']}
{...typeMapStyle[child.type || 'primary'].styles}
{...sizeMapStyle[size].menuItemStyle}
{...child.menuItemStyles}
>
{!!child.icon && (
<Avatar
src={child.icon as any}
borderRadius={iconRadius}
w={iconSize}
mr={3}
mr={2}
{...sizeMapStyle[size].iconStyle}
color={
child.isActive
? 'inherit'
: typeMapStyle[child.type || 'primary'].iconColor
}
sx={{
'[role="menuitem"]:hover &': {
color: 'inherit'
}
}}
/>
)}
<Box w={'100%'}>
<Box
w={'100%'}
color={child.description ? 'myGray.900' : 'inherit'}
fontSize={'sm'}
{...sizeMapStyle[size].labelStyle}
>
{child.label}
</Box>

View File

@@ -60,7 +60,9 @@ export const MultipleRowSelect = ({
const newValue = [...cloneValue];
if (item.value === selectedValue) {
newValue[index] = undefined;
for (let i = index; i < newValue.length; i++) {
newValue[i] = undefined;
}
setCloneValue(newValue);
onSelect(newValue);
} else {

View File

@@ -18,12 +18,13 @@ const FillRowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props
display={'inline-flex'}
px={'3px'}
py={'3px'}
borderRadius={'md'}
borderRadius={'sm'}
borderWidth={'1px'}
borderColor={'borderColor.base'}
borderColor={'myGray.200'}
bg={'myGray.50'}
gap={'4px'}
fontSize={'sm'}
fontWeight={'medium'}
{...props}
>
{list.map((item) => (
@@ -33,7 +34,7 @@ const FillRowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props
alignItems={'center'}
justifyContent={'center'}
cursor={'pointer'}
borderRadius={'md'}
borderRadius={'xs'}
px={px}
py={py}
userSelect={'none'}
@@ -45,10 +46,14 @@ const FillRowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props
color: 'primary.600'
}
: {
color: 'myGray.500',
_hover: {
color: 'primary.600'
},
onClick: () => onChange(item.value)
})}
>
{item.icon && <MyIcon name={item.icon as any} mr={1} w={'14px'} />}
{item.icon && <MyIcon name={item.icon as any} mr={1.5} w={'18px'} />}
<Box>{item.label}</Box>
</Flex>
))}

View File

@@ -224,8 +224,8 @@ const JSONEditor = ({
{resize && (
<Box
position={'absolute'}
right={'0'}
bottom={'0'}
right={'-2'}
bottom={'-3'}
zIndex={10}
cursor={'ns-resize'}
px={'4px'}
@@ -269,6 +269,8 @@ const JSONEditor = ({
fontSize={'xs'}
color={'myGray.500'}
display={placeholderDisplay}
pointerEvents={'none'}
userSelect={'none'}
>
{placeholder}
</Box>

View File

@@ -105,8 +105,8 @@ export default function Editor({
left={0}
right={0}
bottom={0}
py={2}
px={3}
py={3}
px={3.5}
pointerEvents={'none'}
overflow={'hidden'}
>

View File

@@ -1,5 +1,5 @@
import { ChevronRightIcon } from '@chakra-ui/icons';
import { Box } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import Avatar from '../../../../../../../components/common/Avatar';
@@ -28,7 +28,7 @@ export default function VariableLabel({
color={parentLabel !== 'undefined' ? 'myGray.900' : 'red.600'}
>
{parentLabel !== 'undefined' ? (
<span>
<Flex alignItems={'center'} color={'myGray.600'}>
<Avatar
src={nodeAvatar as any}
w={'1rem'}
@@ -36,12 +36,11 @@ export default function VariableLabel({
borderRadius={'xs'}
display={'inline-flex'}
verticalAlign={'middle'}
mb={'3px'}
/>
{parentLabel}
<ChevronRightIcon />
<ChevronRightIcon color={'myGray.500'} />
{childLabel}
</span>
</Flex>
) : (
<>
<Box>{t('common:invalid_variable')}</Box>

View File

@@ -6,7 +6,7 @@ import { TextNode } from 'lexical';
import { getHashtagRegexString } from './utils';
import { mergeRegister } from '@lexical/utils';
import { registerLexicalTextEntity } from '../../utils';
import { useTranslation } from 'react-i18next';
import { useTranslation } from 'next-i18next';
const REGEX = new RegExp(getHashtagRegexString(), 'i');

View File

@@ -1,13 +1,12 @@
import React, { useMemo, useRef } from 'react';
import MyMenu from '../../common/MyMenu';
import MyMenu, { MenuItemType } from '../../common/MyMenu';
import {
FlowNodeInputMap,
FlowNodeInputTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
import { Box, Button, useTheme } from '@chakra-ui/react';
import { Button, useTheme } from '@chakra-ui/react';
import MyIcon from '../../common/Icon';
import { useTranslation } from 'next-i18next';
import { useConfirm } from '../../../hooks/useConfirm';
const NodeInputSelect = ({
renderTypeList,
@@ -19,9 +18,6 @@ const NodeInputSelect = ({
onChange: (e: string) => void;
}) => {
const { t } = useTranslation();
const { openConfirm, ConfirmModal } = useConfirm({
title: t('common:core.workflow.Change input type tip')
});
const renderType = renderTypeList[renderTypeIndex];
const theme = useTheme();
@@ -136,7 +132,22 @@ const NodeInputSelect = ({
);
const filterMenuList = useMemo(
() => renderList.filter((item) => renderTypeList.includes(item.renderType)),
() =>
renderList
.filter((item) => renderTypeList.includes(item.renderType))
.map((item) => ({
...item,
type: 'gray' as MenuItemType,
menuItemStyles: {
fontWeight: 'medium',
minH: 7,
h: 7,
px: 1,
py: 0,
mb: 0,
borderRadius: 'xs'
}
})),
[renderTypeList, renderList]
);
const renderTypeData = useMemo(
@@ -148,24 +159,37 @@ const NodeInputSelect = ({
<MyMenu
offset={[-0.5, 0.5]}
trigger="click"
size="mini"
Button={
<Button
leftIcon={
<MyIcon name={renderTypeData.icon as any} w={'14px'} color={'primary.600'} mr={-0.5} />
}
rightIcon={<MyIcon name={'common/select'} w={'0.8rem'} color={'myGray.500'} ml={-1} />}
rightIcon={
<MyIcon
name={'common/select'}
w={'0.8rem'}
color={'myGray.500'}
mx={-1}
sx={{
'button:hover &': {
color: 'primary.600'
}
}}
/>
}
variant={'grayBase'}
border={theme.borders.base}
borderColor={'myGray.200'}
borderRadius={'sm'}
px={'10px'}
py={'6px'}
px={'8px'}
fontSize={'mini'}
color={'myGray.600'}
h={'28px'}
bg={'myGray.100'}
minH={'28px'}
h={'28px'}
>
<Box fontWeight={'medium'}>{renderTypeData.title}</Box>
{renderTypeData.title}
</Button>
}
menuList={[{ children: filterMenuList }]}

View File

@@ -1,5 +1,6 @@
import Cookies, { CookieAttributes } from 'js-cookie';
import { useTranslation } from 'next-i18next';
import { LangEnum } from '../../../projects/app/src/web/common/utils/i18n';
const setCookie = (key: string, value: string, options?: CookieAttributes) => {
Cookies.set(key, value, options);
@@ -13,11 +14,13 @@ const LANG_KEY = 'NEXT_LOCALE';
export const useI18nLng = () => {
const { i18n } = useTranslation();
const languageMap: Record<string, string> = {
zh: 'zh',
'zh-CN': 'zh',
'zh-Hans': 'zh',
en: 'en',
'en-US': 'en'
zh: LangEnum.zh_CN,
'zh-CN': LangEnum.zh_CN,
'zh-Hans': LangEnum.zh_CN,
'zh-HK': LangEnum.zh_CN,
'zh-TW': LangEnum.zh_TW,
en: LangEnum.en,
'en-US': LangEnum.en
};
const onChangeLng = (lng: string) => {

View File

@@ -188,7 +188,8 @@ export function useScrollPagination<
pageSize = 10,
params = {},
EmptyTip
EmptyTip,
showErrorToast = true
}: {
refreshDeps?: any[];
scrollLoadType?: 'top' | 'bottom';
@@ -196,6 +197,7 @@ export function useScrollPagination<
pageSize?: number;
params?: Record<string, any>;
EmptyTip?: React.JSX.Element;
showErrorToast?: boolean;
}
) {
const { t } = useTranslation();
@@ -249,10 +251,12 @@ export function useScrollPagination<
setData((prevData) => (offset === 0 ? res.list : [...prevData, ...res.list]));
}
} catch (error: any) {
toast({
title: getErrText(error, t('common:core.chat.error.data_error')),
status: 'error'
});
if (showErrorToast) {
toast({
title: getErrText(error, t('common:core.chat.error.data_error')),
status: 'error'
});
}
console.log(error);
}

View File

@@ -1,8 +1,10 @@
import { useToast as uToast, UseToastOptions } from '@chakra-ui/react';
import { CSSProperties, useCallback } from 'react';
import { useTranslation } from 'next-i18next';
export const useToast = (props?: UseToastOptions & { containerStyle?: CSSProperties }) => {
const { containerStyle, ...toastProps } = props || {};
const { t } = useTranslation();
const toast = uToast({
position: 'top',
@@ -17,7 +19,11 @@ export const useToast = (props?: UseToastOptions & { containerStyle?: CSSPropert
const myToast = useCallback(
(options?: UseToastOptions) => {
if (options?.title || options?.description) {
toast(options);
toast({
...(options.title && { title: t(options.title as any) }),
...(options.description && { description: t(options.description as any) }),
...options
});
}
},
[props]

View File

@@ -1,5 +1,5 @@
{
"Role_setting": "Role setting",
"Role_setting": "Permission",
"Run": "Execute",
"Team Tags Set": "Team tags",
"Team_Tags": "Team tags",
@@ -14,6 +14,8 @@
"app.version_past": "Previously Published",
"app.version_publish_tips": "This version will be saved to the team cloud, synchronized with the entire team, and update the app version on all release channels.",
"app_detail": "Application Details",
"auto_execute": "Automatic execution",
"auto_execute_default_prompt_placeholder": "Default questions sent when executing automatically",
"chat_debug": "Chat Preview",
"chat_logs": "Conversation Logs",
"chat_logs_tips": "Logs will record the online, shared, and API (requires chatId) conversation records of this app.",
@@ -36,10 +38,10 @@
"document_quote_tip": "Usually used to accept user-uploaded document content (requires document parsing), and can also be used to reference other string data.",
"document_upload": "Document Upload",
"edit_app": "Edit Application",
"edit_info": "Edit Information",
"edit_info": "Edit",
"execute_time": "Execution Time",
"export_config_successful": "Configuration copied, some sensitive information automatically filtered. Please check for any remaining sensitive data.",
"export_configs": "Export Configurations",
"export_configs": "Export",
"feedback_count": "User Feedback",
"file_quote_link": "Files",
"file_recover": "File will overwrite current content",
@@ -50,7 +52,7 @@
"go_to_run": "Go to Execution",
"image_upload": "Image Upload",
"image_upload_tip": "How to activate model image recognition capabilities",
"import_configs": "Import Configurations",
"import_configs": "Import",
"import_configs_failed": "Import configuration failed, please ensure the configuration is correct!",
"import_configs_success": "Import Successful",
"initial_form": "initial state",
@@ -77,6 +79,7 @@
"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_auto_execute": "Enable automatic execution",
"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).",
"or_drag_JSON": "or drag in JSON file",
"paste_config": "Paste Configuration",

View File

@@ -4,6 +4,7 @@
"LLM_model_response_empty": "The model flow response is empty, please check whether the model flow output is normal.",
"chat_history": "Conversation History",
"chat_input_guide_lexicon_is_empty": "Lexicon not configured yet",
"chat_test_app": "Debug-{{name}}",
"citations": "{{num}} References",
"click_contextual_preview": "Click to see contextual preview",
"config_input_guide": "Set Up Input Guide",

View File

@@ -268,9 +268,11 @@
"core.app.Api request": "API Request",
"core.app.Api request desc": "Integrate into existing systems through API, or WeChat Work, Feishu, etc.",
"core.app.App intro": "App Introduction",
"core.app.Auto execute": "Auto execute",
"core.app.Chat Variable": "Chat Variable",
"core.app.Config schedule plan": "Configure Scheduled Execution",
"core.app.Config whisper": "Configure Voice Input",
"core.app.Config_auto_execute": "Click to configure automatic execution rules",
"core.app.Interval timer config": "Scheduled Execution Configuration",
"core.app.Interval timer run": "Scheduled Execution",
"core.app.Interval timer tip": "Can Execute App on Schedule",
@@ -428,7 +430,7 @@
"core.chat.logs.online": "Online Use",
"core.chat.logs.share": "External Link Call",
"core.chat.logs.team": "Team Space Chat",
"core.chat.logs.test": "Test",
"core.chat.logs.test": "Online debugging",
"core.chat.logs.wecom": "WeChat Work",
"core.chat.markdown.Edit Question": "Edit Question",
"core.chat.markdown.Quick Question": "Click to Ask Immediately",
@@ -732,11 +734,11 @@
"core.module.template.AI function": "AI Capability",
"core.module.template.AI response switch tip": "If you want the current node not to output content, you can turn off this switch. The content output by AI will not be displayed to the user, and you can manually use 'AI Response Content' for special processing.",
"core.module.template.AI support tool tip": "Models that support function calls can better use tool calls.",
"core.module.template.Basic Node": "Basic Function",
"core.module.template.Basic Node": "Basic",
"core.module.template.Query extension": "Question Optimization",
"core.module.template.System Plugin": "System Plugin",
"core.module.template.System Plugin": "System",
"core.module.template.System input module": "System Input",
"core.module.template.Team app": "Team App",
"core.module.template.Team app": "Team",
"core.module.template.Tool module": "Tool",
"core.module.template.UnKnow Module": "Unknown Module",
"core.module.template.ai_chat": "AI conversation",

View File

@@ -14,6 +14,9 @@
"app.version_past": "发布过",
"app.version_publish_tips": "该版本将被保存至团队云端,同步给整个团队,同时更新所有发布渠道的应用版本",
"app_detail": "应用详情",
"auto_execute": "自动执行",
"auto_execute_default_prompt_placeholder": "自动执行时,发送的默认问题",
"auto_execute_tip": "开启后用户进入对话界面将自动触发工作流。执行顺序1、对话开场白2、全局变量3、自动执行。",
"chat_debug": "调试预览",
"chat_logs": "对话日志",
"chat_logs_tips": "日志会记录该应用的在线、分享和 API需填写 chatId对话记录",
@@ -77,6 +80,7 @@
"move.hint": "移动后,所选应用/文件夹将继承新文件夹的权限设置,原先的权限设置失效。",
"move_app": "移动应用",
"not_json_file": "请选择JSON文件",
"open_auto_execute": "启用自动执行",
"open_vision_function_tip": "有图示开关的模型即拥有图片识别能力。若开启模型会解析文件链接里的图片并自动解析用户问题中的图片用户问题≤500字时生效。",
"or_drag_JSON": "或拖入JSON文件",
"paste_config": "粘贴配置",

View File

@@ -4,6 +4,7 @@
"LLM_model_response_empty": "模型流响应为空,请检查模型流输出是否正常",
"chat_history": "聊天记录",
"chat_input_guide_lexicon_is_empty": "还没有配置词库",
"chat_test_app": "调试-{{name}}",
"citations": "{{num}}条引用",
"click_contextual_preview": "点击查看上下文预览",
"config_input_guide": "配置输入引导",

View File

@@ -267,9 +267,11 @@
"core.app.Api request": "API 访问",
"core.app.Api request desc": "通过 API 接入到已有系统中,或企微、飞书等",
"core.app.App intro": "应用介绍",
"core.app.Auto execute": "自动执行",
"core.app.Chat Variable": "对话框变量",
"core.app.Config schedule plan": "配置定时执行",
"core.app.Config whisper": "配置语音输入",
"core.app.Config_auto_execute": "点击配置自动执行规则",
"core.app.Interval timer config": "定时执行配置",
"core.app.Interval timer run": "定时执行",
"core.app.Interval timer tip": "可定时执行应用",
@@ -427,7 +429,7 @@
"core.chat.logs.online": "在线使用",
"core.chat.logs.share": "外部链接调用",
"core.chat.logs.team": "团队空间对话",
"core.chat.logs.test": "试",
"core.chat.logs.test": "在线调试",
"core.chat.logs.wecom": "企业微信",
"core.chat.markdown.Edit Question": "编辑问题",
"core.chat.markdown.Quick Question": "点我立即提问",

View File

@@ -221,7 +221,6 @@ const Button = defineStyleConfig({
boxShadow: '0px 0px 1px 0px rgba(19, 51, 107, 0.08), 0px 1px 2px 0px rgba(19, 51, 107, 0.05)',
_hover: {
color: 'red.600',
background: 'red.1',
borderColor: 'red.300'
},
_active: {
@@ -251,6 +250,16 @@ const Button = defineStyleConfig({
color: 'red.600'
}
},
grayGhost: {
color: 'myGray.500',
fontWeight: '500',
p: 0,
bg: 'transparent',
transition: 'background 0.1s',
_hover: {
bg: 'myGray.05'
}
},
transparentBase: {
color: 'myGray.800',
fontWeight: '500',
@@ -570,6 +579,43 @@ const Table = tableMultiStyle({
}
})
},
variants: {
workflow: {
table: {
bg: 'white'
},
thead: {
tr: {
th: {
p: '0',
px: 4,
bg: 'myGray.50',
borderRadius: 'none !important',
borderBottom: 'none',
height: '32px',
fontSize: 'mini',
fontWeight: 'medium'
}
}
},
tbody: {
tr: {
td: {
p: '0',
px: 4,
fontSize: 'xs',
borderBottom: 'base',
height: '40px'
},
'&:last-child': {
td: {
borderBottom: 'none'
}
}
}
}
}
},
defaultProps: {
size: 'md'
}

View File

@@ -1,13 +1,13 @@
import 'i18next';
import common from '../i18n/zh/common.json';
import dataset from '../i18n/zh/dataset.json';
import app from '../i18n/zh/app.json';
import file from '../i18n/zh/file.json';
import publish from '../i18n/zh/publish.json';
import workflow from '../i18n/zh/workflow.json';
import user from '../i18n/zh/user.json';
import chat from '../i18n/zh/chat.json';
import login from '../i18n/zh/login.json';
import common from '../i18n/zh-CN/common.json';
import dataset from '../i18n/zh-CN/dataset.json';
import app from '../i18n/zh-CN/app.json';
import file from '../i18n/zh-CN/file.json';
import publish from '../i18n/zh-CN/publish.json';
import workflow from '../i18n/zh-CN/workflow.json';
import user from '../i18n/zh-CN/user.json';
import chat from '../i18n/zh-CN/chat.json';
import login from '../i18n/zh-CN/login.json';
export interface I18nNamespaces {
common: typeof common;

View File

@@ -5,8 +5,8 @@
module.exports = {
i18n: {
defaultLocale: 'zh',
locales: ['en', 'zh', 'zh_TW'],
defaultLocale: 'zh-CN',
locales: ['en', 'zh-CN', 'zh-TW'],
localeDetection: false
},
localePath:

View File

@@ -85,7 +85,8 @@ const nextConfig = {
experimental: {
// 优化 Server Components 的构建和运行,避免不必要的客户端打包。
serverComponentsExternalPackages: ['mongoose', 'pg', '@node-rs/jieba', 'duck-duck-scrape'],
outputFileTracingRoot: path.join(__dirname, '../../')
outputFileTracingRoot: path.join(__dirname, '../../'),
instrumentationHook: true
}
};

View File

@@ -1,6 +1,6 @@
{
"name": "app",
"version": "4.8.13",
"version": "4.8.14",
"private": false,
"scripts": {
"dev": "next dev",

View File

@@ -0,0 +1,8 @@
<svg width="36" height="37" viewBox="0 0 36 37" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="0.617554" width="36" height="36" rx="6" fill="#F0EEFF"/>
<path d="M14.8629 19.2382L16.7322 14.679C16.7937 14.5289 16.94 14.4308 17.1023 14.4308H19.7262C20.0163 14.4308 20.2099 14.7299 20.0912 14.9946L18.9433 17.552C18.884 17.6843 18.9808 17.8339 19.1258 17.8339H21.1135C21.4463 17.8339 21.6336 18.2166 21.4294 18.4793L17.7127 23.2613C17.4643 23.5809 16.9528 23.3717 16.9996 22.9696L17.3439 20.0131C17.3577 19.8943 17.2648 19.79 17.1452 19.79H15.233C14.9485 19.79 14.755 19.5014 14.8629 19.2382Z" fill="#8774EE"/>
<path d="M10.2067 23.1292C9.73113 23.4045 9.11745 23.2438 8.89074 22.7433C7.9809 20.7346 7.7568 18.4713 8.27046 16.3077C8.86108 13.8199 10.3822 11.6529 12.5212 10.2521C14.6601 8.85118 17.2545 8.32284 19.7709 8.77564C21.9595 9.16945 23.9446 10.2794 25.4224 11.9162C25.7906 12.324 25.6927 12.9508 25.2503 13.2767C24.8079 13.6026 24.1892 13.503 23.811 13.1043C22.6426 11.8729 21.1061 11.0377 19.4185 10.734C17.4028 10.3713 15.3247 10.7945 13.6114 11.9167C11.898 13.0388 10.6796 14.7746 10.2065 16.7673C9.81043 18.4356 9.96196 20.1778 10.6239 21.741C10.8382 22.247 10.6822 22.854 10.2067 23.1292Z" fill="#8774EE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.6398 21.7804C10.6586 21.8296 10.6739 21.8797 10.6859 21.9302C10.7953 22.3903 10.6241 22.8876 10.2067 23.1292C9.73113 23.4045 9.11745 23.2438 8.89074 22.7433C8.85672 22.6682 8.82366 22.5927 8.79156 22.5169C8.77571 22.4795 8.7601 22.442 8.74472 22.4044C7.95817 20.4821 7.78549 18.3505 8.27046 16.3077C8.86108 13.8199 10.3822 11.6529 12.5212 10.2521C14.6601 8.85118 17.2545 8.32284 19.7709 8.77564C21.8373 9.14745 23.7223 10.1576 25.1701 11.6469C25.1984 11.676 25.2265 11.7053 25.2545 11.7348C25.3112 11.7945 25.3671 11.855 25.4224 11.9162C25.7906 12.324 25.6927 12.9508 25.2503 13.2767C24.8619 13.5628 24.3377 13.521 23.9596 13.2368C23.9181 13.2056 23.8784 13.1715 23.8408 13.1346C23.8307 13.1247 23.8207 13.1146 23.811 13.1043C22.6426 11.8729 21.1061 11.0377 19.4185 10.734C17.4028 10.3713 15.3247 10.7945 13.6114 11.9167C11.898 13.0388 10.6796 14.7746 10.2065 16.7673C9.81043 18.4356 9.96196 20.1778 10.6239 21.741C10.6294 21.7541 10.6347 21.7672 10.6398 21.7804Z" fill="#8774EE"/>
<path d="M26.3355 15.2101C26.8441 15.0022 27.4302 15.2451 27.5866 15.7719C28.1567 17.6924 28.1388 19.7484 27.5225 21.6707C26.8045 23.9101 25.3203 25.8248 23.3306 27.0784C21.3408 28.3319 18.973 28.8442 16.6431 28.5251C14.6431 28.2511 12.7808 27.3797 11.2946 26.0363C10.8869 25.6678 10.9209 25.0344 11.328 24.6653C11.7351 24.2963 12.3607 24.3327 12.7774 24.691C13.9423 25.6928 15.377 26.3432 16.9131 26.5536C18.7794 26.8092 20.6761 26.3989 22.2699 25.3948C23.8636 24.3907 25.0526 22.8569 25.6277 21.0632C26.1011 19.5867 26.1337 18.0119 25.733 16.5286C25.5897 15.9981 25.8269 15.4181 26.3355 15.2101Z" fill="#8774EE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.7226 16.4874C25.7258 16.5011 25.7293 16.5149 25.733 16.5286C26.1337 18.0119 26.1011 19.5867 25.6277 21.0632C25.0526 22.8569 23.8636 24.3907 22.2699 25.3948C20.6761 26.3989 18.7794 26.8092 16.9131 26.5536C15.377 26.3432 13.9423 25.6928 12.7774 24.691C12.7666 24.6817 12.7557 24.6727 12.7446 24.6639C12.7035 24.631 12.6605 24.6011 12.6161 24.5743C12.2111 24.3299 11.6854 24.3414 11.328 24.6653C10.9209 25.0344 10.8869 25.6678 11.2946 26.0363C11.3557 26.0916 11.4175 26.1461 11.48 26.1997C11.5108 26.2262 11.5417 26.2525 11.5729 26.2786C13.0103 27.4846 14.766 28.268 16.6431 28.5251C18.973 28.8442 21.3408 28.3319 23.3306 27.0784C25.3203 25.8248 26.8045 23.9101 27.5225 21.6707C28.101 19.8666 28.1523 17.9447 27.685 16.1275C27.6749 16.0882 27.6646 16.0489 27.654 16.0096C27.6325 15.9302 27.61 15.8509 27.5866 15.7719C27.4302 15.2451 26.8441 15.0022 26.3355 15.2101C25.889 15.3927 25.6517 15.8619 25.6973 16.3327C25.7023 16.3844 25.7107 16.436 25.7226 16.4874Z" fill="#8774EE"/>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
import { Box, BoxProps, Flex, Link, LinkProps } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { HUMAN_ICON } from '@fastgpt/global/common/system/constants';
import NextLink from 'next/link';
import Badge from '../Badge';
@@ -23,14 +23,14 @@ const Navbar = ({ unread }: { unread: number }) => {
const router = useRouter();
const { userInfo } = useUserStore();
const { gitStar, feConfigs } = useSystemStore();
const { lastChatAppId, lastChatId } = useChatStore();
const { lastChatAppId } = useChatStore();
const navbarList = useMemo(
() => [
{
label: t('common:navbar.Chat'),
icon: 'core/chat/chatLight',
activeIcon: 'core/chat/chatFill',
link: `/chat?appId=${lastChatAppId}&chatId=${lastChatId}`,
link: `/chat?appId=${lastChatAppId}`,
activeLink: ['/chat']
},
{
@@ -55,7 +55,7 @@ const Navbar = ({ unread }: { unread: number }) => {
activeLink: ['/account']
}
],
[lastChatAppId, lastChatId, t]
[lastChatAppId, t]
);
const itemStyles: BoxProps & LinkProps = {
@@ -84,6 +84,7 @@ const Navbar = ({ unread }: { unread: number }) => {
h={'100%'}
w={'100%'}
userSelect={'none'}
pb={2}
>
{/* logo */}
<Box
@@ -155,6 +156,7 @@ const Navbar = ({ unread }: { unread: number }) => {
href={`/account?currentTab=inform`}
mb={0}
color={'myGray.500'}
height={'48px'}
>
<Badge count={unread}>
<MyIcon name={'support/user/informLight'} width={'22px'} height={'22px'} />
@@ -171,6 +173,7 @@ const Navbar = ({ unread }: { unread: number }) => {
target="_blank"
mb={0}
color={'myGray.500'}
height={'48px'}
>
<MyIcon name={'common/courseLight'} width={'24px'} height={'24px'} />
</Link>
@@ -186,6 +189,7 @@ const Navbar = ({ unread }: { unread: number }) => {
{...hoverStyle}
mt={0}
color={'myGray.500'}
height={'48px'}
>
<MyIcon name={'common/gitInlight'} width={'26px'} height={'26px'} />
</Link>

View File

@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import { useRouter } from 'next/router';
import { Flex, Box } from '@chakra-ui/react';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { useTranslation } from 'next-i18next';
import Badge from '../Badge';
import MyIcon from '@fastgpt/web/components/common/Icon';
@@ -9,14 +9,14 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
const NavbarPhone = ({ unread }: { unread: number }) => {
const router = useRouter();
const { t } = useTranslation();
const { lastChatAppId, lastChatId } = useChatStore();
const { lastChatAppId } = useChatStore();
const navbarList = useMemo(
() => [
{
label: t('common:navbar.Chat'),
icon: 'core/chat/chatLight',
activeIcon: 'core/chat/chatFill',
link: `/chat?appId=${lastChatAppId}&chatId=${lastChatId}`,
link: `/chat?appId=${lastChatAppId}`,
activeLink: ['/chat'],
unread: 0
},
@@ -45,7 +45,7 @@ const NavbarPhone = ({ unread }: { unread: number }) => {
unread
}
],
[t, lastChatAppId, lastChatId, unread]
[t, lastChatAppId, unread]
);
return (

View File

@@ -1,5 +1,5 @@
import { langMap } from '@/web/common/utils/i18n';
import { Avatar, Box, Flex } from '@chakra-ui/react';
import { Box, Flex } from '@chakra-ui/react';
import MySelect from '@fastgpt/web/components/common/MySelect';
import { useI18nLng } from '@fastgpt/web/hooks/useI18n';
import { useTranslation } from 'next-i18next';
@@ -14,7 +14,7 @@ const I18nLngSelector = () => {
return Object.entries(langMap).map(([key, lang]) => ({
label: (
<Flex alignItems={'center'}>
<MyIcon borderRadius={'0'} mr={2} name={lang.avatar as any} w={'14px'} h={'9px'} />
<MyIcon borderRadius={'0'} mr={2} name={lang.avatar as any} w={'1rem'} />
<Box>{lang.label}</Box>
</Flex>
),

View File

@@ -0,0 +1,87 @@
import { Box, Button, Flex, ModalBody, Switch, Textarea, useDisclosure } from '@chakra-ui/react';
import { defaultAutoExecuteConfig } from '@fastgpt/global/core/app/constants';
import { AppAutoExecuteConfigType } from '@fastgpt/global/core/app/type';
import MyIcon from '@fastgpt/web/components/common/Icon';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import { useTranslation } from 'react-i18next';
import ChatFunctionTip from './Tip';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import MyModal from '@fastgpt/web/components/common/MyModal';
const AutoExecConfig = ({
value = defaultAutoExecuteConfig,
onChange
}: {
value?: AppAutoExecuteConfigType;
onChange: (e: AppAutoExecuteConfigType) => void;
}) => {
const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure();
const isOpenAutoExec = value.open;
const defaultPrompt = value.defaultPrompt;
const formLabel = isOpenAutoExec
? t('common:core.app.whisper.Open')
: t('common:core.app.whisper.Close');
return (
<Flex alignItems={'center'}>
<MyIcon name={'core/app/simpleMode/autoExec'} mr={2} w={'20px'} />
<FormLabel color={'myGray.600'}>{t('app:auto_execute')}</FormLabel>
<ChatFunctionTip type={'autoExec'} />
<Box flex={1} />
<MyTooltip label={t('common:core.app.Config_auto_execute')}>
<Button
variant={'transparentBase'}
iconSpacing={1}
size={'sm'}
mr={'-5px'}
onClick={onOpen}
color={'myGray.600'}
>
{formLabel}
</Button>
</MyTooltip>
<MyModal
title={t('common:core.app.Auto execute')}
iconSrc="core/app/simpleMode/autoExec"
isOpen={isOpen}
onClose={onClose}
>
<ModalBody>
<Flex justifyContent={'space-between'} alignItems={'center'}>
<FormLabel flex={'0 0 100px'}>{t('app:open_auto_execute')}</FormLabel>
<Switch
isChecked={isOpenAutoExec}
onChange={(e) => {
onChange({
...value,
open: e.target.checked
});
}}
/>
</Flex>
{isOpenAutoExec && (
<Box mt={4}>
<FormLabel mb={1}>{t('common:core.app.schedule.Default prompt')}</FormLabel>
<Textarea
value={defaultPrompt}
rows={8}
bg={'myGray.50'}
placeholder={t('app:auto_execute_default_prompt_placeholder')}
onChange={(e) => {
onChange({
...value,
defaultPrompt: e.target.value
});
}}
/>
</Box>
)}
</ModalBody>
</MyModal>
</Flex>
);
};
export default AutoExecConfig;

View File

@@ -139,7 +139,7 @@ const InputGuideConfig = ({
onOpenLexiconConfig();
}}
>
{chatT('config_input_guide_lexicon')}
{t('chat:config_input_guide_lexicon')}
</Button>
</Flex>
<>
@@ -152,7 +152,7 @@ const InputGuideConfig = ({
cursor={'pointer'}
>
<MyIcon name={'book'} w={'17px'} ml={4} mr={1} color={'myGray.600'} />
{commonT('common.Documents')}
{t('common:common.Documents')}
</Flex>
<Box flex={'1 0 0'} />
</Flex>

View File

@@ -23,7 +23,7 @@ import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
const MultipleRowSelect = dynamic(
() => import('@fastgpt/web/components/common/MySelect/MultipleRowSelect')
);
import { i18nT } from '@fastgpt/web/i18n/utils';
// options type:
enum CronJobTypeEnum {
month = 'month',
@@ -233,24 +233,24 @@ const ScheduledTriggerConfig = ({
}
if (cronField[0] === 'month') {
return t('core.app.schedule.Every month', {
return t('common:core.app.schedule.Every month', {
day: cronField[1],
hour: cronField[2]
});
}
if (cronField[0] === 'week') {
return t('core.app.schedule.Every week', {
return t('common:core.app.schedule.Every week', {
day: cronField[1] === 0 ? t('app:day') : cronField[1],
hour: cronField[2]
});
}
if (cronField[0] === 'day') {
return t('core.app.schedule.Every day', {
return t('common:core.app.schedule.Every day', {
hour: cronField[1]
});
}
if (cronField[0] === 'interval') {
return t('core.app.schedule.Interval', {
return t('common:core.app.schedule.Interval', {
interval: cronField[1]
});
}

View File

@@ -12,7 +12,8 @@ enum FnTypeEnum {
welcome = 'welcome',
file = 'file',
visionModel = 'visionModel',
instruction = 'instruction'
instruction = 'instruction',
autoExec = 'autoExec'
}
const ChatFunctionTip = ({ type }: { type: `${FnTypeEnum}` }) => {
@@ -66,6 +67,12 @@ const ChatFunctionTip = ({ type }: { type: `${FnTypeEnum}` }) => {
title: t('workflow:plugin.Instructions'),
desc: t('workflow:plugin.Instruction_Tip'),
imgUrl: '/imgs/app/instruction.svg'
},
[FnTypeEnum.autoExec]: {
icon: '/imgs/app/autoExec-icon.svg',
title: t('common:core.app.Auto execute'),
desc: t('app:auto_execute_tip'),
imgUrl: '/imgs/app/autoExec.svg'
}
});
const data = map.current[type];

View File

@@ -31,6 +31,7 @@ import ChatFunctionTip from './Tip';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import InputTypeConfig from '@/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig';
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
export const defaultVariable: VariableItemType = {
id: nanoid(),
@@ -190,92 +191,59 @@ const VariableEdit = ({
</Flex>
{/* Form render */}
{formatVariables.length > 0 && (
<Box mt={2} borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'} borderBottom="none">
<TableContainer>
<Table bg={'white'}>
<Thead h={8}>
<Tr>
<Th
borderRadius={'none !important'}
fontSize={'mini'}
bg={'myGray.50'}
p={0}
px={4}
fontWeight={'medium'}
>
{t('workflow:Variable_name')}
</Th>
<Th fontSize={'mini'} bg={'myGray.50'} p={0} px={4} fontWeight={'medium'}>
{t('common:common.Require Input')}
</Th>
<Th
fontSize={'mini'}
borderRadius={'none !important'}
bg={'myGray.50'}
p={0}
px={4}
fontWeight={'medium'}
>
{t('common:common.Operation')}
</Th>
<TableContainer mt={2} borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'}>
<Table variant={'workflow'}>
<Thead>
<Tr>
<Th>{t('workflow:Variable_name')}</Th>
<Th>{t('common:common.Require Input')}</Th>
<Th>{t('common:common.Operation')}</Th>
</Tr>
</Thead>
<Tbody>
{formatVariables.map((item, index) => (
<Tr key={item.id}>
<Td fontWeight={'medium'}>
<Flex alignItems={'center'}>
<MyIcon name={item.icon as any} w={'16px'} color={'myGray.400'} mr={2} />
{item.key}
</Flex>
</Td>
<Td>
<Flex alignItems={'center'}>
{item.required ? (
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
) : (
''
)}
</Flex>
</Td>
<Td>
<Flex>
<MyIconButton
icon={'common/settingLight'}
onClick={() => {
const formattedItem = {
...item,
list: item.enums || []
};
reset(formattedItem);
}}
/>
<MyIconButton
icon={'delete'}
hoverColor={'red.500'}
onClick={() =>
onChange(variables.filter((variable) => variable.id !== item.id))
}
/>
</Flex>
</Td>
</Tr>
</Thead>
<Tbody>
{formatVariables.map((item) => (
<Tr key={item.id}>
<Td
p={0}
px={4}
h={8}
color={'myGray.900'}
fontSize={'mini'}
fontWeight={'medium'}
>
<Flex alignItems={'center'}>
<MyIcon name={item.icon as any} w={'16px'} color={'myGray.400'} mr={2} />
{item.key}
</Flex>
</Td>
<Td p={0} px={4} h={8} color={'myGray.900'} fontSize={'mini'}>
<Flex alignItems={'center'}>
{item.required ? (
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
) : (
''
)}
</Flex>
</Td>
<Td p={0} px={4} h={8} color={'myGray.600'} fontSize={'mini'}>
<Flex alignItems={'center'}>
<MyIcon
mr={3}
name={'common/settingLight'}
w={'16px'}
cursor={'pointer'}
onClick={() => {
const formattedItem = {
...item,
list: item.enums || []
};
reset(formattedItem);
}}
/>
<MyIcon
name={'delete'}
w={'16px'}
cursor={'pointer'}
onClick={() =>
onChange(variables.filter((variable) => variable.id !== item.id))
}
/>
</Flex>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
))}
</Tbody>
</Table>
</TableContainer>
)}
{/* Edit modal */}

View File

@@ -1,7 +1,7 @@
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { Box, Button, Flex, ModalBody, useDisclosure, Switch } from '@chakra-ui/react';
import React, { useMemo } from 'react';
import React from 'react';
import { useTranslation } from 'next-i18next';
import type { AppWhisperConfigType } from '@fastgpt/global/core/app/type.d';
import MyModal from '@fastgpt/web/components/common/MyModal';
@@ -24,12 +24,9 @@ const WhisperConfig = ({
const isOpenWhisper = value.open;
const isAutoSend = value.autoSend;
const formLabel = useMemo(() => {
if (!isOpenWhisper) {
return t('common:core.app.whisper.Close');
}
return t('common:core.app.whisper.Open');
}, [t, isOpenWhisper]);
const formLabel = isOpenWhisper
? t('common:core.app.whisper.Open')
: t('common:core.app.whisper.Close');
return (
<Flex alignItems={'center'}>

View File

@@ -33,32 +33,29 @@ const ChatInput = ({
onStop,
TextareaDom,
resetInputVal,
chatForm,
appId
chatForm
}: {
onSendMessage: SendPromptFnType;
onStop: () => void;
TextareaDom: React.MutableRefObject<HTMLTextAreaElement | null>;
resetInputVal: (val: ChatBoxInputType) => void;
chatForm: UseFormReturn<ChatBoxInputFormType>;
appId: string;
}) => {
const { isPc } = useSystem();
const { t } = useTranslation();
const { toast } = useToast();
const { isPc } = useSystem();
const { setValue, watch, control } = chatForm;
const inputValue = watch('input');
const {
chatId,
isChatting,
whisperConfig,
autoTTSResponse,
chatInputGuide,
outLinkAuthData,
fileSelectConfig
} = useContextSelector(ChatBoxContext, (v) => v);
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
const whisperConfig = useContextSelector(ChatBoxContext, (v) => v.whisperConfig);
const autoTTSResponse = useContextSelector(ChatBoxContext, (v) => v.autoTTSResponse);
const chatInputGuide = useContextSelector(ChatBoxContext, (v) => v.chatInputGuide);
const fileSelectConfig = useContextSelector(ChatBoxContext, (v) => v.fileSelectConfig);
const fileCtrl = useFieldArray({
control,
@@ -78,8 +75,6 @@ const ChatInput = ({
replaceFiles,
hasFileUploading
} = useFileUpload({
outLinkAuthData,
chatId: chatId || '',
fileSelectConfig,
fileCtrl
});

View File

@@ -2,76 +2,73 @@ import React, { useState, useMemo, useCallback } from 'react';
import { useAudioPlay } from '@/web/common/utils/voice';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import {
AppChatConfigType,
AppAutoExecuteConfigType,
AppFileSelectConfigType,
AppTTSConfigType,
AppWhisperConfigType,
ChatInputGuideConfigType,
VariableItemType
} from '@fastgpt/global/core/app/type';
import { ChatHistoryItemResType, ChatSiteItemType } from '@fastgpt/global/core/chat/type';
import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
import {
defaultAppSelectFileConfig,
defaultAutoExecuteConfig,
defaultChatInputGuideConfig,
defaultTTSConfig,
defaultWhisperConfig
} from '@fastgpt/global/core/app/constants';
import { createContext } from 'use-context-selector';
import { UseFormReturn } from 'react-hook-form';
import { createContext, useContextSelector } from 'use-context-selector';
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
import { getChatResData } from '@/web/core/chat/api';
import { ChatBoxInputFormType } from './type';
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
export type ChatProviderProps = OutLinkChatAuthProps & {
appAvatar?: string;
export type ChatProviderProps = {
appId: string;
chatConfig?: AppChatConfigType;
chatId: string;
outLinkAuthData?: OutLinkChatAuthProps;
chatHistories: ChatSiteItemType[];
setChatHistories: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
variablesForm: UseFormReturn<ChatBoxInputFormType, any>;
// not chat test params
chatId?: string;
chatType: 'log' | 'chat' | 'share' | 'team';
showRawSource: boolean;
showNodeStatus: boolean;
};
type useChatStoreType = OutLinkChatAuthProps &
ChatProviderProps & {
welcomeText: string;
variableList: VariableItemType[];
allVariableList: VariableItemType[];
questionGuide: boolean;
ttsConfig: AppTTSConfigType;
whisperConfig: AppWhisperConfigType;
autoTTSResponse: boolean;
startSegmentedAudio: () => Promise<any>;
splitText2Audio: (text: string, done?: boolean | undefined) => void;
finishSegmentedAudio: () => void;
audioLoading: boolean;
audioPlaying: boolean;
hasAudio: boolean;
playAudioByText: ({
text,
buffer
}: {
text: string;
buffer?: Uint8Array | undefined;
}) => Promise<{
buffer?: Uint8Array | undefined;
}>;
cancelAudio: () => void;
audioPlayingChatId: string | undefined;
setAudioPlayingChatId: React.Dispatch<React.SetStateAction<string | undefined>>;
isChatting: boolean;
chatInputGuide: ChatInputGuideConfigType;
outLinkAuthData: OutLinkChatAuthProps;
getHistoryResponseData: ({ dataId }: { dataId: string }) => Promise<ChatHistoryItemResType[]>;
fileSelectConfig: AppFileSelectConfigType;
};
type useChatStoreType = ChatProviderProps & {
welcomeText: string;
variableList: VariableItemType[];
allVariableList: VariableItemType[];
questionGuide: boolean;
ttsConfig: AppTTSConfigType;
whisperConfig: AppWhisperConfigType;
autoTTSResponse: boolean;
autoExecute: AppAutoExecuteConfigType;
startSegmentedAudio: () => Promise<any>;
splitText2Audio: (text: string, done?: boolean | undefined) => void;
finishSegmentedAudio: () => void;
audioLoading: boolean;
audioPlaying: boolean;
hasAudio: boolean;
playAudioByText: ({
text,
buffer
}: {
text: string;
buffer?: Uint8Array | undefined;
}) => Promise<{
buffer?: Uint8Array | undefined;
}>;
cancelAudio: () => void;
audioPlayingChatId: string | undefined;
setAudioPlayingChatId: React.Dispatch<React.SetStateAction<string | undefined>>;
isChatting: boolean;
chatInputGuide: ChatInputGuideConfigType;
getHistoryResponseData: ({ dataId }: { dataId: string }) => Promise<ChatHistoryItemResType[]>;
fileSelectConfig: AppFileSelectConfigType;
appId: string;
chatId: string;
outLinkAuthData: OutLinkChatAuthProps;
};
export const ChatBoxContext = createContext<useChatStoreType>({
welcomeText: '',
@@ -95,10 +92,6 @@ export const ChatBoxContext = createContext<useChatStoreType>({
splitText2Audio: function (text: string, done?: boolean | undefined): void {
throw new Error('Function not implemented.');
},
chatHistories: [],
setChatHistories: function (value: React.SetStateAction<ChatSiteItemType[]>): void {
throw new Error('Function not implemented.');
},
isChatting: false,
audioLoading: false,
audioPlaying: false,
@@ -132,23 +125,24 @@ export const ChatBoxContext = createContext<useChatStoreType>({
});
const Provider = ({
shareId,
outLinkUid,
teamId,
teamToken,
chatHistories,
setChatHistories,
variablesForm,
appId,
chatId,
outLinkAuthData = {},
chatType = 'chat',
showRawSource,
showNodeStatus,
chatConfig = {},
children,
...props
}: ChatProviderProps & {
children: React.ReactNode;
}) => {
const chatConfig = useContextSelector(
ChatItemContext,
(v) => v.chatBoxData?.app?.chatConfig || {}
);
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
const setChatRecords = useContextSelector(ChatRecordContext, (v) => v.setChatRecords);
const {
welcomeText = '',
variables = [],
@@ -156,19 +150,10 @@ const Provider = ({
ttsConfig = defaultTTSConfig,
whisperConfig = defaultWhisperConfig,
chatInputGuide = defaultChatInputGuideConfig,
fileSelectConfig = defaultAppSelectFileConfig
fileSelectConfig = defaultAppSelectFileConfig,
autoExecute = defaultAutoExecuteConfig
} = useMemo(() => chatConfig, [chatConfig]);
const outLinkAuthData = useMemo(
() => ({
shareId,
outLinkUid,
teamId,
teamToken
}),
[shareId, outLinkUid, teamId, teamToken]
);
// segment audio
const [audioPlayingChatId, setAudioPlayingChatId] = useState<string>();
const {
@@ -190,37 +175,34 @@ const Provider = ({
const isChatting = useMemo(
() =>
chatHistories[chatHistories.length - 1] &&
chatHistories[chatHistories.length - 1]?.status !== 'finish',
[chatHistories]
chatRecords[chatRecords.length - 1] &&
chatRecords[chatRecords.length - 1]?.status !== 'finish',
[chatRecords]
);
const getHistoryResponseData = useCallback(
async ({ dataId }: { dataId: string }) => {
const aimItem = chatHistories.find((item) => item.dataId === dataId)!;
if (!!aimItem?.responseData || !props.chatId) {
const aimItem = chatRecords.find((item) => item.dataId === dataId)!;
if (!!aimItem?.responseData || !chatId) {
return aimItem.responseData || [];
} else {
let resData = await getChatResData({
appId: props.appId,
chatId: props.chatId,
appId: appId,
chatId: chatId,
dataId,
...outLinkAuthData
});
setChatHistories((state) =>
setChatRecords((state) =>
state.map((item) => (item.dataId === dataId ? { ...item, responseData: resData } : item))
);
return resData;
}
},
[chatHistories, outLinkAuthData, props.appId, props.chatId, setChatHistories]
[chatRecords, chatId, appId, outLinkAuthData, setChatRecords]
);
const value: useChatStoreType = {
...props,
shareId,
outLinkUid,
teamId,
teamToken,
welcomeText,
autoExecute,
variableList: variables.filter((item) => item.type !== VariableInputEnum.custom),
allVariableList: variables,
questionGuide,
@@ -238,12 +220,11 @@ const Provider = ({
cancelAudio,
audioPlayingChatId,
setAudioPlayingChatId,
chatHistories,
setChatHistories,
isChatting,
chatInputGuide,
appId,
chatId,
outLinkAuthData,
variablesForm,
getHistoryResponseData,
chatType,
showRawSource,

View File

@@ -10,6 +10,7 @@ import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { ChatBoxContext } from '../Provider';
import { useContextSelector } from 'use-context-selector';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext';
export type ChatControllerProps = {
isLastChild: boolean;
@@ -24,6 +25,19 @@ export type ChatControllerProps = {
onAddUserDislike?: () => void;
};
const controlIconStyle = {
w: '14px',
cursor: 'pointer',
p: '5px',
bg: 'white',
borderRight: 'base'
};
const controlContainerStyle = {
className: 'control',
color: 'myGray.400',
display: 'flex'
};
const ChatController = ({
chat,
showVoiceIcon,
@@ -35,34 +49,21 @@ const ChatController = ({
onAddUserDislike,
onAddUserLike
}: ChatControllerProps & FlexProps) => {
const theme = useTheme();
const {
isChatting,
setChatHistories,
audioLoading,
audioPlaying,
hasAudio,
playAudioByText,
cancelAudio,
audioPlayingChatId,
setAudioPlayingChatId
} = useContextSelector(ChatBoxContext, (v) => v);
const controlIconStyle = {
w: '14px',
cursor: 'pointer',
p: '5px',
bg: 'white',
borderRight: theme.borders.base
};
const controlContainerStyle = {
className: 'control',
color: 'myGray.400',
display: 'flex'
};
const { t } = useTranslation();
const { copyData } = useCopyData();
const setChatRecords = useContextSelector(ChatRecordContext, (v) => v.setChatRecords);
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
const audioLoading = useContextSelector(ChatBoxContext, (v) => v.audioLoading);
const audioPlaying = useContextSelector(ChatBoxContext, (v) => v.audioPlaying);
const hasAudio = useContextSelector(ChatBoxContext, (v) => v.hasAudio);
const playAudioByText = useContextSelector(ChatBoxContext, (v) => v.playAudioByText);
const cancelAudio = useContextSelector(ChatBoxContext, (v) => v.cancelAudio);
const audioPlayingChatId = useContextSelector(ChatBoxContext, (v) => v.audioPlayingChatId);
const setAudioPlayingChatId = useContextSelector(ChatBoxContext, (v) => v.setAudioPlayingChatId);
const chatType = useContextSelector(ChatBoxContext, (v) => v.chatType);
const chatText = useMemo(() => formatChatValue2InputType(chat.value).text || '', [chat.value]);
return (
@@ -70,7 +71,7 @@ const ChatController = ({
{...controlContainerStyle}
borderRadius={'sm'}
overflow={'hidden'}
border={theme.borders.base}
border={'base'}
// 最后一个子元素没有border
css={css({
'& > *:last-child, & > *:last-child svg': {
@@ -87,7 +88,7 @@ const ChatController = ({
onClick={() => copyData(chatText)}
/>
</MyTooltip>
{!!onDelete && !isChatting && (
{!!onDelete && !isChatting && chatType !== 'log' && (
<>
{onRetry && (
<MyTooltip label={t('common:core.chat.retry')}>
@@ -125,12 +126,7 @@ const ChatController = ({
onClick={cancelAudio}
/>
</MyTooltip>
<MyImage
src="/icon/speaking.gif"
w={'23px'}
alt={''}
borderRight={theme.borders.base}
/>
<MyImage src="/icon/speaking.gif" w={'23px'} alt={''} borderRight={'base'} />
</Flex>
);
}
@@ -154,8 +150,8 @@ const ChatController = ({
text: chatText
});
if (!setChatHistories || !response.buffer) return;
setChatHistories((state) =>
if (!setChatRecords || !response.buffer) return;
setChatRecords((state) =>
state.map((item) =>
item.dataId === chat.dataId
? {

View File

@@ -216,7 +216,7 @@ const ChatItem = (props: Props) => {
}}
>
{/* control icon */}
<Flex w={'100%'} alignItems={'flex-end'} gap={2} justifyContent={styleMap.justifyContent}>
<Flex w={'100%'} alignItems={'center'} gap={2} justifyContent={styleMap.justifyContent}>
{isChatting && type === ChatRoleEnum.AI && isLastChild ? null : (
<Flex order={styleMap.order} ml={styleMap.ml} align={'center'} gap={'0.62rem'}>
{chat.time && (isPc || isChatLog) && (

View File

@@ -19,10 +19,12 @@ const ContextModal = ({ onClose, dataId }: { onClose: () => void; dataId: string
const flatResData: ChatHistoryItemResType[] =
res
?.map((item) => {
if (item.pluginDetail || item.toolDetail) {
return [item, ...(item.pluginDetail || []), ...(item.toolDetail || [])];
}
return item;
return [
item,
...(item.pluginDetail || []),
...(item.toolDetail || []),
...(item.loopDetail || [])
];
})
.flat() || [];
return flatResData.find(isLLMNode)?.historyPreview || [];

View File

@@ -7,18 +7,22 @@ import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/ty
import QuoteItem from '@/components/core/dataset/QuoteItem';
import RawSourceBox from '@/components/core/dataset/RawSourceBox';
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
import { useContextSelector } from 'use-context-selector';
import { ChatBoxContext } from '../Provider';
const QuoteModal = ({
rawSearch = [],
onClose,
canEditDataset,
showRawSource,
chatItemId,
metadata
}: {
rawSearch: SearchDataResponseItemType[];
onClose: () => void;
canEditDataset: boolean;
showRawSource: boolean;
chatItemId: string;
metadata?: {
collectionId: string;
sourceId?: string;
@@ -37,6 +41,13 @@ const QuoteModal = ({
[metadata, rawSearch]
);
const RawSourceBoxProps = useContextSelector(ChatBoxContext, (v) => ({
appId: v.appId,
chatId: v.chatId,
chatItemId,
...(v.outLinkAuthData || {})
}));
return (
<>
<MyModal
@@ -49,7 +60,7 @@ const QuoteModal = ({
title={
<Box>
{metadata ? (
<RawSourceBox {...metadata} canView={showRawSource} />
<RawSourceBox {...metadata} {...RawSourceBoxProps} canView={showRawSource} />
) : (
<>{t('common:core.chat.Quote Amount', { amount: rawSearch.length })}</>
)}
@@ -64,6 +75,7 @@ const QuoteModal = ({
rawSearch={filterResults}
canEditDataset={canEditDataset}
canViewSource={showRawSource}
chatItemId={chatItemId}
/>
</ModalBody>
</MyModal>
@@ -74,16 +86,25 @@ const QuoteModal = ({
export default QuoteModal;
export const QuoteList = React.memo(function QuoteList({
chatItemId,
rawSearch = [],
canEditDataset,
canViewSource
}: {
chatItemId?: string;
rawSearch: SearchDataResponseItemType[];
canEditDataset: boolean;
canViewSource: boolean;
}) {
const theme = useTheme();
const RawSourceBoxProps = useContextSelector(ChatBoxContext, (v) => ({
chatItemId,
appId: v.appId,
chatId: v.chatId,
...(v.outLinkAuthData || {})
}));
return (
<>
{rawSearch.map((item, i) => (
@@ -101,6 +122,7 @@ export const QuoteList = React.memo(function QuoteList({
quoteItem={item}
canViewSource={canViewSource}
canEditDataset={canEditDataset}
{...RawSourceBoxProps}
/>
</Box>
))}

View File

@@ -250,6 +250,7 @@ const ResponseTags = ({
{!!quoteModalData && (
<QuoteModal
{...quoteModalData}
chatItemId={historyItem.dataId}
canEditDataset={notSharePage}
showRawSource={showRawSource}
onClose={() => setQuoteModalData(undefined)}

View File

@@ -0,0 +1,19 @@
import { Box } from '@chakra-ui/react';
import React from 'react';
import { useTranslation } from 'next-i18next';
import { formatTimeToChatItemTime } from '@fastgpt/global/common/string/time';
import dayjs from 'dayjs';
const TimeBox = ({ time }: { time: Date }) => {
const { t } = useTranslation();
return (
<Box w={'100%'} fontSize={'mini'} textAlign={'center'} color={'myGray.500'} fontWeight={'400'}>
{t(formatTimeToChatItemTime(time) as any, {
time: dayjs(time).format('HH#mm')
}).replace('#', ':')}
</Box>
);
};
export default TimeBox;

Some files were not shown because too many files have changed in this diff Show More