mirror of
https://github.com/labring/FastGPT.git
synced 2025-12-25 20:02:47 +00:00
Plugin runtime (#2050)
Some checks are pending
Deploy image by kubeconfig / build-fastgpt-docs-images (push) Waiting to run
Deploy image by kubeconfig / update-docs-image (push) Blocked by required conditions
Deploy image to vercel / deploy-production (push) Waiting to run
Build FastGPT images in Personal warehouse / build-fastgpt-images (push) Waiting to run
Some checks are pending
Deploy image by kubeconfig / build-fastgpt-docs-images (push) Waiting to run
Deploy image by kubeconfig / update-docs-image (push) Blocked by required conditions
Deploy image to vercel / deploy-production (push) Waiting to run
Build FastGPT images in Personal warehouse / build-fastgpt-images (push) Waiting to run
* feat: plugin run (#1950) * feat: plugin run * fix * ui * fix * change user input type * fix * fix * temp * split out plugin chat * perf: chatbox * perf: chatbox * fix: plugin runtime (#2032) * fix: plugin runtime * fix * fix build * fix build * perf: chat send prompt * perf: chat log ux * perf: chatbox context and share page plugin runtime * perf: plugin run time config * fix: ts * feat: doc * perf: isPc check * perf: variable input render * feat: app search * fix: response box height * fix: phone ui * perf: lock * perf: plugin route * fix: chat (#2049) --------- Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
parent
090c880860
commit
b5c98a4f63
1
.npmrc
1
.npmrc
|
|
@ -1,2 +1,3 @@
|
||||||
public-hoist-pattern[]=*tiktoken*
|
public-hoist-pattern[]=*tiktoken*
|
||||||
public-hoist-pattern[]=*@zilliz/milvus2-sdk-node*
|
public-hoist-pattern[]=*@zilliz/milvus2-sdk-node*
|
||||||
|
registry=https://registry.npmjs.org/
|
||||||
|
|
@ -7,18 +7,22 @@ toc: true
|
||||||
weight: 852
|
weight: 852
|
||||||
---
|
---
|
||||||
|
|
||||||
## 发起对话
|
|
||||||
|
|
||||||
{{% alert icon="🤖 " context="success" %}}
|
{{% alert icon="🤖 " context="success" %}}
|
||||||
该接口的 API Key 需使用`应用特定的 key`,否则会报错。
|
该接口的 API Key 需使用`应用特定的 key`,否则会报错。
|
||||||
|
|
||||||
有些包调用时,`BaseUrl`需要添加`v1`路径,有些不需要,如果出现404情况,可补充`v1`重试。
|
有些包调用时,`BaseUrl`需要添加`v1`路径,有些不需要,如果出现404情况,可补充`v1`重试。
|
||||||
{{% /alert %}}
|
{{% /alert %}}
|
||||||
|
|
||||||
|
## 发起对话(简易应用和工作流)
|
||||||
|
|
||||||
**对话接口兼容`GPT`的接口!如果你的项目使用的是标准的`GPT`官方接口,可以直接通过修改`BaseUrl`和 `Authorization`来访问 FastGpt 应用。**
|
对话接口兼容`GPT`的接口!如果你的项目使用的是标准的`GPT`官方接口,可以直接通过修改`BaseUrl`和 `Authorization`来访问 FastGpt 应用,不过需要注意下面几个规则:
|
||||||
|
|
||||||
## 请求
|
{{% alert icon="🤖 " context="success" %}}
|
||||||
|
* 传入的`model`,`temperature`等参数字段均无效,这些字段由编排决定。
|
||||||
|
* 不会返回实际消耗`Token`值,如果需要,可以设置`detail=true`,并手动计算 `responseData` 里的`tokens`值。
|
||||||
|
{{% /alert %}}
|
||||||
|
|
||||||
|
### 请求
|
||||||
|
|
||||||
{{< tabs tabTotal="2" >}}
|
{{< tabs tabTotal="2" >}}
|
||||||
{{< tab tabName="请求示例" >}}
|
{{< tab tabName="请求示例" >}}
|
||||||
|
|
@ -67,7 +71,7 @@ curl --location --request POST 'https://api.fastgpt.in/api/v1/chat/completions'
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
{{< /tabs >}}
|
{{< /tabs >}}
|
||||||
|
|
||||||
## 响应
|
### 响应
|
||||||
|
|
||||||
{{< tabs tabTotal="5" >}}
|
{{< tabs tabTotal="5" >}}
|
||||||
{{< tab tabName="detail=false,stream=false 响应" >}}
|
{{< tab tabName="detail=false,stream=false 响应" >}}
|
||||||
|
|
@ -245,7 +249,7 @@ data: [{"moduleName":"知识库搜索","moduleType":"datasetSearchNode","running
|
||||||
{{< /markdownify >}}
|
{{< /markdownify >}}
|
||||||
{{< /tab >}}
|
{{< /tab >}}
|
||||||
|
|
||||||
{{< tab tabName="detail=true,stream=true 时,event值" >}}
|
{{< tab tabName="event值" >}}
|
||||||
{{< markdownify >}}
|
{{< markdownify >}}
|
||||||
|
|
||||||
event取值:
|
event取值:
|
||||||
|
|
@ -265,6 +269,192 @@ event取值:
|
||||||
{{< /tabs >}}
|
{{< /tabs >}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 请求插件
|
||||||
|
|
||||||
|
插件的接口与对话接口一致,仅请求参数略有区别,有以下规定:
|
||||||
|
|
||||||
|
* 调用插件类型的应用时,接口默认为`detail`模式。
|
||||||
|
* 无需传入 `chatId`,因为插件只能运行一轮。
|
||||||
|
* 无需传入`messages`。
|
||||||
|
* 通过传递`variables`来代表插件的输入。
|
||||||
|
* 通过获取`pluginData`来获取插件输出。
|
||||||
|
|
||||||
|
### 请求示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --location --request POST 'http://localhost:3000/api/v1/chat/completions' \
|
||||||
|
--header 'Authorization: Bearer test-xxxxx' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data-raw '{
|
||||||
|
"stream": false,
|
||||||
|
"chatId": "test",
|
||||||
|
"variables": {
|
||||||
|
"query":"你好" # 我的插件输入有一个参数,变量名叫 query
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应示例
|
||||||
|
|
||||||
|
{{< tabs tabTotal="3" >}}
|
||||||
|
|
||||||
|
{{< tab tabName="detail=true,stream=false 响应" >}}
|
||||||
|
{{< markdownify >}}
|
||||||
|
|
||||||
|
* 插件的输出可以通过查找`responseData`中, `moduleType=pluginOutput`的元素,其`pluginOutput`是插件的输出。
|
||||||
|
* 流输出,仍可以通过`choices`进行获取。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"responseData": [
|
||||||
|
{
|
||||||
|
"nodeId": "fdDgXQ6SYn8v",
|
||||||
|
"moduleName": "AI 对话",
|
||||||
|
"moduleType": "chatNode",
|
||||||
|
"totalPoints": 0.685,
|
||||||
|
"model": "FastAI-3.5",
|
||||||
|
"tokens": 685,
|
||||||
|
"query": "你好",
|
||||||
|
"maxToken": 2000,
|
||||||
|
"historyPreview": [
|
||||||
|
{
|
||||||
|
"obj": "Human",
|
||||||
|
"value": "你好"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"obj": "AI",
|
||||||
|
"value": "你好!有什么可以帮助你的吗?欢迎向我提问。"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"contextTotalLen": 14,
|
||||||
|
"runningTime": 1.73
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "pluginOutput",
|
||||||
|
"moduleName": "自定义插件输出",
|
||||||
|
"moduleType": "pluginOutput",
|
||||||
|
"totalPoints": 0,
|
||||||
|
"pluginOutput": {
|
||||||
|
"result": "你好!有什么可以帮助你的吗?欢迎向我提问。"
|
||||||
|
},
|
||||||
|
"runningTime": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"newVariables": {
|
||||||
|
"query": "你好"
|
||||||
|
},
|
||||||
|
"id": "safsafsa",
|
||||||
|
"model": "",
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 1,
|
||||||
|
"completion_tokens": 1,
|
||||||
|
"total_tokens": 1
|
||||||
|
},
|
||||||
|
"choices": [
|
||||||
|
{
|
||||||
|
"message": {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": "你好!有什么可以帮助你的吗?欢迎向我提问。"
|
||||||
|
},
|
||||||
|
"finish_reason": "stop",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /markdownify >}}
|
||||||
|
{{< /tab >}}
|
||||||
|
|
||||||
|
|
||||||
|
{{< tab tabName="detail=true,stream=true 响应" >}}
|
||||||
|
{{< markdownify >}}
|
||||||
|
|
||||||
|
* 插件的输出可以通过获取`event=flowResponses`中的字符串,并将其反序列化后得到一个数组。同样的,查找 `moduleType=pluginOutput`的元素,其`pluginOutput`是插件的输出。
|
||||||
|
* 流输出,仍和对话接口一样获取。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
event: flowNodeStatus
|
||||||
|
data: {"status":"running","name":"AI 对话"}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"role":"assistant","content":""},"index":0,"finish_reason":null}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"role":"assistant","content":"你"},"index":0,"finish_reason":null}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"role":"assistant","content":"好"},"index":0,"finish_reason":null}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"role":"assistant","content":"!"},"index":0,"finish_reason":null}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"role":"assistant","content":"有"},"index":0,"finish_reason":null}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"role":"assistant","content":"什"},"index":0,"finish_reason":null}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"role":"assistant","content":"么"},"index":0,"finish_reason":null}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"role":"assistant","content":"可以"},"index":0,"finish_reason":null}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"role":"assistant","content":"帮"},"index":0,"finish_reason":null}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"role":"assistant","content":"助"},"index":0,"finish_reason":null}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"role":"assistant","content":"你"},"index":0,"finish_reason":null}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"role":"assistant","content":"的"},"index":0,"finish_reason":null}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"role":"assistant","content":"吗"},"index":0,"finish_reason":null}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"role":"assistant","content":"?"},"index":0,"finish_reason":null}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{"role":"assistant","content":""},"index":0,"finish_reason":null}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: {"id":"","object":"","created":0,"model":"","choices":[{"delta":{},"index":0,"finish_reason":"stop"}]}
|
||||||
|
|
||||||
|
event: answer
|
||||||
|
data: [DONE]
|
||||||
|
|
||||||
|
event: flowResponses
|
||||||
|
data: [{"nodeId":"fdDgXQ6SYn8v","moduleName":"AI 对话","moduleType":"chatNode","totalPoints":0.033,"model":"FastAI-3.5","tokens":33,"query":"你好","maxToken":2000,"historyPreview":[{"obj":"Human","value":"你好"},{"obj":"AI","value":"你好!有什么可以帮助你的吗?"}],"contextTotalLen":2,"runningTime":1.42},{"nodeId":"pluginOutput","moduleName":"自定义插件输出","moduleType":"pluginOutput","totalPoints":0,"pluginOutput":{"result":"你好!有什么可以帮助你的吗?"},"runningTime":0}]
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< /markdownify >}}
|
||||||
|
{{< /tab >}}
|
||||||
|
|
||||||
|
{{< tab tabName="输出获取" >}}
|
||||||
|
{{< markdownify >}}
|
||||||
|
|
||||||
|
event取值:
|
||||||
|
|
||||||
|
- answer: 返回给客户端的文本(最终会算作回答)
|
||||||
|
- fastAnswer: 指定回复返回给客户端的文本(最终会算作回答)
|
||||||
|
- toolCall: 执行工具
|
||||||
|
- toolParams: 工具参数
|
||||||
|
- toolResponse: 工具返回
|
||||||
|
- flowNodeStatus: 运行到的节点状态
|
||||||
|
- flowResponses: 节点完整响应
|
||||||
|
- updateVariables: 更新变量
|
||||||
|
- error: 报错
|
||||||
|
|
||||||
|
{{< /markdownify >}}
|
||||||
|
{{< /tab >}}
|
||||||
|
{{< /tabs >}}
|
||||||
|
|
||||||
## 使用案例
|
## 使用案例
|
||||||
|
|
||||||
- [接入 NextWeb/ChatGPT web 等应用](/docs/use-cases/openapi)
|
- [接入 NextWeb/ChatGPT web 等应用](/docs/use-cases/openapi)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
title: 'V4.8.7(进行中)'
|
||||||
|
description: 'FastGPT V4.8.7 更新说明'
|
||||||
|
icon: 'upgrade'
|
||||||
|
draft: false
|
||||||
|
toc: true
|
||||||
|
weight: 817
|
||||||
|
---
|
||||||
|
|
||||||
|
## 升级指南
|
||||||
|
|
||||||
|
### 1. 做好数据库备份
|
||||||
|
|
||||||
|
### 2. 修改镜像
|
||||||
|
|
||||||
|
- fastgpt 镜像 tag 修改成 v4.8.7-alpha
|
||||||
|
- 商业版镜像 tag 修改成 v4.8.7-alpha
|
||||||
|
|
||||||
|
-------
|
||||||
|
|
||||||
|
## V4.8.7 更新说明
|
||||||
|
|
||||||
|
1. 新增 - 插件支持独立运行,发布和日志查看
|
||||||
|
2. 新增 - 应用搜索
|
||||||
|
3. 优化 - 对话框代码
|
||||||
|
4. 优化 - 升级 Dockerfile node 和 pnpm 版本
|
||||||
|
5. 修复 - 简易模式无法变更全局变量
|
||||||
|
|
@ -78,7 +78,7 @@ weight: 404
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"nodeId": "u6IAOEssxoZT",
|
"nodeId": "u6IAOEssxoZT",
|
||||||
"name": "工具调用(实验)",
|
"name": "工具调用",
|
||||||
"intro": "通过AI模型自动选择一个或多个功能块进行调用,也可以对插件进行调用。",
|
"intro": "通过AI模型自动选择一个或多个功能块进行调用,也可以对插件进行调用。",
|
||||||
"avatar": "/imgs/workflow/tool.svg",
|
"avatar": "/imgs/workflow/tool.svg",
|
||||||
"flowNodeType": "tools",
|
"flowNodeType": "tools",
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
"./docSite/**/**/*.md": "npm run format-doc"
|
"./docSite/**/**/*.md": "npm run format-doc"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0",
|
"node": ">=18.16.0",
|
||||||
"pnpm": ">=8.6.0"
|
"pnpm": ">=9.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { StoreNodeItemType } from '../../workflow/type/node';
|
||||||
|
import { FlowNodeInputItemType } from '../../workflow/type/io';
|
||||||
|
import { FlowNodeTypeEnum } from '../../workflow/node/constant';
|
||||||
|
|
||||||
|
export const getPluginInputsFromStoreNodes = (nodes: StoreNodeItemType[]) => {
|
||||||
|
return nodes.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs || [];
|
||||||
|
};
|
||||||
|
export const getPluginRunContent = (e: { pluginInputs: FlowNodeInputItemType[] }) => {
|
||||||
|
return JSON.stringify(e);
|
||||||
|
};
|
||||||
|
|
@ -21,6 +21,7 @@ export type AppSchema = {
|
||||||
name: string;
|
name: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
intro: string;
|
intro: string;
|
||||||
|
|
||||||
updateTime: Date;
|
updateTime: Date;
|
||||||
|
|
||||||
modules: StoreNodeItemType[];
|
modules: StoreNodeItemType[];
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,10 @@ export const chats2GPTMessages = ({
|
||||||
text: item.text?.content || ''
|
text: item.text?.content || ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (item.type === 'file' && item.file?.type === ChatFileTypeEnum.image) {
|
if (
|
||||||
|
item.type === ChatItemValueTypeEnum.file &&
|
||||||
|
item.file?.type === ChatFileTypeEnum.image
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
type: 'image_url',
|
type: 'image_url',
|
||||||
image_url: {
|
image_url: {
|
||||||
|
|
@ -64,7 +67,6 @@ export const chats2GPTMessages = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
})
|
})
|
||||||
.filter(Boolean) as ChatCompletionContentPart[];
|
.filter(Boolean) as ChatCompletionContentPart[];
|
||||||
|
|
||||||
|
|
@ -166,7 +168,7 @@ export const GPTMessages2Chats = (
|
||||||
} else if (item.type === 'image_url') {
|
} else if (item.type === 'image_url') {
|
||||||
value.push({
|
value.push({
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
type: 'file',
|
type: ChatItemValueTypeEnum.file,
|
||||||
file: {
|
file: {
|
||||||
type: ChatFileTypeEnum.image,
|
type: ChatFileTypeEnum.image,
|
||||||
name: '',
|
name: '',
|
||||||
|
|
@ -175,7 +177,6 @@ export const GPTMessages2Chats = (
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// @ts-ignore
|
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
obj === ChatRoleEnum.AI &&
|
obj === ChatRoleEnum.AI &&
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ import { DispatchNodeResponseKeyEnum } from '../workflow/runtime/constants';
|
||||||
import { AppChatConfigType, AppSchema, VariableItemType } from '../app/type';
|
import { AppChatConfigType, AppSchema, VariableItemType } from '../app/type';
|
||||||
import type { AppSchema as AppType } from '@fastgpt/global/core/app/type.d';
|
import type { AppSchema as AppType } from '@fastgpt/global/core/app/type.d';
|
||||||
import { DatasetSearchModeEnum } from '../dataset/constants';
|
import { DatasetSearchModeEnum } from '../dataset/constants';
|
||||||
import { ChatBoxInputType } from '../../../../projects/app/src/components/ChatBox/type';
|
|
||||||
import { DispatchNodeResponseType } from '../workflow/runtime/type.d';
|
import { DispatchNodeResponseType } from '../workflow/runtime/type.d';
|
||||||
|
import { ChatBoxInputType } from '../../../../projects/app/src/components/core/chat/ChatContainer/ChatBox/type';
|
||||||
|
|
||||||
export type ChatSchema = {
|
export type ChatSchema = {
|
||||||
_id: string;
|
_id: string;
|
||||||
|
|
@ -115,6 +115,7 @@ export type ChatSiteItemType = (UserChatItemType | SystemChatItemType | AIChatIt
|
||||||
status: `${ChatStatusEnum}`;
|
status: `${ChatStatusEnum}`;
|
||||||
moduleName?: string;
|
moduleName?: string;
|
||||||
ttsBuffer?: Uint8Array;
|
ttsBuffer?: Uint8Array;
|
||||||
|
responseData?: ChatHistoryItemResType[];
|
||||||
} & ChatBoxInputType;
|
} & ChatBoxInputType;
|
||||||
|
|
||||||
/* --------- team chat --------- */
|
/* --------- team chat --------- */
|
||||||
|
|
|
||||||
|
|
@ -65,11 +65,12 @@ export const filterPublicNodeResponseData = ({
|
||||||
}: {
|
}: {
|
||||||
flowResponses?: ChatHistoryItemResType[];
|
flowResponses?: ChatHistoryItemResType[];
|
||||||
}) => {
|
}) => {
|
||||||
const filedList = ['quoteList', 'moduleType'];
|
const filedList = ['quoteList', 'moduleType', 'pluginOutput'];
|
||||||
const filterModuleTypeList: any[] = [
|
const filterModuleTypeList: any[] = [
|
||||||
FlowNodeTypeEnum.pluginModule,
|
FlowNodeTypeEnum.pluginModule,
|
||||||
FlowNodeTypeEnum.datasetSearchNode,
|
FlowNodeTypeEnum.datasetSearchNode,
|
||||||
FlowNodeTypeEnum.tools
|
FlowNodeTypeEnum.tools,
|
||||||
|
FlowNodeTypeEnum.pluginOutput
|
||||||
];
|
];
|
||||||
|
|
||||||
return flowResponses
|
return flowResponses
|
||||||
|
|
@ -89,14 +90,22 @@ export const filterPublicNodeResponseData = ({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const removeEmptyUserInput = (input: UserChatItemValueItemType[]) => {
|
export const removeEmptyUserInput = (input?: UserChatItemValueItemType[]) => {
|
||||||
return input.filter((item) => {
|
return (
|
||||||
if (item.type === ChatItemValueTypeEnum.text && !item.text?.content?.trim()) {
|
input?.filter((item) => {
|
||||||
return false;
|
if (item.type === ChatItemValueTypeEnum.text && !item.text?.content?.trim()) {
|
||||||
}
|
return false;
|
||||||
if (item.type === ChatItemValueTypeEnum.file && !item.file?.url) {
|
}
|
||||||
return false;
|
if (item.type === ChatItemValueTypeEnum.file && !item.file?.url) {
|
||||||
}
|
return false;
|
||||||
return true;
|
}
|
||||||
});
|
return true;
|
||||||
|
}) || []
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPluginOutputsFromChatResponses = (responses: ChatHistoryItemResType[]) => {
|
||||||
|
const outputs =
|
||||||
|
responses.find((item) => item.moduleType === FlowNodeTypeEnum.pluginOutput)?.pluginOutput ?? {};
|
||||||
|
return outputs;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,9 @@ export type DispatchNodeResponseType = {
|
||||||
|
|
||||||
// code
|
// code
|
||||||
codeLog?: string;
|
codeLog?: string;
|
||||||
|
|
||||||
|
// plugin
|
||||||
|
pluginOutput?: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DispatchNodeResultType<T> = {
|
export type DispatchNodeResultType<T> = {
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@ export const checkNodeRunStatus = ({
|
||||||
(item) => item.target === node.nodeId
|
(item) => item.target === node.nodeId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Entry
|
||||||
if (workflowEdges.length === 0) {
|
if (workflowEdges.length === 0) {
|
||||||
return 'run';
|
return 'run';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export const ToolModule: FlowNodeTemplateType = {
|
||||||
sourceHandle: getHandleConfig(true, true, false, true),
|
sourceHandle: getHandleConfig(true, true, false, true),
|
||||||
targetHandle: getHandleConfig(true, true, false, true),
|
targetHandle: getHandleConfig(true, true, false, true),
|
||||||
avatar: '/imgs/workflow/tool.svg',
|
avatar: '/imgs/workflow/tool.svg',
|
||||||
name: '工具调用(实验)',
|
name: '工具调用',
|
||||||
intro: '通过AI模型自动选择一个或多个功能块进行调用,也可以对插件进行调用。',
|
intro: '通过AI模型自动选择一个或多个功能块进行调用,也可以对插件进行调用。',
|
||||||
showStatus: true,
|
showStatus: true,
|
||||||
version: '481',
|
version: '481',
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import {
|
||||||
defaultWhisperConfig
|
defaultWhisperConfig
|
||||||
} from '../app/constants';
|
} from '../app/constants';
|
||||||
import { IfElseResultEnum } from './template/system/ifElse/constant';
|
import { IfElseResultEnum } from './template/system/ifElse/constant';
|
||||||
|
import { RuntimeNodeItemType } from './runtime/type';
|
||||||
|
|
||||||
export const getHandleId = (nodeId: string, type: 'source' | 'target', key: string) => {
|
export const getHandleId = (nodeId: string, type: 'source' | 'target', key: string) => {
|
||||||
return `${nodeId}-${type}-${key}`;
|
return `${nodeId}-${type}-${key}`;
|
||||||
|
|
@ -190,3 +191,38 @@ export const isReferenceValue = (value: any): boolean => {
|
||||||
export const getElseIFLabel = (i: number) => {
|
export const getElseIFLabel = (i: number) => {
|
||||||
return i === 0 ? IfElseResultEnum.IF : `${IfElseResultEnum.ELSE_IF} ${i}`;
|
return i === 0 ? IfElseResultEnum.IF : `${IfElseResultEnum.ELSE_IF} ${i}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// add value to plugin input node when run plugin
|
||||||
|
export const updatePluginInputByVariables = (
|
||||||
|
nodes: RuntimeNodeItemType[],
|
||||||
|
variables: Record<string, any>
|
||||||
|
) => {
|
||||||
|
return nodes.map((node) =>
|
||||||
|
node.flowNodeType === FlowNodeTypeEnum.pluginInput
|
||||||
|
? {
|
||||||
|
...node,
|
||||||
|
inputs: node.inputs.map((input) => {
|
||||||
|
const parseValue = (() => {
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
input.valueType === WorkflowIOValueTypeEnum.string ||
|
||||||
|
input.valueType === WorkflowIOValueTypeEnum.number ||
|
||||||
|
input.valueType === WorkflowIOValueTypeEnum.boolean
|
||||||
|
)
|
||||||
|
return variables[input.key];
|
||||||
|
|
||||||
|
return JSON.parse(variables[input.key]);
|
||||||
|
} catch (e) {
|
||||||
|
return variables[input.key];
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...input,
|
||||||
|
value: parseValue ?? input.value
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
: node
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { SystemPluginResponseType } from '../../type';
|
import { urlsFetch } from '@fastgpt/service/common/string/cheerio';
|
||||||
import { urlsFetch } from '../../../service/common/string/cheerio';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
url: string;
|
url: string;
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ const AppSchema = new Schema({
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
|
||||||
updateTime: {
|
updateTime: {
|
||||||
type: Date,
|
type: Date,
|
||||||
default: () => new Date()
|
default: () => new Date()
|
||||||
|
|
@ -112,7 +113,7 @@ const AppSchema = new Schema({
|
||||||
...getPermissionSchema(AppDefaultPermissionVal)
|
...getPermissionSchema(AppDefaultPermissionVal)
|
||||||
});
|
});
|
||||||
|
|
||||||
AppSchema.index({ updateTime: -1 });
|
AppSchema.index({ teamId: 1, updateTime: -1 });
|
||||||
AppSchema.index({ teamId: 1, type: 1 });
|
AppSchema.index({ teamId: 1, type: 1 });
|
||||||
AppSchema.index({ scheduledTriggerConfig: 1, intervalNextTime: -1 });
|
AppSchema.index({ scheduledTriggerConfig: 1, intervalNextTime: -1 });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ try {
|
||||||
get chat logs;
|
get chat logs;
|
||||||
close custom feedback;
|
close custom feedback;
|
||||||
*/
|
*/
|
||||||
ChatItemSchema.index({ appId: 1, chatId: 1, dataId: 1 }, { background: true });
|
ChatItemSchema.index({ appId: 1, chatId: 1, dataId: 1 }, { background: true, unique: true });
|
||||||
// admin charts
|
// admin charts
|
||||||
ChatItemSchema.index({ time: -1, obj: 1 }, { background: true });
|
ChatItemSchema.index({ time: -1, obj: 1 }, { background: true });
|
||||||
// timer, clear history
|
// timer, clear history
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ try {
|
||||||
// get user history
|
// get user history
|
||||||
ChatSchema.index({ tmbId: 1, appId: 1, top: -1, updateTime: -1 }, { background: true });
|
ChatSchema.index({ tmbId: 1, appId: 1, top: -1, updateTime: -1 }, { background: true });
|
||||||
// delete by appid; clear history; init chat; update chat; auth chat; get chat;
|
// delete by appid; clear history; init chat; update chat; auth chat; get chat;
|
||||||
ChatSchema.index({ appId: 1, chatId: 1 }, { background: true });
|
ChatSchema.index({ appId: 1, chatId: 1 }, { background: true, unique: true });
|
||||||
|
|
||||||
// get chat logs;
|
// get chat logs;
|
||||||
ChatSchema.index({ teamId: 1, appId: 1, updateTime: -1 }, { background: true });
|
ChatSchema.index({ teamId: 1, appId: 1, updateTime: -1 }, { background: true });
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import {
|
||||||
DragStart,
|
DragStart,
|
||||||
DropResult
|
DropResult
|
||||||
} from 'react-beautiful-dnd';
|
} from 'react-beautiful-dnd';
|
||||||
|
export * from 'react-beautiful-dnd';
|
||||||
|
|
||||||
type Props<T = any> = {
|
type Props<T = any> = {
|
||||||
onDragEndCb: (result: T[]) => void;
|
onDragEndCb: (result: T[]) => void;
|
||||||
|
|
@ -57,5 +58,3 @@ function DndDrag<T>({ children, renderClone, onDragEndCb, dataList }: Props<T>)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DndDrag;
|
export default DndDrag;
|
||||||
|
|
||||||
export * from 'react-beautiful-dnd';
|
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,15 @@ const CloseIcon = (props: FlexProps) => {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
cursor={'pointer'}
|
cursor={'pointer'}
|
||||||
w={'22px'}
|
w={'1.5rem'}
|
||||||
h={'22px'}
|
h={'1.5rem'}
|
||||||
alignItems={'center'}
|
alignItems={'center'}
|
||||||
justifyContent={'center'}
|
justifyContent={'center'}
|
||||||
borderRadius={'50%'}
|
borderRadius={'50%'}
|
||||||
_hover={{ bg: 'myGray.200' }}
|
_hover={{ bg: 'myGray.200' }}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<MyIcon name={'common/closeLight'} w={'12px'} color={'myGray.500'} />
|
<MyIcon name={'common/closeLight'} w={'80%'} h={'80%'} color={'myGray.500'} />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@ import {
|
||||||
ModalCloseButton,
|
ModalCloseButton,
|
||||||
ModalContentProps,
|
ModalContentProps,
|
||||||
Box,
|
Box,
|
||||||
Image,
|
Image
|
||||||
useMediaQuery
|
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import MyIcon from '../Icon';
|
import MyIcon from '../Icon';
|
||||||
import MyBox from '../MyBox';
|
import MyBox from '../MyBox';
|
||||||
|
import { useSystem } from '../../../hooks/useSystem';
|
||||||
|
|
||||||
export interface MyModalProps extends ModalContentProps {
|
export interface MyModalProps extends ModalContentProps {
|
||||||
iconSrc?: string;
|
iconSrc?: string;
|
||||||
|
|
@ -34,7 +34,7 @@ const MyModal = ({
|
||||||
maxW = ['90vw', '600px'],
|
maxW = ['90vw', '600px'],
|
||||||
...props
|
...props
|
||||||
}: MyModalProps) => {
|
}: MyModalProps) => {
|
||||||
const [isPc] = useMediaQuery('(min-width: 900px)');
|
const isPc = useSystem();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,6 @@ const MultipleSelect = <T = any,>({
|
||||||
borderRadius={'md'}
|
borderRadius={'md'}
|
||||||
border={'base'}
|
border={'base'}
|
||||||
userSelect={'none'}
|
userSelect={'none'}
|
||||||
minH={'40px'}
|
|
||||||
cursor={'pointer'}
|
cursor={'pointer'}
|
||||||
_active={{
|
_active={{
|
||||||
transform: 'none'
|
transform: 'none'
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
import React, { useRef, forwardRef, useMemo } from 'react';
|
import React, {
|
||||||
|
useRef,
|
||||||
|
forwardRef,
|
||||||
|
useMemo,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
ForwardedRef
|
||||||
|
} from 'react';
|
||||||
import {
|
import {
|
||||||
Menu,
|
Menu,
|
||||||
MenuList,
|
MenuList,
|
||||||
|
|
@ -28,17 +35,21 @@ export type SelectProps<T = any> = ButtonProps & {
|
||||||
onchange?: (val: T) => void;
|
onchange?: (val: T) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MySelect = <T = any,>({
|
const MySelect = <T = any,>(
|
||||||
placeholder,
|
{
|
||||||
value,
|
placeholder,
|
||||||
width = '100%',
|
value,
|
||||||
list = [],
|
width = '100%',
|
||||||
onchange,
|
list = [],
|
||||||
isLoading = false,
|
onchange,
|
||||||
...props
|
isLoading = false,
|
||||||
}: SelectProps<T>) => {
|
...props
|
||||||
const ref = useRef<HTMLButtonElement>(null);
|
}: SelectProps<T>,
|
||||||
const { Loading } = useLoading();
|
ref: ForwardedRef<{
|
||||||
|
focus: () => void;
|
||||||
|
}>
|
||||||
|
) => {
|
||||||
|
const ButtonRef = useRef<HTMLButtonElement>(null);
|
||||||
const menuItemStyles: MenuItemProps = {
|
const menuItemStyles: MenuItemProps = {
|
||||||
borderRadius: 'sm',
|
borderRadius: 'sm',
|
||||||
py: 2,
|
py: 2,
|
||||||
|
|
@ -54,6 +65,12 @@ const MySelect = <T = any,>({
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const selectItem = useMemo(() => list.find((item) => item.value === value), [list, value]);
|
const selectItem = useMemo(() => list.find((item) => item.value === value), [list, value]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
focus() {
|
||||||
|
onOpen();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
css={css({
|
css={css({
|
||||||
|
|
@ -72,7 +89,7 @@ const MySelect = <T = any,>({
|
||||||
>
|
>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
as={Button}
|
as={Button}
|
||||||
ref={ref}
|
ref={ButtonRef}
|
||||||
width={width}
|
width={width}
|
||||||
px={3}
|
px={3}
|
||||||
rightIcon={<ChevronDownIcon />}
|
rightIcon={<ChevronDownIcon />}
|
||||||
|
|
@ -98,7 +115,7 @@ const MySelect = <T = any,>({
|
||||||
<MenuList
|
<MenuList
|
||||||
className={props.className}
|
className={props.className}
|
||||||
minW={(() => {
|
minW={(() => {
|
||||||
const w = ref.current?.clientWidth;
|
const w = ButtonRef.current?.clientWidth;
|
||||||
if (w) {
|
if (w) {
|
||||||
return `${w}px !important`;
|
return `${w}px !important`;
|
||||||
}
|
}
|
||||||
|
|
@ -152,4 +169,6 @@ const MySelect = <T = any,>({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MySelect;
|
export default forwardRef(MySelect) as <T>(
|
||||||
|
props: SelectProps<T> & { ref?: React.Ref<HTMLSelectElement> }
|
||||||
|
) => JSX.Element;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Tooltip, TooltipProps, css, useMediaQuery } from '@chakra-ui/react';
|
import { Box, Tooltip, TooltipProps } from '@chakra-ui/react';
|
||||||
|
import { useSystem } from '../../../hooks/useSystem';
|
||||||
|
|
||||||
interface Props extends TooltipProps {
|
interface Props extends TooltipProps {}
|
||||||
forceShow?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MyTooltip = ({ children, forceShow = false, shouldWrapChildren = true, ...props }: Props) => {
|
const MyTooltip = ({ children, shouldWrapChildren = true, ...props }: Props) => {
|
||||||
const [isPc] = useMediaQuery('(min-width: 900px)');
|
const { isPc } = useSystem();
|
||||||
|
|
||||||
return isPc || forceShow ? (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
className="chakra-tooltip"
|
className="chakra-tooltip"
|
||||||
bg={'white'}
|
bg={'white'}
|
||||||
|
|
@ -27,8 +26,6 @@ const MyTooltip = ({ children, forceShow = false, shouldWrapChildren = true, ...
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
|
||||||
<>{children}</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ const LightRowTabs = <ValueType = string,>({
|
||||||
borderRadius={'sm'}
|
borderRadius={'sm'}
|
||||||
fontSize={sizeMap.fontSize}
|
fontSize={sizeMap.fontSize}
|
||||||
overflowX={'auto'}
|
overflowX={'auto'}
|
||||||
|
userSelect={'none'}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{list.map((item) => (
|
{list.map((item) => (
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ type Props = Omit<BoxProps, 'resize' | 'onChange'> & {
|
||||||
variables?: EditorVariablePickerType[];
|
variables?: EditorVariablePickerType[];
|
||||||
defaultHeight?: number;
|
defaultHeight?: number;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
isInvalid?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
|
@ -55,6 +57,8 @@ const JSONEditor = ({
|
||||||
variables = [],
|
variables = [],
|
||||||
placeholder,
|
placeholder,
|
||||||
defaultHeight = 100,
|
defaultHeight = 100,
|
||||||
|
isDisabled = false,
|
||||||
|
isInvalid = false,
|
||||||
...props
|
...props
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
@ -209,9 +213,9 @@ const JSONEditor = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
borderWidth={'1px'}
|
borderWidth={isInvalid ? '2px' : '1px'}
|
||||||
borderRadius={'md'}
|
borderRadius={'md'}
|
||||||
borderColor={'myGray.200'}
|
borderColor={isInvalid ? 'red.500' : 'myGray.200'}
|
||||||
py={2}
|
py={2}
|
||||||
height={height}
|
height={height}
|
||||||
position={'relative'}
|
position={'relative'}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
"Export Configs": "Export Configs",
|
"Export Configs": "Export Configs",
|
||||||
"Feedback Count": "User Feedback",
|
"Feedback Count": "User Feedback",
|
||||||
"Go to chat": "To chat",
|
"Go to chat": "To chat",
|
||||||
|
"Go to run": "Run",
|
||||||
"Import Configs": "Import Configs",
|
"Import Configs": "Import Configs",
|
||||||
"Import Configs Failed": "Failed to import configs, please ensure configs are valid!",
|
"Import Configs Failed": "Failed to import configs, please ensure configs are valid!",
|
||||||
"Input Field Settings": "Input Field Settings",
|
"Input Field Settings": "Input Field Settings",
|
||||||
|
|
@ -39,8 +40,12 @@
|
||||||
"My Apps": "My Apps",
|
"My Apps": "My Apps",
|
||||||
"Output Field Settings": "Output Field Settings",
|
"Output Field Settings": "Output Field Settings",
|
||||||
"Paste Config": "Paste Config",
|
"Paste Config": "Paste Config",
|
||||||
|
"Plugin dispatch": "Plugins",
|
||||||
|
"Plugin dispatch tip": "It is up to the model to decide which plug-ins to add additional capabilities to. If the plug-in is selected, the knowledge base call is also treated as a special plug-in.",
|
||||||
"Publish channel": "Publish channel",
|
"Publish channel": "Publish channel",
|
||||||
"Publish success": "Publish success",
|
"Publish success": "Publish success",
|
||||||
|
"Run": "Run",
|
||||||
|
"Search app": "Search app",
|
||||||
"Setting app": "Settings",
|
"Setting app": "Settings",
|
||||||
"Setting plugin": "Setting plugin",
|
"Setting plugin": "Setting plugin",
|
||||||
"To Chat": "Go to Chat",
|
"To Chat": "Go to Chat",
|
||||||
|
|
|
||||||
|
|
@ -107,8 +107,10 @@
|
||||||
"Rename Success": "Rename Success",
|
"Rename Success": "Rename Success",
|
||||||
"Request Error": "Request Error",
|
"Request Error": "Request Error",
|
||||||
"Require Input": "Required Input",
|
"Require Input": "Required Input",
|
||||||
|
"Restart": "Restart",
|
||||||
"Role": "Role",
|
"Role": "Role",
|
||||||
"Root folder": "Root folder",
|
"Root folder": "Root folder",
|
||||||
|
"Run": "Run",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Save Failed": "Save Failed",
|
"Save Failed": "Save Failed",
|
||||||
"Save Success": "Save Success",
|
"Save Success": "Save Success",
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
"Export Configs": "导出配置",
|
"Export Configs": "导出配置",
|
||||||
"Feedback Count": "用户反馈",
|
"Feedback Count": "用户反馈",
|
||||||
"Go to chat": "去对话",
|
"Go to chat": "去对话",
|
||||||
|
"Go to run": "去运行",
|
||||||
"Import Configs": "导入配置",
|
"Import Configs": "导入配置",
|
||||||
"Import Configs Failed": "导入配置失败,请确保配置正常!",
|
"Import Configs Failed": "导入配置失败,请确保配置正常!",
|
||||||
"Input Field Settings": "输入字段编辑",
|
"Input Field Settings": "输入字段编辑",
|
||||||
|
|
@ -38,8 +39,12 @@
|
||||||
"My Apps": "我的应用",
|
"My Apps": "我的应用",
|
||||||
"Output Field Settings": "输出字段编辑",
|
"Output Field Settings": "输出字段编辑",
|
||||||
"Paste Config": "粘贴配置",
|
"Paste Config": "粘贴配置",
|
||||||
|
"Plugin dispatch": "插件调用",
|
||||||
|
"Plugin dispatch tip": "给模型附加额外的能力,具体调用哪些插件,将由模型自主决定。\n若选择了插件,知识库调用将自动作为一个特殊的插件。",
|
||||||
"Publish channel": "发布渠道",
|
"Publish channel": "发布渠道",
|
||||||
"Publish success": "发布成功",
|
"Publish success": "发布成功",
|
||||||
|
"Run": "运行",
|
||||||
|
"Search app": "搜索应用",
|
||||||
"Setting app": "应用配置",
|
"Setting app": "应用配置",
|
||||||
"Setting plugin": "插件配置",
|
"Setting plugin": "插件配置",
|
||||||
"To Chat": "前去对话",
|
"To Chat": "前去对话",
|
||||||
|
|
|
||||||
|
|
@ -108,8 +108,10 @@
|
||||||
"Rename Success": "重命名成功",
|
"Rename Success": "重命名成功",
|
||||||
"Request Error": "请求异常",
|
"Request Error": "请求异常",
|
||||||
"Require Input": "必填",
|
"Require Input": "必填",
|
||||||
|
"Restart": "重新开始",
|
||||||
"Role": "权限",
|
"Role": "权限",
|
||||||
"Root folder": "根目录",
|
"Root folder": "根目录",
|
||||||
|
"Run": "运行",
|
||||||
"Save": "保存",
|
"Save": "保存",
|
||||||
"Save Failed": "保存失败",
|
"Save Failed": "保存失败",
|
||||||
"Save Success": "保存成功",
|
"Save Success": "保存成功",
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash": "^4.14.191",
|
"@types/lodash": "^4.14.191",
|
||||||
"@types/papaparse": "^5.3.7",
|
"@types/papaparse": "^5.3.7",
|
||||||
"@types/react": "18.3.0",
|
"@types/react": "18.3.1",
|
||||||
"@types/react-beautiful-dnd": "^13.1.8",
|
"@types/react-beautiful-dnd": "^13.1.1",
|
||||||
"@types/react-dom": "18.3.0"
|
"@types/react-dom": "18.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,7 @@ const Input: ComponentStyleConfig = {
|
||||||
}),
|
}),
|
||||||
md: defineStyle({
|
md: defineStyle({
|
||||||
field: {
|
field: {
|
||||||
h: '40px',
|
h: '34px',
|
||||||
borderRadius: 'md'
|
borderRadius: 'md'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
4710
pnpm-lock.yaml
4710
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -1,11 +1,11 @@
|
||||||
# --------- install dependence -----------
|
# --------- install dependence -----------
|
||||||
FROM node:18.17-alpine AS mainDeps
|
FROM node:20.14.0-alpine AS mainDeps
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ARG proxy
|
ARG proxy
|
||||||
|
|
||||||
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
||||||
RUN apk add --no-cache libc6-compat && npm install -g pnpm@8.6.0
|
RUN apk add --no-cache libc6-compat && npm install -g pnpm@9.4.0
|
||||||
# if proxy exists, set proxy
|
# if proxy exists, set proxy
|
||||||
RUN [ -z "$proxy" ] || pnpm config set registry https://registry.npmmirror.com
|
RUN [ -z "$proxy" ] || pnpm config set registry https://registry.npmmirror.com
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@ RUN [ -f pnpm-lock.yaml ] || (echo "Lockfile not found." && exit 1)
|
||||||
RUN pnpm i
|
RUN pnpm i
|
||||||
|
|
||||||
# --------- builder -----------
|
# --------- builder -----------
|
||||||
FROM node:18.17-alpine AS builder
|
FROM node:20.14.0-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ARG proxy
|
ARG proxy
|
||||||
|
|
@ -33,13 +33,13 @@ COPY --from=mainDeps /app/projects/app/node_modules ./projects/app/node_modules
|
||||||
|
|
||||||
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
||||||
|
|
||||||
RUN apk add --no-cache libc6-compat && npm install -g pnpm@8.6.0
|
RUN apk add --no-cache libc6-compat && npm install -g pnpm@9.4.0
|
||||||
|
|
||||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||||
RUN pnpm --filter=app build
|
RUN pnpm --filter=app build
|
||||||
|
|
||||||
# --------- runner -----------
|
# --------- runner -----------
|
||||||
FROM node:18.17-alpine AS runner
|
FROM node:20.14.0-alpine AS runner
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ARG proxy
|
ARG proxy
|
||||||
|
|
|
||||||
|
|
@ -72,13 +72,13 @@
|
||||||
"@types/jsonwebtoken": "^9.0.3",
|
"@types/jsonwebtoken": "^9.0.3",
|
||||||
"@types/lodash": "^4.14.191",
|
"@types/lodash": "^4.14.191",
|
||||||
"@types/node": "^20.14.2",
|
"@types/node": "^20.14.2",
|
||||||
"@types/react": "18.3.0",
|
"@types/react": "18.3.1",
|
||||||
"@types/react-dom": "18.3.0",
|
"@types/react-dom": "18.3.0",
|
||||||
"@types/react-syntax-highlighter": "^15.5.6",
|
"@types/react-syntax-highlighter": "^15.5.6",
|
||||||
"@types/request-ip": "^0.0.37",
|
"@types/request-ip": "^0.0.37",
|
||||||
"eslint": "8.56.0",
|
"eslint": "8.56.0",
|
||||||
"eslint-config-next": "14.2.3",
|
"eslint-config-next": "14.2.3",
|
||||||
"nextjs-node-loader": "^1.1.5",
|
"nextjs-node-loader": "^1.1.5",
|
||||||
"typescript": "4.9.5"
|
"typescript": "^5.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,262 +0,0 @@
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
BoxProps,
|
|
||||||
Card,
|
|
||||||
Flex,
|
|
||||||
useTheme,
|
|
||||||
Accordion,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionButton,
|
|
||||||
AccordionPanel,
|
|
||||||
AccordionIcon,
|
|
||||||
Image
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import React, { useMemo } from 'react';
|
|
||||||
import ChatController, { type ChatControllerProps } from './ChatController';
|
|
||||||
import ChatAvatar from './ChatAvatar';
|
|
||||||
import { MessageCardStyle } from '../constants';
|
|
||||||
import { formatChatValue2InputType } from '../utils';
|
|
||||||
import Markdown, { CodeClassName } from '@/components/Markdown';
|
|
||||||
import styles from '../index.module.scss';
|
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
|
||||||
import {
|
|
||||||
ChatItemValueTypeEnum,
|
|
||||||
ChatRoleEnum,
|
|
||||||
ChatStatusEnum
|
|
||||||
} from '@fastgpt/global/core/chat/constants';
|
|
||||||
import FilesBlock from './FilesBox';
|
|
||||||
import { ChatBoxContext } from '../Provider';
|
|
||||||
import Avatar from '@/components/Avatar';
|
|
||||||
import { useContextSelector } from 'use-context-selector';
|
|
||||||
|
|
||||||
const colorMap = {
|
|
||||||
[ChatStatusEnum.loading]: {
|
|
||||||
bg: 'myGray.100',
|
|
||||||
color: 'myGray.600'
|
|
||||||
},
|
|
||||||
[ChatStatusEnum.running]: {
|
|
||||||
bg: 'green.50',
|
|
||||||
color: 'green.700'
|
|
||||||
},
|
|
||||||
[ChatStatusEnum.finish]: {
|
|
||||||
bg: 'green.50',
|
|
||||||
color: 'green.700'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const ChatItem = ({
|
|
||||||
type,
|
|
||||||
avatar,
|
|
||||||
statusBoxData,
|
|
||||||
children,
|
|
||||||
isLastChild,
|
|
||||||
questionGuides = [],
|
|
||||||
...chatControllerProps
|
|
||||||
}: {
|
|
||||||
type: ChatRoleEnum.Human | ChatRoleEnum.AI;
|
|
||||||
avatar?: string;
|
|
||||||
statusBoxData?: {
|
|
||||||
status: `${ChatStatusEnum}`;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
questionGuides?: string[];
|
|
||||||
children?: React.ReactNode;
|
|
||||||
} & ChatControllerProps) => {
|
|
||||||
const styleMap: BoxProps =
|
|
||||||
type === ChatRoleEnum.Human
|
|
||||||
? {
|
|
||||||
order: 0,
|
|
||||||
borderRadius: '8px 0 8px 8px',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
textAlign: 'right',
|
|
||||||
bg: 'primary.100'
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
order: 1,
|
|
||||||
borderRadius: '0 8px 8px 8px',
|
|
||||||
justifyContent: 'flex-start',
|
|
||||||
textAlign: 'left',
|
|
||||||
bg: 'myGray.50'
|
|
||||||
};
|
|
||||||
|
|
||||||
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
|
||||||
const { chat } = chatControllerProps;
|
|
||||||
|
|
||||||
const ContentCard = useMemo(() => {
|
|
||||||
if (type === 'Human') {
|
|
||||||
const { text, files = [] } = formatChatValue2InputType(chat.value);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{files.length > 0 && <FilesBlock files={files} />}
|
|
||||||
<Markdown source={text} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* AI */
|
|
||||||
return (
|
|
||||||
<Flex flexDirection={'column'} key={chat.dataId} gap={2}>
|
|
||||||
{chat.value.map((value, i) => {
|
|
||||||
const key = `${chat.dataId}-ai-${i}`;
|
|
||||||
|
|
||||||
if (value.text) {
|
|
||||||
let source = (value.text?.content || '').trim();
|
|
||||||
|
|
||||||
if (!source && chat.value.length > 1) return null;
|
|
||||||
|
|
||||||
if (
|
|
||||||
isLastChild &&
|
|
||||||
!isChatting &&
|
|
||||||
questionGuides.length > 0 &&
|
|
||||||
i === chat.value.length - 1
|
|
||||||
) {
|
|
||||||
source = `${source}
|
|
||||||
\`\`\`${CodeClassName.questionGuide}
|
|
||||||
${JSON.stringify(questionGuides)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Markdown
|
|
||||||
key={key}
|
|
||||||
source={source}
|
|
||||||
showAnimation={isLastChild && isChatting && i === chat.value.length - 1}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (value.type === ChatItemValueTypeEnum.tool && value.tools) {
|
|
||||||
return (
|
|
||||||
<Box key={key}>
|
|
||||||
{value.tools.map((tool) => {
|
|
||||||
const toolParams = (() => {
|
|
||||||
try {
|
|
||||||
return JSON.stringify(JSON.parse(tool.params), null, 2);
|
|
||||||
} catch (error) {
|
|
||||||
return tool.params;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
const toolResponse = (() => {
|
|
||||||
try {
|
|
||||||
return JSON.stringify(JSON.parse(tool.response), null, 2);
|
|
||||||
} catch (error) {
|
|
||||||
return tool.response;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box key={tool.id}>
|
|
||||||
<Accordion allowToggle>
|
|
||||||
<AccordionItem borderTop={'none'} borderBottom={'none'}>
|
|
||||||
<AccordionButton
|
|
||||||
w={'auto'}
|
|
||||||
bg={'white'}
|
|
||||||
borderRadius={'md'}
|
|
||||||
borderWidth={'1px'}
|
|
||||||
borderColor={'myGray.200'}
|
|
||||||
boxShadow={'1'}
|
|
||||||
_hover={{
|
|
||||||
bg: 'auto'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Avatar src={tool.toolAvatar} w={'1rem'} mr={2} />
|
|
||||||
<Box mr={1} fontSize={'sm'}>
|
|
||||||
{tool.toolName}
|
|
||||||
</Box>
|
|
||||||
{isChatting && !tool.response && (
|
|
||||||
<MyIcon name={'common/loading'} w={'14px'} />
|
|
||||||
)}
|
|
||||||
<AccordionIcon color={'myGray.600'} ml={5} />
|
|
||||||
</AccordionButton>
|
|
||||||
<AccordionPanel
|
|
||||||
py={0}
|
|
||||||
px={0}
|
|
||||||
mt={0}
|
|
||||||
borderRadius={'md'}
|
|
||||||
overflow={'hidden'}
|
|
||||||
maxH={'500px'}
|
|
||||||
overflowY={'auto'}
|
|
||||||
>
|
|
||||||
{toolParams && toolParams !== '{}' && (
|
|
||||||
<Markdown
|
|
||||||
source={`~~~json#Input
|
|
||||||
${toolParams}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{toolResponse && (
|
|
||||||
<Markdown
|
|
||||||
source={`~~~json#Response
|
|
||||||
${toolResponse}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AccordionPanel>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}, [chat.dataId, chat.value, isChatting, isLastChild, questionGuides, type]);
|
|
||||||
|
|
||||||
const chatStatusMap = useMemo(() => {
|
|
||||||
if (!statusBoxData?.status) return;
|
|
||||||
return colorMap[statusBoxData.status];
|
|
||||||
}, [statusBoxData?.status]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* control icon */}
|
|
||||||
<Flex w={'100%'} alignItems={'center'} gap={2} justifyContent={styleMap.justifyContent}>
|
|
||||||
{isChatting && type === ChatRoleEnum.AI && isLastChild ? null : (
|
|
||||||
<Box order={styleMap.order} ml={styleMap.ml}>
|
|
||||||
<ChatController {...chatControllerProps} isLastChild={isLastChild} />
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
<ChatAvatar src={avatar} type={type} />
|
|
||||||
|
|
||||||
{!!chatStatusMap && statusBoxData && isLastChild && (
|
|
||||||
<Flex
|
|
||||||
alignItems={'center'}
|
|
||||||
px={3}
|
|
||||||
py={'1.5px'}
|
|
||||||
borderRadius="md"
|
|
||||||
bg={chatStatusMap.bg}
|
|
||||||
fontSize={'sm'}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
className={styles.statusAnimation}
|
|
||||||
bg={chatStatusMap.color}
|
|
||||||
w="8px"
|
|
||||||
h="8px"
|
|
||||||
borderRadius={'50%'}
|
|
||||||
mt={'1px'}
|
|
||||||
/>
|
|
||||||
<Box ml={2} color={'myGray.600'}>
|
|
||||||
{statusBoxData.name}
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
{/* content */}
|
|
||||||
<Box mt={['6px', 2]} textAlign={styleMap.textAlign}>
|
|
||||||
<Card
|
|
||||||
className="markdown"
|
|
||||||
{...MessageCardStyle}
|
|
||||||
bg={styleMap.bg}
|
|
||||||
borderRadius={styleMap.borderRadius}
|
|
||||||
textAlign={'left'}
|
|
||||||
>
|
|
||||||
{ContentCard}
|
|
||||||
{children}
|
|
||||||
</Card>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default React.memo(ChatItem);
|
|
||||||
|
|
@ -1,325 +0,0 @@
|
||||||
import React, { useMemo, useState } from 'react';
|
|
||||||
import { Box, useTheme, Flex, Image, BoxProps } from '@chakra-ui/react';
|
|
||||||
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
|
|
||||||
import { useTranslation } from 'next-i18next';
|
|
||||||
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
|
|
||||||
|
|
||||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
|
||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
|
||||||
import Markdown from '../../Markdown';
|
|
||||||
import { QuoteList } from './QuoteModal';
|
|
||||||
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
|
|
||||||
import { formatNumber } from '@fastgpt/global/common/math/tools';
|
|
||||||
import { useI18n } from '@/web/context/I18n';
|
|
||||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
|
||||||
|
|
||||||
function RowRender({
|
|
||||||
children,
|
|
||||||
mb,
|
|
||||||
label,
|
|
||||||
...props
|
|
||||||
}: { children: React.ReactNode; label: string } & BoxProps) {
|
|
||||||
return (
|
|
||||||
<Box mb={3}>
|
|
||||||
<Box fontSize={'sm'} mb={mb} flex={'0 0 90px'}>
|
|
||||||
{label}:
|
|
||||||
</Box>
|
|
||||||
<Box borderRadius={'sm'} fontSize={['xs', 'sm']} bg={'myGray.50'} {...props}>
|
|
||||||
{children}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function Row({
|
|
||||||
label,
|
|
||||||
value,
|
|
||||||
rawDom
|
|
||||||
}: {
|
|
||||||
label: string;
|
|
||||||
value?: string | number | boolean | object;
|
|
||||||
rawDom?: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
const theme = useTheme();
|
|
||||||
const val = value || rawDom;
|
|
||||||
const isObject = typeof value === 'object';
|
|
||||||
|
|
||||||
const formatValue = useMemo(() => {
|
|
||||||
if (isObject) {
|
|
||||||
return `~~~json\n${JSON.stringify(value, null, 2)}`;
|
|
||||||
}
|
|
||||||
return `${value}`;
|
|
||||||
}, [isObject, value]);
|
|
||||||
|
|
||||||
if (rawDom) {
|
|
||||||
return (
|
|
||||||
<RowRender label={label} mb={1}>
|
|
||||||
{rawDom}
|
|
||||||
</RowRender>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (val === undefined || val === '' || val === 'undefined') return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RowRender
|
|
||||||
label={label}
|
|
||||||
mb={isObject ? 0 : 1}
|
|
||||||
{...(isObject
|
|
||||||
? { transform: 'translateY(-3px)' }
|
|
||||||
: value
|
|
||||||
? { px: 3, py: 2, border: theme.borders.base }
|
|
||||||
: {})}
|
|
||||||
>
|
|
||||||
<Markdown source={formatValue} />
|
|
||||||
</RowRender>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const WholeResponseModal = ({
|
|
||||||
response,
|
|
||||||
showDetail,
|
|
||||||
onClose
|
|
||||||
}: {
|
|
||||||
response: ChatHistoryItemResType[];
|
|
||||||
showDetail: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MyModal
|
|
||||||
isCentered
|
|
||||||
isOpen={true}
|
|
||||||
onClose={onClose}
|
|
||||||
h={['90vh', '80vh']}
|
|
||||||
minW={['90vw', '600px']}
|
|
||||||
iconSrc="/imgs/modal/wholeRecord.svg"
|
|
||||||
title={
|
|
||||||
<Flex alignItems={'center'}>
|
|
||||||
{t('core.chat.response.Complete Response')}
|
|
||||||
<QuestionTip ml={2} label={'从左往右,为各个模块的响应顺序'}></QuestionTip>
|
|
||||||
</Flex>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Flex h={'100%'} flexDirection={'column'}>
|
|
||||||
<ResponseBox response={response} showDetail={showDetail} />
|
|
||||||
</Flex>
|
|
||||||
</MyModal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WholeResponseModal;
|
|
||||||
|
|
||||||
export const ResponseBox = React.memo(function ResponseBox({
|
|
||||||
response,
|
|
||||||
showDetail,
|
|
||||||
hideTabs = false
|
|
||||||
}: {
|
|
||||||
response: ChatHistoryItemResType[];
|
|
||||||
showDetail: boolean;
|
|
||||||
hideTabs?: boolean;
|
|
||||||
}) {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { workflowT } = useI18n();
|
|
||||||
|
|
||||||
const list = useMemo(
|
|
||||||
() =>
|
|
||||||
response.map((item, i) => ({
|
|
||||||
label: (
|
|
||||||
<Flex alignItems={'center'} justifyContent={'center'} px={2}>
|
|
||||||
<Image
|
|
||||||
mr={2}
|
|
||||||
src={
|
|
||||||
item.moduleLogo ||
|
|
||||||
moduleTemplatesFlat.find((template) => item.moduleType === template.flowNodeType)
|
|
||||||
?.avatar
|
|
||||||
}
|
|
||||||
alt={''}
|
|
||||||
w={['14px', '16px']}
|
|
||||||
/>
|
|
||||||
{t(item.moduleName)}
|
|
||||||
</Flex>
|
|
||||||
),
|
|
||||||
value: `${i}`
|
|
||||||
})),
|
|
||||||
[response, t]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [currentTab, setCurrentTab] = useState(`0`);
|
|
||||||
|
|
||||||
const activeModule = useMemo(() => response[Number(currentTab)], [currentTab, response]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!hideTabs && (
|
|
||||||
<Box>
|
|
||||||
<LightRowTabs list={list} value={currentTab} onChange={setCurrentTab} />
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
<Box py={2} px={4} flex={'1 0 0'} overflow={'auto'}>
|
|
||||||
<>
|
|
||||||
<Row label={t('core.chat.response.module name')} value={t(activeModule.moduleName)} />
|
|
||||||
{activeModule?.totalPoints !== undefined && (
|
|
||||||
<Row
|
|
||||||
label={t('support.wallet.usage.Total points')}
|
|
||||||
value={formatNumber(activeModule.totalPoints)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Row
|
|
||||||
label={t('core.chat.response.module time')}
|
|
||||||
value={`${activeModule?.runningTime || 0}s`}
|
|
||||||
/>
|
|
||||||
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
|
|
||||||
<Row label={t('core.chat.response.module tokens')} value={`${activeModule?.tokens}`} />
|
|
||||||
<Row
|
|
||||||
label={t('core.chat.response.Tool call tokens')}
|
|
||||||
value={`${activeModule?.toolCallTokens}`}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
|
|
||||||
<Row
|
|
||||||
label={t('core.chat.response.context total length')}
|
|
||||||
value={activeModule?.contextTotalLen}
|
|
||||||
/>
|
|
||||||
<Row label={workflowT('response.Error')} value={activeModule?.error} />
|
|
||||||
</>
|
|
||||||
|
|
||||||
{/* ai chat */}
|
|
||||||
<>
|
|
||||||
<Row
|
|
||||||
label={t('core.chat.response.module temperature')}
|
|
||||||
value={activeModule?.temperature}
|
|
||||||
/>
|
|
||||||
<Row label={t('core.chat.response.module maxToken')} value={activeModule?.maxToken} />
|
|
||||||
<Row
|
|
||||||
label={t('core.chat.response.module historyPreview')}
|
|
||||||
rawDom={
|
|
||||||
activeModule.historyPreview ? (
|
|
||||||
<Box px={3} py={2} border={theme.borders.base} borderRadius={'md'}>
|
|
||||||
{activeModule.historyPreview?.map((item, i) => (
|
|
||||||
<Box
|
|
||||||
key={i}
|
|
||||||
_notLast={{
|
|
||||||
borderBottom: '1px solid',
|
|
||||||
borderBottomColor: 'myWhite.700',
|
|
||||||
mb: 2
|
|
||||||
}}
|
|
||||||
pb={2}
|
|
||||||
>
|
|
||||||
<Box fontWeight={'bold'}>{item.obj}</Box>
|
|
||||||
<Box whiteSpace={'pre-wrap'}>{item.value}</Box>
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
|
|
||||||
{/* dataset search */}
|
|
||||||
<>
|
|
||||||
{activeModule?.searchMode && (
|
|
||||||
<Row
|
|
||||||
label={t('core.dataset.search.search mode')}
|
|
||||||
// @ts-ignore
|
|
||||||
value={t(DatasetSearchModeMap[activeModule.searchMode]?.title)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Row label={t('core.chat.response.module similarity')} value={activeModule?.similarity} />
|
|
||||||
<Row label={t('core.chat.response.module limit')} value={activeModule?.limit} />
|
|
||||||
<Row
|
|
||||||
label={t('core.chat.response.search using reRank')}
|
|
||||||
value={`${activeModule?.searchUsingReRank}`}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={t('core.chat.response.Extension model')}
|
|
||||||
value={activeModule?.extensionModel}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={t('support.wallet.usage.Extension result')}
|
|
||||||
value={`${activeModule?.extensionResult}`}
|
|
||||||
/>
|
|
||||||
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
|
|
||||||
<Row
|
|
||||||
label={t('core.chat.response.module quoteList')}
|
|
||||||
rawDom={<QuoteList showDetail={showDetail} rawSearch={activeModule.quoteList} />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
|
|
||||||
{/* classify question */}
|
|
||||||
<>
|
|
||||||
<Row label={t('core.chat.response.module cq result')} value={activeModule?.cqResult} />
|
|
||||||
<Row
|
|
||||||
label={t('core.chat.response.module cq')}
|
|
||||||
value={(() => {
|
|
||||||
if (!activeModule?.cqList) return '';
|
|
||||||
return activeModule.cqList.map((item) => `* ${item.value}`).join('\n');
|
|
||||||
})()}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
|
|
||||||
{/* if-else */}
|
|
||||||
<>
|
|
||||||
<Row
|
|
||||||
label={t('core.chat.response.module if else Result')}
|
|
||||||
value={activeModule?.ifElseResult}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
|
|
||||||
{/* extract */}
|
|
||||||
<>
|
|
||||||
<Row
|
|
||||||
label={t('core.chat.response.module extract description')}
|
|
||||||
value={activeModule?.extractDescription}
|
|
||||||
/>
|
|
||||||
<Row
|
|
||||||
label={t('core.chat.response.module extract result')}
|
|
||||||
value={activeModule?.extractResult}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
|
|
||||||
{/* http */}
|
|
||||||
<>
|
|
||||||
<Row label={'Headers'} value={activeModule?.headers} />
|
|
||||||
<Row label={'Params'} value={activeModule?.params} />
|
|
||||||
<Row label={'Body'} value={activeModule?.body} />
|
|
||||||
<Row
|
|
||||||
label={t('core.chat.response.module http result')}
|
|
||||||
value={activeModule?.httpResult}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
|
|
||||||
{/* plugin */}
|
|
||||||
<>
|
|
||||||
<Row label={t('core.chat.response.plugin output')} value={activeModule?.pluginOutput} />
|
|
||||||
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
|
|
||||||
<Row
|
|
||||||
label={t('core.chat.response.Plugin response detail')}
|
|
||||||
rawDom={<ResponseBox response={activeModule.pluginDetail} showDetail={showDetail} />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
|
|
||||||
{/* text output */}
|
|
||||||
<Row label={t('core.chat.response.text output')} value={activeModule?.textOutput} />
|
|
||||||
|
|
||||||
{/* tool call */}
|
|
||||||
{activeModule?.toolDetail && activeModule?.toolDetail.length > 0 && (
|
|
||||||
<Row
|
|
||||||
label={t('core.chat.response.Tool call response detail')}
|
|
||||||
rawDom={<ResponseBox response={activeModule.toolDetail} showDetail={showDetail} />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* code */}
|
|
||||||
<Row label={workflowT('response.Custom outputs')} value={activeModule?.customOutputs} />
|
|
||||||
<Row label={workflowT('response.Custom inputs')} value={activeModule?.customInputs} />
|
|
||||||
<Row label={workflowT('response.Code log')} value={activeModule?.codeLog} />
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { getUnreadCount } from '@/web/support/user/inform/api';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
import Auth from './auth';
|
import Auth from './auth';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
const Navbar = dynamic(() => import('./navbar'));
|
const Navbar = dynamic(() => import('./navbar'));
|
||||||
const NavbarPhone = dynamic(() => import('./navbarPhone'));
|
const NavbarPhone = dynamic(() => import('./navbarPhone'));
|
||||||
const UpdateInviteModal = dynamic(() => import('@/components/support/user/team/UpdateInviteModal'));
|
const UpdateInviteModal = dynamic(() => import('@/components/support/user/team/UpdateInviteModal'));
|
||||||
|
|
@ -43,7 +44,8 @@ const phoneUnShowLayoutRoute: Record<string, boolean> = {
|
||||||
const Layout = ({ children }: { children: JSX.Element }) => {
|
const Layout = ({ children }: { children: JSX.Element }) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { Loading } = useLoading();
|
const { Loading } = useLoading();
|
||||||
const { loading, setScreenWidth, isPc, feConfigs, isNotSufficientModal } = useSystemStore();
|
const { loading, feConfigs, isNotSufficientModal } = useSystemStore();
|
||||||
|
const { isPc } = useSystem();
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
|
|
||||||
const isChatPage = useMemo(
|
const isChatPage = useMemo(
|
||||||
|
|
@ -51,21 +53,6 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
||||||
[router.pathname, router.query]
|
[router.pathname, router.query]
|
||||||
);
|
);
|
||||||
|
|
||||||
// listen screen width
|
|
||||||
useEffect(() => {
|
|
||||||
const resize = throttle(() => {
|
|
||||||
setScreenWidth(document.documentElement.clientWidth);
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
window.addEventListener('resize', resize);
|
|
||||||
|
|
||||||
resize();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('resize', resize);
|
|
||||||
};
|
|
||||||
}, [setScreenWidth]);
|
|
||||||
|
|
||||||
const { data, refetch: refetchUnRead } = useQuery(['getUnreadCount'], getUnreadCount, {
|
const { data, refetch: refetchUnRead } = useQuery(['getUnreadCount'], getUnreadCount, {
|
||||||
enabled: !!userInfo && !!feConfigs.isPlus,
|
enabled: !!userInfo && !!feConfigs.isPlus,
|
||||||
refetchInterval: 10000
|
refetchInterval: 10000
|
||||||
|
|
|
||||||
|
|
@ -32,20 +32,22 @@ export const useFolderDrag = ({
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setTargetId(undefined);
|
setTargetId(undefined);
|
||||||
},
|
},
|
||||||
onDrop: async (e: DragEvent<HTMLDivElement>) => {
|
...(isFolder && {
|
||||||
e.preventDefault();
|
onDrop: async (e: DragEvent<HTMLDivElement>) => {
|
||||||
setTrue();
|
e.preventDefault();
|
||||||
|
setTrue();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (targetId && dragId && targetId !== dragId) {
|
if (targetId && dragId && targetId !== dragId) {
|
||||||
await onDrop(dragId, targetId);
|
await onDrop(dragId, targetId);
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
|
|
||||||
setTargetId(undefined);
|
setTargetId(undefined);
|
||||||
setDragId(undefined);
|
setDragId(undefined);
|
||||||
setFalse();
|
setFalse();
|
||||||
},
|
}
|
||||||
|
}),
|
||||||
...(activeStyles &&
|
...(activeStyles &&
|
||||||
targetId === dataId && {
|
targetId === dataId && {
|
||||||
...activeStyles
|
...activeStyles
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import { ChatBoxContext } from '../Provider';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
|
|
||||||
const InputGuideBox = dynamic(() => import('./InputGuideBox'));
|
const InputGuideBox = dynamic(() => import('./InputGuideBox'));
|
||||||
|
|
||||||
|
|
@ -53,7 +54,9 @@ const ChatInput = ({
|
||||||
|
|
||||||
const { isChatting, whisperConfig, autoTTSResponse, chatInputGuide, outLinkAuthData } =
|
const { isChatting, whisperConfig, autoTTSResponse, chatInputGuide, outLinkAuthData } =
|
||||||
useContextSelector(ChatBoxContext, (v) => v);
|
useContextSelector(ChatBoxContext, (v) => v);
|
||||||
const { isPc, whisperModel } = useSystemStore();
|
const { whisperModel } = useSystemStore();
|
||||||
|
const { isPc } = useSystem();
|
||||||
|
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|
@ -15,38 +15,53 @@ import {
|
||||||
defaultWhisperConfig
|
defaultWhisperConfig
|
||||||
} from '@fastgpt/global/core/app/constants';
|
} from '@fastgpt/global/core/app/constants';
|
||||||
import { createContext } from 'use-context-selector';
|
import { createContext } from 'use-context-selector';
|
||||||
|
import { FieldValues, UseFormReturn } from 'react-hook-form';
|
||||||
|
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
||||||
|
|
||||||
|
export type ChatProviderProps = OutLinkChatAuthProps & {
|
||||||
|
appAvatar?: string;
|
||||||
|
|
||||||
|
chatConfig?: AppChatConfigType;
|
||||||
|
|
||||||
type useChatStoreType = OutLinkChatAuthProps & {
|
|
||||||
welcomeText: string;
|
|
||||||
variableList: 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>>;
|
|
||||||
chatHistories: ChatSiteItemType[];
|
chatHistories: ChatSiteItemType[];
|
||||||
setChatHistories: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
setChatHistories: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
||||||
isChatting: boolean;
|
variablesForm: UseFormReturn<FieldValues, any>;
|
||||||
chatInputGuide: ChatInputGuideConfigType;
|
|
||||||
outLinkAuthData: OutLinkChatAuthProps;
|
// not chat test params
|
||||||
|
chatId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type useChatStoreType = OutLinkChatAuthProps &
|
||||||
|
ChatProviderProps & {
|
||||||
|
welcomeText: string;
|
||||||
|
variableList: 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;
|
||||||
|
};
|
||||||
|
|
||||||
export const ChatBoxContext = createContext<useChatStoreType>({
|
export const ChatBoxContext = createContext<useChatStoreType>({
|
||||||
welcomeText: '',
|
welcomeText: '',
|
||||||
variableList: [],
|
variableList: [],
|
||||||
|
|
@ -100,27 +115,27 @@ export const ChatBoxContext = createContext<useChatStoreType>({
|
||||||
open: false,
|
open: false,
|
||||||
customUrl: ''
|
customUrl: ''
|
||||||
},
|
},
|
||||||
outLinkAuthData: {}
|
outLinkAuthData: {},
|
||||||
|
// @ts-ignore
|
||||||
|
variablesForm: undefined
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ChatProviderProps = OutLinkChatAuthProps & {
|
|
||||||
chatConfig?: AppChatConfigType;
|
|
||||||
|
|
||||||
// not chat test params
|
|
||||||
chatId?: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Provider = ({
|
const Provider = ({
|
||||||
shareId,
|
shareId,
|
||||||
outLinkUid,
|
outLinkUid,
|
||||||
teamId,
|
teamId,
|
||||||
teamToken,
|
teamToken,
|
||||||
chatConfig = {},
|
|
||||||
children
|
|
||||||
}: ChatProviderProps) => {
|
|
||||||
const [chatHistories, setChatHistories] = useState<ChatSiteItemType[]>([]);
|
|
||||||
|
|
||||||
|
chatHistories,
|
||||||
|
setChatHistories,
|
||||||
|
variablesForm,
|
||||||
|
|
||||||
|
chatConfig = {},
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: ChatProviderProps & {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
const {
|
const {
|
||||||
welcomeText = '',
|
welcomeText = '',
|
||||||
variables = [],
|
variables = [],
|
||||||
|
|
@ -167,12 +182,13 @@ const Provider = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const value: useChatStoreType = {
|
const value: useChatStoreType = {
|
||||||
|
...props,
|
||||||
shareId,
|
shareId,
|
||||||
outLinkUid,
|
outLinkUid,
|
||||||
teamId,
|
teamId,
|
||||||
teamToken,
|
teamToken,
|
||||||
welcomeText,
|
welcomeText,
|
||||||
variableList: variables,
|
variableList: variables.filter((item) => item.type !== VariableInputEnum.custom),
|
||||||
questionGuide,
|
questionGuide,
|
||||||
ttsConfig,
|
ttsConfig,
|
||||||
whisperConfig,
|
whisperConfig,
|
||||||
|
|
@ -191,7 +207,8 @@ const Provider = ({
|
||||||
setChatHistories,
|
setChatHistories,
|
||||||
isChatting,
|
isChatting,
|
||||||
chatInputGuide,
|
chatInputGuide,
|
||||||
outLinkAuthData
|
outLinkAuthData,
|
||||||
|
variablesForm
|
||||||
};
|
};
|
||||||
|
|
||||||
return <ChatBoxContext.Provider value={value}>{children}</ChatBoxContext.Provider>;
|
return <ChatBoxContext.Provider value={value}>{children}</ChatBoxContext.Provider>;
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
import { Box, BoxProps, Card, Flex } from '@chakra-ui/react';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import ChatController, { type ChatControllerProps } from './ChatController';
|
||||||
|
import ChatAvatar from './ChatAvatar';
|
||||||
|
import { MessageCardStyle } from '../constants';
|
||||||
|
import { formatChatValue2InputType } from '../utils';
|
||||||
|
import Markdown from '@/components/Markdown';
|
||||||
|
import styles from '../index.module.scss';
|
||||||
|
import { ChatRoleEnum, ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
|
import FilesBlock from './FilesBox';
|
||||||
|
import { ChatBoxContext } from '../Provider';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import AIResponseBox from '../../../components/AIResponseBox';
|
||||||
|
|
||||||
|
const colorMap = {
|
||||||
|
[ChatStatusEnum.loading]: {
|
||||||
|
bg: 'myGray.100',
|
||||||
|
color: 'myGray.600'
|
||||||
|
},
|
||||||
|
[ChatStatusEnum.running]: {
|
||||||
|
bg: 'green.50',
|
||||||
|
color: 'green.700'
|
||||||
|
},
|
||||||
|
[ChatStatusEnum.finish]: {
|
||||||
|
bg: 'green.50',
|
||||||
|
color: 'green.700'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ChatItem = ({
|
||||||
|
type,
|
||||||
|
avatar,
|
||||||
|
statusBoxData,
|
||||||
|
children,
|
||||||
|
isLastChild,
|
||||||
|
questionGuides = [],
|
||||||
|
...chatControllerProps
|
||||||
|
}: {
|
||||||
|
type: ChatRoleEnum.Human | ChatRoleEnum.AI;
|
||||||
|
avatar?: string;
|
||||||
|
statusBoxData?: {
|
||||||
|
status: `${ChatStatusEnum}`;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
questionGuides?: string[];
|
||||||
|
children?: React.ReactNode;
|
||||||
|
} & ChatControllerProps) => {
|
||||||
|
const styleMap: BoxProps =
|
||||||
|
type === ChatRoleEnum.Human
|
||||||
|
? {
|
||||||
|
order: 0,
|
||||||
|
borderRadius: '8px 0 8px 8px',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
textAlign: 'right',
|
||||||
|
bg: 'primary.100'
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
order: 1,
|
||||||
|
borderRadius: '0 8px 8px 8px',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
textAlign: 'left',
|
||||||
|
bg: 'myGray.50'
|
||||||
|
};
|
||||||
|
|
||||||
|
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
||||||
|
const { chat } = chatControllerProps;
|
||||||
|
|
||||||
|
const ContentCard = useMemo(() => {
|
||||||
|
if (type === 'Human') {
|
||||||
|
const { text, files = [] } = formatChatValue2InputType(chat.value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{files.length > 0 && <FilesBlock files={files} />}
|
||||||
|
<Markdown source={text} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AI */
|
||||||
|
return (
|
||||||
|
<Flex flexDirection={'column'} key={chat.dataId} gap={2}>
|
||||||
|
{chat.value.map((value, i) => {
|
||||||
|
const key = `${chat.dataId}-ai-${i}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AIResponseBox
|
||||||
|
key={key}
|
||||||
|
value={value}
|
||||||
|
index={i}
|
||||||
|
chat={chat}
|
||||||
|
isLastChild={isLastChild}
|
||||||
|
isChatting={isChatting}
|
||||||
|
questionGuides={questionGuides}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}, [chat, isChatting, isLastChild, questionGuides, type]);
|
||||||
|
|
||||||
|
const chatStatusMap = useMemo(() => {
|
||||||
|
if (!statusBoxData?.status) return;
|
||||||
|
return colorMap[statusBoxData.status];
|
||||||
|
}, [statusBoxData?.status]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* control icon */}
|
||||||
|
<Flex w={'100%'} alignItems={'center'} gap={2} justifyContent={styleMap.justifyContent}>
|
||||||
|
{isChatting && type === ChatRoleEnum.AI && isLastChild ? null : (
|
||||||
|
<Box order={styleMap.order} ml={styleMap.ml}>
|
||||||
|
<ChatController {...chatControllerProps} isLastChild={isLastChild} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<ChatAvatar src={avatar} type={type} />
|
||||||
|
|
||||||
|
{!!chatStatusMap && statusBoxData && isLastChild && (
|
||||||
|
<Flex
|
||||||
|
alignItems={'center'}
|
||||||
|
px={3}
|
||||||
|
py={'1.5px'}
|
||||||
|
borderRadius="md"
|
||||||
|
bg={chatStatusMap.bg}
|
||||||
|
fontSize={'sm'}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
className={styles.statusAnimation}
|
||||||
|
bg={chatStatusMap.color}
|
||||||
|
w="8px"
|
||||||
|
h="8px"
|
||||||
|
borderRadius={'50%'}
|
||||||
|
mt={'1px'}
|
||||||
|
/>
|
||||||
|
<Box ml={2} color={'myGray.600'}>
|
||||||
|
{statusBoxData.name}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
{/* content */}
|
||||||
|
<Box mt={['6px', 2]} textAlign={styleMap.textAlign}>
|
||||||
|
<Card
|
||||||
|
className="markdown"
|
||||||
|
{...MessageCardStyle}
|
||||||
|
bg={styleMap.bg}
|
||||||
|
borderRadius={styleMap.borderRadius}
|
||||||
|
textAlign={'left'}
|
||||||
|
>
|
||||||
|
{ContentCard}
|
||||||
|
{children}
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(ChatItem);
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Box, Flex, Grid } from '@chakra-ui/react';
|
import { Box, Flex, Grid } from '@chakra-ui/react';
|
||||||
import MdImage from '@/components/Markdown/img/Image';
|
import MdImage from '@/components/Markdown/img/Image';
|
||||||
import { UserInputFileItemType } from '@/components/ChatBox/type';
|
import { UserInputFileItemType } from '@/components/core/chat/ChatContainer/ChatBox/type';
|
||||||
|
|
||||||
const FilesBlock = ({ files }: { files: UserInputFileItemType[] }) => {
|
const FilesBlock = ({ files }: { files: UserInputFileItemType[] }) => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -4,8 +4,8 @@ import { ModalBody, Box, useTheme } from '@chakra-ui/react';
|
||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||||
import QuoteItem from '../../core/dataset/QuoteItem';
|
import QuoteItem from '@/components/core/dataset/QuoteItem';
|
||||||
import RawSourceBox from '../../core/dataset/RawSourceBox';
|
import RawSourceBox from '@/components/core/dataset/RawSourceBox';
|
||||||
|
|
||||||
const QuoteModal = ({
|
const QuoteModal = ({
|
||||||
rawSearch = [],
|
rawSearch = [],
|
||||||
|
|
@ -3,7 +3,6 @@ import { type ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
|
||||||
import { DispatchNodeResponseType } from '@fastgpt/global/core/workflow/runtime/type.d';
|
import { DispatchNodeResponseType } from '@fastgpt/global/core/workflow/runtime/type.d';
|
||||||
import { Flex, useDisclosure, useTheme, Box } from '@chakra-ui/react';
|
import { Flex, useDisclosure, useTheme, Box } from '@chakra-ui/react';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
|
||||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||||
|
|
@ -13,10 +12,11 @@ import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
|
||||||
import ChatBoxDivider from '@/components/core/chat/Divider';
|
import ChatBoxDivider from '@/components/core/chat/Divider';
|
||||||
import { strIsLink } from '@fastgpt/global/common/string/tools';
|
import { strIsLink } from '@fastgpt/global/common/string/tools';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
|
|
||||||
const QuoteModal = dynamic(() => import('./QuoteModal'));
|
const QuoteModal = dynamic(() => import('./QuoteModal'));
|
||||||
const ContextModal = dynamic(() => import('./ContextModal'));
|
const ContextModal = dynamic(() => import('./ContextModal'));
|
||||||
const WholeResponseModal = dynamic(() => import('./WholeResponseModal'));
|
const WholeResponseModal = dynamic(() => import('../../../components/WholeResponseModal'));
|
||||||
|
|
||||||
const isLLMNode = (item: ChatHistoryItemResType) =>
|
const isLLMNode = (item: ChatHistoryItemResType) =>
|
||||||
item.moduleType === FlowNodeTypeEnum.chatNode || item.moduleType === FlowNodeTypeEnum.tools;
|
item.moduleType === FlowNodeTypeEnum.chatNode || item.moduleType === FlowNodeTypeEnum.tools;
|
||||||
|
|
@ -29,7 +29,7 @@ const ResponseTags = ({
|
||||||
showDetail: boolean;
|
showDetail: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { isPc } = useSystemStore();
|
const { isPc } = useSystem();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [quoteModalData, setQuoteModalData] = useState<{
|
const [quoteModalData, setQuoteModalData] = useState<{
|
||||||
rawSearch: SearchDataResponseItemType[];
|
rawSearch: SearchDataResponseItemType[];
|
||||||
|
|
@ -1,32 +1,27 @@
|
||||||
import { VariableItemType } from '@fastgpt/global/core/app/type.d';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { UseFormReturn } from 'react-hook-form';
|
import { Controller, UseFormReturn } from 'react-hook-form';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { Box, Button, Card, Input, Textarea } from '@chakra-ui/react';
|
import { Box, Button, Card, FormControl, Input, Textarea } from '@chakra-ui/react';
|
||||||
import ChatAvatar from './ChatAvatar';
|
import ChatAvatar from './ChatAvatar';
|
||||||
import { MessageCardStyle } from '../constants';
|
import { MessageCardStyle } from '../constants';
|
||||||
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
||||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import { ChatBoxInputFormType } from '../type.d';
|
import { ChatBoxInputFormType } from '../type.d';
|
||||||
import { useRefresh } from '@fastgpt/web/hooks/useRefresh';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { ChatBoxContext } from '../Provider';
|
||||||
|
|
||||||
const VariableInput = ({
|
const VariableInput = ({
|
||||||
appAvatar,
|
|
||||||
variableList,
|
|
||||||
chatForm,
|
chatForm,
|
||||||
onSubmitVariables
|
chatStarted
|
||||||
}: {
|
}: {
|
||||||
appAvatar?: string;
|
chatStarted: boolean;
|
||||||
variableList: VariableItemType[];
|
|
||||||
onSubmitVariables: (e: Record<string, any>) => void;
|
|
||||||
chatForm: UseFormReturn<ChatBoxInputFormType>;
|
chatForm: UseFormReturn<ChatBoxInputFormType>;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { register, setValue, handleSubmit: handleSubmitChat, watch } = chatForm;
|
|
||||||
const variables = watch('variables');
|
const { appAvatar, variableList, variablesForm } = useContextSelector(ChatBoxContext, (v) => v);
|
||||||
const chatStarted = watch('chatStarted');
|
const { register, getValues, setValue, handleSubmit: handleSubmitChat, control } = variablesForm;
|
||||||
const { refresh } = useRefresh();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box py={3}>
|
<Box py={3}>
|
||||||
|
|
@ -61,7 +56,7 @@ const VariableInput = ({
|
||||||
{item.type === VariableInputEnum.input && (
|
{item.type === VariableInputEnum.input && (
|
||||||
<Input
|
<Input
|
||||||
bg={'myWhite.400'}
|
bg={'myWhite.400'}
|
||||||
{...register(`variables.${item.key}`, {
|
{...register(item.key, {
|
||||||
required: item.required
|
required: item.required
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
|
@ -69,7 +64,7 @@ const VariableInput = ({
|
||||||
{item.type === VariableInputEnum.textarea && (
|
{item.type === VariableInputEnum.textarea && (
|
||||||
<Textarea
|
<Textarea
|
||||||
bg={'myWhite.400'}
|
bg={'myWhite.400'}
|
||||||
{...register(`variables.${item.key}`, {
|
{...register(item.key, {
|
||||||
required: item.required
|
required: item.required
|
||||||
})}
|
})}
|
||||||
rows={5}
|
rows={5}
|
||||||
|
|
@ -77,19 +72,24 @@ const VariableInput = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{item.type === VariableInputEnum.select && (
|
{item.type === VariableInputEnum.select && (
|
||||||
<MySelect
|
<Controller
|
||||||
width={'100%'}
|
key={item.key}
|
||||||
list={(item.enums || []).map((item) => ({
|
control={control}
|
||||||
label: item.value,
|
name={item.key}
|
||||||
value: item.value
|
rules={{ required: item.required }}
|
||||||
}))}
|
render={({ field: { ref, value } }) => {
|
||||||
{...register(`variables.${item.key}`, {
|
return (
|
||||||
required: item.required
|
<MySelect
|
||||||
})}
|
ref={ref}
|
||||||
value={variables[item.key]}
|
width={'100%'}
|
||||||
onchange={(e) => {
|
list={(item.enums || []).map((item) => ({
|
||||||
refresh();
|
label: item.value,
|
||||||
setValue(`variables.${item.key}`, e);
|
value: item.value
|
||||||
|
}))}
|
||||||
|
value={value}
|
||||||
|
onchange={(e) => setValue(item.key, e)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -100,8 +100,8 @@ const VariableInput = ({
|
||||||
leftIcon={<MyIcon name={'core/chat/chatFill'} w={'16px'} />}
|
leftIcon={<MyIcon name={'core/chat/chatFill'} w={'16px'} />}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
maxW={'100px'}
|
maxW={'100px'}
|
||||||
onClick={handleSubmitChat((data) => {
|
onClick={handleSubmitChat(() => {
|
||||||
onSubmitVariables(data);
|
chatForm.setValue('chatStarted', true);
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{t('core.chat.Start Chat')}
|
{t('core.chat.Start Chat')}
|
||||||
|
|
@ -3,8 +3,12 @@ import React from 'react';
|
||||||
import { MessageCardStyle } from '../constants';
|
import { MessageCardStyle } from '../constants';
|
||||||
import Markdown from '@/components/Markdown';
|
import Markdown from '@/components/Markdown';
|
||||||
import ChatAvatar from './ChatAvatar';
|
import ChatAvatar from './ChatAvatar';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { ChatBoxContext } from '../Provider';
|
||||||
|
|
||||||
|
const WelcomeBox = ({ welcomeText }: { welcomeText: string }) => {
|
||||||
|
const appAvatar = useContextSelector(ChatBoxContext, (v) => v.appAvatar);
|
||||||
|
|
||||||
const WelcomeBox = ({ appAvatar, welcomeText }: { appAvatar?: string; welcomeText: string }) => {
|
|
||||||
return (
|
return (
|
||||||
<Box py={3}>
|
<Box py={3}>
|
||||||
{/* avatar */}
|
{/* avatar */}
|
||||||
|
|
@ -9,7 +9,6 @@ import React, {
|
||||||
useEffect
|
useEffect
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
import { throttle } from 'lodash';
|
|
||||||
import type {
|
import type {
|
||||||
AIChatItemValueItemType,
|
AIChatItemValueItemType,
|
||||||
ChatSiteItemType,
|
ChatSiteItemType,
|
||||||
|
|
@ -20,7 +19,6 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||||
import { Box, Flex, Checkbox } from '@chakra-ui/react';
|
import { Box, Flex, Checkbox } from '@chakra-ui/react';
|
||||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||||
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
||||||
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
|
|
@ -35,30 +33,25 @@ import type { AdminMarkType } from './components/SelectMarkCollection';
|
||||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||||
|
|
||||||
import { postQuestionGuide } from '@/web/core/ai/api';
|
import { postQuestionGuide } from '@/web/core/ai/api';
|
||||||
import type {
|
import type { ComponentRef, ChatBoxInputType, ChatBoxInputFormType } from './type.d';
|
||||||
generatingMessageProps,
|
import type { StartChatFnProps, generatingMessageProps } from '../type';
|
||||||
StartChatFnProps,
|
|
||||||
ComponentRef,
|
|
||||||
ChatBoxInputType,
|
|
||||||
ChatBoxInputFormType
|
|
||||||
} from './type.d';
|
|
||||||
import ChatInput from './Input/ChatInput';
|
import ChatInput from './Input/ChatInput';
|
||||||
import ChatBoxDivider from '../core/chat/Divider';
|
import ChatBoxDivider from '../../Divider';
|
||||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
import { formatChatValue2InputType } from './utils';
|
import { formatChatValue2InputType } from './utils';
|
||||||
import { textareaMinH } from './constants';
|
import { textareaMinH } from './constants';
|
||||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
import ChatProvider, { ChatBoxContext } from './Provider';
|
import ChatProvider, { ChatBoxContext, ChatProviderProps } from './Provider';
|
||||||
|
|
||||||
import ChatItem from './components/ChatItem';
|
import ChatItem from './components/ChatItem';
|
||||||
|
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import { useCreation } from 'ahooks';
|
|
||||||
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
|
||||||
import type { StreamResponseType } from '@/web/common/api/fetch';
|
import type { StreamResponseType } from '@/web/common/api/fetch';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
|
import { useThrottleFn } from 'ahooks';
|
||||||
|
|
||||||
const ResponseTags = dynamic(() => import('./components/ResponseTags'));
|
const ResponseTags = dynamic(() => import('./components/ResponseTags'));
|
||||||
const FeedbackModal = dynamic(() => import('./components/FeedbackModal'));
|
const FeedbackModal = dynamic(() => import('./components/FeedbackModal'));
|
||||||
|
|
@ -74,29 +67,26 @@ enum FeedbackTypeEnum {
|
||||||
hidden = 'hidden'
|
hidden = 'hidden'
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = OutLinkChatAuthProps & {
|
type Props = OutLinkChatAuthProps &
|
||||||
feedbackType?: `${FeedbackTypeEnum}`;
|
ChatProviderProps & {
|
||||||
showMarkIcon?: boolean; // admin mark dataset
|
feedbackType?: `${FeedbackTypeEnum}`;
|
||||||
showVoiceIcon?: boolean;
|
showMarkIcon?: boolean; // admin mark dataset
|
||||||
showEmptyIntro?: boolean;
|
showVoiceIcon?: boolean;
|
||||||
appAvatar?: string;
|
showEmptyIntro?: boolean;
|
||||||
userAvatar?: string;
|
userAvatar?: string;
|
||||||
chatConfig?: AppChatConfigType;
|
showFileSelector?: boolean;
|
||||||
showFileSelector?: boolean;
|
active?: boolean; // can use
|
||||||
active?: boolean; // can use
|
appId: string;
|
||||||
appId: string;
|
|
||||||
|
|
||||||
// not chat test params
|
// not chat test params
|
||||||
chatId?: string;
|
|
||||||
|
|
||||||
onUpdateVariable?: (e: Record<string, any>) => void;
|
onStartChat?: (e: StartChatFnProps) => Promise<
|
||||||
onStartChat?: (e: StartChatFnProps) => Promise<
|
StreamResponseType & {
|
||||||
StreamResponseType & {
|
isNewChat?: boolean;
|
||||||
isNewChat?: boolean;
|
}
|
||||||
}
|
>;
|
||||||
>;
|
onDelMessage?: (e: { contentId: string }) => void;
|
||||||
onDelMessage?: (e: { contentId: string }) => void;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The input is divided into sections
|
The input is divided into sections
|
||||||
|
|
@ -122,7 +112,6 @@ const ChatBox = (
|
||||||
outLinkUid,
|
outLinkUid,
|
||||||
teamId,
|
teamId,
|
||||||
teamToken,
|
teamToken,
|
||||||
onUpdateVariable,
|
|
||||||
onStartChat,
|
onStartChat,
|
||||||
onDelMessage
|
onDelMessage
|
||||||
}: Props,
|
}: Props,
|
||||||
|
|
@ -132,10 +121,12 @@ const ChatBox = (
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { isPc, setLoading, feConfigs } = useSystemStore();
|
const { setLoading, feConfigs } = useSystemStore();
|
||||||
|
const { isPc } = useSystem();
|
||||||
const TextareaDom = useRef<HTMLTextAreaElement>(null);
|
const TextareaDom = useRef<HTMLTextAreaElement>(null);
|
||||||
const chatController = useRef(new AbortController());
|
const chatController = useRef(new AbortController());
|
||||||
const questionGuideController = useRef(new AbortController());
|
const questionGuideController = useRef(new AbortController());
|
||||||
|
const pluginController = useRef(new AbortController());
|
||||||
const isNewChatReplace = useRef(false);
|
const isNewChatReplace = useRef(false);
|
||||||
|
|
||||||
const [feedbackId, setFeedbackId] = useState<string>();
|
const [feedbackId, setFeedbackId] = useState<string>();
|
||||||
|
|
@ -156,6 +147,7 @@ const ChatBox = (
|
||||||
splitText2Audio,
|
splitText2Audio,
|
||||||
chatHistories,
|
chatHistories,
|
||||||
setChatHistories,
|
setChatHistories,
|
||||||
|
variablesForm,
|
||||||
isChatting
|
isChatting
|
||||||
} = useContextSelector(ChatBoxContext, (v) => v);
|
} = useContextSelector(ChatBoxContext, (v) => v);
|
||||||
|
|
||||||
|
|
@ -164,41 +156,44 @@ const ChatBox = (
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
input: '',
|
input: '',
|
||||||
files: [],
|
files: [],
|
||||||
variables: {},
|
|
||||||
chatStarted: false
|
chatStarted: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const { setValue, watch, handleSubmit } = chatForm;
|
const { setValue, watch } = chatForm;
|
||||||
const chatStarted = watch('chatStarted');
|
const chatStartedWatch = watch('chatStarted');
|
||||||
|
const chatStarted = chatStartedWatch || chatHistories.length > 0 || variableList.length === 0;
|
||||||
/* variable */
|
|
||||||
const filterVariableNodes = useCreation(
|
|
||||||
() => variableList.filter((item) => item.type !== VariableInputEnum.custom),
|
|
||||||
[variableList]
|
|
||||||
);
|
|
||||||
|
|
||||||
// 滚动到底部
|
// 滚动到底部
|
||||||
const scrollToBottom = (behavior: 'smooth' | 'auto' = 'smooth') => {
|
const scrollToBottom = useCallback((behavior: 'smooth' | 'auto' = 'smooth', delay = 0) => {
|
||||||
if (!ChatBoxRef.current) return;
|
setTimeout(() => {
|
||||||
ChatBoxRef.current.scrollTo({
|
if (!ChatBoxRef.current) {
|
||||||
top: ChatBoxRef.current.scrollHeight,
|
setTimeout(() => {
|
||||||
behavior
|
scrollToBottom(behavior);
|
||||||
});
|
}, 500);
|
||||||
};
|
} else {
|
||||||
|
ChatBoxRef.current.scrollTo({
|
||||||
|
top: ChatBoxRef.current.scrollHeight,
|
||||||
|
behavior
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 聊天信息生成中……获取当前滚动条位置,判断是否需要滚动到底部
|
// 聊天信息生成中……获取当前滚动条位置,判断是否需要滚动到底部
|
||||||
const generatingScroll = useCallback(
|
const { run: generatingScroll } = useThrottleFn(
|
||||||
throttle(() => {
|
() => {
|
||||||
if (!ChatBoxRef.current) return;
|
if (!ChatBoxRef.current) return;
|
||||||
const isBottom =
|
const isBottom =
|
||||||
ChatBoxRef.current.scrollTop + ChatBoxRef.current.clientHeight + 150 >=
|
ChatBoxRef.current.scrollTop + ChatBoxRef.current.clientHeight + 150 >=
|
||||||
ChatBoxRef.current.scrollHeight;
|
ChatBoxRef.current.scrollHeight;
|
||||||
|
|
||||||
isBottom && scrollToBottom('auto');
|
isBottom && scrollToBottom('auto');
|
||||||
}, 100),
|
},
|
||||||
[]
|
{
|
||||||
|
wait: 100
|
||||||
|
}
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
const generatingMessage = useCallback(
|
const generatingMessage = useCallback(
|
||||||
({
|
({
|
||||||
event,
|
event,
|
||||||
|
|
@ -291,7 +286,7 @@ const ChatBox = (
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
} else if (event === SseResponseEventEnum.updateVariables && variables) {
|
} else if (event === SseResponseEventEnum.updateVariables && variables) {
|
||||||
setValue('variables', variables);
|
variablesForm.reset(variables);
|
||||||
}
|
}
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
|
|
@ -299,7 +294,7 @@ const ChatBox = (
|
||||||
);
|
);
|
||||||
generatingScroll();
|
generatingScroll();
|
||||||
},
|
},
|
||||||
[generatingScroll, setChatHistories, setValue, splitText2Audio]
|
[generatingScroll, setChatHistories, splitText2Audio, variablesForm]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 重置输入内容
|
// 重置输入内容
|
||||||
|
|
@ -347,13 +342,14 @@ const ChatBox = (
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
},
|
},
|
||||||
[questionGuide, shareId, outLinkUid, teamId, teamToken]
|
[questionGuide, shareId, outLinkUid, teamId, teamToken, scrollToBottom]
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Abort chat completions, questionGuide */
|
/* Abort chat completions, questionGuide */
|
||||||
const abortRequest = useCallback(() => {
|
const abortRequest = useCallback(() => {
|
||||||
chatController.current?.abort('stop');
|
chatController.current?.abort('stop');
|
||||||
questionGuideController.current?.abort('stop');
|
questionGuideController.current?.abort('stop');
|
||||||
|
pluginController.current?.abort('stop');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -369,8 +365,8 @@ const ChatBox = (
|
||||||
autoTTSResponse?: boolean;
|
autoTTSResponse?: boolean;
|
||||||
history?: ChatSiteItemType[];
|
history?: ChatSiteItemType[];
|
||||||
}) => {
|
}) => {
|
||||||
handleSubmit(
|
variablesForm.handleSubmit(
|
||||||
async ({ variables }) => {
|
async (variables) => {
|
||||||
if (!onStartChat) return;
|
if (!onStartChat) return;
|
||||||
if (isChatting) {
|
if (isChatting) {
|
||||||
toast({
|
toast({
|
||||||
|
|
@ -455,9 +451,7 @@ const ChatBox = (
|
||||||
// 清空输入内容
|
// 清空输入内容
|
||||||
resetInputVal({});
|
resetInputVal({});
|
||||||
setQuestionGuide([]);
|
setQuestionGuide([]);
|
||||||
setTimeout(() => {
|
scrollToBottom('smooth', 100);
|
||||||
scrollToBottom();
|
|
||||||
}, 100);
|
|
||||||
try {
|
try {
|
||||||
// create abort obj
|
// create abort obj
|
||||||
const abortSignal = new AbortController();
|
const abortSignal = new AbortController();
|
||||||
|
|
@ -470,8 +464,8 @@ const ChatBox = (
|
||||||
responseText,
|
responseText,
|
||||||
isNewChat = false
|
isNewChat = false
|
||||||
} = await onStartChat({
|
} = await onStartChat({
|
||||||
chatList: newChatList,
|
messages: messages.slice(0, -1),
|
||||||
messages,
|
responseChatItemId: responseChatId,
|
||||||
controller: abortSignal,
|
controller: abortSignal,
|
||||||
generatingMessage: (e) => generatingMessage({ ...e, autoTTSResponse }),
|
generatingMessage: (e) => generatingMessage({ ...e, autoTTSResponse }),
|
||||||
variables: requestVariables
|
variables: requestVariables
|
||||||
|
|
@ -542,7 +536,7 @@ const ChatBox = (
|
||||||
autoTTSResponse && finishSegmentedAudio();
|
autoTTSResponse && finishSegmentedAudio();
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
console.log(err?.variables);
|
console.log(err);
|
||||||
}
|
}
|
||||||
)();
|
)();
|
||||||
},
|
},
|
||||||
|
|
@ -553,18 +547,19 @@ const ChatBox = (
|
||||||
finishSegmentedAudio,
|
finishSegmentedAudio,
|
||||||
generatingMessage,
|
generatingMessage,
|
||||||
generatingScroll,
|
generatingScroll,
|
||||||
handleSubmit,
|
|
||||||
isChatting,
|
isChatting,
|
||||||
isPc,
|
isPc,
|
||||||
onStartChat,
|
onStartChat,
|
||||||
resetInputVal,
|
resetInputVal,
|
||||||
|
scrollToBottom,
|
||||||
setAudioPlayingChatId,
|
setAudioPlayingChatId,
|
||||||
setChatHistories,
|
setChatHistories,
|
||||||
splitText2Audio,
|
splitText2Audio,
|
||||||
startSegmentedAudio,
|
startSegmentedAudio,
|
||||||
t,
|
t,
|
||||||
toast,
|
toast,
|
||||||
variableList
|
variableList,
|
||||||
|
variablesForm
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -720,7 +715,7 @@ const ChatBox = (
|
||||||
},
|
},
|
||||||
[appId, chatId, feedbackType, setChatHistories, teamId, teamToken]
|
[appId, chatId, feedbackType, setChatHistories, teamId, teamToken]
|
||||||
);
|
);
|
||||||
const onADdUserDislike = useCallback(
|
const onAddUserDislike = useCallback(
|
||||||
(chat: ChatSiteItemType) => {
|
(chat: ChatSiteItemType) => {
|
||||||
if (
|
if (
|
||||||
feedbackType !== FeedbackTypeEnum.user ||
|
feedbackType !== FeedbackTypeEnum.user ||
|
||||||
|
|
@ -797,30 +792,18 @@ const ChatBox = (
|
||||||
[appId, chatId, setChatHistories]
|
[appId, chatId, setChatHistories]
|
||||||
);
|
);
|
||||||
|
|
||||||
const resetVariables = useCallback(
|
|
||||||
(e: Record<string, any> = {}) => {
|
|
||||||
const value: Record<string, any> = { ...e };
|
|
||||||
filterVariableNodes?.forEach((item) => {
|
|
||||||
value[item.key] = e[item.key] || '';
|
|
||||||
});
|
|
||||||
|
|
||||||
setValue('variables', value);
|
|
||||||
},
|
|
||||||
[filterVariableNodes, setValue]
|
|
||||||
);
|
|
||||||
|
|
||||||
const showEmpty = useMemo(
|
const showEmpty = useMemo(
|
||||||
() =>
|
() =>
|
||||||
feConfigs?.show_emptyChat &&
|
feConfigs?.show_emptyChat &&
|
||||||
showEmptyIntro &&
|
showEmptyIntro &&
|
||||||
chatHistories.length === 0 &&
|
chatHistories.length === 0 &&
|
||||||
!filterVariableNodes?.length &&
|
!variableList?.length &&
|
||||||
!welcomeText,
|
!welcomeText,
|
||||||
[
|
[
|
||||||
chatHistories.length,
|
chatHistories.length,
|
||||||
feConfigs?.show_emptyChat,
|
feConfigs?.show_emptyChat,
|
||||||
showEmptyIntro,
|
showEmptyIntro,
|
||||||
filterVariableNodes?.length,
|
variableList?.length,
|
||||||
welcomeText
|
welcomeText
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
@ -878,12 +861,10 @@ const ChatBox = (
|
||||||
|
|
||||||
// output data
|
// output data
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
getChatHistories: () => chatHistories,
|
restartChat() {
|
||||||
resetVariables,
|
|
||||||
resetHistory(e) {
|
|
||||||
abortRequest();
|
abortRequest();
|
||||||
setValue('chatStarted', e.length > 0);
|
setValue('chatStarted', false);
|
||||||
setChatHistories(e);
|
scrollToBottom('smooth', 500);
|
||||||
},
|
},
|
||||||
scrollToBottom,
|
scrollToBottom,
|
||||||
sendPrompt: (question: string) => {
|
sendPrompt: (question: string) => {
|
||||||
|
|
@ -900,41 +881,33 @@ const ChatBox = (
|
||||||
<Box ref={ChatBoxRef} flex={'1 0 0'} h={0} w={'100%'} overflow={'overlay'} px={[4, 0]} pb={3}>
|
<Box ref={ChatBoxRef} flex={'1 0 0'} h={0} w={'100%'} overflow={'overlay'} px={[4, 0]} pb={3}>
|
||||||
<Box id="chat-container" maxW={['100%', '92%']} h={'100%'} mx={'auto'}>
|
<Box id="chat-container" maxW={['100%', '92%']} h={'100%'} mx={'auto'}>
|
||||||
{showEmpty && <Empty />}
|
{showEmpty && <Empty />}
|
||||||
{!!welcomeText && <WelcomeBox appAvatar={appAvatar} welcomeText={welcomeText} />}
|
{!!welcomeText && <WelcomeBox welcomeText={welcomeText} />}
|
||||||
{/* variable input */}
|
{/* variable input */}
|
||||||
{!!filterVariableNodes?.length && (
|
{!!variableList?.length && (
|
||||||
<VariableInput
|
<VariableInput chatStarted={chatStarted} chatForm={chatForm} />
|
||||||
appAvatar={appAvatar}
|
|
||||||
variableList={filterVariableNodes}
|
|
||||||
chatForm={chatForm}
|
|
||||||
onSubmitVariables={(data) => {
|
|
||||||
setValue('chatStarted', true);
|
|
||||||
onUpdateVariable?.(data);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{/* chat history */}
|
{/* chat history */}
|
||||||
<Box id={'history'}>
|
<Box id={'history'}>
|
||||||
{chatHistories.map((item, index) => (
|
{chatHistories.map((item, index) => (
|
||||||
<Box key={item.dataId} py={5}>
|
<Box key={item.dataId} py={5}>
|
||||||
{item.obj === 'Human' && (
|
{item.obj === ChatRoleEnum.Human && (
|
||||||
<ChatItem
|
<ChatItem
|
||||||
type={item.obj}
|
type={item.obj}
|
||||||
avatar={item.obj === 'Human' ? userAvatar : appAvatar}
|
avatar={userAvatar}
|
||||||
chat={item}
|
chat={item}
|
||||||
onRetry={retryInput(item.dataId)}
|
onRetry={retryInput(item.dataId)}
|
||||||
onDelete={delOneMessage(item.dataId)}
|
onDelete={delOneMessage(item.dataId)}
|
||||||
isLastChild={index === chatHistories.length - 1}
|
isLastChild={index === chatHistories.length - 1}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{item.obj === 'AI' && (
|
{item.obj === ChatRoleEnum.AI && (
|
||||||
<>
|
<>
|
||||||
<ChatItem
|
<ChatItem
|
||||||
type={item.obj}
|
type={item.obj}
|
||||||
avatar={appAvatar}
|
avatar={appAvatar}
|
||||||
chat={item}
|
chat={item}
|
||||||
isLastChild={index === chatHistories.length - 1}
|
isLastChild={index === chatHistories.length - 1}
|
||||||
{...(item.obj === 'AI' && {
|
{...(item.obj === ChatRoleEnum.AI && {
|
||||||
showVoiceIcon,
|
showVoiceIcon,
|
||||||
shareId,
|
shareId,
|
||||||
outLinkUid,
|
outLinkUid,
|
||||||
|
|
@ -948,7 +921,7 @@ const ChatBox = (
|
||||||
),
|
),
|
||||||
onAddUserLike: onAddUserLike(item),
|
onAddUserLike: onAddUserLike(item),
|
||||||
onCloseUserLike: onCloseUserLike(item),
|
onCloseUserLike: onCloseUserLike(item),
|
||||||
onAddUserDislike: onADdUserDislike(item),
|
onAddUserDislike: onAddUserDislike(item),
|
||||||
onReadUserDislike: onReadUserDislike(item)
|
onReadUserDislike: onReadUserDislike(item)
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|
@ -997,7 +970,7 @@ const ChatBox = (
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
{/* message input */}
|
{/* message input */}
|
||||||
{onStartChat && (chatStarted || filterVariableNodes.length === 0) && active && appId && (
|
{onStartChat && chatStarted && active && appId && (
|
||||||
<ChatInput
|
<ChatInput
|
||||||
onSendMessage={sendPrompt}
|
onSendMessage={sendPrompt}
|
||||||
onStop={() => chatController.current?.abort('stop')}
|
onStop={() => chatController.current?.abort('stop')}
|
||||||
|
|
@ -7,15 +7,6 @@ import {
|
||||||
} from '@fastgpt/global/core/chat/type';
|
} from '@fastgpt/global/core/chat/type';
|
||||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
|
|
||||||
export type generatingMessageProps = {
|
|
||||||
event: SseResponseEventEnum;
|
|
||||||
text?: string;
|
|
||||||
name?: string;
|
|
||||||
status?: 'running' | 'finish';
|
|
||||||
tool?: ToolModuleResponseItemType;
|
|
||||||
variables?: Record<string, any>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UserInputFileItemType = {
|
export type UserInputFileItemType = {
|
||||||
id: string;
|
id: string;
|
||||||
rawFile?: File;
|
rawFile?: File;
|
||||||
|
|
@ -28,7 +19,6 @@ export type UserInputFileItemType = {
|
||||||
export type ChatBoxInputFormType = {
|
export type ChatBoxInputFormType = {
|
||||||
input: string;
|
input: string;
|
||||||
files: UserInputFileItemType[];
|
files: UserInputFileItemType[];
|
||||||
variables: Record<string, any>;
|
|
||||||
chatStarted: boolean;
|
chatStarted: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -37,18 +27,8 @@ export type ChatBoxInputType = {
|
||||||
files?: UserInputFileItemType[];
|
files?: UserInputFileItemType[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StartChatFnProps = {
|
|
||||||
chatList: ChatSiteItemType[];
|
|
||||||
messages: ChatCompletionMessageParam[];
|
|
||||||
controller: AbortController;
|
|
||||||
variables: Record<string, any>;
|
|
||||||
generatingMessage: (e: generatingMessageProps) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ComponentRef = {
|
export type ComponentRef = {
|
||||||
getChatHistories: () => ChatSiteItemType[];
|
restartChat: () => void;
|
||||||
resetVariables: (data?: Record<string, any>) => void;
|
|
||||||
resetHistory: (history: ChatSiteItemType[]) => void;
|
|
||||||
scrollToBottom: (behavior?: 'smooth' | 'auto') => void;
|
scrollToBottom: (behavior?: 'smooth' | 'auto') => void;
|
||||||
sendPrompt: (question: string) => void;
|
sendPrompt: (question: string) => void;
|
||||||
};
|
};
|
||||||
|
|
@ -2,7 +2,11 @@ import { ChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||||
import { ChatBoxInputType, UserInputFileItemType } from './type';
|
import { ChatBoxInputType, UserInputFileItemType } from './type';
|
||||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||||
|
|
||||||
export const formatChatValue2InputType = (value: ChatItemValueItemType[]): ChatBoxInputType => {
|
export const formatChatValue2InputType = (value?: ChatItemValueItemType[]): ChatBoxInputType => {
|
||||||
|
if (!value) {
|
||||||
|
return { text: '', files: [] };
|
||||||
|
}
|
||||||
|
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
console.error('value is error', value);
|
console.error('value is error', value);
|
||||||
return { text: '', files: [] };
|
return { text: '', files: [] };
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Controller } from 'react-hook-form';
|
||||||
|
import RenderPluginInput from './renderPluginInput';
|
||||||
|
import { Button, Flex } from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { PluginRunContext } from '../context';
|
||||||
|
|
||||||
|
const RenderInput = () => {
|
||||||
|
const { pluginInputs, variablesForm, histories, onStartChat, onNewChat, onSubmit, isChatting } =
|
||||||
|
useContextSelector(PluginRunContext, (v) => v);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { errors }
|
||||||
|
} = variablesForm;
|
||||||
|
const isDisabledInput = histories.length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{pluginInputs.map((input) => {
|
||||||
|
return (
|
||||||
|
<Controller
|
||||||
|
key={input.key}
|
||||||
|
control={control}
|
||||||
|
name={input.key}
|
||||||
|
rules={{ required: input.required }}
|
||||||
|
render={({ field: { onChange, value } }) => {
|
||||||
|
return (
|
||||||
|
<RenderPluginInput
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
label={input.label}
|
||||||
|
description={input.description}
|
||||||
|
isDisabled={isDisabledInput}
|
||||||
|
valueType={input.valueType}
|
||||||
|
placeholder={input.placeholder}
|
||||||
|
required={input.required}
|
||||||
|
min={input.min}
|
||||||
|
max={input.max}
|
||||||
|
isInvalid={errors && Object.keys(errors).includes(input.key)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{onStartChat && onNewChat && (
|
||||||
|
<Flex justifyContent={'end'} mt={8}>
|
||||||
|
<Button
|
||||||
|
isLoading={isChatting}
|
||||||
|
onClick={() => {
|
||||||
|
if (histories.length > 0) {
|
||||||
|
return onNewChat();
|
||||||
|
}
|
||||||
|
handleSubmit(onSubmit)();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{histories.length > 0 ? t('common.Restart') : t('common.Run')}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RenderInput;
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { PluginRunContext } from '../context';
|
||||||
|
import Markdown from '@/components/Markdown';
|
||||||
|
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
|
import AIResponseBox from '../../../components/AIResponseBox';
|
||||||
|
|
||||||
|
const RenderOutput = () => {
|
||||||
|
const { histories, isChatting } = useContextSelector(PluginRunContext, (v) => v);
|
||||||
|
|
||||||
|
const pluginOutputs = useMemo(() => {
|
||||||
|
const pluginOutputs = histories?.[1]?.responseData?.find(
|
||||||
|
(item) => item.moduleType === FlowNodeTypeEnum.pluginOutput
|
||||||
|
)?.pluginOutput;
|
||||||
|
|
||||||
|
return JSON.stringify(pluginOutputs, null, 2);
|
||||||
|
}, [histories]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box border={'base'} rounded={'md'} bg={'myGray.25'}>
|
||||||
|
<Box p={4} color={'myGray.900'}>
|
||||||
|
<Box color={'myGray.900'} fontWeight={'bold'}>
|
||||||
|
流输出
|
||||||
|
</Box>
|
||||||
|
{histories.length > 0 && histories[1]?.value.length > 0 ? (
|
||||||
|
<Box mt={2}>
|
||||||
|
{histories[1].value.map((value, i) => {
|
||||||
|
const key = `${histories[1].dataId}-ai-${i}`;
|
||||||
|
return (
|
||||||
|
<AIResponseBox
|
||||||
|
key={key}
|
||||||
|
value={value}
|
||||||
|
index={i}
|
||||||
|
chat={histories[1]}
|
||||||
|
isLastChild={true}
|
||||||
|
isChatting={isChatting}
|
||||||
|
questionGuides={[]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box border={'base'} mt={4} rounded={'md'} bg={'myGray.25'}>
|
||||||
|
<Box p={4} color={'myGray.900'} fontWeight={'bold'}>
|
||||||
|
<Box>插件输出</Box>
|
||||||
|
{histories.length > 0 && histories[1].responseData ? (
|
||||||
|
<Markdown source={`~~~json\n${pluginOutputs}`} />
|
||||||
|
) : null}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RenderOutput;
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { ResponseBox } from '../../../components/WholeResponseModal';
|
||||||
|
import React from 'react';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { PluginRunContext } from '../context';
|
||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const RenderResponseDetail = () => {
|
||||||
|
const { histories, isChatting } = useContextSelector(PluginRunContext, (v) => v);
|
||||||
|
|
||||||
|
const responseData = histories?.[1]?.responseData || [];
|
||||||
|
|
||||||
|
return isChatting ? (
|
||||||
|
<>{'进行中'}</>
|
||||||
|
) : (
|
||||||
|
<Box flex={'1 0 0'} h={'100%'} overflow={'auto'}>
|
||||||
|
<ResponseBox response={responseData} showDetail={true} />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RenderResponseDetail;
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
NumberDecrementStepper,
|
||||||
|
NumberIncrementStepper,
|
||||||
|
NumberInput,
|
||||||
|
NumberInputField,
|
||||||
|
NumberInputStepper,
|
||||||
|
Switch,
|
||||||
|
Textarea
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||||
|
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
|
const JsonEditor = dynamic(() => import('@fastgpt/web/components/common/Textarea/JsonEditor'));
|
||||||
|
|
||||||
|
const RenderPluginInput = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
isDisabled,
|
||||||
|
valueType,
|
||||||
|
placeholder,
|
||||||
|
required,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
isInvalid
|
||||||
|
}: {
|
||||||
|
value: any;
|
||||||
|
onChange: () => void;
|
||||||
|
label: string;
|
||||||
|
description?: string;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
valueType: WorkflowIOValueTypeEnum | undefined;
|
||||||
|
placeholder?: string;
|
||||||
|
required?: boolean;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
isInvalid: boolean;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const render = (() => {
|
||||||
|
if (valueType === WorkflowIOValueTypeEnum.string) {
|
||||||
|
return (
|
||||||
|
<Textarea
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
placeholder={t(placeholder)}
|
||||||
|
bg={'myGray.50'}
|
||||||
|
isInvalid={isInvalid}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (valueType === WorkflowIOValueTypeEnum.number) {
|
||||||
|
return (
|
||||||
|
<NumberInput
|
||||||
|
step={1}
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
bg={'myGray.50'}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
isInvalid={isInvalid}
|
||||||
|
>
|
||||||
|
<NumberInputField value={value} onChange={onChange} />
|
||||||
|
<NumberInputStepper>
|
||||||
|
<NumberIncrementStepper />
|
||||||
|
<NumberDecrementStepper />
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (valueType === WorkflowIOValueTypeEnum.boolean) {
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
isChecked={value}
|
||||||
|
onChange={onChange}
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
isInvalid={isInvalid}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<JsonEditor
|
||||||
|
bg={'myGray.50'}
|
||||||
|
placeholder={t(placeholder || '')}
|
||||||
|
resize
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
isInvalid={isInvalid}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
return !!render ? (
|
||||||
|
<Box _notLast={{ mb: 4 }} px={1}>
|
||||||
|
<Flex alignItems={'center'} mb={1}>
|
||||||
|
<Box position={'relative'}>
|
||||||
|
{required && (
|
||||||
|
<Box position={'absolute'} left={-2} top={'-1px'} color={'red.600'}>
|
||||||
|
*
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{label}
|
||||||
|
</Box>
|
||||||
|
{description && <QuestionTip ml={2} label={description} />}
|
||||||
|
</Flex>
|
||||||
|
{render}
|
||||||
|
</Box>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RenderPluginInput;
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export enum PluginRunBoxTabEnum {
|
||||||
|
input = 'input',
|
||||||
|
output = 'output',
|
||||||
|
detail = 'detail'
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,234 @@
|
||||||
|
import React, { ReactNode, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
|
import { createContext } from 'use-context-selector';
|
||||||
|
import { PluginRunBoxProps } from './type';
|
||||||
|
import { AIChatItemValueItemType, ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||||
|
import { FieldValues } from 'react-hook-form';
|
||||||
|
import { PluginRunBoxTabEnum } from './constants';
|
||||||
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
|
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||||
|
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
|
import { generatingMessageProps } from '../type';
|
||||||
|
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
|
import { getPluginRunContent } from '@fastgpt/global/core/app/plugin/utils';
|
||||||
|
|
||||||
|
type PluginRunContextType = PluginRunBoxProps & {
|
||||||
|
isChatting: boolean;
|
||||||
|
onSubmit: (e: FieldValues) => Promise<any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PluginRunContext = createContext<PluginRunContextType>({
|
||||||
|
pluginInputs: [],
|
||||||
|
//@ts-ignore
|
||||||
|
variablesForm: undefined,
|
||||||
|
histories: [],
|
||||||
|
setHistories: function (value: React.SetStateAction<ChatSiteItemType[]>): void {
|
||||||
|
throw new Error('Function not implemented.');
|
||||||
|
},
|
||||||
|
appId: '',
|
||||||
|
tab: PluginRunBoxTabEnum.input,
|
||||||
|
setTab: function (value: React.SetStateAction<PluginRunBoxTabEnum>): void {
|
||||||
|
throw new Error('Function not implemented.');
|
||||||
|
},
|
||||||
|
isChatting: false,
|
||||||
|
onSubmit: function (e: FieldValues): Promise<any> {
|
||||||
|
throw new Error('Function not implemented.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const PluginRunContextProvider = ({
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: PluginRunBoxProps & { children: ReactNode }) => {
|
||||||
|
const { pluginInputs, onStartChat, setHistories, histories, setTab } = props;
|
||||||
|
|
||||||
|
const { toast } = useToast();
|
||||||
|
const chatController = useRef(new AbortController());
|
||||||
|
|
||||||
|
/* Abort chat completions, questionGuide */
|
||||||
|
const abortRequest = useCallback(() => {
|
||||||
|
chatController.current?.abort('stop');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const generatingMessage = useCallback(
|
||||||
|
({ event, text = '', status, name, tool, variables }: generatingMessageProps) => {
|
||||||
|
setHistories((state) =>
|
||||||
|
state.map((item, index) => {
|
||||||
|
if (index !== state.length - 1 || item.obj !== ChatRoleEnum.AI) return item;
|
||||||
|
|
||||||
|
const lastValue: AIChatItemValueItemType = JSON.parse(
|
||||||
|
JSON.stringify(item.value[item.value.length - 1])
|
||||||
|
);
|
||||||
|
|
||||||
|
if (event === SseResponseEventEnum.flowNodeStatus && status) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
status,
|
||||||
|
moduleName: name
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
(event === SseResponseEventEnum.answer || event === SseResponseEventEnum.fastAnswer) &&
|
||||||
|
text
|
||||||
|
) {
|
||||||
|
if (!lastValue || !lastValue.text) {
|
||||||
|
const newValue: AIChatItemValueItemType = {
|
||||||
|
type: ChatItemValueTypeEnum.text,
|
||||||
|
text: {
|
||||||
|
content: text
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
value: item.value.concat(newValue)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
lastValue.text.content += text;
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
value: item.value.slice(0, -1).concat(lastValue)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (event === SseResponseEventEnum.toolCall && tool) {
|
||||||
|
const val: AIChatItemValueItemType = {
|
||||||
|
type: ChatItemValueTypeEnum.tool,
|
||||||
|
tools: [tool]
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
value: item.value.concat(val)
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
event === SseResponseEventEnum.toolParams &&
|
||||||
|
tool &&
|
||||||
|
lastValue.type === ChatItemValueTypeEnum.tool &&
|
||||||
|
lastValue?.tools
|
||||||
|
) {
|
||||||
|
lastValue.tools = lastValue.tools.map((item) => {
|
||||||
|
if (item.id === tool.id) {
|
||||||
|
item.params += tool.params;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
value: item.value.slice(0, -1).concat(lastValue)
|
||||||
|
};
|
||||||
|
} else if (event === SseResponseEventEnum.toolResponse && tool) {
|
||||||
|
// replace tool response
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
value: item.value.map((val) => {
|
||||||
|
if (val.type === ChatItemValueTypeEnum.tool && val.tools) {
|
||||||
|
const tools = val.tools.map((item) =>
|
||||||
|
item.id === tool.id ? { ...item, response: tool.response } : item
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...val,
|
||||||
|
tools
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[setHistories]
|
||||||
|
);
|
||||||
|
|
||||||
|
const isChatting = useMemo(
|
||||||
|
() => histories[histories.length - 1] && histories[histories.length - 1]?.status !== 'finish',
|
||||||
|
[histories]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { runAsync: onSubmit } = useRequest2(async (e: FieldValues) => {
|
||||||
|
if (!onStartChat) return;
|
||||||
|
if (isChatting) {
|
||||||
|
toast({
|
||||||
|
title: '正在聊天中...请等待结束',
|
||||||
|
status: 'warning'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTab(PluginRunBoxTabEnum.output);
|
||||||
|
|
||||||
|
// reset controller
|
||||||
|
abortRequest();
|
||||||
|
const abortSignal = new AbortController();
|
||||||
|
chatController.current = abortSignal;
|
||||||
|
|
||||||
|
setHistories([
|
||||||
|
{
|
||||||
|
dataId: getNanoid(24),
|
||||||
|
obj: ChatRoleEnum.Human,
|
||||||
|
status: 'finish',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: ChatItemValueTypeEnum.text,
|
||||||
|
text: {
|
||||||
|
content: getPluginRunContent({
|
||||||
|
pluginInputs
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataId: getNanoid(24),
|
||||||
|
obj: ChatRoleEnum.AI,
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: ChatItemValueTypeEnum.text,
|
||||||
|
text: {
|
||||||
|
content: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
status: 'loading'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { responseData } = await onStartChat({
|
||||||
|
messages: [],
|
||||||
|
controller: chatController.current,
|
||||||
|
generatingMessage,
|
||||||
|
variables: e
|
||||||
|
});
|
||||||
|
|
||||||
|
setHistories((state) =>
|
||||||
|
state.map((item, index) => {
|
||||||
|
if (index !== state.length - 1) return item;
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
status: 'finish',
|
||||||
|
responseData
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (err: any) {
|
||||||
|
toast({ title: err.message, status: 'error' });
|
||||||
|
setHistories((state) =>
|
||||||
|
state.map((item, index) => {
|
||||||
|
if (index !== state.length - 1) return item;
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
status: 'finish'
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const contextValue: PluginRunContextType = {
|
||||||
|
...props,
|
||||||
|
isChatting,
|
||||||
|
onSubmit
|
||||||
|
};
|
||||||
|
return <PluginRunContext.Provider value={contextValue}>{children}</PluginRunContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PluginRunContextProvider;
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { PluginRunBoxTabEnum } from './constants';
|
||||||
|
import { PluginRunBoxProps } from './type';
|
||||||
|
import RenderInput from './components/RenderInput';
|
||||||
|
import PluginRunContextProvider, { PluginRunContext } from './context';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import RenderOutput from './components/RenderOutput';
|
||||||
|
import RenderResponseDetail from './components/RenderResponseDetail';
|
||||||
|
|
||||||
|
const PluginRunBox = () => {
|
||||||
|
const { tab } = useContextSelector(PluginRunContext, (v) => v);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{tab === PluginRunBoxTabEnum.input && <RenderInput />}
|
||||||
|
{tab === PluginRunBoxTabEnum.output && <RenderOutput />}
|
||||||
|
{tab === PluginRunBoxTabEnum.detail && <RenderResponseDetail />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Render = (props: PluginRunBoxProps) => {
|
||||||
|
return (
|
||||||
|
<PluginRunContextProvider {...props}>
|
||||||
|
<PluginRunBox />
|
||||||
|
</PluginRunContextProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Render;
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||||
|
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||||
|
import { FieldValues, UseFormReturn } from 'react-hook-form';
|
||||||
|
import { PluginRunBoxTabEnum } from './constants';
|
||||||
|
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||||
|
import React from 'react';
|
||||||
|
import { onStartChatType } from '../type';
|
||||||
|
|
||||||
|
export type PluginRunBoxProps = OutLinkChatAuthProps & {
|
||||||
|
pluginInputs: FlowNodeInputItemType[];
|
||||||
|
variablesForm: UseFormReturn<FieldValues, any>;
|
||||||
|
histories: ChatSiteItemType[]; // chatHistories[1] is the response
|
||||||
|
setHistories: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
||||||
|
|
||||||
|
onStartChat?: onStartChatType;
|
||||||
|
onNewChat?: () => void;
|
||||||
|
|
||||||
|
appId: string;
|
||||||
|
chatId?: string;
|
||||||
|
tab: PluginRunBoxTabEnum;
|
||||||
|
setTab: React.Dispatch<React.SetStateAction<PluginRunBoxTabEnum>>;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { StreamResponseType } from '@/web/common/api/fetch';
|
||||||
|
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
|
||||||
|
import { ChatSiteItemType, ToolModuleResponseItemType } from '@fastgpt/global/core/chat/type';
|
||||||
|
|
||||||
|
export type generatingMessageProps = {
|
||||||
|
event: SseResponseEventEnum;
|
||||||
|
text?: string;
|
||||||
|
name?: string;
|
||||||
|
status?: 'running' | 'finish';
|
||||||
|
tool?: ToolModuleResponseItemType;
|
||||||
|
variables?: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type StartChatFnProps = {
|
||||||
|
messages: ChatCompletionMessageParam[];
|
||||||
|
responseChatItemId?: string;
|
||||||
|
controller: AbortController;
|
||||||
|
variables: Record<string, any>;
|
||||||
|
generatingMessage: (e: generatingMessageProps) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type onStartChatType = (e: StartChatFnProps) => Promise<
|
||||||
|
StreamResponseType & {
|
||||||
|
isNewChat?: boolean;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||||
|
import { useCallback, useRef, useState } from 'react';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { PluginRunBoxTabEnum } from './PluginRunBox/constants';
|
||||||
|
import { ComponentRef as ChatComponentRef } from './ChatBox/type';
|
||||||
|
|
||||||
|
export const useChat = () => {
|
||||||
|
const ChatBoxRef = useRef<ChatComponentRef>(null);
|
||||||
|
|
||||||
|
const [chatRecords, setChatRecords] = useState<ChatSiteItemType[]>([]);
|
||||||
|
const variablesForm = useForm();
|
||||||
|
// plugin
|
||||||
|
const [pluginRunTab, setPluginRunTab] = useState<PluginRunBoxTabEnum>(PluginRunBoxTabEnum.input);
|
||||||
|
|
||||||
|
const resetChatRecords = useCallback(
|
||||||
|
(props?: { records?: ChatSiteItemType[]; variables?: Record<string, any> }) => {
|
||||||
|
const { records = [], variables = {} } = props || {};
|
||||||
|
|
||||||
|
setChatRecords(records);
|
||||||
|
|
||||||
|
// Reset to empty input
|
||||||
|
const data = variablesForm.getValues();
|
||||||
|
for (const key in data) {
|
||||||
|
data[key] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
variablesForm.reset({
|
||||||
|
...data,
|
||||||
|
...variables
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(
|
||||||
|
() => {
|
||||||
|
ChatBoxRef.current?.restartChat?.();
|
||||||
|
},
|
||||||
|
ChatBoxRef.current?.restartChat ? 0 : 500
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[variablesForm, setChatRecords]
|
||||||
|
);
|
||||||
|
|
||||||
|
const clearChatRecords = useCallback(() => {
|
||||||
|
setChatRecords([]);
|
||||||
|
|
||||||
|
const data = variablesForm.getValues();
|
||||||
|
for (const key in data) {
|
||||||
|
variablesForm.setValue(key, '');
|
||||||
|
}
|
||||||
|
console.log(ChatBoxRef.current);
|
||||||
|
ChatBoxRef.current?.restartChat?.();
|
||||||
|
}, [variablesForm]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ChatBoxRef,
|
||||||
|
chatRecords,
|
||||||
|
setChatRecords,
|
||||||
|
variablesForm,
|
||||||
|
pluginRunTab,
|
||||||
|
setPluginRunTab,
|
||||||
|
clearChatRecords,
|
||||||
|
resetChatRecords
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
import Markdown, { CodeClassName } from '@/components/Markdown';
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionButton,
|
||||||
|
AccordionIcon,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionPanel,
|
||||||
|
Box
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
|
import {
|
||||||
|
AIChatItemValueItemType,
|
||||||
|
ChatSiteItemType,
|
||||||
|
UserChatItemValueItemType
|
||||||
|
} from '@fastgpt/global/core/chat/type';
|
||||||
|
import React from 'react';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import Avatar from '@/components/Avatar';
|
||||||
|
|
||||||
|
type props = {
|
||||||
|
value: UserChatItemValueItemType | AIChatItemValueItemType;
|
||||||
|
index: number;
|
||||||
|
chat: ChatSiteItemType;
|
||||||
|
isLastChild: boolean;
|
||||||
|
isChatting: boolean;
|
||||||
|
questionGuides: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const AIResponseBox = ({ value, index, chat, isLastChild, isChatting, questionGuides }: props) => {
|
||||||
|
if (value.text) {
|
||||||
|
let source = (value.text?.content || '').trim();
|
||||||
|
|
||||||
|
// First empty line
|
||||||
|
if (!source && chat.value.length > 1) return null;
|
||||||
|
|
||||||
|
// computed question guide
|
||||||
|
if (
|
||||||
|
isLastChild &&
|
||||||
|
!isChatting &&
|
||||||
|
questionGuides.length > 0 &&
|
||||||
|
index === chat.value.length - 1
|
||||||
|
) {
|
||||||
|
source = `${source}
|
||||||
|
\`\`\`${CodeClassName.questionGuide}
|
||||||
|
${JSON.stringify(questionGuides)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Markdown
|
||||||
|
source={source}
|
||||||
|
showAnimation={isLastChild && isChatting && index === chat.value.length - 1}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (value.type === ChatItemValueTypeEnum.tool && value.tools) {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
{value.tools.map((tool) => {
|
||||||
|
const toolParams = (() => {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(JSON.parse(tool.params), null, 2);
|
||||||
|
} catch (error) {
|
||||||
|
return tool.params;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
const toolResponse = (() => {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(JSON.parse(tool.response), null, 2);
|
||||||
|
} catch (error) {
|
||||||
|
return tool.response;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion key={tool.id} allowToggle>
|
||||||
|
<AccordionItem borderTop={'none'} borderBottom={'none'}>
|
||||||
|
<AccordionButton
|
||||||
|
w={'auto'}
|
||||||
|
bg={'white'}
|
||||||
|
borderRadius={'md'}
|
||||||
|
borderWidth={'1px'}
|
||||||
|
borderColor={'myGray.200'}
|
||||||
|
boxShadow={'1'}
|
||||||
|
_hover={{
|
||||||
|
bg: 'auto'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar src={tool.toolAvatar} w={'1rem'} h={'1rem'} mr={2} />
|
||||||
|
<Box mr={1} fontSize={'sm'}>
|
||||||
|
{tool.toolName}
|
||||||
|
</Box>
|
||||||
|
{isChatting && !tool.response && <MyIcon name={'common/loading'} w={'14px'} />}
|
||||||
|
<AccordionIcon color={'myGray.600'} ml={5} />
|
||||||
|
</AccordionButton>
|
||||||
|
<AccordionPanel
|
||||||
|
py={0}
|
||||||
|
px={0}
|
||||||
|
mt={0}
|
||||||
|
borderRadius={'md'}
|
||||||
|
overflow={'hidden'}
|
||||||
|
maxH={'500px'}
|
||||||
|
overflowY={'auto'}
|
||||||
|
>
|
||||||
|
{toolParams && toolParams !== '{}' && (
|
||||||
|
<Markdown
|
||||||
|
source={`~~~json#Input
|
||||||
|
${toolParams}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{toolResponse && (
|
||||||
|
<Markdown
|
||||||
|
source={`~~~json#Response
|
||||||
|
${toolResponse}`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(AIResponseBox);
|
||||||
|
|
@ -0,0 +1,347 @@
|
||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import { Box, useTheme, Flex, Image, BoxProps } from '@chakra-ui/react';
|
||||||
|
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
|
||||||
|
|
||||||
|
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||||
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
|
import Markdown from '@/components/Markdown';
|
||||||
|
import { QuoteList } from '../ChatContainer/ChatBox/components/QuoteModal';
|
||||||
|
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
|
||||||
|
import { formatNumber } from '@fastgpt/global/common/math/tools';
|
||||||
|
import { useI18n } from '@/web/context/I18n';
|
||||||
|
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||||
|
|
||||||
|
function RowRender({
|
||||||
|
children,
|
||||||
|
mb,
|
||||||
|
label,
|
||||||
|
...props
|
||||||
|
}: { children: React.ReactNode; label: string } & BoxProps) {
|
||||||
|
return (
|
||||||
|
<Box mb={3}>
|
||||||
|
<Box fontSize={'sm'} mb={mb} flex={'0 0 90px'}>
|
||||||
|
{label}:
|
||||||
|
</Box>
|
||||||
|
<Box borderRadius={'sm'} fontSize={['xs', 'sm']} bg={'myGray.50'} {...props}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function Row({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
rawDom
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
value?: string | number | boolean | object;
|
||||||
|
rawDom?: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const val = value || rawDom;
|
||||||
|
const isObject = typeof value === 'object';
|
||||||
|
|
||||||
|
const formatValue = useMemo(() => {
|
||||||
|
if (isObject) {
|
||||||
|
return `~~~json\n${JSON.stringify(value, null, 2)}`;
|
||||||
|
}
|
||||||
|
return `${value}`;
|
||||||
|
}, [isObject, value]);
|
||||||
|
|
||||||
|
if (rawDom) {
|
||||||
|
return (
|
||||||
|
<RowRender label={label} mb={1}>
|
||||||
|
{rawDom}
|
||||||
|
</RowRender>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val === undefined || val === '' || val === 'undefined') return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RowRender
|
||||||
|
label={label}
|
||||||
|
mb={isObject ? 0 : 1}
|
||||||
|
{...(isObject
|
||||||
|
? { transform: 'translateY(-3px)' }
|
||||||
|
: value
|
||||||
|
? { px: 3, py: 2, border: theme.borders.base }
|
||||||
|
: {})}
|
||||||
|
>
|
||||||
|
<Markdown source={formatValue} />
|
||||||
|
</RowRender>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const WholeResponseModal = ({
|
||||||
|
response,
|
||||||
|
showDetail,
|
||||||
|
onClose
|
||||||
|
}: {
|
||||||
|
response: ChatHistoryItemResType[];
|
||||||
|
showDetail: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MyModal
|
||||||
|
isCentered
|
||||||
|
isOpen={true}
|
||||||
|
onClose={onClose}
|
||||||
|
h={['90vh', '80vh']}
|
||||||
|
minW={['90vw', '600px']}
|
||||||
|
iconSrc="/imgs/modal/wholeRecord.svg"
|
||||||
|
title={
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
{t('core.chat.response.Complete Response')}
|
||||||
|
<QuestionTip ml={2} label={'从左往右,为各个模块的响应顺序'}></QuestionTip>
|
||||||
|
</Flex>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ResponseBox response={response} showDetail={showDetail} />
|
||||||
|
</MyModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WholeResponseModal;
|
||||||
|
|
||||||
|
export const ResponseBox = React.memo(function ResponseBox({
|
||||||
|
response,
|
||||||
|
showDetail,
|
||||||
|
hideTabs = false
|
||||||
|
}: {
|
||||||
|
response: ChatHistoryItemResType[];
|
||||||
|
showDetail: boolean;
|
||||||
|
hideTabs?: boolean;
|
||||||
|
}) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { workflowT } = useI18n();
|
||||||
|
|
||||||
|
const list = useMemo(
|
||||||
|
() =>
|
||||||
|
response.map((item, i) => ({
|
||||||
|
label: (
|
||||||
|
<Flex alignItems={'center'} justifyContent={'center'} px={2}>
|
||||||
|
<Image
|
||||||
|
mr={2}
|
||||||
|
src={
|
||||||
|
item.moduleLogo ||
|
||||||
|
moduleTemplatesFlat.find((template) => item.moduleType === template.flowNodeType)
|
||||||
|
?.avatar
|
||||||
|
}
|
||||||
|
alt={''}
|
||||||
|
w={['14px', '16px']}
|
||||||
|
/>
|
||||||
|
{t(item.moduleName)}
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
value: `${i}`
|
||||||
|
})),
|
||||||
|
[response, t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [currentTab, setCurrentTab] = useState(`0`);
|
||||||
|
|
||||||
|
const activeModule = useMemo(() => response[Number(currentTab)], [currentTab, response]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
{...(hideTabs ? { overflow: 'auto' } : { display: 'flex', flexDirection: 'column' })}
|
||||||
|
h={'100%'}
|
||||||
|
>
|
||||||
|
{!hideTabs && (
|
||||||
|
<Box>
|
||||||
|
<LightRowTabs
|
||||||
|
list={list}
|
||||||
|
value={currentTab}
|
||||||
|
inlineStyles={{ pt: 0 }}
|
||||||
|
onChange={setCurrentTab}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{activeModule && (
|
||||||
|
<Box
|
||||||
|
py={2}
|
||||||
|
px={4}
|
||||||
|
{...(hideTabs
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
flex: '1 0 0',
|
||||||
|
overflow: 'auto'
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<Row label={t('core.chat.response.module name')} value={t(activeModule.moduleName)} />
|
||||||
|
{activeModule?.totalPoints !== undefined && (
|
||||||
|
<Row
|
||||||
|
label={t('support.wallet.usage.Total points')}
|
||||||
|
value={formatNumber(activeModule.totalPoints)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.module time')}
|
||||||
|
value={`${activeModule?.runningTime || 0}s`}
|
||||||
|
/>
|
||||||
|
<Row label={t('core.chat.response.module model')} value={activeModule?.model} />
|
||||||
|
<Row label={t('core.chat.response.module tokens')} value={`${activeModule?.tokens}`} />
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.Tool call tokens')}
|
||||||
|
value={`${activeModule?.toolCallTokens}`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Row label={t('core.chat.response.module query')} value={activeModule?.query} />
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.context total length')}
|
||||||
|
value={activeModule?.contextTotalLen}
|
||||||
|
/>
|
||||||
|
<Row label={workflowT('response.Error')} value={activeModule?.error} />
|
||||||
|
</>
|
||||||
|
|
||||||
|
{/* ai chat */}
|
||||||
|
<>
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.module temperature')}
|
||||||
|
value={activeModule?.temperature}
|
||||||
|
/>
|
||||||
|
<Row label={t('core.chat.response.module maxToken')} value={activeModule?.maxToken} />
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.module historyPreview')}
|
||||||
|
rawDom={
|
||||||
|
activeModule.historyPreview ? (
|
||||||
|
<Box px={3} py={2} border={theme.borders.base} borderRadius={'md'}>
|
||||||
|
{activeModule.historyPreview?.map((item, i) => (
|
||||||
|
<Box
|
||||||
|
key={i}
|
||||||
|
_notLast={{
|
||||||
|
borderBottom: '1px solid',
|
||||||
|
borderBottomColor: 'myWhite.700',
|
||||||
|
mb: 2
|
||||||
|
}}
|
||||||
|
pb={2}
|
||||||
|
>
|
||||||
|
<Box fontWeight={'bold'}>{item.obj}</Box>
|
||||||
|
<Box whiteSpace={'pre-wrap'}>{item.value}</Box>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
|
||||||
|
{/* dataset search */}
|
||||||
|
<>
|
||||||
|
{activeModule?.searchMode && (
|
||||||
|
<Row
|
||||||
|
label={t('core.dataset.search.search mode')}
|
||||||
|
// @ts-ignore
|
||||||
|
value={t(DatasetSearchModeMap[activeModule.searchMode]?.title)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.module similarity')}
|
||||||
|
value={activeModule?.similarity}
|
||||||
|
/>
|
||||||
|
<Row label={t('core.chat.response.module limit')} value={activeModule?.limit} />
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.search using reRank')}
|
||||||
|
value={`${activeModule?.searchUsingReRank}`}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.Extension model')}
|
||||||
|
value={activeModule?.extensionModel}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={t('support.wallet.usage.Extension result')}
|
||||||
|
value={`${activeModule?.extensionResult}`}
|
||||||
|
/>
|
||||||
|
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.module quoteList')}
|
||||||
|
rawDom={<QuoteList showDetail={showDetail} rawSearch={activeModule.quoteList} />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
|
||||||
|
{/* classify question */}
|
||||||
|
<>
|
||||||
|
<Row label={t('core.chat.response.module cq result')} value={activeModule?.cqResult} />
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.module cq')}
|
||||||
|
value={(() => {
|
||||||
|
if (!activeModule?.cqList) return '';
|
||||||
|
return activeModule.cqList.map((item) => `* ${item.value}`).join('\n');
|
||||||
|
})()}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
|
||||||
|
{/* if-else */}
|
||||||
|
<>
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.module if else Result')}
|
||||||
|
value={activeModule?.ifElseResult}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
|
||||||
|
{/* extract */}
|
||||||
|
<>
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.module extract description')}
|
||||||
|
value={activeModule?.extractDescription}
|
||||||
|
/>
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.module extract result')}
|
||||||
|
value={activeModule?.extractResult}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
|
||||||
|
{/* http */}
|
||||||
|
<>
|
||||||
|
<Row label={'Headers'} value={activeModule?.headers} />
|
||||||
|
<Row label={'Params'} value={activeModule?.params} />
|
||||||
|
<Row label={'Body'} value={activeModule?.body} />
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.module http result')}
|
||||||
|
value={activeModule?.httpResult}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
|
||||||
|
{/* plugin */}
|
||||||
|
<>
|
||||||
|
<Row label={t('core.chat.response.plugin output')} value={activeModule?.pluginOutput} />
|
||||||
|
{activeModule?.pluginDetail && activeModule?.pluginDetail.length > 0 && (
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.Plugin response detail')}
|
||||||
|
rawDom={
|
||||||
|
<ResponseBox response={activeModule.pluginDetail} showDetail={showDetail} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
|
||||||
|
{/* text output */}
|
||||||
|
<Row label={t('core.chat.response.text output')} value={activeModule?.textOutput} />
|
||||||
|
|
||||||
|
{/* tool call */}
|
||||||
|
{activeModule?.toolDetail && activeModule?.toolDetail.length > 0 && (
|
||||||
|
<Row
|
||||||
|
label={t('core.chat.response.Tool call response detail')}
|
||||||
|
rawDom={<ResponseBox response={activeModule.toolDetail} showDetail={showDetail} />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* code */}
|
||||||
|
<Row label={workflowT('response.Custom outputs')} value={activeModule?.customOutputs} />
|
||||||
|
<Row label={workflowT('response.Custom inputs')} value={activeModule?.customInputs} />
|
||||||
|
<Row label={workflowT('response.Code log')} value={activeModule?.codeLog} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { AppChatConfigType, AppTTSConfigType } from '@fastgpt/global/core/app/type.d';
|
import type { AppChatConfigType, AppTTSConfigType } from '@fastgpt/global/core/app/type.d';
|
||||||
import { AdminFbkType, ChatItemType } from '@fastgpt/global/core/chat/type';
|
import { AdminFbkType, ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||||
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d';
|
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d';
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
|
||||||
export type GetChatSpeechProps = {
|
export type GetChatSpeechProps = {
|
||||||
ttsConfig: AppTTSConfigType;
|
ttsConfig: AppTTSConfigType;
|
||||||
|
|
@ -39,6 +40,8 @@ export type InitChatResponse = {
|
||||||
avatar: string;
|
avatar: string;
|
||||||
intro: string;
|
intro: string;
|
||||||
canUse?: boolean;
|
canUse?: boolean;
|
||||||
|
type: `${AppTypeEnum}`;
|
||||||
|
pluginInputs: FlowNodeInputItemType[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
import { InitChatResponse } from './api';
|
import { InitChatResponse } from './api';
|
||||||
|
|
||||||
export const defaultChatData: InitChatResponse = {
|
export const defaultChatData: InitChatResponse = {
|
||||||
|
|
@ -7,7 +8,9 @@ export const defaultChatData: InitChatResponse = {
|
||||||
name: 'Loading',
|
name: 'Loading',
|
||||||
avatar: '/icon/logo.svg',
|
avatar: '/icon/logo.svg',
|
||||||
intro: '',
|
intro: '',
|
||||||
canUse: false
|
canUse: false,
|
||||||
|
type: AppTypeEnum.simple,
|
||||||
|
pluginInputs: []
|
||||||
},
|
},
|
||||||
title: '新对话',
|
title: '新对话',
|
||||||
variables: {},
|
variables: {},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
|
import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
|
||||||
import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
|
import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||||
import { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type';
|
||||||
|
import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||||
|
|
||||||
export type PostWorkflowDebugProps = {
|
export type PostWorkflowDebugProps = {
|
||||||
nodes: RuntimeNodeItemType[];
|
nodes: RuntimeNodeItemType[];
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ import {
|
||||||
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
|
import StandardPlanContentList from '@/components/support/wallet/StandardPlanContentList';
|
||||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
|
|
||||||
const StandDetailModal = dynamic(() => import('./standardDetailModal'));
|
const StandDetailModal = dynamic(() => import('./standardDetailModal'));
|
||||||
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
|
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
|
||||||
|
|
@ -54,7 +55,7 @@ const LafAccountModal = dynamic(() => import('@/components/support/laf/LafAccoun
|
||||||
const CommunityModal = dynamic(() => import('@/components/CommunityModal'));
|
const CommunityModal = dynamic(() => import('@/components/CommunityModal'));
|
||||||
|
|
||||||
const Account = () => {
|
const Account = () => {
|
||||||
const { isPc } = useSystemStore();
|
const { isPc } = useSystem();
|
||||||
const { teamPlanStatus } = useUserStore();
|
const { teamPlanStatus } = useUserStore();
|
||||||
const standardPlan = teamPlanStatus?.standardConstants;
|
const standardPlan = teamPlanStatus?.standardConstants;
|
||||||
|
|
||||||
|
|
@ -99,7 +100,7 @@ const MyInfo = () => {
|
||||||
const { reset } = useForm<UserUpdateParams>({
|
const { reset } = useForm<UserUpdateParams>({
|
||||||
defaultValues: userInfo as UserType
|
defaultValues: userInfo as UserType
|
||||||
});
|
});
|
||||||
const { isPc } = useSystemStore();
|
const { isPc } = useSystem();
|
||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const {
|
const {
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,14 @@ import { usePagination } from '@fastgpt/web/hooks/usePagination';
|
||||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
|
|
||||||
const InformTable = () => {
|
const InformTable = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { Loading } = useLoading();
|
const { Loading } = useLoading();
|
||||||
const { isPc } = useSystemStore();
|
const { isPc } = useSystem();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: informs,
|
data: informs,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import Avatar from '@/components/Avatar';
|
||||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||||
import { formatNumber } from '@fastgpt/global/common/math/tools';
|
import { formatNumber } from '@fastgpt/global/common/math/tools';
|
||||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
const UsageDetail = dynamic(() => import('./UsageDetail'));
|
const UsageDetail = dynamic(() => import('./UsageDetail'));
|
||||||
|
|
||||||
const UsageTable = () => {
|
const UsageTable = () => {
|
||||||
|
|
@ -41,7 +42,7 @@ const UsageTable = () => {
|
||||||
to: new Date()
|
to: new Date()
|
||||||
});
|
});
|
||||||
const [usageSource, setUsageSource] = useState<UsageSourceEnum | ''>('');
|
const [usageSource, setUsageSource] = useState<UsageSourceEnum | ''>('');
|
||||||
const { isPc } = useSystemStore();
|
const { isPc } = useSystem();
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
const [usageDetail, setUsageDetail] = useState<UsageItemType>();
|
const [usageDetail, setUsageDetail] = useState<UsageItemType>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import UserInfo from './components/Info';
|
||||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import Script from 'next/script';
|
import Script from 'next/script';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
|
|
||||||
const Promotion = dynamic(() => import('./components/Promotion'));
|
const Promotion = dynamic(() => import('./components/Promotion'));
|
||||||
const UsageTable = dynamic(() => import('./components/UsageTable'));
|
const UsageTable = dynamic(() => import('./components/UsageTable'));
|
||||||
|
|
@ -34,7 +35,8 @@ enum TabEnum {
|
||||||
const Account = ({ currentTab }: { currentTab: TabEnum }) => {
|
const Account = ({ currentTab }: { currentTab: TabEnum }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { userInfo, setUserInfo } = useUserStore();
|
const { userInfo, setUserInfo } = useUserStore();
|
||||||
const { feConfigs, isPc, systemVersion } = useSystemStore();
|
const { feConfigs, systemVersion } = useSystemStore();
|
||||||
|
const { isPc } = useSystem();
|
||||||
|
|
||||||
const tabList = [
|
const tabList = [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { AppFolderTypeList, AppTypeEnum } from '@fastgpt/global/core/app/constan
|
||||||
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
|
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
|
||||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||||
|
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
|
||||||
|
|
||||||
export type ListAppBody = {
|
export type ListAppBody = {
|
||||||
parentId?: ParentIdType;
|
parentId?: ParentIdType;
|
||||||
|
|
@ -55,8 +56,8 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
|
||||||
const searchMatch = searchKey
|
const searchMatch = searchKey
|
||||||
? {
|
? {
|
||||||
$or: [
|
$or: [
|
||||||
{ name: { $regex: searchKey, $options: 'i' } },
|
{ name: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } },
|
||||||
{ intro: { $regex: searchKey, $options: 'i' } }
|
{ intro: { $regex: new RegExp(`${replaceRegChars(searchKey)}`, 'i') } }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|
@ -65,7 +66,14 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
|
||||||
return {
|
return {
|
||||||
// get all chat app
|
// get all chat app
|
||||||
teamId,
|
teamId,
|
||||||
type: { $in: [AppTypeEnum.workflow, AppTypeEnum.simple] },
|
type: { $in: [AppTypeEnum.workflow, AppTypeEnum.simple, AppTypeEnum.plugin] },
|
||||||
|
...searchMatch
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchKey) {
|
||||||
|
return {
|
||||||
|
teamId,
|
||||||
...searchMatch
|
...searchMatch
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -74,8 +82,7 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
|
||||||
teamId,
|
teamId,
|
||||||
...(type && Array.isArray(type) && { type: { $in: type } }),
|
...(type && Array.isArray(type) && { type: { $in: type } }),
|
||||||
...(type && { type }),
|
...(type && { type }),
|
||||||
...parseParentIdInMongo(parentId),
|
...parseParentIdInMongo(parentId)
|
||||||
...searchMatch
|
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,10 @@
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { connectToDatabase } from '@/service/mongo';
|
|
||||||
import { sseErrRes } from '@fastgpt/service/common/response';
|
import { sseErrRes } from '@fastgpt/service/common/response';
|
||||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
import { responseWrite } from '@fastgpt/service/common/response';
|
import { responseWrite } from '@fastgpt/service/common/response';
|
||||||
import { pushChatUsage } from '@/service/support/wallet/usage/push';
|
import { pushChatUsage } from '@/service/support/wallet/usage/push';
|
||||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||||
import type {
|
import type { UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||||
ChatItemType,
|
|
||||||
ChatItemValueItemType,
|
|
||||||
UserChatItemValueItemType
|
|
||||||
} from '@fastgpt/global/core/chat/type';
|
|
||||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||||
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
|
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
|
||||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||||
|
|
@ -18,10 +13,14 @@ import { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||||
import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
|
import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||||
import { removeEmptyUserInput } from '@fastgpt/global/core/chat/utils';
|
import { removeEmptyUserInput } from '@fastgpt/global/core/chat/utils';
|
||||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
import { updatePluginInputByVariables } from '@fastgpt/global/core/workflow/utils';
|
||||||
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
|
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||||
|
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
history: ChatItemType[];
|
messages: ChatCompletionMessageParam[];
|
||||||
prompt: UserChatItemValueItemType[];
|
|
||||||
nodes: RuntimeNodeItemType[];
|
nodes: RuntimeNodeItemType[];
|
||||||
edges: RuntimeEdgeItemType[];
|
edges: RuntimeEdgeItemType[];
|
||||||
variables: Record<string, any>;
|
variables: Record<string, any>;
|
||||||
|
|
@ -29,7 +28,7 @@ export type Props = {
|
||||||
appName: string;
|
appName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
res.on('close', () => {
|
res.on('close', () => {
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
@ -38,26 +37,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
let {
|
let { nodes = [], edges = [], messages = [], variables = {}, appName, appId } = req.body as Props;
|
||||||
nodes = [],
|
|
||||||
edges = [],
|
|
||||||
history = [],
|
|
||||||
prompt,
|
|
||||||
variables = {},
|
|
||||||
appName,
|
|
||||||
appId
|
|
||||||
} = req.body as Props;
|
|
||||||
try {
|
try {
|
||||||
await connectToDatabase();
|
// [histories, user]
|
||||||
if (!history || !nodes || !prompt || prompt.length === 0) {
|
const chatMessages = GPTMessages2Chats(messages);
|
||||||
throw new Error('Prams Error');
|
const userInput = chatMessages.pop()?.value as UserChatItemValueItemType[] | undefined;
|
||||||
}
|
|
||||||
if (!Array.isArray(nodes)) {
|
|
||||||
throw new Error('Nodes is not array');
|
|
||||||
}
|
|
||||||
if (!Array.isArray(edges)) {
|
|
||||||
throw new Error('Edges is not array');
|
|
||||||
}
|
|
||||||
|
|
||||||
/* user auth */
|
/* user auth */
|
||||||
const [{ app }, { teamId, tmbId }] = await Promise.all([
|
const [{ app }, { teamId, tmbId }] = await Promise.all([
|
||||||
|
|
@ -67,6 +51,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
authToken: true
|
authToken: true
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
const isPlugin = app.type === AppTypeEnum.plugin;
|
||||||
|
|
||||||
|
if (!Array.isArray(nodes)) {
|
||||||
|
throw new Error('Nodes is not array');
|
||||||
|
}
|
||||||
|
if (!Array.isArray(edges)) {
|
||||||
|
throw new Error('Edges is not array');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin need to replace inputs
|
||||||
|
if (isPlugin) {
|
||||||
|
nodes = updatePluginInputByVariables(nodes, variables);
|
||||||
|
} else {
|
||||||
|
if (!userInput) {
|
||||||
|
throw new Error('Params Error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// auth balance
|
// auth balance
|
||||||
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
||||||
|
|
@ -82,8 +83,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
runtimeNodes: nodes,
|
runtimeNodes: nodes,
|
||||||
runtimeEdges: edges,
|
runtimeEdges: edges,
|
||||||
variables,
|
variables,
|
||||||
query: removeEmptyUserInput(prompt),
|
query: removeEmptyUserInput(userInput),
|
||||||
histories: history,
|
histories: chatMessages,
|
||||||
stream: true,
|
stream: true,
|
||||||
detail: true,
|
detail: true,
|
||||||
maxRunTimes: 200
|
maxRunTimes: 200
|
||||||
|
|
@ -117,6 +118,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default NextAPI(handler);
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
api: {
|
api: {
|
||||||
bodyParser: {
|
bodyParser: {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runti
|
||||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
||||||
import { NextAPI } from '@/service/middleware/entry';
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
|
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
|
|
||||||
async function handler(
|
async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
|
|
@ -53,6 +54,8 @@ async function handler(
|
||||||
}),
|
}),
|
||||||
getAppLatestVersion(app._id, app)
|
getAppLatestVersion(app._id, app)
|
||||||
]);
|
]);
|
||||||
|
const pluginInputs =
|
||||||
|
app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs ?? [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chatId,
|
chatId,
|
||||||
|
|
@ -72,7 +75,9 @@ async function handler(
|
||||||
chatModels: getChatModelNameListByModules(nodes),
|
chatModels: getChatModelNameListByModules(nodes),
|
||||||
name: app.name,
|
name: app.name,
|
||||||
avatar: app.avatar,
|
avatar: app.avatar,
|
||||||
intro: app.intro
|
intro: app.intro,
|
||||||
|
type: app.type,
|
||||||
|
pluginInputs
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
|
||||||
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
|
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
|
||||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
||||||
|
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -47,7 +49,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
chatId,
|
chatId,
|
||||||
limit: 30,
|
limit: 30,
|
||||||
field: `dataId obj value userGoodFeedback userBadFeedback ${
|
field: `dataId obj value userGoodFeedback userBadFeedback ${
|
||||||
shareChat.responseDetail
|
shareChat.responseDetail || app.type === AppTypeEnum.plugin
|
||||||
? `adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}`
|
? `adminFeedback ${DispatchNodeResponseKeyEnum.nodeResponse}`
|
||||||
: ''
|
: ''
|
||||||
} `
|
} `
|
||||||
|
|
@ -56,11 +58,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// pick share response field
|
// pick share response field
|
||||||
histories.forEach((item) => {
|
app.type !== AppTypeEnum.plugin &&
|
||||||
if (item.obj === ChatRoleEnum.AI) {
|
histories.forEach((item) => {
|
||||||
item.responseData = filterPublicNodeResponseData({ flowResponses: item.responseData });
|
if (item.obj === ChatRoleEnum.AI) {
|
||||||
}
|
item.responseData = filterPublicNodeResponseData({ flowResponses: item.responseData });
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
jsonRes<InitChatResponse>(res, {
|
jsonRes<InitChatResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -82,7 +85,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
chatModels: getChatModelNameListByModules(nodes),
|
chatModels: getChatModelNameListByModules(nodes),
|
||||||
name: app.name,
|
name: app.name,
|
||||||
avatar: app.avatar,
|
avatar: app.avatar,
|
||||||
intro: app.intro
|
intro: app.intro,
|
||||||
|
type: app.type,
|
||||||
|
pluginInputs:
|
||||||
|
app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)
|
||||||
|
?.inputs ?? []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
|
||||||
import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils';
|
import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils';
|
||||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
||||||
|
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -58,11 +60,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// pick share response field
|
// pick share response field
|
||||||
histories.forEach((item) => {
|
app.type !== AppTypeEnum.plugin &&
|
||||||
if (item.obj === ChatRoleEnum.AI) {
|
histories.forEach((item) => {
|
||||||
item.responseData = filterPublicNodeResponseData({ flowResponses: item.responseData });
|
if (item.obj === ChatRoleEnum.AI) {
|
||||||
}
|
item.responseData = filterPublicNodeResponseData({ flowResponses: item.responseData });
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
jsonRes<InitChatResponse>(res, {
|
jsonRes<InitChatResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -83,7 +86,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||||
chatModels: getChatModelNameListByModules(nodes),
|
chatModels: getChatModelNameListByModules(nodes),
|
||||||
name: app.name,
|
name: app.name,
|
||||||
avatar: app.avatar,
|
avatar: app.avatar,
|
||||||
intro: app.intro
|
intro: app.intro,
|
||||||
|
type: app.type,
|
||||||
|
pluginInputs:
|
||||||
|
app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)
|
||||||
|
?.inputs ?? []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,11 @@ import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||||
import { sseErrRes, jsonRes } from '@fastgpt/service/common/response';
|
import { sseErrRes, jsonRes } from '@fastgpt/service/common/response';
|
||||||
import { addLog } from '@fastgpt/service/common/system/log';
|
import { addLog } from '@fastgpt/service/common/system/log';
|
||||||
import { ChatRoleEnum, ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
|
import {
|
||||||
|
ChatItemValueTypeEnum,
|
||||||
|
ChatRoleEnum,
|
||||||
|
ChatSourceEnum
|
||||||
|
} from '@fastgpt/global/core/chat/constants';
|
||||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||||
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
|
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
|
||||||
import type { ChatCompletionCreateParams } from '@fastgpt/global/core/ai/type.d';
|
import type { ChatCompletionCreateParams } from '@fastgpt/global/core/ai/type.d';
|
||||||
|
|
@ -28,10 +32,10 @@ import { authTeamSpaceToken } from '@/service/support/permission/auth/team';
|
||||||
import {
|
import {
|
||||||
concatHistories,
|
concatHistories,
|
||||||
filterPublicNodeResponseData,
|
filterPublicNodeResponseData,
|
||||||
|
getChatTitleFromChatMessage,
|
||||||
removeEmptyUserInput
|
removeEmptyUserInput
|
||||||
} from '@fastgpt/global/core/chat/utils';
|
} from '@fastgpt/global/core/chat/utils';
|
||||||
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
||||||
import { connectToDatabase } from '@/service/mongo';
|
|
||||||
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
||||||
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
|
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||||
|
|
@ -49,9 +53,17 @@ import { setEntryEntries } from '@fastgpt/service/core/workflow/dispatchV1/utils
|
||||||
import { NextAPI } from '@/service/middleware/entry';
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
||||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
import { updatePluginInputByVariables } from '@fastgpt/global/core/workflow/utils';
|
||||||
|
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||||
|
import {
|
||||||
|
getPluginInputsFromStoreNodes,
|
||||||
|
getPluginRunContent
|
||||||
|
} from '@fastgpt/global/core/app/plugin/utils';
|
||||||
|
import { getSystemTime } from '@fastgpt/global/common/time/timezone';
|
||||||
|
|
||||||
type FastGptWebChatProps = {
|
type FastGptWebChatProps = {
|
||||||
chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history
|
chatId?: string; // undefined: get histories from messages, '': new chat, 'xxxxx': get histories from db
|
||||||
appId?: string;
|
appId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -59,14 +71,11 @@ export type Props = ChatCompletionCreateParams &
|
||||||
FastGptWebChatProps &
|
FastGptWebChatProps &
|
||||||
OutLinkChatAuthProps & {
|
OutLinkChatAuthProps & {
|
||||||
messages: ChatCompletionMessageParam[];
|
messages: ChatCompletionMessageParam[];
|
||||||
|
responseChatItemId?: string;
|
||||||
stream?: boolean;
|
stream?: boolean;
|
||||||
detail?: boolean;
|
detail?: boolean;
|
||||||
variables: Record<string, any>;
|
variables: Record<string, any>; // Global variables or plugin inputs
|
||||||
};
|
};
|
||||||
export type ChatResponseType = {
|
|
||||||
newChatId: string;
|
|
||||||
quoteLen?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type AuthResponseType = {
|
type AuthResponseType = {
|
||||||
teamId: string;
|
teamId: string;
|
||||||
|
|
@ -89,7 +98,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
res.end();
|
res.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
let {
|
||||||
chatId,
|
chatId,
|
||||||
appId,
|
appId,
|
||||||
// share chat
|
// share chat
|
||||||
|
|
@ -98,41 +107,39 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
// team chat
|
// team chat
|
||||||
teamId: spaceTeamId,
|
teamId: spaceTeamId,
|
||||||
teamToken,
|
teamToken,
|
||||||
|
|
||||||
stream = false,
|
stream = false,
|
||||||
detail = false,
|
detail = false,
|
||||||
messages = [],
|
messages = [],
|
||||||
variables = {}
|
variables = {},
|
||||||
|
responseChatItemId = getNanoid()
|
||||||
} = req.body as Props;
|
} = req.body as Props;
|
||||||
try {
|
|
||||||
const originIp = requestIp.getClientIp(req);
|
|
||||||
|
|
||||||
await connectToDatabase();
|
const originIp = requestIp.getClientIp(req);
|
||||||
// body data check
|
|
||||||
if (!messages) {
|
const startTime = Date.now();
|
||||||
throw new Error('Prams Error');
|
|
||||||
}
|
try {
|
||||||
if (!Array.isArray(messages)) {
|
if (!Array.isArray(messages)) {
|
||||||
throw new Error('messages is not array');
|
throw new Error('messages is not array');
|
||||||
}
|
}
|
||||||
if (messages.length === 0) {
|
|
||||||
throw new Error('messages is empty');
|
|
||||||
}
|
|
||||||
|
|
||||||
let startTime = Date.now();
|
/*
|
||||||
|
Web params: chatId + [Human]
|
||||||
// Web chat params: [Human, AI]
|
API params: chatId + [Human]
|
||||||
|
API params: [histories, Human]
|
||||||
|
*/
|
||||||
const chatMessages = GPTMessages2Chats(messages);
|
const chatMessages = GPTMessages2Chats(messages);
|
||||||
if (chatMessages[chatMessages.length - 1].obj !== ChatRoleEnum.Human) {
|
|
||||||
chatMessages.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
// user question
|
// Computed start hook params
|
||||||
const question = chatMessages.pop() as UserChatItemType;
|
const startHookText = (() => {
|
||||||
if (!question) {
|
// Chat
|
||||||
throw new Error('Question is empty');
|
const userQuestion = chatMessages[chatMessages.length - 1] as UserChatItemType | undefined;
|
||||||
}
|
if (userQuestion) return chatValue2RuntimePrompt(userQuestion.value).text;
|
||||||
|
|
||||||
const { text, files } = chatValue2RuntimePrompt(question.value);
|
// plugin
|
||||||
|
return JSON.stringify(variables);
|
||||||
|
})();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
1. auth app permission
|
1. auth app permission
|
||||||
|
|
@ -149,7 +156,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
outLinkUid,
|
outLinkUid,
|
||||||
chatId,
|
chatId,
|
||||||
ip: originIp,
|
ip: originIp,
|
||||||
question: text
|
question: startHookText
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// team space chat
|
// team space chat
|
||||||
|
|
@ -169,8 +176,45 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
chatId
|
chatId
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
const isPlugin = app.type === AppTypeEnum.plugin;
|
||||||
|
|
||||||
// 1. get and concat history; 2. get app workflow
|
// Check message type
|
||||||
|
if (isPlugin) {
|
||||||
|
detail = true;
|
||||||
|
} else {
|
||||||
|
if (messages.length === 0) {
|
||||||
|
throw new Error('messages is empty');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get obj=Human history
|
||||||
|
const userQuestion: UserChatItemType = (() => {
|
||||||
|
if (isPlugin) {
|
||||||
|
return {
|
||||||
|
dataId: getNanoid(24),
|
||||||
|
obj: ChatRoleEnum.Human,
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
type: ChatItemValueTypeEnum.text,
|
||||||
|
text: {
|
||||||
|
content: getPluginRunContent({
|
||||||
|
pluginInputs: getPluginInputsFromStoreNodes(app.modules)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestHumanChat = chatMessages.pop() as UserChatItemType | undefined;
|
||||||
|
if (!latestHumanChat) {
|
||||||
|
throw new Error('User question is empty');
|
||||||
|
}
|
||||||
|
return latestHumanChat;
|
||||||
|
})();
|
||||||
|
const { text, files } = chatValue2RuntimePrompt(userQuestion.value);
|
||||||
|
|
||||||
|
// Get and concat history;
|
||||||
const limit = getMaxHistoryLimitFromNodes(app.modules);
|
const limit = getMaxHistoryLimitFromNodes(app.modules);
|
||||||
const [{ histories }, { nodes, edges, chatConfig }] = await Promise.all([
|
const [{ histories }, { nodes, edges, chatConfig }] = await Promise.all([
|
||||||
getChatItems({
|
getChatItems({
|
||||||
|
|
@ -182,7 +226,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
getAppLatestVersion(app._id, app)
|
getAppLatestVersion(app._id, app)
|
||||||
]);
|
]);
|
||||||
const newHistories = concatHistories(histories, chatMessages);
|
const newHistories = concatHistories(histories, chatMessages);
|
||||||
const responseChatItemId: string | undefined = messages[messages.length - 1].dataId;
|
|
||||||
|
// Get runtimeNodes
|
||||||
|
const runtimeNodes = isPlugin
|
||||||
|
? updatePluginInputByVariables(
|
||||||
|
storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)),
|
||||||
|
variables
|
||||||
|
)
|
||||||
|
: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes));
|
||||||
|
|
||||||
/* start flow controller */
|
/* start flow controller */
|
||||||
const { flowResponses, flowUsages, assistantResponses, newVariables } = await (async () => {
|
const { flowResponses, flowUsages, assistantResponses, newVariables } = await (async () => {
|
||||||
|
|
@ -196,10 +247,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
app,
|
app,
|
||||||
chatId,
|
chatId,
|
||||||
responseChatItemId,
|
responseChatItemId,
|
||||||
runtimeNodes: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)),
|
runtimeNodes,
|
||||||
runtimeEdges: initWorkflowEdgeStatus(edges),
|
runtimeEdges: initWorkflowEdgeStatus(edges),
|
||||||
variables,
|
variables,
|
||||||
query: removeEmptyUserInput(question.value),
|
query: removeEmptyUserInput(userQuestion.value),
|
||||||
histories: newHistories,
|
histories: newHistories,
|
||||||
stream,
|
stream,
|
||||||
detail,
|
detail,
|
||||||
|
|
@ -245,6 +296,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
return ChatSourceEnum.online;
|
return ChatSourceEnum.online;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const newTitle = isPlugin
|
||||||
|
? variables.cTime ?? getSystemTime(user.timezone)
|
||||||
|
: getChatTitleFromChatMessage(userQuestion);
|
||||||
|
|
||||||
await saveChat({
|
await saveChat({
|
||||||
chatId,
|
chatId,
|
||||||
appId: app._id,
|
appId: app._id,
|
||||||
|
|
@ -254,11 +309,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
appChatConfig: chatConfig,
|
appChatConfig: chatConfig,
|
||||||
variables: newVariables,
|
variables: newVariables,
|
||||||
isUpdateUseTime: isOwnerUse && source === ChatSourceEnum.online, // owner update use time
|
isUpdateUseTime: isOwnerUse && source === ChatSourceEnum.online, // owner update use time
|
||||||
|
newTitle,
|
||||||
shareId,
|
shareId,
|
||||||
outLinkUid: outLinkUserId,
|
outLinkUid: outLinkUserId,
|
||||||
source,
|
source,
|
||||||
content: [
|
content: [
|
||||||
question,
|
userQuestion,
|
||||||
{
|
{
|
||||||
dataId: responseChatItemId,
|
dataId: responseChatItemId,
|
||||||
obj: ChatRoleEnum.AI,
|
obj: ChatRoleEnum.AI,
|
||||||
|
|
@ -295,7 +351,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (detail) {
|
if (detail) {
|
||||||
if (responseDetail) {
|
if (responseDetail || isPlugin) {
|
||||||
responseWrite({
|
responseWrite({
|
||||||
res,
|
res,
|
||||||
event: SseResponseEventEnum.flowResponses,
|
event: SseResponseEventEnum.flowResponses,
|
||||||
|
|
@ -312,6 +368,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
return assistantResponses[0].text?.content;
|
return assistantResponses[0].text?.content;
|
||||||
return assistantResponses;
|
return assistantResponses;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
...(detail ? { responseData: feResponseData, newVariables } : {}),
|
...(detail ? { responseData: feResponseData, newVariables } : {}),
|
||||||
id: chatId || '',
|
id: chatId || '',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,198 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Flex, Box, useTheme } from '@chakra-ui/react';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { HUMAN_ICON } from '@fastgpt/global/common/system/constants';
|
||||||
|
import { getInitChatInfo } from '@/web/core/chat/api';
|
||||||
|
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||||
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||||
|
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
|
||||||
|
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||||
|
import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/PluginRunBox/constants';
|
||||||
|
import CloseIcon from '@fastgpt/web/components/common/Icon/close';
|
||||||
|
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
const PluginRunBox = dynamic(() => import('@/components/core/chat/ChatContainer/PluginRunBox'));
|
||||||
|
|
||||||
|
const DetailLogsModal = ({
|
||||||
|
appId,
|
||||||
|
chatId,
|
||||||
|
onClose
|
||||||
|
}: {
|
||||||
|
appId: string;
|
||||||
|
chatId: string;
|
||||||
|
onClose: () => void;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { isPc } = useSystem();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const {
|
||||||
|
ChatBoxRef,
|
||||||
|
chatRecords,
|
||||||
|
setChatRecords,
|
||||||
|
variablesForm,
|
||||||
|
pluginRunTab,
|
||||||
|
setPluginRunTab,
|
||||||
|
resetChatRecords
|
||||||
|
} = useChat();
|
||||||
|
|
||||||
|
const { data: chat, isFetching } = useQuery(
|
||||||
|
['getChatDetail', chatId],
|
||||||
|
() => getInitChatInfo({ appId, chatId, loadCustomFeedbacks: true }),
|
||||||
|
{
|
||||||
|
onSuccess(res) {
|
||||||
|
const history = res.history.map((item) => ({
|
||||||
|
...item,
|
||||||
|
dataId: item.dataId || getNanoid(),
|
||||||
|
status: 'finish' as any
|
||||||
|
}));
|
||||||
|
|
||||||
|
resetChatRecords({
|
||||||
|
records: history,
|
||||||
|
variables: res.variables
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const title = chat?.title;
|
||||||
|
const chatModels = chat?.app?.chatModels;
|
||||||
|
const isPlugin = chat?.app.type === AppTypeEnum.plugin;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MyBox
|
||||||
|
isLoading={isFetching}
|
||||||
|
display={'flex'}
|
||||||
|
flexDirection={'column'}
|
||||||
|
zIndex={3}
|
||||||
|
position={['fixed', 'absolute']}
|
||||||
|
top={[0, '2%']}
|
||||||
|
right={0}
|
||||||
|
h={['100%', '96%']}
|
||||||
|
w={'100%'}
|
||||||
|
maxW={['100%', '600px']}
|
||||||
|
bg={'white'}
|
||||||
|
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
|
||||||
|
borderRadius={'md'}
|
||||||
|
overflow={'hidden'}
|
||||||
|
transition={'.2s ease'}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
{isPlugin ? (
|
||||||
|
<Flex
|
||||||
|
alignItems={'flex-start'}
|
||||||
|
justifyContent={'space-between'}
|
||||||
|
px={3}
|
||||||
|
pt={3}
|
||||||
|
bg={'myGray.25'}
|
||||||
|
borderBottom={'base'}
|
||||||
|
>
|
||||||
|
<LightRowTabs<PluginRunBoxTabEnum>
|
||||||
|
list={[
|
||||||
|
{ label: t('common.Input'), value: PluginRunBoxTabEnum.input },
|
||||||
|
...(chatRecords.length > 0
|
||||||
|
? [
|
||||||
|
{ label: t('common.Output'), value: PluginRunBoxTabEnum.output },
|
||||||
|
{ label: '完整结果', value: PluginRunBoxTabEnum.detail }
|
||||||
|
]
|
||||||
|
: [])
|
||||||
|
]}
|
||||||
|
value={pluginRunTab}
|
||||||
|
onChange={setPluginRunTab}
|
||||||
|
inlineStyles={{ px: 0.5, pb: 2 }}
|
||||||
|
gap={5}
|
||||||
|
py={0}
|
||||||
|
fontSize={'sm'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CloseIcon onClick={onClose} />
|
||||||
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<Flex
|
||||||
|
alignItems={'center'}
|
||||||
|
px={[3, 5]}
|
||||||
|
h={['46px', '60px']}
|
||||||
|
borderBottom={theme.borders.base}
|
||||||
|
borderBottomColor={'gray.200'}
|
||||||
|
color={'myGray.900'}
|
||||||
|
>
|
||||||
|
{isPc ? (
|
||||||
|
<>
|
||||||
|
<Box mr={3} color={'myGray.1000'}>
|
||||||
|
{title}
|
||||||
|
</Box>
|
||||||
|
{chatRecords.length > 0 && (
|
||||||
|
<>
|
||||||
|
<MyTag colorSchema="blue">
|
||||||
|
<MyIcon name={'history'} w={'14px'} />
|
||||||
|
<Box ml={1}>{`${chatRecords.length}条记录`}</Box>
|
||||||
|
</MyTag>
|
||||||
|
{!!chatModels && (
|
||||||
|
<MyTag ml={2} colorSchema={'green'}>
|
||||||
|
<MyIcon name={'core/chat/chatModelTag'} w={'14px'} />
|
||||||
|
<Box ml={1}>{chatModels.join(',')}</Box>
|
||||||
|
</MyTag>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box flex={1} />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Flex px={3} alignItems={'center'} flex={'1 0 0'} w={0} justifyContent={'center'}>
|
||||||
|
<Box ml={1} className="textEllipsis">
|
||||||
|
{title}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<CloseIcon onClick={onClose} />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Chat container */}
|
||||||
|
<Box pt={2} flex={'1 0 0'}>
|
||||||
|
{isPlugin ? (
|
||||||
|
<Box px={5} pt={2} h={'100%'}>
|
||||||
|
<PluginRunBox
|
||||||
|
pluginInputs={chat?.app.pluginInputs}
|
||||||
|
variablesForm={variablesForm}
|
||||||
|
histories={chatRecords}
|
||||||
|
setHistories={setChatRecords}
|
||||||
|
appId={chat.appId}
|
||||||
|
tab={pluginRunTab}
|
||||||
|
setTab={setPluginRunTab}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<ChatBox
|
||||||
|
ref={ChatBoxRef}
|
||||||
|
chatHistories={chatRecords}
|
||||||
|
setChatHistories={setChatRecords}
|
||||||
|
variablesForm={variablesForm}
|
||||||
|
appAvatar={chat?.app.avatar}
|
||||||
|
userAvatar={HUMAN_ICON}
|
||||||
|
feedbackType={'admin'}
|
||||||
|
showMarkIcon
|
||||||
|
showVoiceIcon={false}
|
||||||
|
chatConfig={chat?.app?.chatConfig}
|
||||||
|
appId={appId}
|
||||||
|
chatId={chatId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</MyBox>
|
||||||
|
<Box zIndex={2} position={'fixed'} top={0} left={0} bottom={0} right={0} onClick={onClose} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default DetailLogsModal;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useMemo, useRef, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Flex,
|
Flex,
|
||||||
Box,
|
Box,
|
||||||
|
|
@ -9,7 +9,6 @@ import {
|
||||||
Th,
|
Th,
|
||||||
Td,
|
Td,
|
||||||
Tbody,
|
Tbody,
|
||||||
useTheme,
|
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
ModalBody,
|
ModalBody,
|
||||||
HStack
|
HStack
|
||||||
|
|
@ -19,31 +18,26 @@ import { useTranslation } from 'next-i18next';
|
||||||
import { getAppChatLogs } from '@/web/core/app/api';
|
import { getAppChatLogs } from '@/web/core/app/api';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { ChatSourceMap } from '@fastgpt/global/core/chat/constants';
|
import { ChatSourceMap } from '@fastgpt/global/core/chat/constants';
|
||||||
import { HUMAN_ICON } from '@fastgpt/global/common/system/constants';
|
|
||||||
import { AppLogsListItemType } from '@/types/app';
|
import { AppLogsListItemType } from '@/types/app';
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
import ChatBox from '@/components/ChatBox';
|
|
||||||
import type { ComponentRef } from '@/components/ChatBox/type.d';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { getInitChatInfo } from '@/web/core/chat/api';
|
|
||||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
|
||||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
import { addDays } from 'date-fns';
|
import { addDays } from 'date-fns';
|
||||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
|
||||||
import { usePagination } from '@fastgpt/web/hooks/usePagination';
|
import { usePagination } from '@fastgpt/web/hooks/usePagination';
|
||||||
import DateRangePicker, { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker';
|
import DateRangePicker, { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker';
|
||||||
import { formatChatValue2InputType } from '@/components/ChatBox/utils';
|
|
||||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
|
||||||
import { useI18n } from '@/web/context/I18n';
|
import { useI18n } from '@/web/context/I18n';
|
||||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { AppContext } from '../context';
|
import { AppContext } from '../context';
|
||||||
import { cardStyles } from '../constants';
|
import { cardStyles } from '../constants';
|
||||||
|
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
|
const DetailLogsModal = dynamic(() => import('./DetailLogsModal'));
|
||||||
|
|
||||||
const Logs = () => {
|
const Logs = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { appT } = useI18n();
|
const { appT } = useI18n();
|
||||||
const { isPc } = useSystemStore();
|
const { isPc } = useSystem();
|
||||||
|
|
||||||
const appId = useContextSelector(AppContext, (v) => v.appId);
|
const appId = useContextSelector(AppContext, (v) => v.appId);
|
||||||
|
|
||||||
|
|
@ -225,131 +219,3 @@ const Logs = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(Logs);
|
export default React.memo(Logs);
|
||||||
|
|
||||||
const DetailLogsModal = ({
|
|
||||||
appId,
|
|
||||||
chatId,
|
|
||||||
onClose
|
|
||||||
}: {
|
|
||||||
appId: string;
|
|
||||||
chatId: string;
|
|
||||||
onClose: () => void;
|
|
||||||
}) => {
|
|
||||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
|
||||||
const { isPc } = useSystemStore();
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const { data: chat, isFetching } = useQuery(
|
|
||||||
['getChatDetail', chatId],
|
|
||||||
() => getInitChatInfo({ appId, chatId, loadCustomFeedbacks: true }),
|
|
||||||
{
|
|
||||||
onSuccess(res) {
|
|
||||||
const history = res.history.map((item) => ({
|
|
||||||
...item,
|
|
||||||
dataId: item.dataId || getNanoid(),
|
|
||||||
status: 'finish' as any
|
|
||||||
}));
|
|
||||||
ChatBoxRef.current?.resetHistory(history);
|
|
||||||
ChatBoxRef.current?.resetVariables(res.variables);
|
|
||||||
if (res.history.length > 0) {
|
|
||||||
setTimeout(() => {
|
|
||||||
ChatBoxRef.current?.scrollToBottom('auto');
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const history = useMemo(() => (chat?.history ? chat.history : []), [chat]);
|
|
||||||
|
|
||||||
const title = useMemo(() => {
|
|
||||||
const { text } = formatChatValue2InputType(history[history.length - 2]?.value);
|
|
||||||
return text?.slice(0, 8);
|
|
||||||
}, [history]);
|
|
||||||
const chatModels = chat?.app?.chatModels;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<MyBox
|
|
||||||
isLoading={isFetching}
|
|
||||||
display={'flex'}
|
|
||||||
flexDirection={'column'}
|
|
||||||
zIndex={3}
|
|
||||||
position={['fixed', 'absolute']}
|
|
||||||
top={[0, '2%']}
|
|
||||||
right={0}
|
|
||||||
h={['100%', '96%']}
|
|
||||||
w={'100%'}
|
|
||||||
maxW={['100%', '600px']}
|
|
||||||
bg={'white'}
|
|
||||||
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
|
|
||||||
borderRadius={'md'}
|
|
||||||
overflow={'hidden'}
|
|
||||||
transition={'.2s ease'}
|
|
||||||
>
|
|
||||||
<Flex
|
|
||||||
alignItems={'center'}
|
|
||||||
px={[3, 5]}
|
|
||||||
h={['46px', '60px']}
|
|
||||||
borderBottom={theme.borders.base}
|
|
||||||
borderBottomColor={'gray.200'}
|
|
||||||
color={'myGray.900'}
|
|
||||||
>
|
|
||||||
{isPc ? (
|
|
||||||
<>
|
|
||||||
<Box mr={3} color={'myGray.1000'}>
|
|
||||||
{title}
|
|
||||||
</Box>
|
|
||||||
<MyTag colorSchema="blue">
|
|
||||||
<MyIcon name={'history'} w={'14px'} />
|
|
||||||
<Box ml={1}>{`${history.length}条记录`}</Box>
|
|
||||||
</MyTag>
|
|
||||||
{!!chatModels && chatModels.length > 0 && (
|
|
||||||
<MyTag ml={2} colorSchema={'green'}>
|
|
||||||
<MyIcon name={'core/chat/chatModelTag'} w={'14px'} />
|
|
||||||
<Box ml={1}>{chatModels.join(',')}</Box>
|
|
||||||
</MyTag>
|
|
||||||
)}
|
|
||||||
<Box flex={1} />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Flex px={3} alignItems={'center'} flex={'1 0 0'} w={0} justifyContent={'center'}>
|
|
||||||
<Box ml={1} className="textEllipsis">
|
|
||||||
{title}
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Flex
|
|
||||||
alignItems={'center'}
|
|
||||||
justifyContent={'center'}
|
|
||||||
w={'20px'}
|
|
||||||
h={'20px'}
|
|
||||||
borderRadius={'50%'}
|
|
||||||
cursor={'pointer'}
|
|
||||||
_hover={{ bg: 'myGray.100' }}
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
<MyIcon name={'common/closeLight'} w={'12px'} h={'12px'} color={'myGray.700'} />
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
<Box pt={2} flex={'1 0 0'}>
|
|
||||||
<ChatBox
|
|
||||||
ref={ChatBoxRef}
|
|
||||||
appAvatar={chat?.app.avatar}
|
|
||||||
userAvatar={HUMAN_ICON}
|
|
||||||
feedbackType={'admin'}
|
|
||||||
showMarkIcon
|
|
||||||
showVoiceIcon={false}
|
|
||||||
chatConfig={chat?.app?.chatConfig}
|
|
||||||
appId={appId}
|
|
||||||
chatId={chatId}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</MyBox>
|
|
||||||
<Box zIndex={2} position={'fixed'} top={0} left={0} bottom={0} right={0} onClick={onClose} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,15 @@ import { useRouter } from 'next/router';
|
||||||
|
|
||||||
import AppCard from '../WorkflowComponents/AppCard';
|
import AppCard from '../WorkflowComponents/AppCard';
|
||||||
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
|
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
|
||||||
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
|
import RouteTab from '../RouteTab';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
const PublishHistories = dynamic(() => import('../PublishHistoriesSlider'));
|
const PublishHistories = dynamic(() => import('../PublishHistoriesSlider'));
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { isPc } = useSystem();
|
||||||
|
|
||||||
const { appDetail, onPublish, currentTab } = useContextSelector(AppContext, (v) => v);
|
const { appDetail, onPublish, currentTab } = useContextSelector(AppContext, (v) => v);
|
||||||
const isV2Workflow = appDetail?.version === 'v2';
|
const isV2Workflow = appDetail?.version === 'v2';
|
||||||
|
|
@ -27,6 +31,7 @@ const Header = () => {
|
||||||
flowData2StoreDataAndCheck,
|
flowData2StoreDataAndCheck,
|
||||||
onSaveWorkflow,
|
onSaveWorkflow,
|
||||||
setHistoriesDefaultData,
|
setHistoriesDefaultData,
|
||||||
|
setWorkflowTestData,
|
||||||
historiesDefaultData,
|
historiesDefaultData,
|
||||||
initData
|
initData
|
||||||
} = useContextSelector(WorkflowContext, (v) => v);
|
} = useContextSelector(WorkflowContext, (v) => v);
|
||||||
|
|
@ -62,11 +67,11 @@ const Header = () => {
|
||||||
const Render = useMemo(() => {
|
const Render = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* {!isPc && (
|
{!isPc && (
|
||||||
<Flex pt={2} justifyContent={'center'}>
|
<Flex pt={2} justifyContent={'center'}>
|
||||||
<RouteTab />
|
<RouteTab />
|
||||||
</Flex>
|
</Flex>
|
||||||
)} */}
|
)}
|
||||||
<Flex
|
<Flex
|
||||||
mt={[2, 0]}
|
mt={[2, 0]}
|
||||||
py={3}
|
py={3}
|
||||||
|
|
@ -101,18 +106,18 @@ const Header = () => {
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* {isPc && (
|
{isPc && (
|
||||||
<Box position={'absolute'} left={'50%'} transform={'translateX(-50%)'}>
|
<Box position={'absolute'} left={'50%'} transform={'translateX(-50%)'}>
|
||||||
<RouteTab />
|
<RouteTab />
|
||||||
</Box>
|
</Box>
|
||||||
)} */}
|
)}
|
||||||
<Box flex={1} />
|
<Box flex={1} />
|
||||||
|
|
||||||
{currentTab === TabEnum.appEdit && (
|
{currentTab === TabEnum.appEdit && (
|
||||||
<>
|
<>
|
||||||
{!historiesDefaultData && (
|
{!historiesDefaultData && (
|
||||||
<IconButton
|
<IconButton
|
||||||
// mr={[2, 4]}
|
mr={[2, 4]}
|
||||||
icon={<MyIcon name={'history'} w={'18px'} />}
|
icon={<MyIcon name={'history'} w={'18px'} />}
|
||||||
aria-label={''}
|
aria-label={''}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
|
|
@ -129,7 +134,7 @@ const Header = () => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{/* <Button
|
<Button
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
leftIcon={<MyIcon name={'core/workflow/debug'} w={['14px', '16px']} />}
|
leftIcon={<MyIcon name={'core/workflow/debug'} w={['14px', '16px']} />}
|
||||||
variant={'whitePrimary'}
|
variant={'whitePrimary'}
|
||||||
|
|
@ -141,7 +146,7 @@ const Header = () => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('core.workflow.Debug')}
|
{t('core.workflow.Debug')}
|
||||||
</Button> */}
|
</Button>
|
||||||
|
|
||||||
{!historiesDefaultData && (
|
{!historiesDefaultData && (
|
||||||
<PopoverConfirm
|
<PopoverConfirm
|
||||||
|
|
@ -176,12 +181,15 @@ const Header = () => {
|
||||||
}, [
|
}, [
|
||||||
appDetail.chatConfig,
|
appDetail.chatConfig,
|
||||||
currentTab,
|
currentTab,
|
||||||
|
flowData2StoreDataAndCheck,
|
||||||
historiesDefaultData,
|
historiesDefaultData,
|
||||||
initData,
|
initData,
|
||||||
|
isPc,
|
||||||
isV2Workflow,
|
isV2Workflow,
|
||||||
onclickPublish,
|
onclickPublish,
|
||||||
saveAndBack,
|
saveAndBack,
|
||||||
setHistoriesDefaultData,
|
setHistoriesDefaultData,
|
||||||
|
setWorkflowTestData,
|
||||||
t
|
t
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ const ChatTest = ({ appForm }: { appForm: AppSimpleEditFormType }) => {
|
||||||
setWorkflowData({ nodes, edges });
|
setWorkflowData({ nodes, edges });
|
||||||
}, [appForm, setWorkflowData]);
|
}, [appForm, setWorkflowData]);
|
||||||
|
|
||||||
const { resetChatBox, ChatBox } = useChatTest({
|
const { restartChat, ChatContainer } = useChatTest({
|
||||||
...workflowData,
|
...workflowData,
|
||||||
chatConfig: appForm.chatConfig
|
chatConfig: appForm.chatConfig
|
||||||
});
|
});
|
||||||
|
|
@ -48,13 +48,13 @@ const ChatTest = ({ appForm }: { appForm: AppSimpleEditFormType }) => {
|
||||||
aria-label={'delete'}
|
aria-label={'delete'}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
resetChatBox();
|
restartChat();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</MyTooltip>
|
</MyTooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box flex={1}>
|
<Box flex={1}>
|
||||||
<ChatBox />
|
<ChatContainer />
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
|
||||||
import { useMount } from 'ahooks';
|
import { useMount } from 'ahooks';
|
||||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||||
import { appWorkflow2Form } from '@fastgpt/global/core/app/utils';
|
import { appWorkflow2Form } from '@fastgpt/global/core/app/utils';
|
||||||
|
|
@ -15,6 +14,7 @@ import { useContextSelector } from 'use-context-selector';
|
||||||
import { cardStyles } from '../constants';
|
import { cardStyles } from '../constants';
|
||||||
|
|
||||||
import styles from './styles.module.scss';
|
import styles from './styles.module.scss';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
|
|
||||||
const Edit = ({
|
const Edit = ({
|
||||||
appForm,
|
appForm,
|
||||||
|
|
@ -23,7 +23,7 @@ const Edit = ({
|
||||||
appForm: AppSimpleEditFormType;
|
appForm: AppSimpleEditFormType;
|
||||||
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
|
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
|
||||||
}) => {
|
}) => {
|
||||||
const { isPc } = useSystemStore();
|
const { isPc } = useSystem();
|
||||||
const { loadAllDatasets } = useDatasetStore();
|
const { loadAllDatasets } = useDatasetStore();
|
||||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -257,8 +257,8 @@ const EditForm = ({
|
||||||
<Flex alignItems={'center'}>
|
<Flex alignItems={'center'}>
|
||||||
<Flex alignItems={'center'} flex={1}>
|
<Flex alignItems={'center'} flex={1}>
|
||||||
<MyIcon name={'core/app/toolCall'} w={'20px'} />
|
<MyIcon name={'core/app/toolCall'} w={'20px'} />
|
||||||
<FormLabel ml={2}>{t('core.app.Tool call')}(实验功能)</FormLabel>
|
<FormLabel ml={2}>{appT('Plugin dispatch')}</FormLabel>
|
||||||
<QuestionTip ml={1} label={t('core.app.Tool call tip')} />
|
<QuestionTip ml={1} label={appT('Plugin dispatch tip')} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<Button
|
<Button
|
||||||
variant={'transparentBase'}
|
variant={'transparentBase'}
|
||||||
|
|
@ -315,7 +315,13 @@ const EditForm = ({
|
||||||
<VariableEdit
|
<VariableEdit
|
||||||
variables={appForm.chatConfig.variables}
|
variables={appForm.chatConfig.variables}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
appForm.chatConfig.variables = e;
|
setAppForm((state) => ({
|
||||||
|
...state,
|
||||||
|
chatConfig: {
|
||||||
|
...state.chatConfig,
|
||||||
|
variables: e
|
||||||
|
}
|
||||||
|
}));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import { compareWorkflow } from '@/web/core/workflow/utils';
|
||||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||||
import { publishStatusStyle } from '../constants';
|
import { publishStatusStyle } from '../constants';
|
||||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
|
|
||||||
const Header = ({
|
const Header = ({
|
||||||
appForm,
|
appForm,
|
||||||
|
|
@ -30,7 +31,7 @@ const Header = ({
|
||||||
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
|
setAppForm: React.Dispatch<React.SetStateAction<AppSimpleEditFormType>>;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isPc } = useSystemStore();
|
const { isPc } = useSystem();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { appId, appDetail, onPublish, currentTab } = useContextSelector(AppContext, (v) => v);
|
const { appId, appDetail, onPublish, currentTab } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,12 @@ import { useRouter } from 'next/router';
|
||||||
import AppCard from '../WorkflowComponents/AppCard';
|
import AppCard from '../WorkflowComponents/AppCard';
|
||||||
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
|
import { uiWorkflow2StoreWorkflow } from '../WorkflowComponents/utils';
|
||||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
const PublishHistories = dynamic(() => import('../PublishHistoriesSlider'));
|
const PublishHistories = dynamic(() => import('../PublishHistoriesSlider'));
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isPc } = useSystemStore();
|
const { isPc } = useSystem();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const { appDetail, onPublish, currentTab } = useContextSelector(AppContext, (v) => v);
|
const { appDetail, onPublish, currentTab } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||||
import React, { forwardRef, ForwardedRef } from 'react';
|
import React from 'react';
|
||||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||||
import { Box, Flex, IconButton } from '@chakra-ui/react';
|
import { Box, Flex, HStack, IconButton } from '@chakra-ui/react';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
@ -10,29 +10,27 @@ import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { AppContext } from '@/pages/app/detail/components/context';
|
import { AppContext } from '@/pages/app/detail/components/context';
|
||||||
import { useChatTest } from '@/pages/app/detail/components/useChatTest';
|
import { useChatTest } from '@/pages/app/detail/components/useChatTest';
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||||
|
import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/PluginRunBox/constants';
|
||||||
|
import CloseIcon from '@fastgpt/web/components/common/Icon/close';
|
||||||
|
|
||||||
export type ChatTestComponentRef = {
|
const ChatTest = ({
|
||||||
resetChatTest: () => void;
|
isOpen,
|
||||||
};
|
nodes = [],
|
||||||
|
edges = [],
|
||||||
const ChatTest = (
|
onClose
|
||||||
{
|
}: {
|
||||||
isOpen,
|
isOpen: boolean;
|
||||||
nodes = [],
|
nodes?: StoreNodeItemType[];
|
||||||
edges = [],
|
edges?: StoreEdgeItemType[];
|
||||||
onClose
|
onClose: () => void;
|
||||||
}: {
|
}) => {
|
||||||
isOpen: boolean;
|
|
||||||
nodes?: StoreNodeItemType[];
|
|
||||||
edges?: StoreEdgeItemType[];
|
|
||||||
onClose: () => void;
|
|
||||||
},
|
|
||||||
ref: ForwardedRef<ChatTestComponentRef>
|
|
||||||
) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
|
const isPlugin = appDetail.type === AppTypeEnum.plugin;
|
||||||
|
|
||||||
const { resetChatBox, ChatBox } = useChatTest({
|
const { restartChat, ChatContainer, pluginRunTab, setPluginRunTab, chatRecords } = useChatTest({
|
||||||
nodes,
|
nodes,
|
||||||
edges,
|
edges,
|
||||||
chatConfig: appDetail.chatConfig
|
chatConfig: appDetail.chatConfig
|
||||||
|
|
@ -64,40 +62,76 @@ const ChatTest = (
|
||||||
overflow={'hidden'}
|
overflow={'hidden'}
|
||||||
transition={'.2s ease'}
|
transition={'.2s ease'}
|
||||||
>
|
>
|
||||||
<Flex py={4} px={5} whiteSpace={'nowrap'}>
|
{isPlugin ? (
|
||||||
<Box fontSize={'lg'} fontWeight={'bold'} flex={1}>
|
<Flex
|
||||||
{t('core.chat.Debug test')}
|
alignItems={'flex-start'}
|
||||||
</Box>
|
justifyContent={'space-between'}
|
||||||
<MyTooltip label={t('core.chat.Restart')}>
|
px={3}
|
||||||
<IconButton
|
pt={3}
|
||||||
className="chat"
|
bg={'myGray.25'}
|
||||||
size={'smSquare'}
|
borderBottom={'base'}
|
||||||
icon={<MyIcon name={'common/clearLight'} w={'14px'} />}
|
>
|
||||||
variant={'whiteDanger'}
|
<LightRowTabs<PluginRunBoxTabEnum>
|
||||||
borderRadius={'md'}
|
list={[
|
||||||
aria-label={'delete'}
|
{ label: t('common.Input'), value: PluginRunBoxTabEnum.input },
|
||||||
onClick={(e) => {
|
...(chatRecords.length > 0
|
||||||
resetChatBox();
|
? [
|
||||||
}}
|
{ label: t('common.Output'), value: PluginRunBoxTabEnum.output },
|
||||||
|
{ label: '完整结果', value: PluginRunBoxTabEnum.detail }
|
||||||
|
]
|
||||||
|
: [])
|
||||||
|
]}
|
||||||
|
value={pluginRunTab}
|
||||||
|
onChange={setPluginRunTab}
|
||||||
|
inlineStyles={{ px: 0.5, pb: 2 }}
|
||||||
|
gap={5}
|
||||||
|
py={0}
|
||||||
|
fontSize={'sm'}
|
||||||
/>
|
/>
|
||||||
</MyTooltip>
|
|
||||||
<MyTooltip label={t('common.Close')}>
|
<CloseIcon mt={1} onClick={onClose} />
|
||||||
<IconButton
|
</Flex>
|
||||||
ml={3}
|
) : (
|
||||||
icon={<SmallCloseIcon fontSize={'22px'} />}
|
<Flex
|
||||||
variant={'grayBase'}
|
py={4}
|
||||||
size={'smSquare'}
|
px={5}
|
||||||
aria-label={''}
|
whiteSpace={'nowrap'}
|
||||||
onClick={onClose}
|
bg={isPlugin ? 'myGray.25' : ''}
|
||||||
/>
|
borderBottom={isPlugin ? '1px solid #F4F4F7' : ''}
|
||||||
</MyTooltip>
|
>
|
||||||
</Flex>
|
<Box fontSize={'lg'} fontWeight={'bold'} flex={1}>
|
||||||
<Box flex={1}>
|
{t('core.chat.Debug test')}
|
||||||
<ChatBox />
|
</Box>
|
||||||
|
<MyTooltip label={t('core.chat.Restart')}>
|
||||||
|
<IconButton
|
||||||
|
className="chat"
|
||||||
|
size={'smSquare'}
|
||||||
|
icon={<MyIcon name={'common/clearLight'} w={'14px'} />}
|
||||||
|
variant={'whiteDanger'}
|
||||||
|
borderRadius={'md'}
|
||||||
|
aria-label={'delete'}
|
||||||
|
onClick={restartChat}
|
||||||
|
/>
|
||||||
|
</MyTooltip>
|
||||||
|
<MyTooltip label={t('common.Close')}>
|
||||||
|
<IconButton
|
||||||
|
ml={4}
|
||||||
|
icon={<SmallCloseIcon fontSize={'22px'} />}
|
||||||
|
variant={'grayBase'}
|
||||||
|
size={'smSquare'}
|
||||||
|
aria-label={''}
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
</MyTooltip>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Box flex={'1 0 0'} overflow={'auto'}>
|
||||||
|
<ChatContainer />
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(forwardRef(ChatTest));
|
export default React.memo(ChatTest);
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import { getAppFolderPath } from '@/web/core/app/api/app';
|
||||||
import { useWorkflowUtils } from './hooks/useUtils';
|
import { useWorkflowUtils } from './hooks/useUtils';
|
||||||
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
|
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
|
|
||||||
type ModuleTemplateListProps = {
|
type ModuleTemplateListProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
|
|
@ -268,7 +269,7 @@ const RenderList = React.memo(function RenderList({
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { appT } = useI18n();
|
const { appT } = useI18n();
|
||||||
|
|
||||||
const { isPc } = useSystemStore();
|
const { isPc } = useSystem();
|
||||||
const { x, y, zoom } = useViewport();
|
const { x, y, zoom } = useViewport();
|
||||||
const { setLoading } = useSystemStore();
|
const { setLoading } = useSystemStore();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ export const useDebug = () => {
|
||||||
setRuntimeEdges(undefined);
|
setRuntimeEdges(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onclickRun = (data: Record<string, any>) => {
|
const onClickRun = (data: Record<string, any>) => {
|
||||||
onStartNodeDebug({
|
onStartNodeDebug({
|
||||||
entryNodeId: runtimeNode.nodeId,
|
entryNodeId: runtimeNode.nodeId,
|
||||||
runtimeNodes: runtimeNodes.map((node) =>
|
runtimeNodes: runtimeNodes.map((node) =>
|
||||||
|
|
@ -261,7 +261,7 @@ export const useDebug = () => {
|
||||||
})}
|
})}
|
||||||
</Box>
|
</Box>
|
||||||
<Flex py={2} justifyContent={'flex-end'} px={6}>
|
<Flex py={2} justifyContent={'flex-end'} px={6}>
|
||||||
<Button onClick={handleSubmit(onclickRun)}>运行</Button>
|
<Button onClick={handleSubmit(onClickRun)}>运行</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</MyRightDrawer>
|
</MyRightDrawer>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import { ToolTargetHandle } from './Handle/ToolHandle';
|
||||||
import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';
|
import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';
|
||||||
import { ConnectionSourceHandle, ConnectionTargetHandle } from './Handle/ConnectionHandle';
|
import { ConnectionSourceHandle, ConnectionTargetHandle } from './Handle/ConnectionHandle';
|
||||||
import { useDebug } from '../../hooks/useDebug';
|
import { useDebug } from '../../hooks/useDebug';
|
||||||
import { ResponseBox } from '@/components/ChatBox/components/WholeResponseModal';
|
|
||||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||||
import { getPreviewPluginNode } from '@/web/core/app/api/plugin';
|
import { getPreviewPluginNode } from '@/web/core/app/api/plugin';
|
||||||
import { storeNode2FlowNode, getLatestNodeTemplate } from '@/web/core/workflow/utils';
|
import { storeNode2FlowNode, getLatestNodeTemplate } from '@/web/core/workflow/utils';
|
||||||
|
|
@ -26,6 +25,7 @@ import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { useWorkflowUtils } from '../../hooks/useUtils';
|
import { useWorkflowUtils } from '../../hooks/useUtils';
|
||||||
|
import { ResponseBox } from '@/components/core/chat/components/WholeResponseModal';
|
||||||
|
|
||||||
type Props = FlowNodeItemType & {
|
type Props = FlowNodeItemType & {
|
||||||
children?: React.ReactNode | React.ReactNode[] | string;
|
children?: React.ReactNode | React.ReactNode[] | string;
|
||||||
|
|
@ -566,11 +566,10 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
|
||||||
w={'420px'}
|
w={'420px'}
|
||||||
maxH={'100%'}
|
maxH={'100%'}
|
||||||
minH={'300px'}
|
minH={'300px'}
|
||||||
overflowY={'auto'}
|
|
||||||
border={'base'}
|
border={'base'}
|
||||||
>
|
>
|
||||||
{/* Status header */}
|
{/* Status header */}
|
||||||
<Flex px={4} mb={1} py={3} alignItems={'center'} borderBottom={'base'}>
|
<Flex h={'54x'} px={4} mb={1} py={3} alignItems={'center'} borderBottom={'base'}>
|
||||||
<MyIcon mr={1} name={'core/workflow/debugResult'} w={'20px'} color={'primary.600'} />
|
<MyIcon mr={1} name={'core/workflow/debugResult'} w={'20px'} color={'primary.600'} />
|
||||||
<Box fontWeight={'bold'} flex={'1'}>
|
<Box fontWeight={'bold'} flex={'1'}>
|
||||||
{t('core.workflow.debug.Run result')}
|
{t('core.workflow.debug.Run result')}
|
||||||
|
|
@ -606,7 +605,7 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
{/* Show result */}
|
{/* Show result */}
|
||||||
<Box maxH={'100%'} overflow={'auto'}>
|
<Box maxH={'calc(100%-54px)'} overflow={'auto'}>
|
||||||
{!debugResult.message && !response && (
|
{!debugResult.message && !response && (
|
||||||
<EmptyTip text={t('core.workflow.debug.Not result')} pt={2} pb={5} />
|
<EmptyTip text={t('core.workflow.debug.Not result')} pt={2} pb={5} />
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||||
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
|
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
|
||||||
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||||
import { AppContext } from '@/pages/app/detail/components/context';
|
import { AppContext } from '@/pages/app/detail/components/context';
|
||||||
import ChatTest, { type ChatTestComponentRef } from './Flow/ChatTest';
|
import ChatTest from './Flow/ChatTest';
|
||||||
import { useDisclosure } from '@chakra-ui/react';
|
import { useDisclosure } from '@chakra-ui/react';
|
||||||
import { uiWorkflow2StoreWorkflow } from './utils';
|
import { uiWorkflow2StoreWorkflow } from './utils';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
@ -750,7 +750,6 @@ const WorkflowContextProvider = ({
|
||||||
}, [edges, nodes]);
|
}, [edges, nodes]);
|
||||||
|
|
||||||
/* chat test */
|
/* chat test */
|
||||||
const ChatTestRef = useRef<ChatTestComponentRef>(null);
|
|
||||||
const { isOpen: isOpenTest, onOpen: onOpenTest, onClose: onCloseTest } = useDisclosure();
|
const { isOpen: isOpenTest, onOpen: onOpenTest, onClose: onCloseTest } = useDisclosure();
|
||||||
const [workflowTestData, setWorkflowTestData] = useState<{
|
const [workflowTestData, setWorkflowTestData] = useState<{
|
||||||
nodes: StoreNodeItemType[];
|
nodes: StoreNodeItemType[];
|
||||||
|
|
@ -813,7 +812,7 @@ const WorkflowContextProvider = ({
|
||||||
return (
|
return (
|
||||||
<WorkflowContext.Provider value={value}>
|
<WorkflowContext.Provider value={value}>
|
||||||
{children}
|
{children}
|
||||||
<ChatTest ref={ChatTestRef} isOpen={isOpenTest} {...workflowTestData} onClose={onCloseTest} />
|
<ChatTest isOpen={isOpenTest} {...workflowTestData} onClose={onCloseTest} />
|
||||||
</WorkflowContext.Provider>
|
</WorkflowContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
import React, { useCallback, useRef } from 'react';
|
import React from 'react';
|
||||||
import ChatBox from '@/components/ChatBox';
|
import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type';
|
||||||
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
|
|
||||||
import { streamFetch } from '@/web/common/api/fetch';
|
import { streamFetch } from '@/web/common/api/fetch';
|
||||||
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
|
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
|
||||||
import {
|
import {
|
||||||
|
|
@ -16,6 +15,14 @@ import { useContextSelector } from 'use-context-selector';
|
||||||
import { AppContext } from './context';
|
import { AppContext } from './context';
|
||||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||||
|
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
|
||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
|
||||||
|
|
||||||
|
const PluginRunBox = dynamic(() => import('@/components/core/chat/ChatContainer/PluginRunBox'));
|
||||||
|
|
||||||
export const useChatTest = ({
|
export const useChatTest = ({
|
||||||
nodes,
|
nodes,
|
||||||
|
|
@ -27,22 +34,19 @@ export const useChatTest = ({
|
||||||
chatConfig: AppChatConfigType;
|
chatConfig: AppChatConfigType;
|
||||||
}) => {
|
}) => {
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
|
||||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
|
|
||||||
const startChat = useMemoizedFn(
|
const startChat = useMemoizedFn(
|
||||||
async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => {
|
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||||
/* get histories */
|
/* get histories */
|
||||||
let historyMaxLen = getMaxHistoryLimitFromNodes(nodes);
|
const historyMaxLen = getMaxHistoryLimitFromNodes(nodes);
|
||||||
|
|
||||||
const history = chatList.slice(-historyMaxLen - 2, -2);
|
|
||||||
|
|
||||||
// 流请求,获取数据
|
// 流请求,获取数据
|
||||||
const { responseText, responseData } = await streamFetch({
|
const { responseText, responseData } = await streamFetch({
|
||||||
url: '/api/core/chat/chatTest',
|
url: '/api/core/chat/chatTest',
|
||||||
data: {
|
data: {
|
||||||
history,
|
// Send histories and user messages
|
||||||
prompt: chatList[chatList.length - 2].value,
|
messages: messages.slice(-historyMaxLen - 2),
|
||||||
nodes: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)),
|
nodes: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)),
|
||||||
edges: initWorkflowEdgeStatus(edges),
|
edges: initWorkflowEdgeStatus(edges),
|
||||||
variables,
|
variables,
|
||||||
|
|
@ -57,28 +61,57 @@ export const useChatTest = ({
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const resetChatBox = useCallback(() => {
|
const pluginInputs =
|
||||||
ChatBoxRef.current?.resetHistory([]);
|
nodes.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs || [];
|
||||||
ChatBoxRef.current?.resetVariables();
|
const {
|
||||||
}, []);
|
ChatBoxRef,
|
||||||
|
chatRecords,
|
||||||
|
setChatRecords,
|
||||||
|
variablesForm,
|
||||||
|
pluginRunTab,
|
||||||
|
setPluginRunTab,
|
||||||
|
clearChatRecords
|
||||||
|
} = useChat();
|
||||||
|
|
||||||
const CustomChatBox = useMemoizedFn(() => (
|
const CustomChatContainer = useMemoizedFn(() =>
|
||||||
<ChatBox
|
appDetail.type === AppTypeEnum.plugin ? (
|
||||||
ref={ChatBoxRef}
|
<Box h={'100%'} p={3}>
|
||||||
appId={appDetail._id}
|
<PluginRunBox
|
||||||
appAvatar={appDetail.avatar}
|
pluginInputs={pluginInputs}
|
||||||
userAvatar={userInfo?.avatar}
|
variablesForm={variablesForm}
|
||||||
showMarkIcon
|
histories={chatRecords}
|
||||||
chatConfig={chatConfig}
|
setHistories={setChatRecords}
|
||||||
showFileSelector={checkChatSupportSelectFileByModules(nodes)}
|
appId={appDetail._id}
|
||||||
onStartChat={startChat}
|
tab={pluginRunTab}
|
||||||
onDelMessage={() => {}}
|
setTab={setPluginRunTab}
|
||||||
/>
|
onNewChat={clearChatRecords}
|
||||||
));
|
onStartChat={startChat}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<ChatBox
|
||||||
|
ref={ChatBoxRef}
|
||||||
|
chatHistories={chatRecords}
|
||||||
|
setChatHistories={setChatRecords}
|
||||||
|
variablesForm={variablesForm}
|
||||||
|
appId={appDetail._id}
|
||||||
|
appAvatar={appDetail.avatar}
|
||||||
|
userAvatar={userInfo?.avatar}
|
||||||
|
showMarkIcon
|
||||||
|
chatConfig={chatConfig}
|
||||||
|
showFileSelector={checkChatSupportSelectFileByModules(nodes)}
|
||||||
|
onStartChat={startChat}
|
||||||
|
onDelMessage={() => {}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
resetChatBox,
|
restartChat: clearChatRecords,
|
||||||
ChatBox: CustomChatBox
|
ChatContainer: CustomChatContainer,
|
||||||
|
chatRecords,
|
||||||
|
pluginRunTab,
|
||||||
|
setPluginRunTab
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue