Compare commits

..

12 Commits

Author SHA1 Message Date
heheer
2d82db01a9 fix: api dataset reference tag preview (#3600) 2025-01-15 14:47:57 +08:00
Archer
80e670600b perf: load members;perf: yuque load;fix: workflow llm params cannot close (#3594)
* chat openapi doc

* feat: dataset openapi doc

* perf: load members

* perf: member load code

* perf: yuque load

* fix: workflow llm params cannot close
2025-01-15 14:47:56 +08:00
heheer
68f5afeba0 fix: yuque dataset file folder can enter (#3593) 2025-01-15 14:47:56 +08:00
Finley Ge
0d9f54cbf3 fix: scroll fetch (#3592) 2025-01-15 14:47:55 +08:00
Finley Ge
11cfe8a809 fix: collection list api old version (#3591)
* fix: collection list api format

* fix: type error of addSourceMemeber
2025-01-15 14:47:55 +08:00
Archer
6f8c6b6ad1 4.8.19 test (#3584)
* faet: dataset search filter

* fix: scroll page
2025-01-15 14:47:54 +08:00
Finley Ge
f468ba2f30 fix: pagination bug (#3577) 2025-01-15 14:47:54 +08:00
Finley Ge
19abfd1a3e docs: add custom uid docs (#3572) 2025-01-15 14:47:54 +08:00
Jiangween
ace304c619 docs:更新用户答疑 (#3576) 2025-01-15 14:47:54 +08:00
archer
923d0f85e9 perf: list data 2025-01-15 14:47:53 +08:00
archer
62bcff2ff0 perf: scroll page code 2025-01-15 14:47:53 +08:00
Finley Ge
ec0cef09a2 feat: sync org from wecom, pref: member list pagination (#3549)
* feat: sync org

* chore: fe

* chore: loading

* chore: type

* pref: team member list change to pagination. Edit a sort of list apis.

* feat: member update avatar

* chore: user avatar move to tmb

* chore: init scripts move user avatar

* chore: sourceMember

* fix: list api sourceMember

* fix: member sync

* fix: pagination

* chore: adjust code

* chore: move changeOwner to pro

* chore: init v4819 script

* chore: adjust code

* chore: UserBox
2025-01-15 14:47:50 +08:00
45 changed files with 185 additions and 638 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -29,43 +29,6 @@ images: []
如图
![](/imgs/faq2.png)
### 数据库3306端口被占用了启动服务失败
![](/imgs/faq3.png)
mysql 只有 oneAPI 用到,外面一般不需要调用,所以可以
- 把 3306:3306 的映射去掉/或者直接改一个映射。
```yaml
# 在 docker-compose.yaml 文件内
# ...
mysql:
image: mysql:8.0.36
ports:
- 3306:3306 # 这个端口被占用了!
# - 3307:3306 # 直接改一个。。和外面的不冲突
# *empty* 或者直接删了,反正外面用不到
oneapi:
container_name: oneapi
image: ghcr.io/songquanpeng/one-api:latest
environment:
- SQL_DSN=root:oneapimmysql@tcp(mysql:3306)/oneapi # 这不用改,容器内外网络是隔离的
```
- 另一种做法是可以直接连现有的 mysql 要改 oneAPI 的环境变量。
```yaml
# 在 docker-compose.yaml 文件内
# ...
# mysql: # 要连外面的,这个玩意用不到了
# image: mysql:8.0.36
# ports:
# - 3306:3306 # 这个端口被占用了!
oneapi:
container_name: oneapi
image: ghcr.io/songquanpeng/one-api:latest
environment:
- SQL_DSN=root:oneapimmysql@tcp(mysql:3306)/oneapi # 改成外面的链接字符串
```
### 本地部署的限制
具体内容参考https://fael3z0zfze.feishu.cn/wiki/OFpAw8XzAi36Guk8dfucrCKUnjg。

View File

@@ -11,12 +11,7 @@ weight: 806
## 完整更新内容
1. 新增 - 工作流知识库检索支持按知识库权限进行过滤。
2. 新增 - 飞书/语雀知识库查看原文
3. 新增 - 流程等待插件,可以等待 n 毫秒后继续执行流程
4. 优化 - 成员列表分页加载
5. 优化 - 统一分页加载代码。
6. 优化 - 对话页面加载时,可配置是否为独立页面。
7. 修复 - 语雀文件库导入时,嵌套文件内容无法展开的问题。
8. 修复 - 工作流编排中LLM 参数无法关闭问题。
9. 修复 - 工作流编排中,代码运行节点还原模板问题。
10. 修复 - HTTP 接口适配对象字符串解析。
2. 优化 - 成员列表分页加载
3. 优化 - 统一分页加载代码
4. 修复 - 语雀文件库导入时,嵌套文件内容无法展开的问题
5. 修复 - 工作流编排中LLM 参数无法关闭问题。

View File

@@ -21,19 +21,6 @@ weight: 908
定时执行会在应用发布后生效,会在后台生效。
## V4.8.18-FIX2中提到“ 1. 修复 HTTP 节点, {{}} 格式引用变量兼容问题。建议尽快替换 / 模式取变量, {{}} 语法已弃用。”替换{{}}引用格式是仅仅只有在http节点还是所有节点的都会有影响
只有 http 节点用到这个语法。
## 工作流类型的应用在运行预览可以正常提问返回,但是发布免登录窗口之后有问题。
一般是没正确发布,在工作流右上角点击【保存并发布】。
## 如何解决猜你想问使用中文回答显示
注意需要更新到V4.8.17及以上,把猜你想问的提示词改成中文。
![](/imgs/quizApp2.png)
## AI对话回答要求中的Markdown语法取消
修改知识库默认提示词, 默认用的是标准模板提示词,会要求按 Markdown 输出,可以去除该要求:

View File

@@ -24,17 +24,6 @@ xlsx等都可以上传的不止支持CSV。
统一按gpt3.5标准。
## 误删除重排模型后重排模型怎么加入到fastgpt
![](/imgs/dataset3.png)
config.json文件里面配置后就可以勾选重排模型
## 线上平台上创建了应用和知识库,到期之后如果短期内不续费,数据是否会被清理。
免费版是三十天不登录后清空知识库,应用不会动。其他付费套餐到期后自动切免费版。
![](/imgs/dataset4.png)
## 基于知识库的查询但是问题相关的答案过多。ai回答到一半就不继续回答。
FastGPT回复长度计算公式:

View File

@@ -8,14 +8,4 @@ weight: 918
## oneapi 官网是哪个
只有开源的 README没官网GitHub: https://github.com/songquanpeng/one-api
## 想做多用户和多chat key
![](/imgs/other1.png)
![](/imgs/other2.png)
多用户问题:只能采取二开或者商业版
多chat key问题1. 私有化部署情况下oneapi解决。2. Saas或者商业版中可以为每个团队设置单独的key。
![](/imgs/other3.png)
只有开源的 README没官网GitHub: https://github.com/songquanpeng/one-api

View File

@@ -22,11 +22,10 @@ FastGPT v4.8.16 版本开始,商业版用户支持飞书知识库导入,用
## 2. 配置应用权限
创建应用后,进入应用可以配置相关权限,这里需要增加**3个权限**
创建应用后,进入应用可以配置相关权限,这里需要增加个权限:
1. 获取云空间文件夹下的云文档清单
2. 查看新版文档
3. 查看、评论、编辑和管理云空间中所有文件
![alt text](/imgs/image-41.png)

View File

@@ -1,6 +1,6 @@
---
title: "批量运行"
description: "FastGPT 批量运行节点介绍和使用"
title: "循环执行"
description: "FastGPT 循环运行节点介绍和使用"
icon: "input"
draft: false
toc: true
@@ -9,15 +9,15 @@ weight: 260
## 节点概述
【**批量运行**】节点是 FastGPT V4.8.11 版本新增的一个重要功能模块。它允许工作流对数组类型的输入数据进行迭代处理,每次处理数组中的一个元素,并自动执行后续节点,直到完成整个数组的处理。
【**循环运行**】节点是 FastGPT V4.8.11 版本新增的一个重要功能模块。它允许工作流对数组类型的输入数据进行迭代处理,每次处理数组中的一个元素,并自动执行后续节点,直到完成整个数组的处理。
这个节点的设计灵感来自编程语言中的循环结构,但以可视化的方式呈现。
![批量运行节点](/imgs/fastgpt-loop-node.png)
![循环运行节点](/imgs/fastgpt-loop-node.png)
> 在程序中,节点可以理解为一个个 Function 或者接口。可以理解为它就是一个**步骤**。将多个节点一个个拼接起来,即可一步步的去实现最终的 AI 输出。
【**批量运行**】节点本质上也是一个 Function它的主要职责是自动化地重复执行特定的工作流程。
【**循环运行**】节点本质上也是一个 Function它的主要职责是自动化地重复执行特定的工作流程。
## 核心特性
@@ -41,9 +41,9 @@ weight: 260
## 应用场景
【**批量运行**】节点的主要作用是通过自动化的方式扩展工作流的处理能力,使 FastGPT 能够更好地处理批量任务和复杂的数据处理流程。特别是在处理大规模数据或需要多轮迭代的场景下,批量运行节点能显著提升工作流的效率和自动化程度。
【**循环运行**】节点的主要作用是通过自动化的方式扩展工作流的处理能力,使 FastGPT 能够更好地处理批量任务和复杂的数据处理流程。特别是在处理大规模数据或需要多轮迭代的场景下,循环运行节点能显著提升工作流的效率和自动化程度。
【**批量运行**】节点特别适合以下场景:
【**循环运行**】节点特别适合以下场景:
1. **批量数据处理**
- 批量翻译文本
@@ -64,7 +64,7 @@ weight: 260
### 输入参数设置
【**批量运行**】节点需要配置两个核心输入参数:
【**循环运行**】节点需要配置两个核心输入参数:
1. **数组 (必填)**:接收一个数组类型的输入,可以是:
- 字符串数组 (`Array<string>`)
@@ -95,7 +95,7 @@ weight: 260
### 批量处理数组
假设我们有一个包含多个文本的数组,需要对每个文本进行 AI 处理。这是批量运行节点最基础也最常见的应用场景。
假设我们有一个包含多个文本的数组,需要对每个文本进行 AI 处理。这是循环运行节点最基础也最常见的应用场景。
#### 实现步骤
@@ -114,9 +114,9 @@ weight: 260
return { textArray: texts };
```
2. 配置批量运行节点
2. 配置循环运行节点
![配置批量运行节点](/imgs/fastgpt-loop-node-example-2.png)
![配置循环运行节点](/imgs/fastgpt-loop-node-example-2.png)
- 数组输入:选择上一步代码运行节点的输出变量 `textArray`。
- 循环体内添加一个【AI 对话】节点,用于处理每个文本。这里我们输入的 prompt 为:`请将这段文本翻译成英文`。
@@ -128,7 +128,7 @@ weight: 260
![运行流程](/imgs/fastgpt-loop-node-example-3.png)
1. 【代码运行】节点执行,生成测试数组
2. 【批量运行】节点接收数组,开始遍历
2. 【循环运行】节点接收数组,开始遍历
3. 对每个数组元素:
- 【AI 对话】节点处理当前元素
- 【指定回复】节点输出翻译后的文本
@@ -144,7 +144,7 @@ weight: 260
- 需要维护上下文的连贯性
- 翻译质量需要多轮优化
【**批量运行**】节点可以很好地解决这些问题。
【**循环运行**】节点可以很好地解决这些问题。
#### 实现步骤
@@ -281,9 +281,9 @@ weight: 260
这里我们用到了 [Jina AI 开源的一个强大的正则表达式](https://x.com/JinaAI_/status/1823756993108304135),它能利用所有可能的边界线索和启发式方法来精确切分文本。
2. 配置批量运行节点
2. 配置循环运行节点
![配置批量运行节点](/imgs/fastgpt-loop-node-example-5.png)
![配置循环运行节点](/imgs/fastgpt-loop-node-example-5.png)
- 数组输入:选择上一步代码运行节点的输出变量 `chunks`。
- 循环体内添加一个【代码运行】节点,对源文本进行格式化。

View File

@@ -212,7 +212,7 @@ export default async function (ctx: FunctionContext): Promise<IResponse>{
![](/imgs/translate13.png)
## 批量运
## 循环执
长文反思翻译比较关键的一个部分,就是对多个文本块进行循环反思翻译

View File

@@ -91,9 +91,9 @@ weight: 604
这个过程不仅提高了效率,还最大限度地减少了人为错误的可能性。
## 批量运
## 循环执
为了处理整个长字幕文件,我们需要一个批量运行机制。这是通过一个简单但有效的判断模块实现的:
为了处理整个长字幕文件,我们需要一个循环执行机制。这是通过一个简单但有效的判断模块实现的:
1. 检查当前翻译的文本块是否为最后一个。
2. 如果不是,则将工作流重定向到格式化原文本块节点。

View File

@@ -33,7 +33,7 @@ export const defaultWhisperConfig: AppWhisperConfigType = {
export const defaultQGConfig: AppQGConfigType = {
open: false,
model: 'gpt-4o-mini',
customPrompt: ''
customPrompt: PROMPT_QUESTION_GUIDE
};
export const defaultChatInputGuideConfig = {

View File

@@ -34,7 +34,7 @@ export function getSourceNameIcon({
}
} catch (error) {}
return 'file/fill/file';
return 'file/fill/manual';
}
/* get dataset data default index */

View File

@@ -12,8 +12,7 @@ const staticPluginList = [
'DingTalkWebhook',
'WeWorkWebhook',
'google',
'bing',
'delay'
'bing'
];
// Run in worker thread (Have npm packages)
const packagePluginList = [

View File

@@ -1,18 +0,0 @@
import { delay } from '@fastgpt/global/common/system/utils';
type Props = {
ms: number;
};
type Response = Promise<Number>;
const main = async ({ ms }: Props): Response => {
if (typeof ms !== 'number' || ms <= 0 || ms > 300000) {
return ms;
}
await delay(ms);
return ms;
};
export default main;

View File

@@ -1,318 +0,0 @@
{
"author": "collin",
"version": "4817",
"name": "流程等待",
"avatar": "core/workflow/template/sleep",
"intro": "让工作流等待指定时间后运行",
"showStatus": true,
"weight": 1,
"isTool": true,
"templateType": "tools",
"workflow": {
"nodes": [
{
"nodeId": "pluginInput",
"name": "workflow:template.plugin_start",
"intro": "workflow:intro_plugin_input",
"avatar": "core/workflow/template/workflowStart",
"flowNodeType": "pluginInput",
"showStatus": false,
"position": {
"x": 627.6352390819724,
"y": -165.05298493910118
},
"version": "481",
"inputs": [
{
"renderTypeList": ["numberInput", "reference"],
"selectedTypeIndex": 0,
"valueType": "number",
"canEdit": true,
"key": "延迟时长",
"label": "延迟时长",
"description": "需要暂停的时间,单位毫秒",
"defaultValue": 1000,
"list": [
{
"label": "",
"value": ""
}
],
"maxFiles": 5,
"canSelectFile": true,
"canSelectImg": true,
"required": true,
"toolDescription": "需要暂停的时间,单位毫秒",
"max": 300000,
"min": 1
}
],
"outputs": [
{
"id": "ms",
"valueType": "number",
"key": "延迟时长",
"label": "延迟时长",
"type": "hidden"
}
]
},
{
"nodeId": "pluginOutput",
"name": "common:core.module.template.self_output",
"intro": "workflow:intro_custom_plugin_output",
"avatar": "core/workflow/template/pluginOutput",
"flowNodeType": "pluginOutput",
"showStatus": false,
"position": {
"x": 1921.839722563351,
"y": -160.05298493910115
},
"version": "481",
"inputs": [
{
"renderTypeList": ["reference"],
"valueType": "any",
"canEdit": true,
"key": "result",
"label": "result",
"isToolOutput": true,
"description": "",
"required": true,
"value": ["zCJC6zw7c14i", "httpRawResponse"]
}
],
"outputs": []
},
{
"nodeId": "pluginConfig",
"name": "common:core.module.template.system_config",
"intro": "",
"avatar": "core/workflow/template/systemConfig",
"flowNodeType": "pluginConfig",
"position": {
"x": 184.66337662472682,
"y": -216.05298493910115
},
"version": "4811",
"inputs": [],
"outputs": []
},
{
"nodeId": "zCJC6zw7c14i",
"name": "HTTP 请求",
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
"avatar": "core/workflow/template/httpRequest",
"flowNodeType": "httpRequest468",
"showStatus": true,
"position": {
"x": 1154.4041630064592,
"y": -455.0529849391012
},
"version": "481",
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
"description": "common:core.module.input.description.HTTP Dynamic Input",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"valueDesc": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpMethod",
"renderTypeList": ["custom"],
"valueType": "string",
"label": "",
"value": "POST",
"required": true,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpTimeout",
"renderTypeList": ["custom"],
"valueType": "number",
"label": "",
"value": 30,
"min": 5,
"max": 600,
"required": true,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpReqUrl",
"renderTypeList": ["hidden"],
"valueType": "string",
"label": "",
"description": "common:core.module.input.description.Http Request Url",
"placeholder": "https://api.ai.com/getInventory",
"required": false,
"value": "delay",
"valueDesc": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpHeader",
"renderTypeList": ["custom"],
"valueType": "any",
"value": [],
"label": "",
"description": "common:core.module.input.description.Http Request Header",
"placeholder": "common:core.module.input.description.Http Request Header",
"required": false,
"valueDesc": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpParams",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpJsonBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": "{\n\"ms\": {{$pluginInput.ms$}}\n}",
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpFormBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpContentType",
"renderTypeList": ["hidden"],
"valueType": "string",
"value": "json",
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
}
],
"outputs": [
{
"id": "error",
"key": "error",
"label": "workflow:request_error",
"description": "HTTP请求错误信息成功时返回空",
"valueType": "object",
"type": "static"
},
{
"id": "httpRawResponse",
"key": "httpRawResponse",
"required": true,
"label": "workflow:raw_response",
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
"valueType": "any",
"type": "static"
},
{
"id": "system_addOutputParam",
"key": "system_addOutputParam",
"type": "dynamic",
"valueType": "dynamic",
"label": "输出字段提取",
"customFieldConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": false
},
"description": "可以通过 JSONPath 语法来提取响应值中的指定字段",
"valueDesc": ""
}
]
}
],
"edges": [
{
"source": "pluginInput",
"target": "zCJC6zw7c14i",
"sourceHandle": "pluginInput-source-right",
"targetHandle": "zCJC6zw7c14i-target-left"
},
{
"source": "zCJC6zw7c14i",
"target": "pluginOutput",
"sourceHandle": "zCJC6zw7c14i-source-right",
"targetHandle": "pluginOutput-target-left"
}
],
"chatConfig": {
"welcomeText": ""
}
}
}

View File

@@ -72,7 +72,6 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
showStatus: false
};
});
const runtimeVariables = {
...filterSystemVariables(props.variables),
appId: String(plugin.id)

View File

@@ -127,16 +127,8 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
if (typeof val === 'object') return JSON.stringify(val);
if (typeof val === 'string') {
try {
const parsed = JSON.parse(val);
if (typeof parsed === 'object') {
return JSON.stringify(parsed);
}
return val;
} catch (error) {
const str = JSON.stringify(val);
return str.startsWith('"') && str.endsWith('"') ? str.slice(1, -1) : str;
}
const str = JSON.stringify(val);
return str.startsWith('"') && str.endsWith('"') ? str.slice(1, -1) : str;
}
return String(val);
@@ -243,9 +235,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
}
if (!httpJsonBody) return {};
if (httpContentType === ContentTypes.json) {
httpJsonBody = replaceJsonBodyString(httpJsonBody);
console.log(httpJsonBody);
return json5.parse(httpJsonBody);
return json5.parse(replaceJsonBodyString(httpJsonBody));
}
// Raw text, xml

View File

@@ -270,7 +270,6 @@ export const iconPaths = {
import('./icons/core/workflow/template/datasource.svg'),
'core/workflow/template/duckduckgo': () =>
import('./icons/core/workflow/template/duckduckgo.svg'),
'core/workflow/template/sleep': () => import('./icons/core/workflow/template/sleep.svg'),
'core/workflow/template/extractJson': () =>
import('./icons/core/workflow/template/extractJson.svg'),
'core/workflow/template/fetchUrl': () => import('./icons/core/workflow/template/fetchUrl.svg'),

View File

@@ -1,7 +0,0 @@
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
width="200" height="200">
<path d="M-70.54222222-70.54222222m582.54222222 0l0 0q582.54222222 0 582.54222222 582.54222222l0 0q0 582.54222222-582.54222222 582.54222222l0 0q-582.54222222 0-582.54222222-582.54222222l0 0q0-582.54222222 582.54222222-582.54222222Z"
fill="#1296db"/>
<path d="M422.64477924 243.86730561H243.86730561v536.26538878h178.77747363V243.86730561zM780.13269439 243.86730561h-178.77747363v536.26538878H780.13269439V243.86730561z"
fill="#ffffff"/>
</svg>

Before

Width:  |  Height:  |  Size: 593 B

View File

@@ -135,7 +135,7 @@ const MySelect = <T = any,>(
))}
</>
);
}, [list, value]);
}, []);
return (
<Box

View File

@@ -29,6 +29,8 @@ export type ChatProviderProps = {
outLinkAuthData?: OutLinkChatAuthProps;
chatType: 'log' | 'chat' | 'share' | 'team';
showRawSource: boolean;
showNodeStatus: boolean;
};
type useChatStoreType = ChatProviderProps & {
@@ -130,6 +132,8 @@ const Provider = ({
chatId,
outLinkAuthData = {},
chatType = 'chat',
showRawSource,
showNodeStatus,
children,
...props
}: ChatProviderProps & {
@@ -245,7 +249,9 @@ const Provider = ({
chatId,
outLinkAuthData,
getHistoryResponseData,
chatType
chatType,
showRawSource,
showNodeStatus
};
return <ChatBoxContext.Provider value={value}>{children}</ChatBoxContext.Provider>;

View File

@@ -25,7 +25,6 @@ import { isEqual } from 'lodash';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { formatTimeToChatItemTime } from '@fastgpt/global/common/string/time';
import dayjs from 'dayjs';
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
const colorMap = {
[ChatStatusEnum.loading]: {
@@ -140,7 +139,7 @@ const ChatItem = (props: Props) => {
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
const chatType = useContextSelector(ChatBoxContext, (v) => v.chatType);
const showNodeStatus = useContextSelector(ChatItemContext, (v) => v.showNodeStatus);
const showNodeStatus = useContextSelector(ChatBoxContext, (v) => v.showNodeStatus);
const isChatLog = chatType === 'log';
const { copyData } = useCopyData();

View File

@@ -9,16 +9,19 @@ import RawSourceBox from '@/components/core/dataset/RawSourceBox';
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
import { useContextSelector } from 'use-context-selector';
import { ChatBoxContext } from '../Provider';
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
const QuoteModal = ({
rawSearch = [],
onClose,
canEditDataset,
showRawSource,
chatItemId,
metadata
}: {
rawSearch: SearchDataResponseItemType[];
onClose: () => void;
canEditDataset: boolean;
showRawSource: boolean;
chatItemId: string;
metadata?: {
collectionId: string;
@@ -44,11 +47,6 @@ const QuoteModal = ({
chatItemId,
...(v.outLinkAuthData || {})
}));
const showRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource);
const showRouteToDatasetDetail = useContextSelector(
ChatItemContext,
(v) => v.showRouteToDatasetDetail
);
return (
<>
@@ -73,7 +71,12 @@ const QuoteModal = ({
}
>
<ModalBody>
<QuoteList rawSearch={filterResults} chatItemId={chatItemId} />
<QuoteList
rawSearch={filterResults}
canEditDataset={canEditDataset}
canViewSource={showRawSource}
chatItemId={chatItemId}
/>
</ModalBody>
</MyModal>
</>
@@ -84,10 +87,14 @@ export default QuoteModal;
export const QuoteList = React.memo(function QuoteList({
chatItemId,
rawSearch = []
rawSearch = [],
canEditDataset,
canViewSource
}: {
chatItemId?: string;
rawSearch: SearchDataResponseItemType[];
canEditDataset: boolean;
canViewSource: boolean;
}) {
const theme = useTheme();
@@ -97,11 +104,6 @@ export const QuoteList = React.memo(function QuoteList({
chatId: v.chatId,
...(v.outLinkAuthData || {})
}));
const showRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource);
const showRouteToDatasetDetail = useContextSelector(
ChatItemContext,
(v) => v.showRouteToDatasetDetail
);
return (
<>
@@ -118,8 +120,8 @@ export const QuoteList = React.memo(function QuoteList({
>
<QuoteItem
quoteItem={item}
canViewSource={showRawSource}
canEditDataset={showRouteToDatasetDetail}
canViewSource={canViewSource}
canEditDataset={canEditDataset}
{...RawSourceBoxProps}
/>
</Box>

View File

@@ -49,6 +49,7 @@ const ResponseTags = ({
const [quoteFolded, setQuoteFolded] = useState<boolean>(true);
const chatType = useContextSelector(ChatBoxContext, (v) => v.chatType);
const showRawSource = useContextSelector(ChatBoxContext, (v) => v.showRawSource);
const notSharePage = useMemo(() => chatType !== 'share', [chatType]);
const {
@@ -250,6 +251,8 @@ const ResponseTags = ({
<QuoteModal
{...quoteModalData}
chatItemId={historyItem.dataId}
canEditDataset={notSharePage}
showRawSource={showRawSource}
onClose={() => setQuoteModalData(undefined)}
/>
)}

View File

@@ -18,6 +18,7 @@ import { Box, Checkbox } from '@chakra-ui/react';
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
import { useForm } from 'react-hook-form';
import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useTranslation } from 'next-i18next';
import {

View File

@@ -249,7 +249,14 @@ export const WholeResponseContent = ({
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
<Row
label={t('common:core.chat.response.module quoteList')}
rawDom={<QuoteList chatItemId={dataId} rawSearch={activeModule.quoteList} />}
rawDom={
<QuoteList
canEditDataset
canViewSource
chatItemId={dataId}
rawSearch={activeModule.quoteList}
/>
}
/>
)}
</>

View File

@@ -164,6 +164,8 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => {
showMarkIcon
showVoiceIcon={false}
chatType="log"
showRawSource
showNodeStatus
/>
)}
</Box>
@@ -185,12 +187,7 @@ const Render = (props: Props) => {
}, [appId, chatId]);
return (
<ChatItemContextProvider
showRouteToAppDetail={true}
showRouteToDatasetDetail={true}
isShowReadRawSource={true}
showNodeStatus
>
<ChatItemContextProvider>
<ChatRecordContextProvider params={params}>
<DetailLogsModal {...props} />
</ChatRecordContextProvider>

View File

@@ -91,12 +91,7 @@ const Render = ({ appForm }: Props) => {
);
return (
<ChatItemContextProvider
showRouteToAppDetail={true}
showRouteToDatasetDetail={true}
isShowReadRawSource={true}
showNodeStatus
>
<ChatItemContextProvider>
<ChatRecordContextProvider params={chatRecordProviderParams}>
<ChatTest appForm={appForm} />
</ChatRecordContextProvider>

View File

@@ -158,12 +158,7 @@ const Render = (Props: Props) => {
);
return (
<ChatItemContextProvider
showRouteToAppDetail={true}
showRouteToDatasetDetail={true}
isShowReadRawSource={true}
showNodeStatus
>
<ChatItemContextProvider>
<ChatRecordContextProvider params={chatRecordProviderParams}>
<ChatTest {...Props} />
</ChatRecordContextProvider>

View File

@@ -14,18 +14,22 @@ import RenderToolInput from './render/RenderToolInput';
import RenderOutput from './render/RenderOutput';
import CodeEditor from '@fastgpt/web/components/common/Textarea/CodeEditor';
import { Box, Flex } from '@chakra-ui/react';
import { useI18n } from '@/web/context/I18n';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { JS_TEMPLATE } from '@fastgpt/global/core/workflow/template/system/sandbox/constants';
import { getLatestNodeTemplate } from '@/web/core/workflow/utils';
import { CodeNode } from '@fastgpt/global/core/workflow/template/system/sandbox';
const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { workflowT } = useI18n();
const { nodeId, inputs, outputs } = data;
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
const onChangeNode = useContextSelector(WorkflowContext, (ctx) => ctx.onChangeNode);
const { splitToolInputs, onChangeNode, onResetNode } = useContextSelector(
WorkflowContext,
(ctx) => ctx
);
const { ConfirmModal, openConfirm } = useConfirm({
content: t('workflow:code.Reset template confirm')
content: workflowT('code.Reset template confirm')
});
const CustomComponent = useMemo(() => {
@@ -34,24 +38,19 @@ const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
return (
<Box mt={-3}>
<Flex mb={2} alignItems={'flex-end'}>
<Box flex={'1'}>{'Javascript ' + t('workflow:Code')}</Box>
<Box flex={'1'}>{'Javascript ' + workflowT('Code')}</Box>
<Box
cursor={'pointer'}
color={'primary.500'}
fontSize={'xs'}
onClick={openConfirm(() => {
onChangeNode({
nodeId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: JS_TEMPLATE
}
onResetNode({
id: nodeId,
node: getLatestNodeTemplate(data, CodeNode)
});
})}
>
{t('workflow:code.Reset template')}
{workflowT('code.Reset template')}
</Box>
</Flex>
<CodeEditor
@@ -74,33 +73,37 @@ const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
);
}
};
}, [nodeId, onChangeNode, openConfirm, t]);
}, [data, nodeId, onChangeNode, onResetNode, openConfirm, workflowT]);
const { isTool, commonInputs } = splitToolInputs(inputs, nodeId);
const Render = useMemo(() => {
const { isTool, commonInputs } = splitToolInputs(inputs, nodeId);
return (
<NodeCard minW={'400px'} selected={selected} {...data}>
{isTool && (
<>
<Container>
<RenderToolInput nodeId={nodeId} inputs={inputs} />
</Container>
</>
)}
<Container>
<IOTitle text={t('common:common.Input')} mb={-1} />
<RenderInput
nodeId={nodeId}
flowInputList={commonInputs}
CustomComponent={CustomComponent}
/>
</Container>
<Container>
<IOTitle text={t('common:common.Output')} />
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
</Container>
<ConfirmModal />
</NodeCard>
);
return (
<NodeCard minW={'400px'} selected={selected} {...data}>
{isTool && (
<>
<Container>
<RenderToolInput nodeId={nodeId} inputs={inputs} />
</Container>
</>
)}
<Container>
<IOTitle text={t('common:common.Input')} mb={-1} />
<RenderInput
nodeId={nodeId}
flowInputList={commonInputs}
CustomComponent={CustomComponent}
/>
</Container>
<Container>
<IOTitle text={t('common:common.Output')} />
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
</Container>
<ConfirmModal />
</NodeCard>
);
}, [ConfirmModal, CustomComponent, data, inputs, nodeId, outputs, selected, splitToolInputs, t]);
return Render;
};
export default React.memo(NodeCode);

View File

@@ -141,6 +141,8 @@ export const useChatTest = ({
chatId={chatId}
showMarkIcon
chatType="chat"
showRawSource
showNodeStatus
onStartChat={startChat}
/>
)

View File

@@ -27,11 +27,13 @@ const ChatHeader = ({
history,
showHistory,
apps,
onRouteToAppDetail,
totalRecordsCount
}: {
history: ChatItemType[];
showHistory?: boolean;
apps?: AppListItemType[];
onRouteToAppDetail?: () => void;
totalRecordsCount: number;
}) => {
const { t } = useTranslation();
@@ -69,7 +71,7 @@ const ChatHeader = ({
)}
{/* control */}
{!isPlugin && <ToolMenu history={history} />}
{!isPlugin && <ToolMenu history={history} onRouteToAppDetail={onRouteToAppDetail} />}
</Flex>
);
};

View File

@@ -28,6 +28,7 @@ type HistoryItemType = {
const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) => {
const theme = useTheme();
const router = useRouter();
const isUserChatPage = router.pathname === '/chat';
const { t } = useTranslation();
@@ -45,7 +46,6 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) =
const appName = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.name);
const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.avatar);
const showRouteToAppDetail = useContextSelector(ChatItemContext, (v) => v.showRouteToAppDetail);
const concatHistory = useMemo(() => {
const formatHistories: HistoryItemType[] = histories.map((item) => {
@@ -77,8 +77,8 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) =
});
const canRouteToDetail = useMemo(
() => appId && userInfo?.team.permission.hasWritePer && showRouteToAppDetail,
[appId, userInfo?.team.permission.hasWritePer, showRouteToAppDetail]
() => appId && userInfo?.team.permission.hasWritePer,
[appId, userInfo?.team.permission.hasWritePer]
);
return (
@@ -287,7 +287,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) =
</ScrollData>
{/* exec */}
{!isPc && !!canRouteToDetail && (
{!isPc && isUserChatPage && (
<Flex
mt={2}
borderTop={theme.borders.base}

View File

@@ -14,8 +14,6 @@ import {
} from '@fastgpt/global/common/parentFolder/type';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import dynamic from 'next/dynamic';
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import { useContextSelector } from 'use-context-selector';
const SelectOneResource = dynamic(() => import('@/components/common/folder/SelectOneResource'));
@@ -24,8 +22,6 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
const router = useRouter();
const isTeamChat = router.pathname === '/chat/team';
const showRouteToAppDetail = useContextSelector(ChatItemContext, (v) => v.showRouteToAppDetail);
const getAppList = useCallback(async ({ parentId }: GetResourceFolderListProps) => {
return getMyApps({
parentId,
@@ -54,36 +50,34 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
return (
<Flex flexDirection={'column'} h={'100%'}>
{showRouteToAppDetail && (
<>
<Box mt={4} px={4}>
<Flex
alignItems={'center'}
cursor={'pointer'}
py={2}
px={3}
borderRadius={'md'}
_hover={{ bg: 'myGray.200' }}
onClick={() => router.push('/app/list')}
>
<IconButton
mr={3}
icon={<MyIcon name={'common/backFill'} w={'1rem'} color={'primary.500'} />}
bg={'white'}
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
size={'smSquare'}
borderRadius={'50%'}
aria-label={''}
/>
{t('common:core.chat.Exit Chat')}
</Flex>
</Box>
<MyDivider h={2} my={1} />
</>
)}
<Box mt={4} px={4}>
{!isTeamChat && (
<Flex
alignItems={'center'}
cursor={'pointer'}
py={2}
px={3}
borderRadius={'md'}
_hover={{ bg: 'myGray.200' }}
onClick={() => router.push('/app/list')}
>
<IconButton
mr={3}
icon={<MyIcon name={'common/backFill'} w={'1rem'} color={'primary.500'} />}
bg={'white'}
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
size={'smSquare'}
borderRadius={'50%'}
aria-label={''}
/>
{t('common:core.chat.Exit Chat')}
</Flex>
)}
</Box>
{!isTeamChat && (
<>
<MyDivider h={2} my={1} />
<HStack
px={4}
my={2}

View File

@@ -7,19 +7,20 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { useContextSelector } from 'use-context-selector';
import { ChatContext } from '@/web/core/chat/context/chatContext';
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import { useRouter } from 'next/router';
const ToolMenu = ({ history }: { history: ChatItemType[] }) => {
const router = useRouter();
const ToolMenu = ({
history,
onRouteToAppDetail
}: {
history: ChatItemType[];
onRouteToAppDetail?: () => void;
}) => {
const { t } = useTranslation();
const { onExportChat } = useChatBox();
const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId);
const chatData = useContextSelector(ChatItemContext, (v) => v.chatBoxData);
const showRouteToAppDetail = useContextSelector(ChatItemContext, (v) => v.showRouteToAppDetail);
return (
return history.length > 0 ? (
<MyMenu
Button={
<IconButton
@@ -60,14 +61,14 @@ const ToolMenu = ({ history }: { history: ChatItemType[] }) => {
// }
]
},
...(showRouteToAppDetail
...(onRouteToAppDetail
? [
{
children: [
{
icon: 'core/app/aiLight',
label: t('app:app_detail'),
onClick: () => router.push(`/app/detail?appId=${chatData.appId}`)
onClick: onRouteToAppDetail
}
]
}
@@ -75,6 +76,8 @@ const ToolMenu = ({ history }: { history: ChatItemType[] }) => {
: [])
]}
/>
) : (
<Box w={'28px'} h={'28px'} />
);
};

View File

@@ -44,7 +44,6 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
const theme = useTheme();
const { t } = useTranslation();
const { isPc } = useSystem();
const { userInfo } = useUserStore();
const { setLastChatAppId, chatId, appId, outLinkAuthData } = useChatStore();
@@ -187,6 +186,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
apps={myApps}
history={chatRecords}
showHistory
onRouteToAppDetail={() => router.push(`/app/detail?appId=${appId}`)}
/>
{/* chat box */}
@@ -208,6 +208,8 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
feedbackType={'user'}
onStartChat={onStartChat}
chatType={'chat'}
showRawSource
showNodeStatus
isReady={!loading}
/>
)}
@@ -219,8 +221,8 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
);
};
const Render = (props: { appId: string; isStandalone?: string }) => {
const { appId, isStandalone } = props;
const Render = (props: { appId: string }) => {
const { appId } = props;
const { t } = useTranslation();
const { toast } = useToast();
const router = useRouter();
@@ -274,12 +276,7 @@ const Render = (props: { appId: string; isStandalone?: string }) => {
return source === ChatSourceEnum.online ? (
<ChatContextProvider params={chatHistoryProviderParams}>
<ChatItemContextProvider
showRouteToAppDetail={isStandalone !== '1'}
showRouteToDatasetDetail={isStandalone !== '1'}
isShowReadRawSource={true}
showNodeStatus
>
<ChatItemContextProvider>
<ChatRecordContextProvider params={chatRecordProviderParams}>
<Chat myApps={myApps} />
</ChatRecordContextProvider>
@@ -292,7 +289,6 @@ export async function getServerSideProps(context: any) {
return {
props: {
appId: context?.query?.appId || '',
isStandalone: context?.query?.isStandalone || '',
...(await serviceSideProps(context, ['file', 'app', 'chat', 'workflow']))
}
};

View File

@@ -55,6 +55,7 @@ type Props = {
const OutLink = (props: Props) => {
const { t } = useTranslation();
const router = useRouter();
const { showRawSource, showNodeStatus } = props;
const {
shareId = '',
showHistory = '1',
@@ -286,6 +287,8 @@ const OutLink = (props: Props) => {
feedbackType={'user'}
onStartChat={startChat}
chatType="share"
showRawSource={showRawSource}
showNodeStatus={showNodeStatus}
/>
)}
</Box>
@@ -337,12 +340,7 @@ const Render = (props: Props) => {
return source === ChatSourceEnum.share ? (
<ChatContextProvider params={chatHistoryProviderParams}>
<ChatItemContextProvider
showRouteToAppDetail={false}
showRouteToDatasetDetail={false}
isShowReadRawSource={props.showRawSource}
showNodeStatus={props.showNodeStatus}
>
<ChatItemContextProvider>
<ChatRecordContextProvider params={chatRecordProviderParams}>
<OutLink {...props} />
</ChatRecordContextProvider>

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import NextHead from '@/components/common/NextHead';
import { getTeamChatInfo } from '@/web/core/chat/api';
import { useRouter } from 'next/router';
@@ -20,7 +20,8 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
import { AppListItemType } from '@fastgpt/global/core/app/type';
import { useContextSelector } from 'use-context-selector';
import { GetChatTypeEnum } from '@/global/core/chat/constants';
import { InitChatResponse } from '@/global/core/chat/api';
import { defaultChatData, GetChatTypeEnum } from '@/global/core/chat/constants';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { getNanoid } from '@fastgpt/global/common/string/tools';
@@ -225,6 +226,8 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
feedbackType={'user'}
onStartChat={startChat}
chatType="team"
showRawSource
showNodeStatus
/>
)}
</Box>
@@ -296,12 +299,7 @@ const Render = (props: Props) => {
return source === ChatSourceEnum.team ? (
<ChatContextProvider params={contextParams}>
<ChatItemContextProvider
showRouteToAppDetail={false}
showRouteToDatasetDetail={false}
isShowReadRawSource={true}
showNodeStatus
>
<ChatItemContextProvider>
<ChatRecordContextProvider params={chatRecordProviderParams}>
<Chat {...props} myApps={myApps} />
</ChatRecordContextProvider>

View File

@@ -9,12 +9,6 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { AppChatConfigType, VariableItemType } from '@fastgpt/global/core/app/type';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
type ContextProps = {
showRouteToAppDetail: boolean;
showRouteToDatasetDetail: boolean;
isShowReadRawSource: boolean;
showNodeStatus: boolean;
};
type ChatBoxDataType = {
appId: string;
title?: string;
@@ -43,7 +37,7 @@ type ChatItemContextType = {
chatBoxData: ChatBoxDataType;
setChatBoxData: React.Dispatch<React.SetStateAction<ChatBoxDataType>>;
isPlugin: boolean;
} & ContextProps;
};
export const ChatItemContext = createContext<ChatItemContextType>({
ChatBoxRef: null,
@@ -67,15 +61,7 @@ export const ChatItemContext = createContext<ChatItemContextType>({
/*
Chat 对象的上下文
*/
const ChatItemContextProvider = ({
children,
showRouteToAppDetail,
showRouteToDatasetDetail,
isShowReadRawSource,
showNodeStatus
}: {
children: ReactNode;
} & ContextProps) => {
const ChatItemContextProvider = ({ children }: { children: ReactNode }) => {
const ChatBoxRef = useRef<ChatComponentRef>(null);
const variablesForm = useForm<ChatBoxInputFormType>();
@@ -127,23 +113,16 @@ const ChatItemContextProvider = ({
pluginRunTab,
setPluginRunTab,
resetVariables,
clearChatRecords,
showRouteToAppDetail,
showRouteToDatasetDetail,
isShowReadRawSource,
showNodeStatus
clearChatRecords
};
}, [
chatBoxData,
setChatBoxData,
clearChatRecords,
isPlugin,
variablesForm,
pluginRunTab,
resetVariables,
clearChatRecords,
showRouteToAppDetail,
showRouteToDatasetDetail,
isShowReadRawSource,
showNodeStatus
variablesForm
]);
return <ChatItemContext.Provider value={contextValue}>{children}</ChatItemContext.Provider>;