Compare commits
38 Commits
v4.8.12
...
v4.8.13-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
608e58ba41 | ||
|
|
044b0c57f7 | ||
|
|
7d7454ef3b | ||
|
|
0d658c0114 | ||
|
|
d58cf44778 | ||
|
|
7537330a3b | ||
|
|
a7f881fc5e | ||
|
|
fc7304d3cd | ||
|
|
aa50174066 | ||
|
|
5b2cc097b0 | ||
|
|
7a933f73b6 | ||
|
|
3e5d7d0d7a | ||
|
|
d15ec1ae69 | ||
|
|
3b82ed0aa1 | ||
|
|
dc95ab1dc1 | ||
|
|
fa2fbc1ddd | ||
|
|
10421d73f4 | ||
|
|
a9ee6e6a5e | ||
|
|
0f1932aadc | ||
|
|
65a39e80b8 | ||
|
|
0db0cbf376 | ||
|
|
f4dbe7c021 | ||
|
|
07b3a0a35d | ||
|
|
fd49ad1342 | ||
|
|
f90803c558 | ||
|
|
49cd2d7a3c | ||
|
|
727bd7144c | ||
|
|
469858877e | ||
|
|
7a929db0a5 | ||
|
|
0645b274da | ||
|
|
cf8786b194 | ||
|
|
be6269688b | ||
|
|
912b264a47 | ||
|
|
7ef1821557 | ||
|
|
4061b11922 | ||
|
|
bc171db945 | ||
|
|
eb365fef44 | ||
|
|
2e7047cb3b |
42
.github/workflows/fastgpt-image.yml
vendored
@@ -90,3 +90,45 @@ jobs:
|
||||
-t ${Docker_Hub_Tag} \
|
||||
-t ${Docker_Hub_Latest} \
|
||||
.
|
||||
build-fastgpt-images-child-route:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
# Set tag
|
||||
- name: Set image name and tag
|
||||
run: |
|
||||
if [[ "${{ github.ref_name }}" == "main" ]]; then
|
||||
echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt-child-route:latest" >> $GITHUB_ENV
|
||||
echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt-child-route:latest" >> $GITHUB_ENV
|
||||
echo "Ali_Tag=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-child-route:latest" >> $GITHUB_ENV
|
||||
echo "Ali_Latest=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-child-route:latest" >> $GITHUB_ENV
|
||||
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-child-route:latest" >> $GITHUB_ENV
|
||||
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-child-route:latest" >> $GITHUB_ENV
|
||||
else
|
||||
echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt-child-route:${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt-child-route:latest" >> $GITHUB_ENV
|
||||
echo "Ali_Tag=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-child-route:${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
echo "Ali_Latest=${{ secrets.ALI_IMAGE_NAME }}/fastgpt-child-route:latest" >> $GITHUB_ENV
|
||||
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-child-route:${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-child-route:latest" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Build and publish image for main branch or tag push event
|
||||
env:
|
||||
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
|
||||
run: |
|
||||
docker buildx build \
|
||||
-f projects/app/Dockerfile \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--build-arg base_url=fastai \
|
||||
--label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \
|
||||
--label "org.opencontainers.image.description=fastgpt image" \
|
||||
--push \
|
||||
--cache-from=type=local,src=/tmp/.buildx-cache \
|
||||
--cache-to=type=local,dest=/tmp/.buildx-cache \
|
||||
-t ${Git_Tag} \
|
||||
-t ${Git_Latest} \
|
||||
-t ${Ali_Tag} \
|
||||
-t ${Ali_Latest} \
|
||||
-t ${Docker_Hub_Tag} \
|
||||
-t ${Docker_Hub_Latest} \
|
||||
.
|
||||
|
||||
BIN
docSite/assets/imgs/fastgpt-loop-node-config.png
Normal file
|
After Width: | Height: | Size: 369 KiB |
BIN
docSite/assets/imgs/fastgpt-loop-node-example-1.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
docSite/assets/imgs/fastgpt-loop-node-example-2.png
Normal file
|
After Width: | Height: | Size: 249 KiB |
BIN
docSite/assets/imgs/fastgpt-loop-node-example-3.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
docSite/assets/imgs/fastgpt-loop-node-example-4.png
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
docSite/assets/imgs/fastgpt-loop-node-example-5.png
Normal file
|
After Width: | Height: | Size: 288 KiB |
BIN
docSite/assets/imgs/fastgpt-loop-node.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.12(进行中)'
|
||||
title: 'V4.8.12(需要初始化)'
|
||||
description: 'FastGPT V4.8.12 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
@@ -13,8 +13,8 @@ weight: 812
|
||||
|
||||
### 2. 修改镜像
|
||||
|
||||
- 更新 FastGPT 镜像 tag: v4.8.12-beta
|
||||
- 更新 FastGPT 管理端镜像 tag: v4.8.12-beta (fastgpt-pro镜像)
|
||||
- 更新 FastGPT 镜像 tag: v4.8.12-fix
|
||||
- 更新 FastGPT 管理端镜像 tag: v4.8.12 (fastgpt-pro镜像)
|
||||
- Sandbox 镜像,可以不更新
|
||||
|
||||
|
||||
|
||||
25
docSite/content/zh-cn/docs/development/upgrading/4813.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: 'V4.8.13(进行中)'
|
||||
description: 'FastGPT V4.8.13 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 811
|
||||
---
|
||||
|
||||
## 更新说明
|
||||
|
||||
1. 新增 - 数组变量选择支持多选,可以选多个数组或对应的单一数据类型,会自动按选择顺序进行合并。
|
||||
2. 新增 - 文件上传方案调整,节点直接支持接收文件链接,插件自定义变量支持文件上传。
|
||||
3. 新增 - 对话记录增加时间显示。
|
||||
4. 新增 - 工作流校验错误时,跳转至错误节点。
|
||||
5. 新增 - 循环节点增加下标值。
|
||||
6. 新增 - 部分对话错误提醒增加翻译。
|
||||
7. 优化 - 合并多个 system 提示词成 1 个,避免部分模型不支持多个 system 提示词。
|
||||
8. 优化 - 知识库上传文件,优化报错提示。
|
||||
9. 优化 - 全文检索语句,减少一轮查询。
|
||||
10. 优化 - 修改 findLast 为 [...array].reverse().find,适配旧版浏览器。
|
||||
11. 优化 - Markdown 组件自动空格,避免分割 url 中的中文。
|
||||
12. 优化 - 工作流上下文拆分,性能优化。
|
||||
13. 修复 - Dockerfile pnpm install 支持代理。
|
||||
14. 修复 - BI 图表生成无法写入文件。
|
||||
297
docSite/content/zh-cn/docs/workflow/modules/loop.md
Normal file
@@ -0,0 +1,297 @@
|
||||
---
|
||||
title: "循环运行"
|
||||
description: "FastGPT 循环运行节点介绍和使用"
|
||||
icon: "input"
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 366
|
||||
---
|
||||
|
||||
## 节点概述
|
||||
|
||||
【**循环运行**】节点是 FastGPT V4.8.11 版本新增的一个重要功能模块。它允许工作流对数组类型的输入数据进行迭代处理,每次处理数组中的一个元素,并自动执行后续节点,直到完成整个数组的处理。
|
||||
|
||||
这个节点的设计灵感来自编程语言中的循环结构,但以可视化的方式呈现。
|
||||
|
||||

|
||||
|
||||
> 在程序中,节点可以理解为一个个 Function 或者接口。可以理解为它就是一个**步骤**。将多个节点一个个拼接起来,即可一步步的去实现最终的 AI 输出。
|
||||
|
||||
【**循环运行**】节点本质上也是一个 Function,它的主要职责是自动化地重复执行特定的工作流程。
|
||||
|
||||
## 核心特性
|
||||
|
||||
1. **数组批量处理**
|
||||
- 支持输入数组类型数据
|
||||
- 自动遍历数组元素
|
||||
- 保持处理顺序
|
||||
- 支持并行处理 (性能优化)
|
||||
|
||||
2. **自动迭代执行**
|
||||
- 自动触发后续节点
|
||||
- 支持条件终止
|
||||
- 支持循环计数
|
||||
- 维护执行上下文
|
||||
|
||||
3. **与其他节点协同**
|
||||
- 支持与 AI 对话节点配合
|
||||
- 支持与 HTTP 节点配合
|
||||
- 支持与内容提取节点配合
|
||||
- 支持与判断器节点配合
|
||||
|
||||
## 应用场景
|
||||
|
||||
【**循环运行**】节点的主要作用是通过自动化的方式扩展工作流的处理能力,使 FastGPT 能够更好地处理批量任务和复杂的数据处理流程。特别是在处理大规模数据或需要多轮迭代的场景下,循环运行节点能显著提升工作流的效率和自动化程度。
|
||||
|
||||
【**循环运行**】节点特别适合以下场景:
|
||||
|
||||
1. **批量数据处理**
|
||||
- 批量翻译文本
|
||||
- 批量总结文档
|
||||
- 批量生成内容
|
||||
|
||||
2. **数据流水线处理**
|
||||
- 对搜索结果逐条分析
|
||||
- 对知识库检索结果逐条处理
|
||||
- 对 HTTP 请求返回的数组数据逐项处理
|
||||
|
||||
3. **递归或迭代任务**
|
||||
- 长文本分段处理
|
||||
- 多轮优化内容
|
||||
- 链式数据处理
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 输入参数设置
|
||||
|
||||
【**循环运行**】节点需要配置两个核心输入参数:
|
||||
|
||||
1. **数组 (必填)**:接收一个数组类型的输入,可以是:
|
||||
- 字符串数组 (`Array<string>`)
|
||||
- 数字数组 (`Array<number>`)
|
||||
- 布尔数组 (`Array<boolean>`)
|
||||
- 对象数组 (`Array<object>`)
|
||||
|
||||
2. **循环体 (必填)**:定义每次循环需要执行的节点流程,包含:
|
||||
- 循环体开始:标记循环开始的位置。
|
||||
- 循环体结束:标记循环结束的位置,并可选择输出结果变量。
|
||||
|
||||
### 循环体配置
|
||||
|
||||

|
||||
|
||||
1. 在循环体内部,可以添加任意类型的节点,如:
|
||||
- AI 对话节点
|
||||
- HTTP 请求节点
|
||||
- 内容提取节点
|
||||
- 文本加工节点等
|
||||
|
||||
2. 循环体结束节点配置:
|
||||
- 通过下拉菜单选择要输出的变量
|
||||
- 该变量将作为当前循环的结果被收集
|
||||
- 所有循环的结果将组成一个新的数组作为最终输出
|
||||
|
||||
## 场景示例
|
||||
|
||||
### 批量处理数组
|
||||
|
||||
假设我们有一个包含多个文本的数组,需要对每个文本进行 AI 处理。这是循环运行节点最基础也最常见的应用场景。
|
||||
|
||||
#### 实现步骤
|
||||
|
||||
1. 准备输入数组
|
||||
|
||||

|
||||
|
||||
使用【代码运行】节点创建测试数组:
|
||||
|
||||
```javascript
|
||||
const texts = [
|
||||
"这是第一段文本",
|
||||
"这是第二段文本",
|
||||
"这是第三段文本"
|
||||
];
|
||||
return { textArray: texts };
|
||||
```
|
||||
|
||||
2. 配置循环运行节点
|
||||
|
||||

|
||||
|
||||
- 数组输入:选择上一步代码运行节点的输出变量 `textArray`。
|
||||
- 循环体内添加一个【AI 对话】节点,用于处理每个文本。这里我们输入的 prompt 为:`请将这段文本翻译成英文`。
|
||||
- 再添加一个【指定回复】节点,用于输出翻译后的文本。
|
||||
- 循环体结束节点选择输出变量为 AI 回复内容。
|
||||
|
||||
#### 运行流程
|
||||
|
||||

|
||||
|
||||
1. 【代码运行】节点执行,生成测试数组
|
||||
2. 【循环运行】节点接收数组,开始遍历
|
||||
3. 对每个数组元素:
|
||||
- 【AI 对话】节点处理当前元素
|
||||
- 【指定回复】节点输出翻译后的文本
|
||||
- 【循环体结束】节点收集处理结果
|
||||
4. 完成所有元素处理后,输出结果数组
|
||||
|
||||
### 长文本翻译
|
||||
|
||||
在处理长文本翻译时,我们经常会遇到以下挑战:
|
||||
|
||||
- 文本长度超出 LLM 的 token 限制
|
||||
- 需要保持翻译风格的一致性
|
||||
- 需要维护上下文的连贯性
|
||||
- 翻译质量需要多轮优化
|
||||
|
||||
【**循环运行**】节点可以很好地解决这些问题。
|
||||
|
||||
#### 实现步骤
|
||||
|
||||
1. 文本预处理与分段
|
||||
|
||||

|
||||
|
||||
使用【代码运行】节点进行文本分段,代码如下:
|
||||
|
||||
```javascript
|
||||
const MAX_HEADING_LENGTH = 7; // 最大标题长度
|
||||
const MAX_HEADING_CONTENT_LENGTH = 200; // 最大标题内容长度
|
||||
const MAX_HEADING_UNDERLINE_LENGTH = 200; // 最大标题下划线长度
|
||||
const MAX_HTML_HEADING_ATTRIBUTES_LENGTH = 100; // 最大HTML标题属性长度
|
||||
const MAX_LIST_ITEM_LENGTH = 200; // 最大列表项长度
|
||||
const MAX_NESTED_LIST_ITEMS = 6; // 最大嵌套列表项数
|
||||
const MAX_LIST_INDENT_SPACES = 7; // 最大列表缩进空格数
|
||||
const MAX_BLOCKQUOTE_LINE_LENGTH = 200; // 最大块引用行长度
|
||||
const MAX_BLOCKQUOTE_LINES = 15; // 最大块引用行数
|
||||
const MAX_CODE_BLOCK_LENGTH = 1500; // 最大代码块长度
|
||||
const MAX_CODE_LANGUAGE_LENGTH = 20; // 最大代码语言长度
|
||||
const MAX_INDENTED_CODE_LINES = 20; // 最大缩进代码行数
|
||||
const MAX_TABLE_CELL_LENGTH = 200; // 最大表格单元格长度
|
||||
const MAX_TABLE_ROWS = 20; // 最大表格行数
|
||||
const MAX_HTML_TABLE_LENGTH = 2000; // 最大HTML表格长度
|
||||
const MIN_HORIZONTAL_RULE_LENGTH = 3; // 最小水平分隔线长度
|
||||
const MAX_SENTENCE_LENGTH = 400; // 最大句子长度
|
||||
const MAX_QUOTED_TEXT_LENGTH = 300; // 最大引用文本长度
|
||||
const MAX_PARENTHETICAL_CONTENT_LENGTH = 200; // 最大括号内容长度
|
||||
const MAX_NESTED_PARENTHESES = 5; // 最大嵌套括号数
|
||||
const MAX_MATH_INLINE_LENGTH = 100; // 最大行内数学公式长度
|
||||
const MAX_MATH_BLOCK_LENGTH = 500; // 最大数学公式块长度
|
||||
const MAX_PARAGRAPH_LENGTH = 1000; // 最大段落长度
|
||||
const MAX_STANDALONE_LINE_LENGTH = 800; // 最大独立行长度
|
||||
const MAX_HTML_TAG_ATTRIBUTES_LENGTH = 100; // 最大HTML标签属性长度
|
||||
const MAX_HTML_TAG_CONTENT_LENGTH = 1000; // 最大HTML标签内容长度
|
||||
const LOOKAHEAD_RANGE = 100; // 向前查找句子边界的字符数
|
||||
|
||||
const AVOID_AT_START = `[\\s\\]})>,']`; // 避免在开头匹配的字符
|
||||
const PUNCTUATION = `[.!?…]|\\.{3}|[\\u2026\\u2047-\\u2049]|[\\p{Emoji_Presentation}\\p{Extended_Pictographic}]`; // 标点符号
|
||||
const QUOTE_END = `(?:'(?=\`)|''(?=\`\`))`; // 引号结束
|
||||
const SENTENCE_END = `(?:${PUNCTUATION}(?<!${AVOID_AT_START}(?=${PUNCTUATION}))|${QUOTE_END})(?=\\S|$)`; // 句子结束
|
||||
const SENTENCE_BOUNDARY = `(?:${SENTENCE_END}|(?=[\\r\\n]|$))`; // 句子边界
|
||||
const LOOKAHEAD_PATTERN = `(?:(?!${SENTENCE_END}).){1,${LOOKAHEAD_RANGE}}${SENTENCE_END}`; // 向前查找句子结束的模式
|
||||
const NOT_PUNCTUATION_SPACE = `(?!${PUNCTUATION}\\s)`; // 非标点符号空格
|
||||
const SENTENCE_PATTERN = `${NOT_PUNCTUATION_SPACE}(?:[^\\r\\n]{1,{MAX_LENGTH}}${SENTENCE_BOUNDARY}|[^\\r\\n]{1,{MAX_LENGTH}}(?=${PUNCTUATION}|$ {QUOTE_END})(?:${LOOKAHEAD_PATTERN})?)${AVOID_AT_START}*`; // 句子模式
|
||||
|
||||
const regex = new RegExp(
|
||||
"(" +
|
||||
// 1. Headings (Setext-style, Markdown, and HTML-style, with length constraints)
|
||||
`(?:^(?:[#*=-]{1,${MAX_HEADING_LENGTH}}|\\w[^\\r\\n]{0,${MAX_HEADING_CONTENT_LENGTH}}\\r?\\n[-=]{2,${MAX_HEADING_UNDERLINE_LENGTH}}|<h[1-6][^>] {0,${MAX_HTML_HEADING_ATTRIBUTES_LENGTH}}>)[^\\r\\n]{1,${MAX_HEADING_CONTENT_LENGTH}}(?:</h[1-6]>)?(?:\\r?\\n|$))` +
|
||||
"|" +
|
||||
// New pattern for citations
|
||||
`(?:\\[[0-9]+\\][^\\r\\n]{1,${MAX_STANDALONE_LINE_LENGTH}})` +
|
||||
"|" +
|
||||
// 2. List items (bulleted, numbered, lettered, or task lists, including nested, up to three levels, with length constraints)
|
||||
`(?:(?:^|\\r?\\n)[ \\t]{0,3}(?:[-*+•]|\\d{1,3}\\.\\w\\.|\\[[ xX]\\])[ \\t]+${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String (MAX_LIST_ITEM_LENGTH))}` +
|
||||
`(?:(?:\\r?\\n[ \\t]{2,5}(?:[-*+•]|\\d{1,3}\\.\\w\\.|\\[[ xX]\\])[ \\t]+${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String (MAX_LIST_ITEM_LENGTH))}){0,${MAX_NESTED_LIST_ITEMS}}` +
|
||||
`(?:\\r?\\n[ \\t]{4,${MAX_LIST_INDENT_SPACES}}(?:[-*+•]|\\d{1,3}\\.\\w\\.|\\[[ xX]\\])[ \\t]+${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String (MAX_LIST_ITEM_LENGTH))}){0,${MAX_NESTED_LIST_ITEMS}})?)` +
|
||||
"|" +
|
||||
// 3. Block quotes (including nested quotes and citations, up to three levels, with length constraints)
|
||||
`(?:(?:^>(?:>|\\s{2,}){0,2}${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_BLOCKQUOTE_LINE_LENGTH))}\\r?\\n?){1,$ {MAX_BLOCKQUOTE_LINES}})` +
|
||||
"|" +
|
||||
// 4. Code blocks (fenced, indented, or HTML pre/code tags, with length constraints)
|
||||
`(?:(?:^|\\r?\\n)(?:\`\`\`|~~~)(?:\\w{0,${MAX_CODE_LANGUAGE_LENGTH}})?\\r?\\n[\\s\\S]{0,${MAX_CODE_BLOCK_LENGTH}}?(?:\`\`\`|~~~)\\r?\\n?` +
|
||||
`|(?:(?:^|\\r?\\n)(?: {4}|\\t)[^\\r\\n]{0,${MAX_LIST_ITEM_LENGTH}}(?:\\r?\\n(?: {4}|\\t)[^\\r\\n]{0,${MAX_LIST_ITEM_LENGTH}}){0,$ {MAX_INDENTED_CODE_LINES}}\\r?\\n?)` +
|
||||
`|(?:<pre>(?:<code>)?[\\s\\S]{0,${MAX_CODE_BLOCK_LENGTH}}?(?:</code>)?</pre>))` +
|
||||
"|" +
|
||||
// 5. Tables (Markdown, grid tables, and HTML tables, with length constraints)
|
||||
`(?:(?:^|\\r?\\n)(?:\\|[^\\r\\n]{0,${MAX_TABLE_CELL_LENGTH}}\\|(?:\\r?\\n\\|[-:]{1,${MAX_TABLE_CELL_LENGTH}}\\|){0,1}(?:\\r?\\n\\|[^\\r\\n]{0,$ {MAX_TABLE_CELL_LENGTH}}\\|){0,${MAX_TABLE_ROWS}}` +
|
||||
`|<table>[\\s\\S]{0,${MAX_HTML_TABLE_LENGTH}}?</table>))` +
|
||||
"|" +
|
||||
// 6. Horizontal rules (Markdown and HTML hr tag)
|
||||
`(?:^(?:[-*_]){${MIN_HORIZONTAL_RULE_LENGTH},}\\s*$|<hr\\s*/?>)` +
|
||||
"|" +
|
||||
// 10. Standalone lines or phrases (including single-line blocks and HTML elements, with length constraints)
|
||||
`(?!${AVOID_AT_START})(?:^(?:<[a-zA-Z][^>]{0,${MAX_HTML_TAG_ATTRIBUTES_LENGTH}}>)?${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String (MAX_STANDALONE_LINE_LENGTH))}(?:</[a-zA-Z]+>)?(?:\\r?\\n|$))` +
|
||||
"|" +
|
||||
// 7. Sentences or phrases ending with punctuation (including ellipsis and Unicode punctuation)
|
||||
`(?!${AVOID_AT_START})${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_SENTENCE_LENGTH))}` +
|
||||
"|" +
|
||||
// 8. Quoted text, parenthetical phrases, or bracketed content (with length constraints)
|
||||
"(?:" +
|
||||
`(?<!\\w)\"\"\"[^\"]{0,${MAX_QUOTED_TEXT_LENGTH}}\"\"\"(?!\\w)` +
|
||||
`|(?<!\\w)(?:['\"\`'"])[^\\r\\n]{0,${MAX_QUOTED_TEXT_LENGTH}}\\1(?!\\w)` +
|
||||
`|(?<!\\w)\`[^\\r\\n]{0,${MAX_QUOTED_TEXT_LENGTH}}'(?!\\w)` +
|
||||
`|(?<!\\w)\`\`[^\\r\\n]{0,${MAX_QUOTED_TEXT_LENGTH}}''(?!\\w)` +
|
||||
`|\\([^\\r\\n()]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}(?:\\([^\\r\\n()]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}\\)[^\\r\\n()]{0,$ {MAX_PARENTHETICAL_CONTENT_LENGTH}}){0,${MAX_NESTED_PARENTHESES}}\\)` +
|
||||
`|\\[[^\\r\\n\\[\\]]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}(?:\\[[^\\r\\n\\[\\]]{0,${MAX_PARENTHETICAL_CONTENT_LENGTH}}\\][^\\r\\n\\[\\]]{0,$ {MAX_PARENTHETICAL_CONTENT_LENGTH}}){0,${MAX_NESTED_PARENTHESES}}\\]` +
|
||||
`|\\$[^\\r\\n$]{0,${MAX_MATH_INLINE_LENGTH}}\\$` +
|
||||
`|\`[^\`\\r\\n]{0,${MAX_MATH_INLINE_LENGTH}}\`` +
|
||||
")" +
|
||||
"|" +
|
||||
// 9. Paragraphs (with length constraints)
|
||||
`(?!${AVOID_AT_START})(?:(?:^|\\r?\\n\\r?\\n)(?:<p>)?${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_PARAGRAPH_LENGTH))}(?:</p>)?(?=\\r? \\n\\r?\\n|$))` +
|
||||
"|" +
|
||||
// 11. HTML-like tags and their content (including self-closing tags and attributes, with length constraints)
|
||||
`(?:<[a-zA-Z][^>]{0,${MAX_HTML_TAG_ATTRIBUTES_LENGTH}}(?:>[\\s\\S]{0,${MAX_HTML_TAG_CONTENT_LENGTH}}?</[a-zA-Z]+>|\\s*/>))` +
|
||||
"|" +
|
||||
// 12. LaTeX-style math expressions (inline and block, with length constraints)
|
||||
`(?:(?:\\$\\$[\\s\\S]{0,${MAX_MATH_BLOCK_LENGTH}}?\\$\\$)|(?:\\$[^\\$\\r\\n]{0,${MAX_MATH_INLINE_LENGTH}}\\$))` +
|
||||
"|" +
|
||||
// 14. Fallback for any remaining content (with length constraints)
|
||||
`(?!${AVOID_AT_START})${SENTENCE_PATTERN.replace(/{MAX_LENGTH}/g, String(MAX_STANDALONE_LINE_LENGTH))}` +
|
||||
")",
|
||||
"gmu"
|
||||
);
|
||||
|
||||
function main({text}){
|
||||
const chunks = [];
|
||||
let currentChunk = '';
|
||||
const tokens = countToken(text)
|
||||
|
||||
const matches = text.match(regex);
|
||||
if (matches) {
|
||||
matches.forEach((match) => {
|
||||
if (currentChunk.length + match.length <= 1000) {
|
||||
currentChunk += match;
|
||||
} else {
|
||||
if (currentChunk) {
|
||||
chunks.push(currentChunk);
|
||||
}
|
||||
currentChunk = match;
|
||||
}
|
||||
});
|
||||
if (currentChunk) {
|
||||
chunks.push(currentChunk);
|
||||
}
|
||||
}
|
||||
|
||||
return {chunks, tokens};
|
||||
}
|
||||
```
|
||||
|
||||
这里我们用到了 [Jina AI 开源的一个强大的正则表达式](https://x.com/JinaAI_/status/1823756993108304135),它能利用所有可能的边界线索和启发式方法来精确切分文本。
|
||||
|
||||
2. 配置循环运行节点
|
||||
|
||||

|
||||
|
||||
- 数组输入:选择上一步代码运行节点的输出变量 `chunks`。
|
||||
- 循环体内添加一个【代码运行】节点,对源文本进行格式化。
|
||||
- 添加一个【搜索词库】节点,将专有名词的词库作为知识库,在翻译前进行搜索。
|
||||
- 添加一个【AI 对话】节点,使用 CoT 思维链,让 LLM 显式地、系统地生成推理链条,展示翻译的完整思考过程。
|
||||
- 添加一个【代码运行】节点,将【AI 对话】节点最后一轮的翻译结果提取出来。
|
||||
- 添加一个【指定回复】节点,输出翻译后的文本。
|
||||
- 循环体结束节点选择输出变量为【取出翻译文本】的输出变量 `result`。
|
||||
|
||||
|
||||
|
||||
@@ -139,6 +139,8 @@ services:
|
||||
- OPENAI_BASE_URL=http://oneapi:3000/v1
|
||||
# AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改)
|
||||
- CHAT_API_KEY=sk-fastgpt
|
||||
# 是否将图片转成 base64 传递给模型,本地开发和内网环境使用共有模型时候需要设置为 true
|
||||
- MULTIPLE_DATA_TO_BASE64=false
|
||||
# 数据库最大连接数
|
||||
- DB_MAX_LINK=30
|
||||
# 登录凭证密钥
|
||||
|
||||
@@ -97,6 +97,8 @@ services:
|
||||
- OPENAI_BASE_URL=http://oneapi:3000/v1
|
||||
# AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改)
|
||||
- CHAT_API_KEY=sk-fastgpt
|
||||
# 是否将图片转成 base64 传递给模型,本地开发和内网环境使用共有模型时候需要设置为 true
|
||||
- MULTIPLE_DATA_TO_BASE64=false
|
||||
# 数据库最大连接数
|
||||
- DB_MAX_LINK=30
|
||||
# 登录凭证密钥
|
||||
|
||||
@@ -77,6 +77,8 @@ services:
|
||||
- OPENAI_BASE_URL=http://oneapi:3000/v1
|
||||
# AI模型的API Key。(这里默认填写了OneAPI的快速默认key,测试通后,务必及时修改)
|
||||
- CHAT_API_KEY=sk-fastgpt
|
||||
# 是否将图片转成 base64 传递给模型,本地开发和内网环境使用共有模型时候需要设置为 true
|
||||
- MULTIPLE_DATA_TO_BASE64=false
|
||||
# 数据库最大连接数
|
||||
- DB_MAX_LINK=30
|
||||
# 登录凭证密钥
|
||||
|
||||
@@ -16,6 +16,8 @@ export const bucketNameMap = {
|
||||
}
|
||||
};
|
||||
|
||||
export const ReadFileBaseUrl = `${process.env.FE_DOMAIN || ''}/api/common/file/read`;
|
||||
export const ReadFileBaseUrl = `${process.env.FE_DOMAIN || ''}${process.env.NEXT_PUBLIC_BASE_URL}/api/common/file/read`;
|
||||
|
||||
export const documentFileType = '.txt, .docx, .csv, .xlsx, .pdf, .md, .html, .pptx';
|
||||
export const imageFileType =
|
||||
'.jpg, .jpeg, .png, .gif, .bmp, .webp, .svg, .tiff, .tif, .ico, .heic, .heif, .avif';
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { detect } from 'jschardet';
|
||||
import { documentFileType, imageFileType } from './constants';
|
||||
import { ChatFileTypeEnum } from '../../core/chat/constants';
|
||||
import { UserChatItemValueItemType } from '../../core/chat/type';
|
||||
|
||||
export const formatFileSize = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B';
|
||||
@@ -13,3 +16,40 @@ export const formatFileSize = (bytes: number): string => {
|
||||
export const detectFileEncoding = (buffer: Buffer) => {
|
||||
return detect(buffer.slice(0, 200))?.encoding?.toLocaleLowerCase();
|
||||
};
|
||||
|
||||
// Url => user upload file type
|
||||
export const parseUrlToFileType = (url: string): UserChatItemValueItemType['file'] | undefined => {
|
||||
if (typeof url !== 'string') return;
|
||||
const parseUrl = new URL(url, 'https://locaohost:3000');
|
||||
|
||||
const filename = (() => {
|
||||
// Old version file url: https://xxx.com/file/read?filename=xxx.pdf
|
||||
const filenameQuery = parseUrl.searchParams.get('filename');
|
||||
if (filenameQuery) return filenameQuery;
|
||||
|
||||
// Common file: https://xxx.com/xxx.pdf?xxxx=xxx
|
||||
const pathname = parseUrl.pathname;
|
||||
if (pathname) return pathname.split('/').pop();
|
||||
})();
|
||||
|
||||
if (!filename) return;
|
||||
|
||||
const extension = filename.split('.').pop()?.toLowerCase() || '';
|
||||
|
||||
if (!extension) return;
|
||||
|
||||
if (documentFileType.includes(extension)) {
|
||||
return {
|
||||
type: ChatFileTypeEnum.file,
|
||||
name: filename,
|
||||
url
|
||||
};
|
||||
}
|
||||
if (imageFileType.includes(extension)) {
|
||||
return {
|
||||
type: ChatFileTypeEnum.image,
|
||||
name: filename,
|
||||
url
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import dayjs from 'dayjs';
|
||||
import cronParser from 'cron-parser';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import { i18nT } from '../../../web/i18n/utils';
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
@@ -23,31 +24,51 @@ export const formatTimeToChatTime = (time: Date) => {
|
||||
|
||||
// 如果传入时间小于60秒,返回刚刚
|
||||
if (now.diff(target, 'second') < 60) {
|
||||
return '刚刚';
|
||||
return i18nT('common:just_now');
|
||||
}
|
||||
|
||||
// 如果时间是今天,展示几时:几分
|
||||
//用#占位,i18n生效后replace成:
|
||||
if (now.isSame(target, 'day')) {
|
||||
return target.format('HH : mm');
|
||||
return target.format('HH#mm');
|
||||
}
|
||||
|
||||
// 如果是昨天,展示昨天
|
||||
if (now.subtract(1, 'day').isSame(target, 'day')) {
|
||||
return '昨天';
|
||||
}
|
||||
|
||||
// 如果是前天,展示前天
|
||||
if (now.subtract(2, 'day').isSame(target, 'day')) {
|
||||
return '前天';
|
||||
return i18nT('common:yesterday');
|
||||
}
|
||||
|
||||
// 如果是今年,展示某月某日
|
||||
if (now.isSame(target, 'year')) {
|
||||
return target.format('MM/DD');
|
||||
return target.format('MM-DD');
|
||||
}
|
||||
|
||||
// 如果是更久之前,展示某年某月某日
|
||||
return target.format('YYYY/M/D');
|
||||
return target.format('YYYY-M-D');
|
||||
};
|
||||
|
||||
export const formatTimeToChatItemTime = (time: Date) => {
|
||||
const now = dayjs();
|
||||
const target = dayjs(time);
|
||||
const detailTime = target.format('HH#mm');
|
||||
|
||||
// 如果时间是今天,展示几时:几分
|
||||
if (now.isSame(target, 'day')) {
|
||||
return detailTime;
|
||||
}
|
||||
|
||||
// 如果是昨天,展示昨天+几时:几分
|
||||
if (now.subtract(1, 'day').isSame(target, 'day')) {
|
||||
return i18nT('common:yesterday_detail_time');
|
||||
}
|
||||
|
||||
// 如果是今年,展示某月某日+几时:几分
|
||||
if (now.isSame(target, 'year')) {
|
||||
return target.format('MM-DD') + ' ' + detailTime;
|
||||
}
|
||||
|
||||
// 如果是更久之前,展示某年某月某日+几时:几分
|
||||
return target.format('YYYY-M-D') + ' ' + detailTime;
|
||||
};
|
||||
|
||||
/* cron time parse */
|
||||
|
||||
@@ -207,8 +207,8 @@ export const Prompt_systemQuotePromptList: PromptTemplateItem[] = [
|
||||
];
|
||||
|
||||
// Document quote prompt
|
||||
export const Prompt_DocumentQuote = `将 <Reference></Reference> 中的内容作为本次对话的参考:
|
||||
<Reference>
|
||||
export const Prompt_DocumentQuote = `将 <FilesContent></FilesContent> 中的内容作为本次对话的参考:
|
||||
<FilesContent>
|
||||
{{quote}}
|
||||
</Reference>
|
||||
</FilesContent>
|
||||
`;
|
||||
|
||||
@@ -14,7 +14,6 @@ import type {
|
||||
ChatCompletionToolMessageParam
|
||||
} from '../../core/ai/type.d';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '../../core/ai/constants';
|
||||
|
||||
const GPT2Chat = {
|
||||
[ChatCompletionRequestMessageRoleEnum.System]: ChatRoleEnum.System,
|
||||
[ChatCompletionRequestMessageRoleEnum.User]: ChatRoleEnum.Human,
|
||||
@@ -61,14 +60,14 @@ export const chats2GPTMessages = ({
|
||||
return {
|
||||
type: 'image_url',
|
||||
image_url: {
|
||||
url: item.file?.url || ''
|
||||
url: item.file.url
|
||||
}
|
||||
};
|
||||
} else if (item.file?.type === ChatFileTypeEnum.file) {
|
||||
return {
|
||||
type: 'file_url',
|
||||
name: item.file?.name || '',
|
||||
url: item.file?.url || ''
|
||||
url: item.file.url
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
1
packages/global/core/chat/type.d.ts
vendored
@@ -126,6 +126,7 @@ export type ChatSiteItemType = (UserChatItemType | SystemChatItemType | AIChatIt
|
||||
moduleName?: string;
|
||||
ttsBuffer?: Uint8Array;
|
||||
responseData?: ChatHistoryItemResType[];
|
||||
time?: Date;
|
||||
} & ChatBoxInputType &
|
||||
ResponseTagItemType;
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ export const getChatTitleFromChatMessage = (message?: ChatItemType, defaultValue
|
||||
// Keep the first n and last n characters
|
||||
export const getHistoryPreview = (
|
||||
completeMessages: ChatItemType[],
|
||||
size = 100
|
||||
size = 100,
|
||||
useVision = false
|
||||
): {
|
||||
obj: `${ChatRoleEnum}`;
|
||||
value: string;
|
||||
@@ -48,7 +49,8 @@ export const getHistoryPreview = (
|
||||
item.value
|
||||
?.map((item) => {
|
||||
if (item?.text?.content) return item?.text?.content;
|
||||
if (item.file?.type === 'image') return 'Input an image';
|
||||
if (item.file?.type === 'image' && useVision)
|
||||
return `}...)`;
|
||||
return '';
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
@@ -201,6 +201,7 @@ export enum NodeInputKeyEnum {
|
||||
nodeHeight = 'nodeHeight',
|
||||
// loop start
|
||||
loopStartInput = 'loopStartInput',
|
||||
loopStartIndex = 'loopStartIndex',
|
||||
// loop end
|
||||
loopEndInput = 'loopEndInput',
|
||||
|
||||
@@ -256,9 +257,9 @@ export enum NodeOutputKeyEnum {
|
||||
|
||||
// loop
|
||||
loopArray = 'loopArray',
|
||||
|
||||
// loop start
|
||||
loopStartInput = 'loopStartInput',
|
||||
loopStartIndex = 'loopStartIndex',
|
||||
|
||||
// form input
|
||||
formInputResult = 'formInputResult'
|
||||
@@ -334,3 +335,21 @@ export enum ContentTypes {
|
||||
xml = 'xml',
|
||||
raw = 'raw-text'
|
||||
}
|
||||
|
||||
export const ArrayTypeMap: Record<WorkflowIOValueTypeEnum, WorkflowIOValueTypeEnum> = {
|
||||
[WorkflowIOValueTypeEnum.string]: WorkflowIOValueTypeEnum.arrayString,
|
||||
[WorkflowIOValueTypeEnum.number]: WorkflowIOValueTypeEnum.arrayNumber,
|
||||
[WorkflowIOValueTypeEnum.boolean]: WorkflowIOValueTypeEnum.arrayBoolean,
|
||||
[WorkflowIOValueTypeEnum.object]: WorkflowIOValueTypeEnum.arrayObject,
|
||||
[WorkflowIOValueTypeEnum.arrayString]: WorkflowIOValueTypeEnum.arrayString,
|
||||
[WorkflowIOValueTypeEnum.arrayNumber]: WorkflowIOValueTypeEnum.arrayNumber,
|
||||
[WorkflowIOValueTypeEnum.arrayBoolean]: WorkflowIOValueTypeEnum.arrayBoolean,
|
||||
[WorkflowIOValueTypeEnum.arrayObject]: WorkflowIOValueTypeEnum.arrayObject,
|
||||
[WorkflowIOValueTypeEnum.chatHistory]: WorkflowIOValueTypeEnum.arrayObject,
|
||||
[WorkflowIOValueTypeEnum.datasetQuote]: WorkflowIOValueTypeEnum.arrayObject,
|
||||
[WorkflowIOValueTypeEnum.dynamic]: WorkflowIOValueTypeEnum.arrayObject,
|
||||
[WorkflowIOValueTypeEnum.selectDataset]: WorkflowIOValueTypeEnum.arrayObject,
|
||||
[WorkflowIOValueTypeEnum.selectApp]: WorkflowIOValueTypeEnum.arrayObject,
|
||||
[WorkflowIOValueTypeEnum.arrayAny]: WorkflowIOValueTypeEnum.arrayAny,
|
||||
[WorkflowIOValueTypeEnum.any]: WorkflowIOValueTypeEnum.arrayAny
|
||||
};
|
||||
|
||||
@@ -27,7 +27,9 @@ export enum FlowNodeInputTypeEnum { // render ui
|
||||
settingDatasetQuotePrompt = 'settingDatasetQuotePrompt',
|
||||
|
||||
hidden = 'hidden',
|
||||
custom = 'custom'
|
||||
custom = 'custom',
|
||||
|
||||
fileSelect = 'fileSelect'
|
||||
}
|
||||
export const FlowNodeInputMap: Record<
|
||||
FlowNodeInputTypeEnum,
|
||||
@@ -85,6 +87,9 @@ export const FlowNodeInputMap: Record<
|
||||
},
|
||||
[FlowNodeInputTypeEnum.textarea]: {
|
||||
icon: 'core/workflow/inputType/textarea'
|
||||
},
|
||||
[FlowNodeInputTypeEnum.fileSelect]: {
|
||||
icon: 'core/workflow/inputType/file'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -137,43 +142,43 @@ export enum FlowNodeTypeEnum {
|
||||
// node IO value type
|
||||
export const FlowValueTypeMap = {
|
||||
[WorkflowIOValueTypeEnum.string]: {
|
||||
label: 'string',
|
||||
label: 'String',
|
||||
value: WorkflowIOValueTypeEnum.string
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.number]: {
|
||||
label: 'number',
|
||||
label: 'Number',
|
||||
value: WorkflowIOValueTypeEnum.number
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.boolean]: {
|
||||
label: 'boolean',
|
||||
label: 'Boolean',
|
||||
value: WorkflowIOValueTypeEnum.boolean
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.object]: {
|
||||
label: 'object',
|
||||
label: 'Object',
|
||||
value: WorkflowIOValueTypeEnum.object
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.arrayString]: {
|
||||
label: 'array<string>',
|
||||
label: 'Array<string>',
|
||||
value: WorkflowIOValueTypeEnum.arrayString
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.arrayNumber]: {
|
||||
label: 'array<number>',
|
||||
label: 'Array<number>',
|
||||
value: WorkflowIOValueTypeEnum.arrayNumber
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.arrayBoolean]: {
|
||||
label: 'array<boolean>',
|
||||
label: 'Array<boolean>',
|
||||
value: WorkflowIOValueTypeEnum.arrayBoolean
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.arrayObject]: {
|
||||
label: 'array<object>',
|
||||
label: 'Array<object>',
|
||||
value: WorkflowIOValueTypeEnum.arrayObject
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.arrayAny]: {
|
||||
label: 'array',
|
||||
label: 'Array',
|
||||
value: WorkflowIOValueTypeEnum.arrayAny
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.any]: {
|
||||
label: 'any',
|
||||
label: 'Any',
|
||||
value: WorkflowIOValueTypeEnum.any
|
||||
},
|
||||
[WorkflowIOValueTypeEnum.chatHistory]: {
|
||||
|
||||
@@ -135,6 +135,9 @@ export type DispatchNodeResponseType = {
|
||||
extensionResult?: string;
|
||||
extensionTokens?: number;
|
||||
|
||||
// dataset concat
|
||||
concatLength?: number;
|
||||
|
||||
// cq
|
||||
cqList?: ClassifyQuestionAgentItemType[];
|
||||
cqResult?: string;
|
||||
@@ -216,5 +219,7 @@ export type AIChatNodeProps = {
|
||||
[NodeInputKeyEnum.aiChatQuoteTemplate]?: string;
|
||||
[NodeInputKeyEnum.aiChatQuotePrompt]?: string;
|
||||
[NodeInputKeyEnum.aiChatVision]?: boolean;
|
||||
|
||||
[NodeInputKeyEnum.stringQuoteText]?: string;
|
||||
[NodeInputKeyEnum.fileUrlList]?: string[];
|
||||
};
|
||||
|
||||
@@ -5,8 +5,8 @@ import { StoreNodeItemType } from '../type/node';
|
||||
import { StoreEdgeItemType } from '../type/edge';
|
||||
import { RuntimeEdgeItemType, RuntimeNodeItemType } from './type';
|
||||
import { VARIABLE_NODE_ID } from '../constants';
|
||||
import { isReferenceValue } from '../utils';
|
||||
import { FlowNodeOutputItemType, ReferenceValueProps } from '../type/io';
|
||||
import { isValidReferenceValueFormat } from '../utils';
|
||||
import { FlowNodeOutputItemType, ReferenceValueType } from '../type/io';
|
||||
import { ChatItemType, NodeOutputItemType } from '../../../core/chat/type';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '../../../core/chat/constants';
|
||||
|
||||
@@ -34,7 +34,7 @@ export const getMaxHistoryLimitFromNodes = (nodes: StoreNodeItemType[]): number
|
||||
2. Check that the workflow starts at the interaction node
|
||||
*/
|
||||
export const getLastInteractiveValue = (histories: ChatItemType[]) => {
|
||||
const lastAIMessage = histories.findLast((item) => item.obj === ChatRoleEnum.AI);
|
||||
const lastAIMessage = [...histories].reverse().find((item) => item.obj === ChatRoleEnum.AI);
|
||||
|
||||
if (lastAIMessage) {
|
||||
const lastValue = lastAIMessage.value[lastAIMessage.value.length - 1];
|
||||
@@ -225,37 +225,129 @@ export const checkNodeRunStatus = ({
|
||||
return 'wait';
|
||||
};
|
||||
|
||||
/*
|
||||
Get the value of the reference variable/node output
|
||||
1. [string,string]
|
||||
2. [string,string][]
|
||||
*/
|
||||
export const getReferenceVariableValue = ({
|
||||
value,
|
||||
nodes,
|
||||
variables
|
||||
}: {
|
||||
value: ReferenceValueProps;
|
||||
value?: ReferenceValueType;
|
||||
nodes: RuntimeNodeItemType[];
|
||||
variables: Record<string, any>;
|
||||
}) => {
|
||||
const nodeIds = nodes.map((node) => node.nodeId);
|
||||
if (!isReferenceValue(value, nodeIds)) {
|
||||
return value;
|
||||
}
|
||||
const sourceNodeId = value[0];
|
||||
const outputId = value[1];
|
||||
if (!value) return value;
|
||||
|
||||
if (sourceNodeId === VARIABLE_NODE_ID && outputId) {
|
||||
return variables[outputId];
|
||||
// handle single reference value
|
||||
if (isValidReferenceValueFormat(value)) {
|
||||
const sourceNodeId = value[0];
|
||||
const outputId = value[1];
|
||||
|
||||
if (sourceNodeId === VARIABLE_NODE_ID) {
|
||||
if (!outputId) return undefined;
|
||||
return variables[outputId];
|
||||
}
|
||||
|
||||
const node = nodes.find((node) => node.nodeId === sourceNodeId);
|
||||
if (!node) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return node.outputs.find((output) => output.id === outputId)?.value;
|
||||
}
|
||||
|
||||
const node = nodes.find((node) => node.nodeId === sourceNodeId);
|
||||
// handle reference array
|
||||
if (
|
||||
Array.isArray(value) &&
|
||||
value.length > 0 &&
|
||||
value.every((item) => isValidReferenceValueFormat(item))
|
||||
) {
|
||||
const result = value.map<any>((val) => {
|
||||
return getReferenceVariableValue({
|
||||
value: val,
|
||||
nodes,
|
||||
variables
|
||||
});
|
||||
});
|
||||
|
||||
if (!node) {
|
||||
return undefined;
|
||||
return result.flat().filter((item) => item !== undefined);
|
||||
}
|
||||
|
||||
const outputValue = node.outputs.find((output) => output.id === outputId)?.value;
|
||||
|
||||
return outputValue;
|
||||
return value;
|
||||
};
|
||||
|
||||
// replace {{$xx.xx$}} variables for text
|
||||
export function replaceEditorVariable({
|
||||
text,
|
||||
nodes,
|
||||
variables,
|
||||
runningNode
|
||||
}: {
|
||||
text: any;
|
||||
nodes: RuntimeNodeItemType[];
|
||||
variables: Record<string, any>; // global variables
|
||||
runningNode: RuntimeNodeItemType;
|
||||
}) {
|
||||
if (typeof text !== 'string') return text;
|
||||
|
||||
const globalVariables = Object.keys(variables).map((key) => {
|
||||
return {
|
||||
nodeId: VARIABLE_NODE_ID,
|
||||
id: key,
|
||||
value: variables[key]
|
||||
};
|
||||
});
|
||||
|
||||
// Upstream node outputs
|
||||
const nodeVariables = nodes
|
||||
.map((node) => {
|
||||
return node.outputs.map((output) => {
|
||||
return {
|
||||
nodeId: node.nodeId,
|
||||
id: output.id,
|
||||
value: output.value
|
||||
};
|
||||
});
|
||||
})
|
||||
.flat();
|
||||
|
||||
// Get runningNode inputs(Will be replaced with reference)
|
||||
const customInputs = runningNode.inputs.flatMap((item) => {
|
||||
return [
|
||||
{
|
||||
id: item.key,
|
||||
value: getReferenceVariableValue({
|
||||
value: item.value,
|
||||
nodes,
|
||||
variables
|
||||
}),
|
||||
nodeId: runningNode.nodeId
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
const allVariables = [...globalVariables, ...nodeVariables, ...customInputs];
|
||||
|
||||
// Replace {{$xxx.xxx$}} to value
|
||||
for (const key in allVariables) {
|
||||
const variable = allVariables[key];
|
||||
const val = variable.value;
|
||||
const formatVal = (() => {
|
||||
if (val === undefined) return '';
|
||||
if (val === null) return 'null';
|
||||
|
||||
return typeof val === 'object' ? JSON.stringify(val) : String(val);
|
||||
})();
|
||||
|
||||
const regex = new RegExp(`\\{\\{\\$(${variable.nodeId}\\.${variable.id})\\$\\}\\}`, 'g');
|
||||
text = text.replace(regex, formatVal);
|
||||
}
|
||||
return text || '';
|
||||
}
|
||||
|
||||
export const textAdaptGptResponse = ({
|
||||
text,
|
||||
model = '',
|
||||
|
||||
@@ -75,10 +75,17 @@ export const Input_Template_Text_Quote: FlowNodeInputItemType = {
|
||||
description: i18nT('app:document_quote_tip'),
|
||||
valueType: WorkflowIOValueTypeEnum.string
|
||||
};
|
||||
|
||||
export const Input_Template_File_Link_Prompt: FlowNodeInputItemType = {
|
||||
key: NodeInputKeyEnum.fileUrlList,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference, FlowNodeInputTypeEnum.input],
|
||||
label: i18nT('app:file_quote_link'),
|
||||
debugLabel: i18nT('app:file_quote_link'),
|
||||
valueType: WorkflowIOValueTypeEnum.arrayString
|
||||
};
|
||||
export const Input_Template_File_Link: FlowNodeInputItemType = {
|
||||
key: NodeInputKeyEnum.fileUrlList,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
required: true,
|
||||
label: i18nT('app:workflow.user_file_input'),
|
||||
debugLabel: i18nT('app:workflow.user_file_input'),
|
||||
description: i18nT('app:workflow.user_file_input_desc'),
|
||||
@@ -104,7 +111,7 @@ export const Input_Template_Node_Height: FlowNodeInputItemType = {
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.number,
|
||||
label: '',
|
||||
value: 900
|
||||
value: 600
|
||||
};
|
||||
|
||||
export const Input_Template_Stream_MODE: FlowNodeInputItemType = {
|
||||
|
||||
@@ -17,7 +17,8 @@ import {
|
||||
Input_Template_History,
|
||||
Input_Template_System_Prompt,
|
||||
Input_Template_UserChatInput,
|
||||
Input_Template_Text_Quote
|
||||
Input_Template_Text_Quote,
|
||||
Input_Template_File_Link_Prompt
|
||||
} from '../../input';
|
||||
import { chatNodeSystemPromptTip, systemPromptTip } from '../../tip';
|
||||
import { getHandleConfig } from '../../utils';
|
||||
@@ -55,7 +56,7 @@ export const AiChatModule: FlowNodeTemplateType = {
|
||||
showStatus: true,
|
||||
isTool: true,
|
||||
courseUrl: '/docs/workflow/modules/ai_chat/',
|
||||
version: '481',
|
||||
version: '4813',
|
||||
inputs: [
|
||||
Input_Template_SettingAiModel,
|
||||
// --- settings modal
|
||||
@@ -89,7 +90,7 @@ export const AiChatModule: FlowNodeTemplateType = {
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
label: '',
|
||||
valueType: WorkflowIOValueTypeEnum.boolean,
|
||||
value: false
|
||||
value: true
|
||||
},
|
||||
// settings modal ---
|
||||
{
|
||||
@@ -100,7 +101,7 @@ export const AiChatModule: FlowNodeTemplateType = {
|
||||
},
|
||||
Input_Template_History,
|
||||
Input_Template_Dataset_Quote,
|
||||
Input_Template_Text_Quote,
|
||||
Input_Template_File_Link_Prompt,
|
||||
|
||||
{ ...Input_Template_UserChatInput, toolDescription: i18nT('workflow:user_question') }
|
||||
],
|
||||
|
||||
@@ -25,7 +25,7 @@ export const getOneQuoteInputTemplate = ({
|
||||
}): FlowNodeInputItemType => ({
|
||||
key,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
label: `${i18nT('workflow:quote_num')},{ num: ${index} }`,
|
||||
label: `${i18nT('workflow:quote_num')}-${index}`,
|
||||
debugLabel: i18nT('workflow:knowledge_base_reference'),
|
||||
canEdit: true,
|
||||
valueType: WorkflowIOValueTypeEnum.datasetQuote
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ReferenceValueProps } from 'core/workflow/type/io';
|
||||
import { ReferenceItemValueType } from '../../../type/io';
|
||||
import { VariableConditionEnum } from './constant';
|
||||
|
||||
export type IfElseConditionType = 'AND' | 'OR';
|
||||
export type ConditionListItemType = {
|
||||
variable?: ReferenceValueProps;
|
||||
variable?: ReferenceItemValueType;
|
||||
condition?: VariableConditionEnum;
|
||||
value?: string;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '../../../node/constant';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '../../../node/constant';
|
||||
import { FlowNodeTemplateType } from '../../../type/node.d';
|
||||
import {
|
||||
FlowNodeTemplateTypeEnum,
|
||||
NodeInputKeyEnum,
|
||||
NodeOutputKeyEnum,
|
||||
WorkflowIOValueTypeEnum
|
||||
} from '../../../constants';
|
||||
import { getHandleConfig } from '../../utils';
|
||||
@@ -28,7 +33,21 @@ export const LoopStartNode: FlowNodeTemplateType = {
|
||||
label: '',
|
||||
required: true,
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
key: NodeInputKeyEnum.loopStartIndex,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.number,
|
||||
label: i18nT('workflow:Array_element_index')
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
outputs: [
|
||||
{
|
||||
id: NodeOutputKeyEnum.loopStartIndex,
|
||||
key: NodeOutputKeyEnum.loopStartIndex,
|
||||
label: i18nT('workflow:Array_element_index'),
|
||||
type: FlowNodeOutputTypeEnum.static,
|
||||
valueType: WorkflowIOValueTypeEnum.number
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ export const ReadFilesNode: FlowNodeTemplateType = {
|
||||
name: i18nT('app:workflow.read_files'),
|
||||
intro: i18nT('app:workflow.read_files_tip'),
|
||||
showStatus: true,
|
||||
version: '489',
|
||||
version: '4812',
|
||||
isTool: true,
|
||||
inputs: [
|
||||
{
|
||||
|
||||
@@ -24,17 +24,8 @@ export const TextEditorNode: FlowNodeTemplateType = {
|
||||
name: i18nT('workflow:text_concatenation'),
|
||||
intro: i18nT('workflow:intro_text_concatenation'),
|
||||
courseUrl: '/docs/workflow/modules/text_editor/',
|
||||
version: '486',
|
||||
version: '4813',
|
||||
inputs: [
|
||||
{
|
||||
...Input_Template_DynamicInput,
|
||||
description: i18nT('workflow:dynamic_input_description_concat'),
|
||||
customInputConfig: {
|
||||
selectValueTypeList: Object.values(WorkflowIOValueTypeEnum),
|
||||
showDescription: false,
|
||||
showDefaultValue: false
|
||||
}
|
||||
},
|
||||
{
|
||||
key: NodeInputKeyEnum.textareaInput,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.textarea],
|
||||
|
||||
@@ -20,6 +20,7 @@ import { chatNodeSystemPromptTip, systemPromptTip } from '../tip';
|
||||
import { LLMModelTypeEnum } from '../../../ai/constants';
|
||||
import { getHandleConfig } from '../utils';
|
||||
import { i18nT } from '../../../../../web/i18n/utils';
|
||||
import { Input_Template_File_Link_Prompt } from '../input';
|
||||
|
||||
export const ToolModule: FlowNodeTemplateType = {
|
||||
id: FlowNodeTypeEnum.tools,
|
||||
@@ -32,7 +33,7 @@ export const ToolModule: FlowNodeTemplateType = {
|
||||
intro: i18nT('workflow:template.tool_call_intro'),
|
||||
showStatus: true,
|
||||
courseUrl: '/docs/workflow/modules/tool/',
|
||||
version: '481',
|
||||
version: '4813',
|
||||
inputs: [
|
||||
{
|
||||
...Input_Template_SettingAiModel,
|
||||
@@ -67,6 +68,7 @@ export const ToolModule: FlowNodeTemplateType = {
|
||||
placeholder: chatNodeSystemPromptTip
|
||||
},
|
||||
Input_Template_History,
|
||||
Input_Template_File_Link_Prompt,
|
||||
Input_Template_UserChatInput
|
||||
],
|
||||
outputs: [
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { FlowNodeInputTypeEnum } from '../../../node/constant';
|
||||
import { ReferenceValueProps } from '../../..//type/io';
|
||||
import { ReferenceItemValueType, ReferenceValueType } from '../../..//type/io';
|
||||
import { WorkflowIOValueTypeEnum } from '../../../constants';
|
||||
|
||||
export type TUpdateListItem = {
|
||||
variable?: ReferenceValueProps;
|
||||
value: ReferenceValueProps;
|
||||
variable?: ReferenceItemValueType;
|
||||
value?: ReferenceValueType; // input: ['',value], reference: [nodeId,outputId]
|
||||
valueType?: WorkflowIOValueTypeEnum;
|
||||
renderType: FlowNodeInputTypeEnum.input | FlowNodeInputTypeEnum.reference;
|
||||
};
|
||||
|
||||
@@ -43,6 +43,3 @@ export const WorkflowStart: FlowNodeTemplateType = {
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const isWorkflowStartOutput = (key?: string) =>
|
||||
!!WorkflowStart.outputs.find((output) => output.key === key);
|
||||
|
||||
9
packages/global/core/workflow/type/io.d.ts
vendored
@@ -56,6 +56,11 @@ export type FlowNodeInputItemType = InputComponentPropsType & {
|
||||
canEdit?: boolean; // dynamic inputs
|
||||
isPro?: boolean; // Pro version field
|
||||
isToolOutput?: boolean;
|
||||
|
||||
// file
|
||||
canSelectFile?: boolean;
|
||||
canSelectImg?: boolean;
|
||||
maxFiles?: number;
|
||||
};
|
||||
|
||||
export type FlowNodeOutputItemType = {
|
||||
@@ -75,4 +80,6 @@ export type FlowNodeOutputItemType = {
|
||||
customFieldConfig?: CustomFieldConfigType;
|
||||
};
|
||||
|
||||
export type ReferenceValueProps = [string, string | undefined];
|
||||
export type ReferenceItemValueType = [string, string | undefined];
|
||||
export type ReferenceArrayValueType = ReferenceItemValueType[];
|
||||
export type ReferenceValueType = ReferenceItemValueType | ReferenceArrayValueType;
|
||||
|
||||
@@ -12,7 +12,12 @@ import {
|
||||
VARIABLE_NODE_ID,
|
||||
NodeOutputKeyEnum
|
||||
} from './constants';
|
||||
import { FlowNodeInputItemType, FlowNodeOutputItemType, ReferenceValueProps } from './type/io.d';
|
||||
import {
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType,
|
||||
ReferenceArrayValueType,
|
||||
ReferenceItemValueType
|
||||
} from './type/io.d';
|
||||
import { StoreNodeItemType } from './type/node';
|
||||
import type {
|
||||
VariableItemType,
|
||||
@@ -30,8 +35,8 @@ import {
|
||||
} from '../app/constants';
|
||||
import { IfElseResultEnum } from './template/system/ifElse/constant';
|
||||
import { RuntimeNodeItemType } from './runtime/type';
|
||||
import { getReferenceVariableValue } from './runtime/utils';
|
||||
import {
|
||||
Input_Template_File_Link,
|
||||
Input_Template_History,
|
||||
Input_Template_Stream_MODE,
|
||||
Input_Template_UserChatInput
|
||||
@@ -261,8 +266,10 @@ export const appData2FlowNodeIO = ({
|
||||
inputs: [
|
||||
Input_Template_Stream_MODE,
|
||||
Input_Template_History,
|
||||
...(chatConfig?.fileSelectConfig?.canSelectFile || chatConfig?.fileSelectConfig?.canSelectImg
|
||||
? [Input_Template_File_Link]
|
||||
: []),
|
||||
Input_Template_UserChatInput,
|
||||
// ...(showFileLink ? [Input_Template_File_Link] : []),
|
||||
...variableInput
|
||||
],
|
||||
outputs: [
|
||||
@@ -298,9 +305,37 @@ export const formatEditorVariablePickerIcon = (
|
||||
}));
|
||||
};
|
||||
|
||||
export const isReferenceValue = (value: any, nodeIds: string[]): boolean => {
|
||||
const validIdList = [VARIABLE_NODE_ID, ...nodeIds];
|
||||
return Array.isArray(value) && value.length === 2 && validIdList.includes(value[0]);
|
||||
// Check the value is a valid reference value format: [variableId, outputId]
|
||||
export const isValidReferenceValueFormat = (value: any): value is ReferenceItemValueType => {
|
||||
return Array.isArray(value) && value.length === 2 && typeof value[0] === 'string';
|
||||
};
|
||||
/*
|
||||
Check whether the value([variableId, outputId]) value is a valid reference value:
|
||||
1. The value must be an array of length 2
|
||||
2. The first item of the array must be one of VARIABLE_NODE_ID or nodeIds
|
||||
*/
|
||||
export const isValidReferenceValue = (
|
||||
value: any,
|
||||
nodeIds: string[]
|
||||
): value is ReferenceItemValueType => {
|
||||
if (!isValidReferenceValueFormat(value)) return false;
|
||||
|
||||
const validIdSet = new Set([VARIABLE_NODE_ID, ...nodeIds]);
|
||||
return validIdSet.has(value[0]);
|
||||
};
|
||||
/*
|
||||
Check whether the value([variableId, outputId][]) value is a valid reference value array:
|
||||
1. The value must be an array
|
||||
2. The array must contain at least one element
|
||||
3. Each element in the array must be a valid reference value
|
||||
*/
|
||||
export const isValidArrayReferenceValue = (
|
||||
value: any,
|
||||
nodeIds: string[]
|
||||
): value is ReferenceArrayValueType => {
|
||||
if (!Array.isArray(value)) return false;
|
||||
|
||||
return value.every((item) => isValidReferenceValue(item, nodeIds));
|
||||
};
|
||||
|
||||
export const getElseIFLabel = (i: number) => {
|
||||
@@ -342,79 +377,6 @@ export const updatePluginInputByVariables = (
|
||||
);
|
||||
};
|
||||
|
||||
// replace {{$xx.xx$}} variables for text
|
||||
export function replaceEditorVariable({
|
||||
text,
|
||||
nodes,
|
||||
variables,
|
||||
runningNode
|
||||
}: {
|
||||
text: any;
|
||||
nodes: RuntimeNodeItemType[];
|
||||
variables: Record<string, any>; // global variables
|
||||
runningNode: RuntimeNodeItemType;
|
||||
}) {
|
||||
if (typeof text !== 'string') return text;
|
||||
|
||||
const globalVariables = Object.keys(variables).map((key) => {
|
||||
return {
|
||||
nodeId: VARIABLE_NODE_ID,
|
||||
id: key,
|
||||
value: variables[key]
|
||||
};
|
||||
});
|
||||
|
||||
// Upstream node outputs
|
||||
const nodeVariables = nodes
|
||||
.map((node) => {
|
||||
return node.outputs.map((output) => {
|
||||
return {
|
||||
nodeId: node.nodeId,
|
||||
id: output.id,
|
||||
value: output.value
|
||||
};
|
||||
});
|
||||
})
|
||||
.flat();
|
||||
|
||||
// Get runningNode inputs(Will be replaced with reference)
|
||||
const customInputs = runningNode.inputs.flatMap((item) => {
|
||||
if (Array.isArray(item.value)) {
|
||||
return [
|
||||
{
|
||||
id: item.key,
|
||||
value: getReferenceVariableValue({
|
||||
value: item.value as ReferenceValueProps,
|
||||
nodes,
|
||||
variables
|
||||
}),
|
||||
nodeId: runningNode.nodeId
|
||||
}
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
id: item.key,
|
||||
value: item.value,
|
||||
nodeId: runningNode.nodeId
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
const allVariables = [...globalVariables, ...nodeVariables, ...customInputs];
|
||||
|
||||
// Replace {{$xxx.xxx$}} to value
|
||||
for (const key in allVariables) {
|
||||
const variable = allVariables[key];
|
||||
const val = variable.value;
|
||||
const formatVal = typeof val === 'object' ? JSON.stringify(val) : String(val);
|
||||
|
||||
const regex = new RegExp(`\\{\\{\\$(${variable.nodeId}\\.${variable.id})\\$\\}\\}`, 'g');
|
||||
text = text.replace(regex, formatVal);
|
||||
}
|
||||
return text || '';
|
||||
}
|
||||
|
||||
/* Get plugin runtime input user query */
|
||||
export const getPluginRunUserQuery = ({
|
||||
pluginInputs,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import * as echarts from 'echarts';
|
||||
import json5 from 'json5';
|
||||
import { getFileSavePath } from '../../../utils';
|
||||
import * as fs from 'fs';
|
||||
import { SystemPluginSpecialResponse } from '../../../type.d';
|
||||
|
||||
type Props = {
|
||||
@@ -82,25 +80,23 @@ const generateChart = async (title: string, xAxis: string, yAxis: string, chartT
|
||||
|
||||
chart.setOption(option);
|
||||
// 生成 Base64 图像
|
||||
const base64Image = chart.getDataURL();
|
||||
const svgData = decodeURIComponent(base64Image.split(',')[1]);
|
||||
const base64Image = chart.getDataURL({
|
||||
type: 'png',
|
||||
pixelRatio: 2 // 可以设置更高的像素比以获得更清晰的图像
|
||||
});
|
||||
const svgContent = decodeURIComponent(base64Image.split(',')[1]);
|
||||
const base64 = `data:image/svg+xml;base64,${Buffer.from(svgContent).toString('base64')}`;
|
||||
|
||||
const fileName = `chart_${Date.now()}.svg`;
|
||||
const filePath = getFileSavePath(fileName);
|
||||
fs.writeFileSync(filePath, svgData);
|
||||
// 释放图表实例
|
||||
chart.dispose();
|
||||
|
||||
return filePath;
|
||||
return base64;
|
||||
};
|
||||
|
||||
const main = async ({ title, xAxis, yAxis, chartType }: Props): Response => {
|
||||
const filePath = await generateChart(title, xAxis, yAxis, chartType);
|
||||
const base64 = await generateChart(title, xAxis, yAxis, chartType);
|
||||
return {
|
||||
result: {
|
||||
type: 'SYSTEM_PLUGIN_FILE',
|
||||
path: filePath,
|
||||
contentType: 'image/svg+xml'
|
||||
type: 'SYSTEM_PLUGIN_BASE64',
|
||||
value: base64,
|
||||
extension: 'svg'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
6
packages/plugins/type.d.ts
vendored
@@ -4,9 +4,9 @@ import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type
|
||||
|
||||
export type SystemPluginResponseType = Promise<Record<string, any>>;
|
||||
export type SystemPluginSpecialResponse = {
|
||||
type: 'SYSTEM_PLUGIN_FILE';
|
||||
path: string;
|
||||
contentType: string;
|
||||
type: 'SYSTEM_PLUGIN_BASE64';
|
||||
value: string;
|
||||
extension: string;
|
||||
};
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
export const getFileSavePath = (name: string) => {
|
||||
if (isProduction) {
|
||||
return `/app/plugin_file/${name}`;
|
||||
}
|
||||
const filePath = path.join(process.cwd(), 'local', 'plugin_file', name);
|
||||
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
|
||||
return filePath;
|
||||
};
|
||||
@@ -12,6 +12,7 @@ import { gridFsStream2Buffer, stream2Encoding } from './utils';
|
||||
import { addLog } from '../../system/log';
|
||||
import { readFromSecondary } from '../../mongo/utils';
|
||||
import { parseFileExtensionFromUrl } from '@fastgpt/global/common/string/tools';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
export function getGFSCollection(bucket: `${BucketNameEnum}`) {
|
||||
MongoDatasetFileSchema;
|
||||
@@ -76,6 +77,59 @@ export async function uploadFile({
|
||||
|
||||
return String(stream.id);
|
||||
}
|
||||
export async function uploadFileFromBase64Img({
|
||||
bucketName,
|
||||
teamId,
|
||||
tmbId,
|
||||
base64,
|
||||
filename,
|
||||
metadata = {}
|
||||
}: {
|
||||
bucketName: `${BucketNameEnum}`;
|
||||
teamId: string;
|
||||
tmbId: string;
|
||||
base64: string;
|
||||
filename: string;
|
||||
metadata?: Record<string, any>;
|
||||
}) {
|
||||
if (!base64) return Promise.reject(`filePath is empty`);
|
||||
if (!filename) return Promise.reject(`filename is empty`);
|
||||
|
||||
const base64Data = base64.split(',')[1];
|
||||
const contentType = base64.split(',')?.[0]?.split?.(':')?.[1];
|
||||
const buffer = Buffer.from(base64Data, 'base64');
|
||||
const readableStream = new Readable({
|
||||
read() {
|
||||
this.push(buffer);
|
||||
this.push(null);
|
||||
}
|
||||
});
|
||||
|
||||
const { stream: readStream, encoding } = await stream2Encoding(readableStream);
|
||||
|
||||
// Add default metadata
|
||||
metadata.teamId = teamId;
|
||||
metadata.tmbId = tmbId;
|
||||
metadata.encoding = encoding;
|
||||
|
||||
// create a gridfs bucket
|
||||
const bucket = getGridBucket(bucketName);
|
||||
|
||||
const stream = bucket.openUploadStream(filename, {
|
||||
metadata,
|
||||
contentType
|
||||
});
|
||||
|
||||
// save to gridfs
|
||||
await new Promise((resolve, reject) => {
|
||||
readStream
|
||||
.pipe(stream as any)
|
||||
.on('finish', resolve)
|
||||
.on('error', reject);
|
||||
});
|
||||
|
||||
return String(stream.id);
|
||||
}
|
||||
|
||||
export async function getFileById({
|
||||
bucketName,
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { ReadFileResponse } from '../../../worker/readFile/type';
|
||||
import axios from 'axios';
|
||||
import { addLog } from '../../system/log';
|
||||
import { batchRun } from '@fastgpt/global/common/fn/utils';
|
||||
import { addHours } from 'date-fns';
|
||||
|
||||
export type readRawTextByLocalFileParams = {
|
||||
teamId: string;
|
||||
@@ -111,6 +112,7 @@ export const readRawContentByFileBuffer = async ({
|
||||
type: MongoImageTypeEnum.collectionImage,
|
||||
base64Img: `data:${item.mime};base64,${item.base64}`,
|
||||
teamId,
|
||||
expiredTime: addHours(new Date(), 1),
|
||||
metadata: {
|
||||
...metadata,
|
||||
mime: item.mime
|
||||
|
||||
@@ -7,14 +7,20 @@ import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
|
||||
1. Commercial plugin: n points per times
|
||||
2. Other plugin: sum of children points
|
||||
*/
|
||||
export const computedPluginUsage = async (
|
||||
plugin: PluginRuntimeType,
|
||||
childrenUsage: ChatNodeUsageType[]
|
||||
) => {
|
||||
export const computedPluginUsage = async ({
|
||||
plugin,
|
||||
childrenUsage,
|
||||
error
|
||||
}: {
|
||||
plugin: PluginRuntimeType;
|
||||
childrenUsage: ChatNodeUsageType[];
|
||||
error?: boolean;
|
||||
}) => {
|
||||
const { source } = await splitCombinePluginId(plugin.id);
|
||||
|
||||
// Commercial plugin: n points per times
|
||||
if (source === PluginSourceEnum.commercial) {
|
||||
if (error) return 0;
|
||||
return plugin.currentCost ?? 0;
|
||||
}
|
||||
|
||||
|
||||
155
packages/service/core/chat/pushChatLog.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { addLog } from '../../common/system/log';
|
||||
import { MongoChatItem } from './chatItemSchema';
|
||||
import { MongoChat } from './chatSchema';
|
||||
import axios from 'axios';
|
||||
import { AIChatItemType, ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
|
||||
export type Metadata = {
|
||||
[key: string]: {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const pushChatLog = ({
|
||||
chatId,
|
||||
chatItemIdHuman,
|
||||
chatItemIdAi,
|
||||
appId,
|
||||
metadata
|
||||
}: {
|
||||
chatId: string;
|
||||
chatItemIdHuman: string;
|
||||
chatItemIdAi: string;
|
||||
appId: string;
|
||||
metadata?: Metadata;
|
||||
}) => {
|
||||
const interval = Number(process.env.CHAT_LOG_INTERVAL);
|
||||
const url = process.env.CHAT_LOG_URL;
|
||||
if (interval > 0 && url) {
|
||||
addLog.info(`[ChatLogPush] push chat log after ${interval}ms`, {
|
||||
appId,
|
||||
chatItemIdHuman,
|
||||
chatItemIdAi
|
||||
});
|
||||
setTimeout(() => {
|
||||
pushChatLogInternal({ chatId, chatItemIdHuman, chatItemIdAi, appId, url, metadata });
|
||||
}, interval);
|
||||
}
|
||||
};
|
||||
|
||||
type ChatItem = ChatItemType & {
|
||||
userGoodFeedback?: string;
|
||||
userBadFeedback?: string;
|
||||
chatId: string;
|
||||
responseData: {
|
||||
moduleType: string;
|
||||
runningTime: number; //s
|
||||
historyPreview: { obj: string; value: string }[];
|
||||
}[];
|
||||
time: Date;
|
||||
};
|
||||
|
||||
type ChatLog = {
|
||||
title: string;
|
||||
feedback: 'like' | 'dislike' | null;
|
||||
chatItemId: string;
|
||||
uid: string;
|
||||
question: string;
|
||||
answer: string;
|
||||
chatId: string;
|
||||
responseTime: number;
|
||||
metadata: string;
|
||||
sourceName: string;
|
||||
createdAt: number;
|
||||
sourceId: string;
|
||||
};
|
||||
|
||||
const pushChatLogInternal = async ({
|
||||
chatId,
|
||||
chatItemIdHuman,
|
||||
chatItemIdAi,
|
||||
appId,
|
||||
url,
|
||||
metadata
|
||||
}: {
|
||||
chatId: string;
|
||||
chatItemIdHuman: string;
|
||||
chatItemIdAi: string;
|
||||
appId: string;
|
||||
url: string;
|
||||
metadata?: Metadata;
|
||||
}) => {
|
||||
try {
|
||||
const [chatItemHuman, chatItemAi] = await Promise.all([
|
||||
MongoChatItem.findById(chatItemIdHuman).lean(),
|
||||
MongoChatItem.findById(chatItemIdAi).lean() as Promise<AIChatItemType>
|
||||
]);
|
||||
|
||||
if (!chatItemHuman || !chatItemAi) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chat = await MongoChat.findOne({ chatId }).lean();
|
||||
|
||||
// addLog.warn('ChatLogDebug', chat);
|
||||
// addLog.warn('ChatLogDebug', { chatItemHuman, chatItemAi });
|
||||
|
||||
if (!chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
const metadataString = JSON.stringify(metadata ?? {});
|
||||
|
||||
const uid = chat.outLinkUid || chat.tmbId;
|
||||
// Pop last two items
|
||||
const question = chatItemHuman.value[chatItemHuman.value.length - 1]?.text?.content;
|
||||
const answer = chatItemAi.value[chatItemAi.value.length - 1]?.text?.content;
|
||||
if (!question || !answer) {
|
||||
addLog.error('[ChatLogPush] question or answer is empty', {
|
||||
question: chatItemHuman.value,
|
||||
answer: chatItemAi.value
|
||||
});
|
||||
return;
|
||||
}
|
||||
const responseData = chatItemAi.responseData;
|
||||
const responseTime =
|
||||
responseData?.reduce((acc, item) => acc + (item?.runningTime ?? 0), 0) || 0;
|
||||
|
||||
const sourceIdPrefix = process.env.SOURCE_ID_PREFIX ?? '';
|
||||
|
||||
const chatLog: ChatLog = {
|
||||
title: chat.title,
|
||||
feedback: (() => {
|
||||
if (chatItemAi.userGoodFeedback) {
|
||||
return 'like';
|
||||
} else if (chatItemAi.userBadFeedback) {
|
||||
return 'dislike';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})(),
|
||||
chatItemId: `${chatItemIdHuman},${chatItemIdAi}`,
|
||||
uid,
|
||||
question,
|
||||
answer,
|
||||
chatId,
|
||||
responseTime: responseTime * 1000,
|
||||
metadata: metadataString,
|
||||
sourceName: chat.source ?? '-',
|
||||
// @ts-ignore
|
||||
createdAt: new Date(chatItemAi.time).getTime(),
|
||||
sourceId: `${sourceIdPrefix}${appId}`
|
||||
};
|
||||
await axios
|
||||
.post(`${url}/api/chat/push`, chatLog)
|
||||
.then((res) => {
|
||||
addLog.info('[ChatLogPush] push success', res.data);
|
||||
})
|
||||
.catch((e) => {
|
||||
addLog.error('[ChatLogPush] push failed', { e, resData: e.response?.data });
|
||||
});
|
||||
} catch (e) {
|
||||
addLog.error('[ChatLogPush] error', e);
|
||||
}
|
||||
};
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { AIChatItemType, UserChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import type {
|
||||
AIChatItemType,
|
||||
ChatItemType,
|
||||
UserChatItemType
|
||||
} from '@fastgpt/global/core/chat/type.d';
|
||||
import axios from 'axios';
|
||||
import { MongoApp } from '../app/schema';
|
||||
import {
|
||||
ChatItemValueTypeEnum,
|
||||
@@ -13,6 +18,7 @@ import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { getAppChatConfig, getGuideModule } from '@fastgpt/global/core/workflow/utils';
|
||||
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||
import { mergeChatResponseData } from '@fastgpt/global/core/chat/utils';
|
||||
import { pushChatLog } from './pushChatLog';
|
||||
|
||||
type Props = {
|
||||
chatId: string;
|
||||
@@ -67,7 +73,7 @@ export async function saveChat({
|
||||
});
|
||||
|
||||
await mongoSessionRun(async (session) => {
|
||||
await MongoChatItem.insertMany(
|
||||
const [{ _id: chatItemIdHuman }, { _id: chatItemIdAi }] = await MongoChatItem.insertMany(
|
||||
content.map((item) => ({
|
||||
chatId,
|
||||
teamId,
|
||||
@@ -105,6 +111,13 @@ export async function saveChat({
|
||||
upsert: true
|
||||
}
|
||||
);
|
||||
|
||||
pushChatLog({
|
||||
chatId,
|
||||
chatItemIdHuman: String(chatItemIdHuman),
|
||||
chatItemIdAi: String(chatItemIdAi),
|
||||
appId
|
||||
});
|
||||
});
|
||||
|
||||
if (isUpdateUseTime) {
|
||||
|
||||
@@ -104,9 +104,12 @@ export const loadRequestMessages = async ({
|
||||
}) => {
|
||||
// Load image to base64
|
||||
const loadImageToBase64 = async (messages: ChatCompletionContentPart[]) => {
|
||||
if (process.env.MULTIPLE_DATA_TO_BASE64 === 'false') {
|
||||
return messages;
|
||||
}
|
||||
return Promise.all(
|
||||
messages.map(async (item) => {
|
||||
if (item.type === 'image_url') {
|
||||
if (item.type === 'image_url' && process.env.MULTIPLE_DATA_TO_BASE64 === 'true') {
|
||||
// Remove url origin
|
||||
const imgUrl = (() => {
|
||||
if (origin && item.image_url.url.startsWith(origin)) {
|
||||
@@ -115,38 +118,51 @@ export const loadRequestMessages = async ({
|
||||
return item.image_url.url;
|
||||
})();
|
||||
|
||||
// If imgUrl is a local path, load image from local, and set url to base64
|
||||
if (imgUrl.startsWith('/')) {
|
||||
addLog.debug('Load image from local server', {
|
||||
baseUrl: serverRequestBaseUrl,
|
||||
requestUrl: imgUrl
|
||||
});
|
||||
const response = await axios.get(imgUrl, {
|
||||
baseURL: serverRequestBaseUrl,
|
||||
responseType: 'arraybuffer',
|
||||
proxy: false
|
||||
});
|
||||
const base64 = Buffer.from(response.data, 'binary').toString('base64');
|
||||
const imageType =
|
||||
getFileContentTypeFromHeader(response.headers['content-type']) ||
|
||||
guessBase64ImageType(base64);
|
||||
try {
|
||||
// If imgUrl is a local path, load image from local, and set url to base64
|
||||
if (imgUrl.startsWith('/')) {
|
||||
addLog.debug('Load image from local server', {
|
||||
baseUrl: serverRequestBaseUrl,
|
||||
requestUrl: imgUrl
|
||||
});
|
||||
const response = await axios.get(imgUrl, {
|
||||
baseURL: serverRequestBaseUrl,
|
||||
responseType: 'arraybuffer',
|
||||
proxy: false
|
||||
});
|
||||
const base64 = Buffer.from(response.data, 'binary').toString('base64');
|
||||
const imageType =
|
||||
getFileContentTypeFromHeader(response.headers['content-type']) ||
|
||||
guessBase64ImageType(base64);
|
||||
|
||||
return {
|
||||
...item,
|
||||
image_url: {
|
||||
...item.image_url,
|
||||
url: `data:${imageType};base64,${base64}`
|
||||
}
|
||||
};
|
||||
return {
|
||||
...item,
|
||||
image_url: {
|
||||
...item.image_url,
|
||||
url: `data:${imageType};base64,${base64}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 检查下这个图片是否可以被访问,如果不行的话,则过滤掉
|
||||
const response = await axios.head(imgUrl, {
|
||||
timeout: 10000
|
||||
});
|
||||
if (response.status < 200 || response.status >= 400) {
|
||||
addLog.info(`Filter invalid image: ${imgUrl}`);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return item;
|
||||
})
|
||||
);
|
||||
).then((res) => res.filter(Boolean) as ChatCompletionContentPart[]);
|
||||
};
|
||||
// Split question text and image
|
||||
const parseStringWithImages = (input: string): ChatCompletionContentPart[] => {
|
||||
if (!useVision) {
|
||||
if (!useVision || input.length > 500) {
|
||||
return [{ type: 'text', text: input || '' }];
|
||||
}
|
||||
|
||||
@@ -167,8 +183,8 @@ export const loadRequestMessages = async ({
|
||||
});
|
||||
});
|
||||
|
||||
// Too many images or too long text, return text
|
||||
if (httpsImages.length > 4 || input.length > 1000) {
|
||||
// Too many images return text
|
||||
if (httpsImages.length > 4) {
|
||||
return [{ type: 'text', text: input || '' }];
|
||||
}
|
||||
|
||||
@@ -176,7 +192,7 @@ export const loadRequestMessages = async ({
|
||||
result.push({ type: 'text', text: input });
|
||||
return result;
|
||||
};
|
||||
// Parse user content(text and img)
|
||||
// Parse user content(text and img) Store history => api messages
|
||||
const parseUserContent = async (content: string | ChatCompletionContentPart[]) => {
|
||||
if (typeof content === 'string') {
|
||||
return loadImageToBase64(parseStringWithImages(content));
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
DatasetDataWithCollectionType,
|
||||
SearchDataResponseItemType
|
||||
} from '@fastgpt/global/core/dataset/type';
|
||||
import { DatasetColCollectionName, MongoDatasetCollection } from '../collection/schema';
|
||||
import { MongoDatasetCollection } from '../collection/schema';
|
||||
import { reRankRecall } from '../../../core/ai/rerank';
|
||||
import { countPromptTokens } from '../../../common/string/tiktoken/index';
|
||||
import { datasetSearchResultConcat } from '@fastgpt/global/core/dataset/search/utils';
|
||||
@@ -320,11 +320,13 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
|
||||
const fullTextRecall = async ({
|
||||
query,
|
||||
limit,
|
||||
filterCollectionIdList
|
||||
filterCollectionIdList,
|
||||
forbidCollectionIdList
|
||||
}: {
|
||||
query: string;
|
||||
limit: number;
|
||||
filterCollectionIdList?: string[];
|
||||
forbidCollectionIdList: string[];
|
||||
}): Promise<{
|
||||
fullTextRecallResults: SearchDataResponseItemType[];
|
||||
tokenLen: number;
|
||||
@@ -351,6 +353,13 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
|
||||
$in: filterCollectionIdList.map((id) => new Types.ObjectId(id))
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
...(forbidCollectionIdList && forbidCollectionIdList.length > 0
|
||||
? {
|
||||
collectionId: {
|
||||
$nin: forbidCollectionIdList.map((id) => new Types.ObjectId(id))
|
||||
}
|
||||
}
|
||||
: {})
|
||||
}
|
||||
},
|
||||
@@ -367,31 +376,6 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
|
||||
{
|
||||
$limit: limit
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: DatasetColCollectionName,
|
||||
let: { collectionId: '$collectionId' },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: { $eq: ['$_id', '$$collectionId'] },
|
||||
forbid: { $eq: true } // 匹配被禁用的数据
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1 // 只需要_id字段来确认匹配
|
||||
}
|
||||
}
|
||||
],
|
||||
as: 'collection'
|
||||
}
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
collection: { $eq: [] } // 没有 forbid=true 的数据
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
@@ -509,7 +493,8 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
|
||||
fullTextRecall({
|
||||
query,
|
||||
limit: fullTextLimit,
|
||||
filterCollectionIdList
|
||||
filterCollectionIdList,
|
||||
forbidCollectionIdList
|
||||
})
|
||||
]);
|
||||
totalTokens += tokens;
|
||||
|
||||
@@ -28,6 +28,7 @@ import { computedMaxToken, llmCompletionsBodyFormat } from '../../../../ai/utils
|
||||
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
|
||||
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { i18nT } from '../../../../../../web/i18n/utils';
|
||||
|
||||
type FunctionRunResponseType = {
|
||||
toolRunResponse: DispatchFlowResponse;
|
||||
@@ -549,7 +550,7 @@ async function streamResponse({
|
||||
}
|
||||
|
||||
if (!textAnswer && functionCalls.length === 0) {
|
||||
return Promise.reject('LLM api response empty');
|
||||
return Promise.reject(i18nT('chat:LLM_model_response_empty'));
|
||||
}
|
||||
|
||||
return { answer: textAnswer, functionCalls };
|
||||
|
||||
@@ -25,45 +25,16 @@ import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import { getMultiplePrompt, Prompt_Tool_Call } from './constants';
|
||||
import { filterToolResponseToPreview } from './utils';
|
||||
import { InteractiveNodeResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||
import { getFileContentFromLinks, getHistoryFileLinks } from '../../tools/readFiles';
|
||||
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
||||
import { Prompt_DocumentQuote } from '@fastgpt/global/core/ai/prompt/AIChat';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
|
||||
type Response = DispatchNodeResultType<{
|
||||
[NodeOutputKeyEnum.answerText]: string;
|
||||
[DispatchNodeResponseKeyEnum.interactive]?: InteractiveNodeResponseType;
|
||||
}>;
|
||||
|
||||
/*
|
||||
Tool call, auth add file prompt to question。
|
||||
Guide the LLM to call tool.
|
||||
*/
|
||||
export const toolCallMessagesAdapt = ({
|
||||
userInput
|
||||
}: {
|
||||
userInput: UserChatItemValueItemType[];
|
||||
}) => {
|
||||
const files = userInput.filter((item) => item.type === 'file');
|
||||
|
||||
if (files.length > 0) {
|
||||
return userInput.map((item) => {
|
||||
if (item.type === 'text') {
|
||||
const filesCount = files.filter((file) => file.file?.type === 'file').length;
|
||||
const imgCount = files.filter((file) => file.file?.type === 'image').length;
|
||||
const text = item.text?.content || '';
|
||||
|
||||
return {
|
||||
...item,
|
||||
text: {
|
||||
content: getMultiplePrompt({ fileCount: filesCount, imgCount, question: text })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
return userInput;
|
||||
};
|
||||
|
||||
export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<Response> => {
|
||||
const {
|
||||
node: { nodeId, name, isEntry },
|
||||
@@ -71,11 +42,21 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
||||
runtimeEdges,
|
||||
histories,
|
||||
query,
|
||||
|
||||
params: { model, systemPrompt, userChatInput, history = 6 }
|
||||
requestOrigin,
|
||||
chatConfig,
|
||||
runningAppInfo: { teamId },
|
||||
params: {
|
||||
model,
|
||||
systemPrompt,
|
||||
userChatInput,
|
||||
history = 6,
|
||||
fileUrlList: fileLinks,
|
||||
aiChatVision
|
||||
}
|
||||
} = props;
|
||||
|
||||
const toolModel = getLLMModel(model);
|
||||
const useVision = aiChatVision && toolModel.vision;
|
||||
const chatHistories = getHistories(history, histories);
|
||||
|
||||
const toolNodeIds = filterToolNodeIdByEdges({ nodeId, edges: runtimeEdges });
|
||||
@@ -109,18 +90,43 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
||||
}
|
||||
})();
|
||||
props.node.isEntry = false;
|
||||
const hasReadFilesTool = toolNodes.some(
|
||||
(item) => item.flowNodeType === FlowNodeTypeEnum.readFiles
|
||||
);
|
||||
|
||||
const globalFiles = chatValue2RuntimePrompt(query).files;
|
||||
const { documentQuoteText, userFiles } = await getMultiInput({
|
||||
histories: chatHistories,
|
||||
requestOrigin,
|
||||
maxFiles: chatConfig?.fileSelectConfig?.maxFiles || 20,
|
||||
teamId,
|
||||
fileLinks,
|
||||
inputFiles: globalFiles
|
||||
});
|
||||
|
||||
const concatenateSystemPrompt = [
|
||||
toolModel.defaultSystemChatPrompt,
|
||||
systemPrompt,
|
||||
documentQuoteText
|
||||
? replaceVariable(Prompt_DocumentQuote, {
|
||||
quote: documentQuoteText
|
||||
})
|
||||
: ''
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n\n===---===---===\n\n');
|
||||
|
||||
const messages: ChatItemType[] = (() => {
|
||||
const value: ChatItemType[] = [
|
||||
...getSystemPrompt_ChatItemType(toolModel.defaultSystemChatPrompt),
|
||||
...getSystemPrompt_ChatItemType(systemPrompt),
|
||||
...getSystemPrompt_ChatItemType(concatenateSystemPrompt),
|
||||
// Add file input prompt to histories
|
||||
...chatHistories.map((item) => {
|
||||
if (item.obj === ChatRoleEnum.Human) {
|
||||
return {
|
||||
...item,
|
||||
value: toolCallMessagesAdapt({
|
||||
userInput: item.value
|
||||
userInput: item.value,
|
||||
skip: !hasReadFilesTool
|
||||
})
|
||||
};
|
||||
}
|
||||
@@ -129,9 +135,10 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
||||
{
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: toolCallMessagesAdapt({
|
||||
skip: !hasReadFilesTool,
|
||||
userInput: runtimePrompt2ChatsValue({
|
||||
text: userChatInput,
|
||||
files: chatValue2RuntimePrompt(query).files
|
||||
files: userFiles
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -185,7 +192,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
||||
// array, replace last element
|
||||
const lastText = lastMessage.content[lastMessage.content.length - 1];
|
||||
if (lastText.type === 'text') {
|
||||
lastMessage.content = replaceVariable(Prompt_Tool_Call, {
|
||||
lastText.text = replaceVariable(Prompt_Tool_Call, {
|
||||
question: lastText.text
|
||||
});
|
||||
} else {
|
||||
@@ -237,7 +244,11 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
||||
childTotalPoints: flatUsages.reduce((sum, item) => sum + item.totalPoints, 0),
|
||||
model: modelName,
|
||||
query: userChatInput,
|
||||
historyPreview: getHistoryPreview(GPTMessages2Chats(completeMessages, false), 10000),
|
||||
historyPreview: getHistoryPreview(
|
||||
GPTMessages2Chats(completeMessages, false),
|
||||
10000,
|
||||
useVision
|
||||
),
|
||||
toolDetail: childToolResponse,
|
||||
mergeSignId: nodeId
|
||||
},
|
||||
@@ -253,3 +264,88 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
||||
[DispatchNodeResponseKeyEnum.interactive]: toolWorkflowInteractiveResponse
|
||||
};
|
||||
};
|
||||
|
||||
const getMultiInput = async ({
|
||||
histories,
|
||||
fileLinks,
|
||||
requestOrigin,
|
||||
maxFiles,
|
||||
teamId,
|
||||
inputFiles
|
||||
}: {
|
||||
histories: ChatItemType[];
|
||||
fileLinks?: string[];
|
||||
requestOrigin?: string;
|
||||
maxFiles: number;
|
||||
teamId: string;
|
||||
inputFiles: UserChatItemValueItemType['file'][];
|
||||
}) => {
|
||||
// Not file quote
|
||||
if (!fileLinks) {
|
||||
return {
|
||||
documentQuoteText: '',
|
||||
userFiles: inputFiles
|
||||
};
|
||||
}
|
||||
|
||||
const filesFromHistories = getHistoryFileLinks(histories);
|
||||
const urls = [...fileLinks, ...filesFromHistories];
|
||||
|
||||
if (urls.length === 0) {
|
||||
return {
|
||||
documentQuoteText: '',
|
||||
userFiles: []
|
||||
};
|
||||
}
|
||||
|
||||
// Get files from histories
|
||||
const { text } = await getFileContentFromLinks({
|
||||
// Concat fileUrlList and filesFromHistories; remove not supported files
|
||||
urls,
|
||||
requestOrigin,
|
||||
maxFiles,
|
||||
teamId
|
||||
});
|
||||
|
||||
return {
|
||||
documentQuoteText: text,
|
||||
userFiles: fileLinks.map((url) => parseUrlToFileType(url))
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
Tool call, auth add file prompt to question。
|
||||
Guide the LLM to call tool.
|
||||
*/
|
||||
const toolCallMessagesAdapt = ({
|
||||
userInput,
|
||||
skip
|
||||
}: {
|
||||
userInput: UserChatItemValueItemType[];
|
||||
skip?: boolean;
|
||||
}) => {
|
||||
if (skip) return userInput;
|
||||
|
||||
const files = userInput.filter((item) => item.type === 'file');
|
||||
|
||||
if (files.length > 0) {
|
||||
return userInput.map((item) => {
|
||||
if (item.type === 'text') {
|
||||
const filesCount = files.filter((file) => file.file?.type === 'file').length;
|
||||
const imgCount = files.filter((file) => file.file?.type === 'image').length;
|
||||
const text = item.text?.content || '';
|
||||
|
||||
return {
|
||||
...item,
|
||||
text: {
|
||||
content: getMultiplePrompt({ fileCount: filesCount, imgCount, question: text })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
return userInput;
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ import { WorkflowResponseType } from '../../type';
|
||||
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
|
||||
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { i18nT } from '../../../../../../web/i18n/utils';
|
||||
|
||||
type FunctionCallCompletion = {
|
||||
id: string;
|
||||
@@ -176,17 +177,29 @@ export const runToolWithPromptCall = async (
|
||||
);
|
||||
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
if (typeof lastMessage.content !== 'string') {
|
||||
if (typeof lastMessage.content === 'string') {
|
||||
lastMessage.content = replaceVariable(lastMessage.content, {
|
||||
toolsPrompt
|
||||
});
|
||||
} else if (Array.isArray(lastMessage.content)) {
|
||||
// array, replace last element
|
||||
const lastText = lastMessage.content[lastMessage.content.length - 1];
|
||||
if (lastText.type === 'text') {
|
||||
lastText.text = replaceVariable(lastText.text, {
|
||||
toolsPrompt
|
||||
});
|
||||
} else {
|
||||
return Promise.reject('Prompt call invalid input');
|
||||
}
|
||||
} else {
|
||||
return Promise.reject('Prompt call invalid input');
|
||||
}
|
||||
lastMessage.content = replaceVariable(lastMessage.content, {
|
||||
toolsPrompt
|
||||
});
|
||||
|
||||
const filterMessages = await filterGPTMessageByMaxTokens({
|
||||
messages,
|
||||
maxTokens: toolModel.maxContext - 500 // filter token. not response maxToken
|
||||
});
|
||||
|
||||
const [requestMessages, max_tokens] = await Promise.all([
|
||||
loadRequestMessages({
|
||||
messages: filterMessages,
|
||||
@@ -398,11 +411,27 @@ export const runToolWithPromptCall = async (
|
||||
: undefined;
|
||||
|
||||
// get the next user prompt
|
||||
lastMessage.content += `${replaceAnswer}
|
||||
if (typeof lastMessage.content === 'string') {
|
||||
lastMessage.content += `${replaceAnswer}
|
||||
TOOL_RESPONSE: """
|
||||
${workflowInteractiveResponseItem ? `{{${INTERACTIVE_STOP_SIGNAL}}}` : toolsRunResponse.toolResponsePrompt}
|
||||
"""
|
||||
ANSWER: `;
|
||||
} else if (Array.isArray(lastMessage.content)) {
|
||||
// array, replace last element
|
||||
const lastText = lastMessage.content[lastMessage.content.length - 1];
|
||||
if (lastText.type === 'text') {
|
||||
lastText.text += `${replaceAnswer}
|
||||
TOOL_RESPONSE: """
|
||||
${workflowInteractiveResponseItem ? `{{${INTERACTIVE_STOP_SIGNAL}}}` : toolsRunResponse.toolResponsePrompt}
|
||||
"""
|
||||
ANSWER: `;
|
||||
} else {
|
||||
return Promise.reject('Prompt call invalid input');
|
||||
}
|
||||
} else {
|
||||
return Promise.reject('Prompt call invalid input');
|
||||
}
|
||||
|
||||
const runTimes = (response?.runTimes || 0) + toolsRunResponse.toolResponse.runTimes;
|
||||
const toolNodeTokens = response?.toolNodeTokens ? response.toolNodeTokens + tokens : tokens;
|
||||
@@ -509,7 +538,7 @@ async function streamResponse({
|
||||
}
|
||||
|
||||
if (!textAnswer) {
|
||||
return Promise.reject('LLM api response empty');
|
||||
return Promise.reject(i18nT('chat:LLM_model_response_empty'));
|
||||
}
|
||||
return { answer: textAnswer.trim() };
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import { addLog } from '../../../../../common/system/log';
|
||||
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
|
||||
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { i18nT } from '../../../../../../web/i18n/utils';
|
||||
|
||||
type ToolRunResponseType = {
|
||||
toolRunResponse: DispatchFlowResponse;
|
||||
@@ -268,7 +269,7 @@ export const runToolWithToolChoice = async (
|
||||
},
|
||||
toolModel
|
||||
);
|
||||
// console.log(JSON.stringify(requestMessages, null, 2), '==requestBody');
|
||||
// console.log(JSON.stringify(requestBody, null, 2), '==requestBody');
|
||||
/* Run llm */
|
||||
const ai = getAIApi({
|
||||
timeout: 480000
|
||||
@@ -656,7 +657,7 @@ async function streamResponse({
|
||||
}
|
||||
|
||||
if (!textAnswer && toolCalls.length === 0) {
|
||||
return Promise.reject('LLM api response empty');
|
||||
return Promise.reject(i18nT('chat:LLM_model_response_empty'));
|
||||
}
|
||||
|
||||
return { answer: textAnswer, toolCalls };
|
||||
|
||||
@@ -21,6 +21,7 @@ export type DispatchToolModuleProps = ModuleDispatchProps<{
|
||||
[NodeInputKeyEnum.aiChatTemperature]: number;
|
||||
[NodeInputKeyEnum.aiChatMaxToken]: number;
|
||||
[NodeInputKeyEnum.aiChatVision]?: boolean;
|
||||
[NodeInputKeyEnum.fileUrlList]?: string[];
|
||||
}> & {
|
||||
messages: ChatCompletionMessageParam[];
|
||||
toolNodes: ToolNodeItemType[];
|
||||
|
||||
@@ -5,11 +5,7 @@ import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { getAIApi } from '../../../ai/config';
|
||||
import type {
|
||||
ChatCompletion,
|
||||
ChatCompletionMessageParam,
|
||||
StreamChatType
|
||||
} from '@fastgpt/global/core/ai/type.d';
|
||||
import type { ChatCompletion, StreamChatType } from '@fastgpt/global/core/ai/type.d';
|
||||
import { formatModelChars2Points } from '../../../../support/wallet/usage/utils';
|
||||
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import { postTextCensor } from '../../../../common/api/requestPlusApi';
|
||||
@@ -46,6 +42,9 @@ import { WorkflowResponseType } from '../type';
|
||||
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
|
||||
import { AiChatQuoteRoleType } from '@fastgpt/global/core/workflow/template/system/aiChat/type';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { getFileContentFromLinks, getHistoryFileLinks } from '../tools/readFiles';
|
||||
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
||||
import { i18nT } from '../../../../../web/i18n/utils';
|
||||
|
||||
export type ChatProps = ModuleDispatchProps<
|
||||
AIChatNodeProps & {
|
||||
@@ -69,7 +68,9 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
histories,
|
||||
node: { name },
|
||||
query,
|
||||
runningAppInfo: { teamId },
|
||||
workflowStreamResponse,
|
||||
chatConfig,
|
||||
params: {
|
||||
model,
|
||||
temperature = 0,
|
||||
@@ -83,14 +84,12 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
quoteTemplate,
|
||||
quotePrompt,
|
||||
aiChatVision,
|
||||
stringQuoteText
|
||||
fileUrlList: fileLinks, // node quote file links
|
||||
stringQuoteText //abandon
|
||||
}
|
||||
} = props;
|
||||
const { files: inputFiles } = chatValue2RuntimePrompt(query);
|
||||
const { files: inputFiles } = chatValue2RuntimePrompt(query); // Chat box input files
|
||||
|
||||
if (!userChatInput && inputFiles.length === 0) {
|
||||
return Promise.reject('Question is empty');
|
||||
}
|
||||
stream = stream && isResponseAnswerText;
|
||||
|
||||
const chatHistories = getHistories(history, histories);
|
||||
@@ -100,11 +99,26 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
return Promise.reject('The chat model is undefined, you need to select a chat model.');
|
||||
}
|
||||
|
||||
const { datasetQuoteText } = await filterDatasetQuote({
|
||||
quoteQA,
|
||||
model: modelConstantsData,
|
||||
quoteTemplate
|
||||
});
|
||||
const [{ datasetQuoteText }, { documentQuoteText, userFiles }] = await Promise.all([
|
||||
filterDatasetQuote({
|
||||
quoteQA,
|
||||
model: modelConstantsData,
|
||||
quoteTemplate
|
||||
}),
|
||||
getMultiInput({
|
||||
histories: chatHistories,
|
||||
inputFiles,
|
||||
fileLinks,
|
||||
stringQuoteText,
|
||||
requestOrigin,
|
||||
maxFiles: chatConfig?.fileSelectConfig?.maxFiles || 20,
|
||||
teamId
|
||||
})
|
||||
]);
|
||||
|
||||
if (!userChatInput && !documentQuoteText && userFiles.length === 0) {
|
||||
return Promise.reject(i18nT('chat:AI_input_is_empty'));
|
||||
}
|
||||
|
||||
const [{ filterMessages }] = await Promise.all([
|
||||
getChatMessages({
|
||||
@@ -115,9 +129,9 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
aiChatQuoteRole,
|
||||
datasetQuotePrompt: quotePrompt,
|
||||
userChatInput,
|
||||
inputFiles,
|
||||
systemPrompt,
|
||||
stringQuoteText
|
||||
userFiles,
|
||||
documentQuoteText
|
||||
}),
|
||||
(() => {
|
||||
// censor model and system key
|
||||
@@ -132,22 +146,9 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
})()
|
||||
]);
|
||||
|
||||
// Get the request messages
|
||||
const concatMessages = [
|
||||
...(modelConstantsData.defaultSystemChatPrompt
|
||||
? [
|
||||
{
|
||||
role: ChatCompletionRequestMessageRoleEnum.System,
|
||||
content: modelConstantsData.defaultSystemChatPrompt
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...filterMessages
|
||||
] as ChatCompletionMessageParam[];
|
||||
|
||||
const [requestMessages, max_tokens] = await Promise.all([
|
||||
loadRequestMessages({
|
||||
messages: concatMessages,
|
||||
messages: filterMessages,
|
||||
useVision: modelConstantsData.vision && aiChatVision,
|
||||
origin: requestOrigin
|
||||
}),
|
||||
@@ -195,7 +196,7 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
});
|
||||
|
||||
if (!answer) {
|
||||
throw new Error('LLM model response empty');
|
||||
return Promise.reject(i18nT('chat:LLM_model_response_empty'));
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -242,7 +243,11 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
tokens,
|
||||
query: `${userChatInput}`,
|
||||
maxToken: max_tokens,
|
||||
historyPreview: getHistoryPreview(chatCompleteMessages, 10000),
|
||||
historyPreview: getHistoryPreview(
|
||||
chatCompleteMessages,
|
||||
10000,
|
||||
modelConstantsData.vision && aiChatVision
|
||||
),
|
||||
contextTotalLen: completeMessages.length
|
||||
},
|
||||
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
|
||||
@@ -302,7 +307,70 @@ async function filterDatasetQuote({
|
||||
datasetQuoteText
|
||||
};
|
||||
}
|
||||
|
||||
async function getMultiInput({
|
||||
histories,
|
||||
inputFiles,
|
||||
fileLinks,
|
||||
stringQuoteText,
|
||||
requestOrigin,
|
||||
maxFiles,
|
||||
teamId
|
||||
}: {
|
||||
histories: ChatItemType[];
|
||||
inputFiles: UserChatItemValueItemType['file'][];
|
||||
fileLinks?: string[];
|
||||
stringQuoteText?: string; // file quote
|
||||
requestOrigin?: string;
|
||||
maxFiles: number;
|
||||
teamId: string;
|
||||
}) {
|
||||
// 旧版本适配====>
|
||||
if (stringQuoteText) {
|
||||
return {
|
||||
documentQuoteText: stringQuoteText,
|
||||
userFiles: inputFiles
|
||||
};
|
||||
}
|
||||
|
||||
// 没有引用文件参考,但是可能用了图片识别
|
||||
if (!fileLinks) {
|
||||
return {
|
||||
documentQuoteText: '',
|
||||
userFiles: inputFiles
|
||||
};
|
||||
}
|
||||
// 旧版本适配<====
|
||||
|
||||
// If fileLinks params is not empty, it means it is a new version, not get the global file.
|
||||
|
||||
// Get files from histories
|
||||
const filesFromHistories = getHistoryFileLinks(histories);
|
||||
const urls = [...fileLinks, ...filesFromHistories];
|
||||
|
||||
if (urls.length === 0) {
|
||||
return {
|
||||
documentQuoteText: '',
|
||||
userFiles: []
|
||||
};
|
||||
}
|
||||
|
||||
const { text } = await getFileContentFromLinks({
|
||||
// Concat fileUrlList and filesFromHistories; remove not supported files
|
||||
urls,
|
||||
requestOrigin,
|
||||
maxFiles,
|
||||
teamId
|
||||
});
|
||||
|
||||
return {
|
||||
documentQuoteText: text,
|
||||
userFiles: fileLinks.map((url) => parseUrlToFileType(url))
|
||||
};
|
||||
}
|
||||
|
||||
async function getChatMessages({
|
||||
model,
|
||||
aiChatQuoteRole,
|
||||
datasetQuotePrompt = '',
|
||||
datasetQuoteText,
|
||||
@@ -310,10 +378,10 @@ async function getChatMessages({
|
||||
histories = [],
|
||||
systemPrompt,
|
||||
userChatInput,
|
||||
inputFiles,
|
||||
model,
|
||||
stringQuoteText
|
||||
userFiles,
|
||||
documentQuoteText
|
||||
}: {
|
||||
model: LLMModelItemType;
|
||||
// dataset quote
|
||||
aiChatQuoteRole: AiChatQuoteRoleType; // user: replace user prompt; system: replace system prompt
|
||||
datasetQuotePrompt?: string;
|
||||
@@ -323,10 +391,11 @@ async function getChatMessages({
|
||||
histories: ChatItemType[];
|
||||
systemPrompt: string;
|
||||
userChatInput: string;
|
||||
inputFiles: UserChatItemValueItemType['file'][];
|
||||
model: LLMModelItemType;
|
||||
stringQuoteText?: string; // file quote
|
||||
|
||||
userFiles: UserChatItemValueItemType['file'][];
|
||||
documentQuoteText?: string; // document quote
|
||||
}) {
|
||||
// Dataset prompt ====>
|
||||
// User role or prompt include question
|
||||
const quoteRole =
|
||||
aiChatQuoteRole === 'user' || datasetQuotePrompt.includes('{{question}}') ? 'user' : 'system';
|
||||
@@ -337,6 +406,7 @@ async function getChatMessages({
|
||||
? Prompt_userQuotePromptList[0].value
|
||||
: Prompt_systemQuotePromptList[0].value;
|
||||
|
||||
// Reset user input, add dataset quote to user input
|
||||
const replaceInputValue =
|
||||
useDatasetQuote && quoteRole === 'user'
|
||||
? replaceVariable(datasetQuotePromptTemplate, {
|
||||
@@ -344,31 +414,33 @@ async function getChatMessages({
|
||||
question: userChatInput
|
||||
})
|
||||
: userChatInput;
|
||||
// Dataset prompt <====
|
||||
|
||||
const replaceSystemPrompt =
|
||||
// Concat system prompt
|
||||
const concatenateSystemPrompt = [
|
||||
model.defaultSystemChatPrompt,
|
||||
systemPrompt,
|
||||
useDatasetQuote && quoteRole === 'system'
|
||||
? `${systemPrompt ? systemPrompt + '\n\n------\n\n' : ''}${replaceVariable(
|
||||
datasetQuotePromptTemplate,
|
||||
{
|
||||
quote: datasetQuoteText
|
||||
}
|
||||
)}`
|
||||
: systemPrompt;
|
||||
? replaceVariable(datasetQuotePromptTemplate, {
|
||||
quote: datasetQuoteText
|
||||
})
|
||||
: '',
|
||||
documentQuoteText
|
||||
? replaceVariable(Prompt_DocumentQuote, {
|
||||
quote: documentQuoteText
|
||||
})
|
||||
: ''
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n\n===---===---===\n\n');
|
||||
|
||||
const messages: ChatItemType[] = [
|
||||
...getSystemPrompt_ChatItemType(replaceSystemPrompt),
|
||||
...(stringQuoteText // file quote
|
||||
? getSystemPrompt_ChatItemType(
|
||||
replaceVariable(Prompt_DocumentQuote, {
|
||||
quote: stringQuoteText
|
||||
})
|
||||
)
|
||||
: []),
|
||||
...getSystemPrompt_ChatItemType(concatenateSystemPrompt),
|
||||
...histories,
|
||||
{
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: runtimePrompt2ChatsValue({
|
||||
files: inputFiles,
|
||||
files: userFiles,
|
||||
text: replaceInputValue
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import type {
|
||||
DispatchNodeResultType,
|
||||
ModuleDispatchProps
|
||||
} from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { datasetSearchResultConcat } from '@fastgpt/global/core/dataset/search/utils';
|
||||
import { filterSearchResultsByMaxChars } from '../../utils';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
|
||||
type DatasetConcatProps = ModuleDispatchProps<
|
||||
{
|
||||
[NodeInputKeyEnum.datasetMaxTokens]: number;
|
||||
} & { [key: string]: SearchDataResponseItemType[] }
|
||||
>;
|
||||
type DatasetConcatResponse = {
|
||||
type DatasetConcatResponse = DispatchNodeResultType<{
|
||||
[NodeOutputKeyEnum.datasetQuoteQA]: SearchDataResponseItemType[];
|
||||
};
|
||||
}>;
|
||||
|
||||
export async function dispatchDatasetConcat(
|
||||
props: DatasetConcatProps
|
||||
@@ -30,6 +34,12 @@ export async function dispatchDatasetConcat(
|
||||
);
|
||||
|
||||
return {
|
||||
[NodeOutputKeyEnum.datasetQuoteQA]: await filterSearchResultsByMaxChars(rrfConcatResults, limit)
|
||||
[NodeOutputKeyEnum.datasetQuoteQA]: await filterSearchResultsByMaxChars(
|
||||
rrfConcatResults,
|
||||
limit
|
||||
),
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
concatLength: rrfConcatResults.length
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { datasetSearchQueryExtension } from '../../../dataset/search/utils';
|
||||
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
|
||||
import { checkTeamReRankPermission } from '../../../../support/permission/teamLimit';
|
||||
import { MongoDataset } from '../../../dataset/schema';
|
||||
import { i18nT } from '../../../../../web/i18n/utils';
|
||||
|
||||
type DatasetSearchProps = ModuleDispatchProps<{
|
||||
[NodeInputKeyEnum.datasetSelectList]: SelectedDatasetType;
|
||||
@@ -56,15 +57,15 @@ export async function dispatchDatasetSearch(
|
||||
} = props as DatasetSearchProps;
|
||||
|
||||
if (!Array.isArray(datasets)) {
|
||||
return Promise.reject('Quote type error');
|
||||
return Promise.reject(i18nT('chat:dataset_quote_type error'));
|
||||
}
|
||||
|
||||
if (datasets.length === 0) {
|
||||
return Promise.reject('core.chat.error.Select dataset empty');
|
||||
return Promise.reject(i18nT('common:core.chat.error.Select dataset empty'));
|
||||
}
|
||||
|
||||
if (!userChatInput) {
|
||||
return Promise.reject('core.chat.error.User input empty');
|
||||
return Promise.reject(i18nT('common:core.chat.error.User input empty'));
|
||||
}
|
||||
|
||||
// query extension
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
} from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { getNanoid, replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import { getSystemTime } from '@fastgpt/global/common/time/timezone';
|
||||
import { replaceEditorVariable } from '@fastgpt/global/core/workflow/utils';
|
||||
|
||||
import { dispatchWorkflowStart } from './init/workflowStart';
|
||||
import { dispatchChatCompletion } from './chat/oneapi';
|
||||
@@ -38,11 +37,12 @@ import { dispatchQueryExtension } from './tools/queryExternsion';
|
||||
import { dispatchRunPlugin } from './plugin/run';
|
||||
import { dispatchPluginInput } from './plugin/runInput';
|
||||
import { dispatchPluginOutput } from './plugin/runOutput';
|
||||
import { removeSystemVariable, valueTypeFormat } from './utils';
|
||||
import { formatHttpError, removeSystemVariable, valueTypeFormat } from './utils';
|
||||
import {
|
||||
filterWorkflowEdges,
|
||||
checkNodeRunStatus,
|
||||
textAdaptGptResponse
|
||||
textAdaptGptResponse,
|
||||
replaceEditorVariable
|
||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
|
||||
import { dispatchRunTools } from './agent/runTool/index';
|
||||
@@ -72,6 +72,7 @@ import { dispatchLoopEnd } from './loop/runLoopEnd';
|
||||
import { dispatchLoopStart } from './loop/runLoopStart';
|
||||
import { dispatchFormInput } from './interactive/formInput';
|
||||
import { dispatchToolParams } from './agent/runTool/toolParams';
|
||||
import { responseWrite } from '../../../common/response';
|
||||
|
||||
const callbackMap: Record<FlowNodeTypeEnum, Function> = {
|
||||
[FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart,
|
||||
@@ -386,6 +387,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
||||
node,
|
||||
runtimeEdges
|
||||
});
|
||||
|
||||
const nodeRunResult = await (() => {
|
||||
if (status === 'run') {
|
||||
nodeRunBeforeHook(node);
|
||||
@@ -481,8 +483,16 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
||||
: {};
|
||||
|
||||
node.inputs.forEach((input) => {
|
||||
// Special input, not format
|
||||
if (input.key === dynamicInput?.key) return;
|
||||
|
||||
// Skip some special key
|
||||
if (input.key === NodeInputKeyEnum.childrenNodeIdList) {
|
||||
params[input.key] = input.value;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// replace {{xx}} variables
|
||||
let value = replaceVariable(input.value, variables);
|
||||
|
||||
@@ -505,7 +515,6 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
||||
if (input.canEdit && dynamicInput && params[dynamicInput.key]) {
|
||||
params[dynamicInput.key][input.key] = valueTypeFormat(value, input.valueType);
|
||||
}
|
||||
|
||||
params[input.key] = valueTypeFormat(value, input.valueType);
|
||||
});
|
||||
|
||||
@@ -548,7 +557,21 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
||||
// run module
|
||||
const dispatchRes: Record<string, any> = await (async () => {
|
||||
if (callbackMap[node.flowNodeType]) {
|
||||
return callbackMap[node.flowNodeType](dispatchData);
|
||||
try {
|
||||
return await callbackMap[node.flowNodeType](dispatchData);
|
||||
} catch (error) {
|
||||
// Get source handles of outgoing edges
|
||||
const targetEdges = runtimeEdges.filter((item) => item.source === node.nodeId);
|
||||
const skipHandleIds = targetEdges.map((item) => item.sourceHandle);
|
||||
|
||||
// Skip all edges and return error
|
||||
return {
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
error: formatHttpError(error)
|
||||
},
|
||||
[DispatchNodeResponseKeyEnum.skipHandleId]: skipHandleIds
|
||||
};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
})();
|
||||
|
||||
@@ -25,7 +25,7 @@ export const dispatchLoop = async (props: Props): Promise<Response> => {
|
||||
user,
|
||||
node: { name }
|
||||
} = props;
|
||||
const { loopInputArray = [], childrenNodeIdList } = params;
|
||||
const { loopInputArray = [], childrenNodeIdList = [] } = params;
|
||||
|
||||
if (!Array.isArray(loopInputArray)) {
|
||||
return Promise.reject('Input value is not an array');
|
||||
@@ -43,23 +43,24 @@ export const dispatchLoop = async (props: Props): Promise<Response> => {
|
||||
let totalPoints = 0;
|
||||
let newVariables: Record<string, any> = props.variables;
|
||||
|
||||
for await (const item of loopInputArray) {
|
||||
let index = 0;
|
||||
for await (const item of loopInputArray.filter(Boolean)) {
|
||||
runtimeNodes.forEach((node) => {
|
||||
if (
|
||||
childrenNodeIdList.includes(node.nodeId) &&
|
||||
node.flowNodeType === FlowNodeTypeEnum.loopStart
|
||||
) {
|
||||
node.isEntry = true;
|
||||
node.inputs = node.inputs.map((input) =>
|
||||
input.key === NodeInputKeyEnum.loopStartInput
|
||||
? {
|
||||
...input,
|
||||
value: item
|
||||
}
|
||||
: input
|
||||
);
|
||||
node.inputs.forEach((input) => {
|
||||
if (input.key === NodeInputKeyEnum.loopStartInput) {
|
||||
input.value = item;
|
||||
} else if (input.key === NodeInputKeyEnum.loopStartIndex) {
|
||||
input.value = index++;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const response = await dispatchWorkFlow({
|
||||
...props,
|
||||
runtimeEdges: cloneDeep(runtimeEdges)
|
||||
@@ -69,11 +70,13 @@ export const dispatchLoop = async (props: Props): Promise<Response> => {
|
||||
(res) => res.moduleType === FlowNodeTypeEnum.loopEnd
|
||||
)?.loopOutputValue;
|
||||
|
||||
// Concat runtime response
|
||||
outputValueArr.push(loopOutputValue);
|
||||
loopDetail.push(...response.flowResponses);
|
||||
assistantResponses.push(...response.assistantResponses);
|
||||
totalPoints += response.flowUsages.reduce((acc, usage) => acc + usage.totalPoints, 0);
|
||||
|
||||
totalPoints = response.flowUsages.reduce((acc, usage) => acc + usage.totalPoints, 0);
|
||||
// Concat new variables
|
||||
newVariables = {
|
||||
...newVariables,
|
||||
...response.newVariables
|
||||
|
||||
@@ -7,9 +7,11 @@ import {
|
||||
|
||||
type Props = ModuleDispatchProps<{
|
||||
[NodeInputKeyEnum.loopStartInput]: any;
|
||||
[NodeInputKeyEnum.loopStartIndex]: number;
|
||||
}>;
|
||||
type Response = DispatchNodeResultType<{
|
||||
[NodeOutputKeyEnum.loopStartInput]: any;
|
||||
[NodeOutputKeyEnum.loopStartIndex]: number;
|
||||
}>;
|
||||
|
||||
export const dispatchLoopStart = async (props: Props): Promise<Response> => {
|
||||
@@ -18,6 +20,7 @@ export const dispatchLoopStart = async (props: Props): Promise<Response> => {
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
loopInputValue: params.loopStartInput
|
||||
},
|
||||
[NodeOutputKeyEnum.loopStartInput]: params.loopStartInput
|
||||
[NodeOutputKeyEnum.loopStartInput]: params.loopStartInput,
|
||||
[NodeOutputKeyEnum.loopStartIndex]: params.loopStartIndex
|
||||
};
|
||||
};
|
||||
|
||||
@@ -112,7 +112,11 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
|
||||
output.moduleLogo = plugin.avatar;
|
||||
}
|
||||
|
||||
const usagePoints = await computedPluginUsage(plugin, flowUsages);
|
||||
const usagePoints = await computedPluginUsage({
|
||||
plugin,
|
||||
childrenUsage: flowUsages,
|
||||
error: !!output?.pluginOutput?.error
|
||||
});
|
||||
|
||||
return {
|
||||
// 嵌套运行时,如果 childApp stream=false,实际上不会有任何内容输出给用户,所以不需要存储
|
||||
|
||||
@@ -17,12 +17,14 @@ import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/ty
|
||||
import { authAppByTmbId } from '../../../../support/permission/app/auth';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { getAppVersionById } from '../../../app/version/controller';
|
||||
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
||||
|
||||
type Props = ModuleDispatchProps<{
|
||||
[NodeInputKeyEnum.userChatInput]: string;
|
||||
[NodeInputKeyEnum.history]?: ChatItemType[] | number;
|
||||
[NodeInputKeyEnum.fileUrlList]?: string[];
|
||||
[NodeInputKeyEnum.forbidStream]?: boolean;
|
||||
[NodeInputKeyEnum.fileUrlList]?: string[];
|
||||
}>;
|
||||
type Response = DispatchNodeResultType<{
|
||||
[NodeOutputKeyEnum.answerText]: string;
|
||||
@@ -40,8 +42,24 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
|
||||
variables
|
||||
} = props;
|
||||
|
||||
const { system_forbid_stream = false, userChatInput, history, ...childrenAppVariables } = params;
|
||||
if (!userChatInput) {
|
||||
const {
|
||||
system_forbid_stream = false,
|
||||
userChatInput,
|
||||
history,
|
||||
fileUrlList,
|
||||
...childrenAppVariables
|
||||
} = params;
|
||||
const { files } = chatValue2RuntimePrompt(query);
|
||||
|
||||
const userInputFiles = (() => {
|
||||
if (fileUrlList) {
|
||||
return fileUrlList.map((url) => parseUrlToFileType(url));
|
||||
}
|
||||
// Adapt version 4.8.13 upgrade
|
||||
return files;
|
||||
})();
|
||||
|
||||
if (!userChatInput && !userInputFiles) {
|
||||
return Promise.reject('Input is empty');
|
||||
}
|
||||
if (!appId) {
|
||||
@@ -72,7 +90,6 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
|
||||
}
|
||||
|
||||
const chatHistories = getHistories(history, histories);
|
||||
const { files } = chatValue2RuntimePrompt(query);
|
||||
|
||||
// Rewrite children app variables
|
||||
const systemVariables = filterSystemVariables(variables);
|
||||
@@ -102,7 +119,7 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
|
||||
histories: chatHistories,
|
||||
variables: childrenRunVariables,
|
||||
query: runtimePrompt2ChatsValue({
|
||||
files,
|
||||
files: userInputFiles,
|
||||
text: userChatInput
|
||||
}),
|
||||
chatConfig
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
||||
import { ChatFileTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
@@ -11,6 +12,26 @@ export const dispatchPluginInput = (props: PluginInputProps) => {
|
||||
const { params, query } = props;
|
||||
const { files } = chatValue2RuntimePrompt(query);
|
||||
|
||||
/*
|
||||
对 params 中文件类型数据进行处理
|
||||
* 插件单独运行时,这里会是一个特殊的数组
|
||||
* 插件调用的话,这个参数是一个 string[] 不会进行处理
|
||||
* 硬性要求:API 单独调用插件时,要避免这种特殊类型冲突
|
||||
|
||||
TODO: 需要 filter max files
|
||||
*/
|
||||
for (const key in params) {
|
||||
const val = params[key];
|
||||
if (
|
||||
Array.isArray(val) &&
|
||||
val.every(
|
||||
(item) => item.type === ChatFileTypeEnum.file || item.type === ChatFileTypeEnum.image
|
||||
)
|
||||
) {
|
||||
params[key] = val.map((item) => item.url);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...params,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {},
|
||||
|
||||
@@ -14,15 +14,17 @@ import { SERVICE_LOCAL_HOST } from '../../../../common/system/tools';
|
||||
import { addLog } from '../../../../common/system/log';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import {
|
||||
textAdaptGptResponse,
|
||||
replaceEditorVariable
|
||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { getSystemPluginCb } from '../../../../../plugins/register';
|
||||
import { ContentTypes } from '@fastgpt/global/core/workflow/constants';
|
||||
import { replaceEditorVariable } from '@fastgpt/global/core/workflow/utils';
|
||||
import { uploadFile } from '../../../../common/file/gridfs/controller';
|
||||
import { uploadFileFromBase64Img } from '../../../../common/file/gridfs/controller';
|
||||
import { ReadFileBaseUrl } from '@fastgpt/global/common/file/constants';
|
||||
import { createFileToken } from '../../../../support/permission/controller';
|
||||
import { removeFilesByPaths } from '../../../../common/file/utils';
|
||||
import { JSONPath } from 'jsonpath-plus';
|
||||
import type { SystemPluginSpecialResponse } from '../../../../../plugins/type';
|
||||
|
||||
type PropsArrType = {
|
||||
key: string;
|
||||
@@ -235,7 +237,9 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
node.outputs
|
||||
.filter(
|
||||
(item) =>
|
||||
item.key !== NodeOutputKeyEnum.error && item.key !== NodeOutputKeyEnum.httpRawResponse
|
||||
item.id !== NodeOutputKeyEnum.error &&
|
||||
item.id !== NodeOutputKeyEnum.httpRawResponse &&
|
||||
item.id !== NodeOutputKeyEnum.addOutputParam
|
||||
)
|
||||
.forEach((item) => {
|
||||
const key = item.key.startsWith('$') ? item.key : `$.${item.key}`;
|
||||
@@ -376,27 +380,25 @@ async function replaceSystemPluginResponse({
|
||||
tmbId: string;
|
||||
}) {
|
||||
for await (const key of Object.keys(response)) {
|
||||
if (typeof response[key] === 'object' && response[key].type === 'SYSTEM_PLUGIN_FILE') {
|
||||
const fileObj = response[key];
|
||||
const filename = fileObj.path.split('/').pop() || `${tmbId}-${Date.now()}`;
|
||||
if (typeof response[key] === 'object' && response[key].type === 'SYSTEM_PLUGIN_BASE64') {
|
||||
const fileObj = response[key] as SystemPluginSpecialResponse;
|
||||
const filename = `${tmbId}-${Date.now()}.${fileObj.extension}`;
|
||||
try {
|
||||
const fileId = await uploadFile({
|
||||
const fileId = await uploadFileFromBase64Img({
|
||||
teamId,
|
||||
tmbId,
|
||||
bucketName: 'chat',
|
||||
path: fileObj.path,
|
||||
base64: fileObj.value,
|
||||
filename,
|
||||
contentType: fileObj.contentType,
|
||||
metadata: {}
|
||||
});
|
||||
response[key] = `${ReadFileBaseUrl}?filename=${filename}&token=${await createFileToken({
|
||||
response[key] = `${ReadFileBaseUrl}/${filename}?token=${await createFileToken({
|
||||
bucketName: 'chat',
|
||||
teamId,
|
||||
tmbId,
|
||||
fileId
|
||||
})}`;
|
||||
} catch (error) {}
|
||||
removeFilesByPaths([fileObj.path]);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
|
||||
@@ -2,16 +2,15 @@ import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runti
|
||||
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { documentFileType } from '@fastgpt/global/common/file/constants';
|
||||
import axios from 'axios';
|
||||
import { serverRequestBaseUrl } from '../../../../common/api/serverRequest';
|
||||
import { MongoRawTextBuffer } from '../../../../common/buffer/rawText/schema';
|
||||
import { readFromSecondary } from '../../../../common/mongo/utils';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { detectFileEncoding } from '@fastgpt/global/common/file/tools';
|
||||
import { detectFileEncoding, parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
||||
import { readRawContentByFileBuffer } from '../../../../common/file/read/utils';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { ChatItemType, UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { parseFileExtensionFromUrl } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
type Props = ModuleDispatchProps<{
|
||||
@@ -48,12 +47,41 @@ export const dispatchReadFiles = async (props: Props): Promise<Response> => {
|
||||
runningAppInfo: { teamId },
|
||||
histories,
|
||||
chatConfig,
|
||||
node: { version },
|
||||
params: { fileUrlList = [] }
|
||||
} = props;
|
||||
const maxFiles = chatConfig?.fileSelectConfig?.maxFiles || 20;
|
||||
|
||||
// Get files from histories
|
||||
const filesFromHistories = histories
|
||||
const filesFromHistories = version !== '489' ? [] : getHistoryFileLinks(histories);
|
||||
|
||||
const { text, readFilesResult } = await getFileContentFromLinks({
|
||||
// Concat fileUrlList and filesFromHistories; remove not supported files
|
||||
urls: [...fileUrlList, ...filesFromHistories],
|
||||
requestOrigin,
|
||||
maxFiles,
|
||||
teamId
|
||||
});
|
||||
|
||||
return {
|
||||
[NodeOutputKeyEnum.text]: text,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
readFiles: readFilesResult.map((item) => ({
|
||||
name: item?.filename || '',
|
||||
url: item?.url || ''
|
||||
})),
|
||||
readFilesResult: readFilesResult
|
||||
.map((item) => item?.nodeResponsePreviewText ?? '')
|
||||
.join('\n******\n')
|
||||
},
|
||||
[DispatchNodeResponseKeyEnum.toolResponses]: {
|
||||
fileContent: text
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const getHistoryFileLinks = (histories: ChatItemType[]) => {
|
||||
return histories
|
||||
.filter((item) => {
|
||||
if (item.obj === ChatRoleEnum.Human) {
|
||||
return item.value.filter((value) => value.type === 'file');
|
||||
@@ -70,28 +98,38 @@ export const dispatchReadFiles = async (props: Props): Promise<Response> => {
|
||||
return files;
|
||||
})
|
||||
.flat();
|
||||
};
|
||||
|
||||
// Concat fileUrlList and filesFromHistories; remove not supported files
|
||||
const parseUrlList = [...fileUrlList, ...filesFromHistories]
|
||||
export const getFileContentFromLinks = async ({
|
||||
urls,
|
||||
requestOrigin,
|
||||
maxFiles,
|
||||
teamId
|
||||
}: {
|
||||
urls: string[];
|
||||
requestOrigin?: string;
|
||||
maxFiles: number;
|
||||
teamId: string;
|
||||
}) => {
|
||||
const parseUrlList = urls
|
||||
// Remove invalid urls
|
||||
.filter((url) => {
|
||||
if (typeof url !== 'string') return false;
|
||||
|
||||
// 检查相对路径
|
||||
const validPrefixList = ['/', 'http', 'ws'];
|
||||
if (validPrefixList.some((prefix) => url.startsWith(prefix))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
// Just get the document type file
|
||||
.filter((url) => parseUrlToFileType(url)?.type === 'file')
|
||||
.map((url) => {
|
||||
try {
|
||||
// Avoid "/api/xxx" file error.
|
||||
const origin = requestOrigin ?? 'http://localhost:3000';
|
||||
|
||||
// Check is system upload file
|
||||
if (url.startsWith('/') || (requestOrigin && url.startsWith(requestOrigin))) {
|
||||
// Parse url, get filename query. Keep only documents that can be parsed
|
||||
const parseUrl = new URL(url, origin);
|
||||
const filenameQuery = parseUrl.searchParams.get('filename');
|
||||
|
||||
// Not document
|
||||
if (filenameQuery) {
|
||||
const extensionQuery = filenameQuery.split('.').pop()?.toLowerCase() || '';
|
||||
if (!documentFileType.includes(extensionQuery)) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the origin(Make intranet requests directly)
|
||||
if (requestOrigin && url.startsWith(requestOrigin)) {
|
||||
url = url.replace(requestOrigin, '');
|
||||
@@ -123,7 +161,7 @@ export const dispatchReadFiles = async (props: Props): Promise<Response> => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Get file buffer
|
||||
// Get file buffer data
|
||||
const response = await axios.get(url, {
|
||||
baseURL: serverRequestBaseUrl,
|
||||
responseType: 'arraybuffer'
|
||||
@@ -197,18 +235,7 @@ export const dispatchReadFiles = async (props: Props): Promise<Response> => {
|
||||
const text = readFilesResult.map((item) => item?.text ?? '').join('\n******\n');
|
||||
|
||||
return {
|
||||
[NodeOutputKeyEnum.text]: text,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
readFiles: readFilesResult.map((item) => ({
|
||||
name: item?.filename || '',
|
||||
url: item?.url || ''
|
||||
})),
|
||||
readFilesResult: readFilesResult
|
||||
.map((item) => item?.nodeResponsePreviewText ?? '')
|
||||
.join('\n******\n')
|
||||
},
|
||||
[DispatchNodeResponseKeyEnum.toolResponses]: {
|
||||
fileContent: text
|
||||
}
|
||||
text,
|
||||
readFilesResult
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,11 +4,14 @@ import {
|
||||
SseResponseEventEnum
|
||||
} from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { getReferenceVariableValue } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import {
|
||||
getReferenceVariableValue,
|
||||
replaceEditorVariable
|
||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { TUpdateListItem } from '@fastgpt/global/core/workflow/template/system/variableUpdate/type';
|
||||
import { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { removeSystemVariable, valueTypeFormat } from '../utils';
|
||||
import { replaceEditorVariable } from '@fastgpt/global/core/workflow/utils';
|
||||
import { isValidReferenceValue } from '@fastgpt/global/core/workflow/utils';
|
||||
|
||||
type Props = ModuleDispatchProps<{
|
||||
[NodeInputKeyEnum.updateList]: TUpdateListItem[];
|
||||
@@ -19,15 +22,24 @@ export const dispatchUpdateVariable = async (props: Props): Promise<Response> =>
|
||||
const { params, variables, runtimeNodes, workflowStreamResponse, node } = props;
|
||||
|
||||
const { updateList } = params;
|
||||
const result = updateList.map((item) => {
|
||||
const varNodeId = item.variable?.[0];
|
||||
const varKey = item.variable?.[1];
|
||||
const nodeIds = runtimeNodes.map((node) => node.nodeId);
|
||||
|
||||
if (!varNodeId || !varKey) {
|
||||
const result = updateList.map((item) => {
|
||||
const variable = item.variable;
|
||||
|
||||
if (!isValidReferenceValue(variable, nodeIds)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const varNodeId = variable[0];
|
||||
const varKey = variable[1];
|
||||
|
||||
if (!varKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = (() => {
|
||||
// If first item is empty, it means it is a input value
|
||||
if (!item.value?.[0]) {
|
||||
const formatValue = valueTypeFormat(item.value?.[1], item.valueType);
|
||||
|
||||
@@ -48,6 +60,7 @@ export const dispatchUpdateVariable = async (props: Props): Promise<Response> =>
|
||||
}
|
||||
})();
|
||||
|
||||
// Update node output
|
||||
// Global variable
|
||||
if (varNodeId === VARIABLE_NODE_ID) {
|
||||
variables[varKey] = value;
|
||||
@@ -72,6 +85,7 @@ export const dispatchUpdateVariable = async (props: Props): Promise<Response> =>
|
||||
});
|
||||
|
||||
return {
|
||||
[DispatchNodeResponseKeyEnum.newVariables]: variables,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
updateVarResult: result
|
||||
}
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
import { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import { countPromptTokens } from '../../common/string/tiktoken/index';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import {
|
||||
getPluginInputsFromStoreNodes,
|
||||
getPluginRunContent
|
||||
} from '@fastgpt/global/core/app/plugin/utils';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { RuntimeUserPromptType, UserChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { runtimePrompt2ChatsValue } from '@fastgpt/global/core/chat/adapt';
|
||||
|
||||
/* filter search result */
|
||||
export const filterSearchResultsByMaxChars = async (
|
||||
|
||||
@@ -9,3 +9,12 @@ export const getUserFingerprint = async () => {
|
||||
export const hasHttps = () => {
|
||||
return window.location.protocol === 'https:';
|
||||
};
|
||||
|
||||
export const getWebReqUrl = (url: string = '') => {
|
||||
if (!url) return '/';
|
||||
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL;
|
||||
if (!baseUrl) return url;
|
||||
|
||||
if (!url.startsWith('/') || url.startsWith(baseUrl)) return url;
|
||||
return `${baseUrl}${url}`;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Box, Flex, Image } from '@chakra-ui/react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import type { ImageProps } from '@chakra-ui/react';
|
||||
import { LOGO_ICON } from '@fastgpt/global/common/system/constants';
|
||||
import MyIcon from '../Icon';
|
||||
import { iconPaths } from '../Icon/constants';
|
||||
import MyImage from '../Image/MyImage';
|
||||
|
||||
const Avatar = ({ w = '30px', src, ...props }: ImageProps) => {
|
||||
// @ts-ignore
|
||||
@@ -14,7 +15,7 @@ const Avatar = ({ w = '30px', src, ...props }: ImageProps) => {
|
||||
<MyIcon name={src as any} w={w} borderRadius={props.borderRadius} />
|
||||
</Box>
|
||||
) : (
|
||||
<Image
|
||||
<MyImage
|
||||
fallbackSrc={LOGO_ICON}
|
||||
fallbackStrategy={'onError'}
|
||||
objectFit={'contain'}
|
||||
|
||||
@@ -4,6 +4,7 @@ export const iconPaths = {
|
||||
book: () => import('./icons/book.svg'),
|
||||
change: () => import('./icons/change.svg'),
|
||||
chatSend: () => import('./icons/chatSend.svg'),
|
||||
check: () => import('./icons/check.svg'),
|
||||
closeSolid: () => import('./icons/closeSolid.svg'),
|
||||
collectionLight: () => import('./icons/collectionLight.svg'),
|
||||
collectionSolid: () => import('./icons/collectionSolid.svg'),
|
||||
@@ -59,7 +60,6 @@ export const iconPaths = {
|
||||
'common/playFill': () => import('./icons/common/playFill.svg'),
|
||||
'common/playLight': () => import('./icons/common/playLight.svg'),
|
||||
'common/publishFill': () => import('./icons/common/publishFill.svg'),
|
||||
'common/questionLight': () => import('./icons/common/questionLight.svg'),
|
||||
'common/refreshLight': () => import('./icons/common/refreshLight.svg'),
|
||||
'common/resultLight': () => import('./icons/common/resultLight.svg'),
|
||||
'common/retryLight': () => import('./icons/common/retryLight.svg'),
|
||||
@@ -217,6 +217,7 @@ export const iconPaths = {
|
||||
'core/workflow/template/FileRead': () => import('./icons/core/workflow/template/FileRead.svg'),
|
||||
'core/workflow/template/aiChat': () => import('./icons/core/workflow/template/aiChat.svg'),
|
||||
'core/workflow/template/baseChart': () => import('./icons/core/workflow/template/baseChart.svg'),
|
||||
'core/workflow/template/bing': () => import('./icons/core/workflow/template/bing.svg'),
|
||||
'core/workflow/template/codeRun': () => import('./icons/core/workflow/template/codeRun.svg'),
|
||||
'core/workflow/template/customFeedback': () =>
|
||||
import('./icons/core/workflow/template/customFeedback.svg'),
|
||||
@@ -224,18 +225,16 @@ export const iconPaths = {
|
||||
import('./icons/core/workflow/template/datasetConcat.svg'),
|
||||
'core/workflow/template/datasetSearch': () =>
|
||||
import('./icons/core/workflow/template/datasetSearch.svg'),
|
||||
'core/workflow/template/datasource': () =>
|
||||
import('./icons/core/workflow/template/datasource.svg'),
|
||||
'core/workflow/template/duckduckgo': () =>
|
||||
import('./icons/core/workflow/template/duckduckgo.svg'),
|
||||
'core/workflow/template/extractJson': () =>
|
||||
import('./icons/core/workflow/template/extractJson.svg'),
|
||||
'core/workflow/template/wiki': () => import('./icons/core/workflow/template/wiki.svg'),
|
||||
'core/workflow/template/datasource': () =>
|
||||
import('./icons/core/workflow/template/datasource.svg'),
|
||||
'core/workflow/template/google': () => import('./icons/core/workflow/template/google.svg'),
|
||||
'core/workflow/template/bing': () => import('./icons/core/workflow/template/bing.svg'),
|
||||
'core/workflow/template/fetchUrl': () => import('./icons/core/workflow/template/fetchUrl.svg'),
|
||||
'core/workflow/template/formInput': () => import('./icons/core/workflow/template/formInput.svg'),
|
||||
'core/workflow/template/getTime': () => import('./icons/core/workflow/template/getTime.svg'),
|
||||
'core/workflow/template/google': () => import('./icons/core/workflow/template/google.svg'),
|
||||
'core/workflow/template/httpRequest': () =>
|
||||
import('./icons/core/workflow/template/httpRequest.svg'),
|
||||
'core/workflow/template/ifelse': () => import('./icons/core/workflow/template/ifelse.svg'),
|
||||
@@ -271,6 +270,7 @@ export const iconPaths = {
|
||||
'core/workflow/template/variable': () => import('./icons/core/workflow/template/variable.svg'),
|
||||
'core/workflow/template/variableUpdate': () =>
|
||||
import('./icons/core/workflow/template/variableUpdate.svg'),
|
||||
'core/workflow/template/wiki': () => import('./icons/core/workflow/template/wiki.svg'),
|
||||
'core/workflow/template/workflowStart': () =>
|
||||
import('./icons/core/workflow/template/workflowStart.svg'),
|
||||
'core/workflow/touchTable': () => import('./icons/core/workflow/touchTable.svg'),
|
||||
@@ -280,6 +280,7 @@ export const iconPaths = {
|
||||
date: () => import('./icons/date.svg'),
|
||||
delete: () => import('./icons/delete.svg'),
|
||||
drag: () => import('./icons/drag.svg'),
|
||||
edgeAdd: () => import('./icons/edgeAdd.svg'),
|
||||
edit: () => import('./icons/edit.svg'),
|
||||
empty: () => import('./icons/empty.svg'),
|
||||
export: () => import('./icons/export.svg'),
|
||||
@@ -302,6 +303,7 @@ export const iconPaths = {
|
||||
'file/pdf': () => import('./icons/file/pdf.svg'),
|
||||
'file/qaImport': () => import('./icons/file/qaImport.svg'),
|
||||
'file/uploadFile': () => import('./icons/file/uploadFile.svg'),
|
||||
help: () => import('./icons/help.svg'),
|
||||
history: () => import('./icons/history.svg'),
|
||||
infoRounded: () => import('./icons/infoRounded.svg'),
|
||||
kbTest: () => import('./icons/kbTest.svg'),
|
||||
@@ -331,7 +333,6 @@ export const iconPaths = {
|
||||
save: () => import('./icons/save.svg'),
|
||||
stop: () => import('./icons/stop.svg'),
|
||||
'support/account/loginoutLight': () => import('./icons/support/account/loginoutLight.svg'),
|
||||
'support/account/passwordLogin': () => import('./icons/support/account/passwordLogin.svg'),
|
||||
'support/account/plans': () => import('./icons/support/account/plans.svg'),
|
||||
'support/account/promotionLight': () => import('./icons/support/account/promotionLight.svg'),
|
||||
'support/bill/extraDatasetsize': () => import('./icons/support/bill/extraDatasetsize.svg'),
|
||||
|
||||
3
packages/web/components/common/Icon/icons/check.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17 17" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.5587 3.69438C13.9492 3.30386 14.5824 3.30386 14.9729 3.69438C15.3634 4.08491 15.3634 4.71807 14.9729 5.1086L7.63956 12.4419C7.24904 12.8325 6.61587 12.8325 6.22535 12.4419L2.89201 9.1086C2.50149 8.71807 2.50149 8.08491 2.89201 7.69438C3.28254 7.30386 3.9157 7.30386 4.30623 7.69438L6.93245 10.3206L13.5587 3.69438Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 452 B |
@@ -1 +1,4 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1704259732773" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4183" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M319.20128 974.56128L348.16 1003.52l655.36-655.36-28.95872-28.95872-655.36 655.36zM675.84 1003.52l327.68-327.68-28.95872-28.95872-327.68 327.68L675.84 1003.52z" fill="#000000" p-id="4184"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="9" height="9" viewBox="0 0 9 9" fill="none">
|
||||
<path d="M0.950928 8.44922L8.32385 1.07629" stroke="#8A95A7" stroke-linecap="round"/>
|
||||
<path d="M4.3418 8.46167L8.32373 4.47974" stroke="#8A95A7" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 534 B After Width: | Height: | Size: 272 B |
@@ -1,6 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" >
|
||||
<path
|
||||
d="M4.25845 0.738983C4.25845 0.606996 4.15146 0.5 4.01947 0.5C2.11725 0.5 0.575195 2.04205 0.575195 3.94428V12.0557C0.575195 13.9579 2.11725 15.5 4.01947 15.5H12.1309C14.0331 15.5 15.5752 13.9579 15.5752 12.0557V3.94428C15.5752 2.04205 14.0331 0.5 12.1309 0.5H10.4017C9.98744 0.5 9.65166 0.835786 9.65166 1.25C9.65166 1.66421 9.98744 2 10.4017 2H12.1309C13.2047 2 14.0752 2.87048 14.0752 3.94428V12.0557C14.0752 13.1295 13.2047 14 12.1309 14H4.01947C2.94568 14 2.0752 13.1295 2.0752 12.0557V3.94428C2.0752 2.87048 2.94568 2 4.01947 2C4.15146 2 4.25845 1.893 4.25845 1.76102V0.738983Z" />
|
||||
<path
|
||||
d="M7.38092 4.3543C7.26179 3.52006 7.01223 2.99621 6.59273 2.70009C5.59427 1.99531 4.85314 2.00002 4.74413 2.00072L4.7369 2.00075H3.9869V0.500749H4.7369C5.00097 0.500749 6.0952 0.512848 7.45775 1.47464C8.37881 2.12479 8.7247 3.15378 8.86586 4.14224C8.98099 4.94841 8.97457 5.85452 8.96865 6.68934C8.96737 6.86971 8.96612 7.04676 8.96612 7.21874C8.96612 7.70483 8.95571 8.16141 8.94035 8.57069L9.57153 7.93951C9.86442 7.64661 10.3393 7.64661 10.6322 7.93951C10.9251 8.2324 10.9251 8.70727 10.6322 9.00017L8.65543 10.9769C8.49629 11.1361 8.28342 11.2087 8.07521 11.1949C7.86701 11.2087 7.65414 11.1361 7.495 10.9769L5.51824 9.00017C5.22535 8.70727 5.22535 8.2324 5.51824 7.93951C5.81113 7.64661 6.28601 7.64661 6.5789 7.93951L7.33793 8.69854L7.42997 8.79058C7.45074 8.33162 7.46612 7.79657 7.46612 7.21874C7.46612 7.02039 7.46735 6.82532 7.46856 6.63372C7.47382 5.80363 7.47866 5.03868 7.38092 4.3543Z" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<path d="M4.67391 1.54593C4.67391 1.4286 4.5788 1.3335 4.46148 1.3335C2.77062 1.3335 1.3999 2.70421 1.3999 4.39507V11.6053C1.3999 13.2961 2.77062 14.6668 4.46148 14.6668H11.6717C13.3625 14.6668 14.7332 13.2961 14.7332 11.6053V4.39507C14.7332 2.70421 13.3625 1.3335 11.6717 1.3335H10.1345C9.76634 1.3335 9.46786 1.63197 9.46786 2.00016C9.46786 2.36835 9.76634 2.66683 10.1345 2.66683H11.6717C12.6261 2.66683 13.3999 3.44059 13.3999 4.39507V11.6053C13.3999 12.5597 12.6261 13.3335 11.6717 13.3335H4.46148C3.507 13.3335 2.73324 12.5597 2.73324 11.6053V4.39507C2.73324 3.44059 3.507 2.66683 4.46148 2.66683C4.5788 2.66683 4.67391 2.57172 4.67391 2.4544V1.54593Z"/>
|
||||
<path d="M7.44944 4.75954C7.34354 4.01799 7.12171 3.55235 6.74882 3.28913C5.8613 2.66266 5.20252 2.66685 5.10562 2.66747L5.0992 2.6675H4.43253V1.33416H5.0992C5.33393 1.33416 6.30657 1.34492 7.51773 2.19984C8.33645 2.77775 8.6439 3.69241 8.76938 4.57104C8.87172 5.28764 8.86601 6.09307 8.86075 6.83513C8.85961 6.99546 8.8585 7.15284 8.8585 7.30571C8.8585 7.73778 8.84925 8.14363 8.83559 8.50744L9.39664 7.94639C9.65699 7.68604 10.0791 7.68604 10.3395 7.94639C10.5998 8.20674 10.5998 8.62885 10.3395 8.8892L8.58233 10.6463C8.44087 10.7878 8.25166 10.8524 8.06659 10.8401C7.88151 10.8524 7.6923 10.7878 7.55084 10.6463L5.79372 8.8892C5.53337 8.62885 5.53337 8.20674 5.79372 7.94639C6.05407 7.68604 6.47618 7.68604 6.73653 7.94639L7.41122 8.62109L7.49304 8.7029C7.5115 8.29493 7.52517 7.81934 7.52517 7.30571C7.52517 7.1294 7.52626 6.956 7.52734 6.78569C7.53201 6.04784 7.53631 5.36788 7.44944 4.75954Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M8.99997 2.196C5.24222 2.196 2.19596 5.24225 2.19596 9C2.19596 12.7577 5.24222 15.804 8.99997 15.804C12.7577 15.804 15.804 12.7577 15.804 9C15.804 5.24225 12.7577 2.196 8.99997 2.196ZM0.529297 9C0.529297 4.32178 4.32174 0.529331 8.99997 0.529331C13.6782 0.529331 17.4706 4.32178 17.4706 9C17.4706 13.6782 13.6782 17.4707 8.99997 17.4707C4.32174 17.4707 0.529297 13.6782 0.529297 9ZM9.18533 6.03224C8.846 5.97403 8.49702 6.0378 8.20019 6.21224C7.90337 6.38669 7.67786 6.66056 7.56361 6.98534C7.41089 7.41949 6.93512 7.64764 6.50097 7.49491C6.06681 7.34218 5.83866 6.86642 5.99139 6.43226C6.23625 5.73619 6.71956 5.14923 7.35572 4.77536C7.99188 4.40148 8.73983 4.26481 9.4671 4.38956C10.1944 4.5143 10.854 4.89241 11.3292 5.45692C11.8043 6.0213 12.0644 6.73558 12.0634 7.4733C12.063 8.67899 11.1697 9.46897 10.5467 9.88431C10.2098 10.1089 9.8788 10.2738 9.63531 10.382C9.51239 10.4367 9.4088 10.4782 9.33398 10.5067C9.2965 10.5209 9.26605 10.532 9.24377 10.54L9.21658 10.5495L9.20781 10.5525L9.20467 10.5535L9.20342 10.554C9.20317 10.554 9.20239 10.5543 8.93887 9.76373L9.20239 10.5543C8.76577 10.6998 8.29384 10.4639 8.1483 10.0273C8.00281 9.59078 8.23857 9.11902 8.67491 8.97331L8.68542 8.9696C8.69671 8.96559 8.71548 8.95878 8.74065 8.94919C8.79114 8.92996 8.86654 8.89986 8.95842 8.85902C9.14454 8.7763 9.38634 8.65481 9.62222 8.49756C10.1447 8.14925 10.3967 7.79388 10.3967 7.47253L10.3967 7.47129C10.3972 7.127 10.2759 6.79364 10.0542 6.53025C9.83245 6.26686 9.52467 6.09044 9.18533 6.03224ZM8.16663 12.8187C8.16663 12.3584 8.53973 11.9853 8.99997 11.9853H9.0076C9.46784 11.9853 9.84094 12.3584 9.84094 12.8187C9.84094 13.2789 9.46784 13.652 9.0076 13.652H8.99997C8.53973 13.652 8.16663 13.2789 8.16663 12.8187Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,4 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 17" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M5.42469 4.94872C5.16434 5.20906 5.16434 5.63117 5.42469 5.89152C5.68504 6.15187 6.10715 6.15187 6.3675 5.89152L8.00002 4.25901L9.63253 5.89152C9.89288 6.15187 10.315 6.15187 10.5753 5.89152C10.8357 5.63117 10.8357 5.20906 10.5753 4.94872L8.47142 2.8448C8.21107 2.58445 7.78896 2.58445 7.52861 2.8448L5.42469 4.94872ZM5.42469 10.8375C5.16434 11.0979 5.16434 11.52 5.42469 11.7803L7.52861 13.8843C7.56115 13.9168 7.59623 13.9453 7.63319 13.9697C7.89196 14.1405 8.24361 14.1121 8.47142 13.8843L10.5753 11.7803C10.8357 11.52 10.8357 11.0979 10.5753 10.8375C10.315 10.5772 9.89288 10.5772 9.63253 10.8375L8.00002 12.47L6.3675 10.8375C6.10715 10.5772 5.68504 10.5772 5.42469 10.8375Z" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" >
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.42475 4.58336C5.1644 4.84371 5.1644 5.26582 5.42475 5.52617C5.6851 5.78652 6.10721 5.78652 6.36756 5.52617L8.00008 3.89366L9.63259 5.52617C9.89294 5.78652 10.315 5.78652 10.5754 5.52617C10.8357 5.26582 10.8357 4.84371 10.5754 4.58336L8.47148 2.47944C8.21113 2.21909 7.78902 2.21909 7.52867 2.47944L5.42475 4.58336ZM5.42475 10.4722C5.1644 10.7325 5.1644 11.1546 5.42475 11.415L7.52867 13.5189C7.56122 13.5514 7.59629 13.5799 7.63325 13.6043C7.89202 13.7752 8.24367 13.7467 8.47148 13.5189L10.5754 11.415C10.8357 11.1546 10.8357 10.7325 10.5754 10.4722C10.315 10.2118 9.89294 10.2118 9.63259 10.4722L8.00008 12.1047L6.36756 10.4722C6.10721 10.2118 5.6851 10.2118 5.42475 10.4722Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 823 B After Width: | Height: | Size: 804 B |
@@ -1,11 +1,10 @@
|
||||
<svg viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="36" height="36" fill="url(#paint0_linear_10832_16007)"/>
|
||||
<path d="M13.9673 10.3122C13.1868 10.8479 13.1935 11.9491 13.863 12.6185C14.3986 13.1541 15.2613 13.1384 15.9008 12.7325C16.5477 12.3219 17.268 12.0318 18.0272 11.8808C19.2374 11.6401 20.4919 11.7636 21.6319 12.2358C22.7719 12.708 23.7463 13.5077 24.4318 14.5337C25.1174 15.5597 25.4833 16.7659 25.4833 17.9999C25.4833 19.2338 25.1174 20.4401 24.4318 21.4661C23.7463 22.4921 22.7719 23.2917 21.6319 23.7639C20.4919 24.2361 19.2374 24.3597 18.0272 24.119C17.268 23.968 16.5477 23.6779 15.9008 23.2673C15.2613 22.8614 14.3986 22.8457 13.863 23.3812C13.1935 24.0507 13.1868 25.1518 13.9673 25.6876C15.0044 26.3995 16.1799 26.8976 17.4252 27.1453C19.234 27.5051 21.1088 27.3204 22.8127 26.6147C24.5165 25.9089 25.9728 24.7138 26.9974 23.1803C28.022 21.6469 28.5689 19.8441 28.5689 17.9999C28.5689 16.1557 28.022 14.3528 26.9974 12.8194C25.9728 11.286 24.5165 10.0908 22.8127 9.38509C21.1088 8.67933 19.234 8.49468 17.4252 8.85447C16.1799 9.10217 15.0044 9.60029 13.9673 10.3122Z" fill="white"/>
|
||||
<path d="M19.2443 22.5497C17.3579 22.5497 15.7397 21.4017 15.0501 19.7663H9.19726C8.22171 19.7663 7.43087 18.9754 7.43087 17.9999C7.43087 17.0243 8.22171 16.2335 9.19726 16.2335H15.0501C15.7397 14.598 17.3579 13.45 19.2443 13.45C21.7571 13.45 23.7942 15.4871 23.7942 17.9999C23.7942 20.5127 21.7571 22.5497 19.2443 22.5497Z" fill="white"/>
|
||||
<rect width="36" height="36" fill="url(#paint0_linear_11_1507)"/>
|
||||
<path d="M9.22505 16.905C8.38096 17.102 7.87904 17.9714 8.13047 18.8009L9.96306 24.8467C10.3439 26.1032 12.0411 26.2986 12.6976 25.1616L13.5842 23.6259C15.0589 24.1044 16.6267 24.2674 18.1843 24.0937C20.4842 23.8372 22.6439 22.8596 24.3544 21.301C26.0649 19.7424 27.2384 17.6826 27.7071 15.4164C28.074 13.6422 27.9941 11.8127 27.4853 10.0898C27.2506 9.29527 26.3514 8.96094 25.5963 9.30173L23.6663 10.1728C22.9113 10.5136 22.597 11.404 22.7351 12.2208C22.8554 12.9325 22.8437 13.6647 22.6957 14.38C22.4458 15.5886 21.8199 16.6872 20.9077 17.5184C19.9954 18.3497 18.8436 18.871 17.617 19.0078C17.1616 19.0586 16.7045 19.0555 16.2549 19.0002L17.0171 17.6799C17.6736 16.5428 16.6558 15.1707 15.3772 15.4692L9.22505 16.905Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_10832_16007" x1="18" y1="0" x2="5.5" y2="33" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#68C0FF"/>
|
||||
<stop offset="1" stop-color="#52A2FF"/>
|
||||
<linearGradient id="paint0_linear_11_1507" x1="18" y1="0" x2="5.5" y2="33" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF8BFD"/>
|
||||
<stop offset="1" stop-color="#7394FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,11 +1,10 @@
|
||||
<svg viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="36" height="36" fill="url(#paint0_linear_10829_15860)"/>
|
||||
<path d="M22.0325 10.3122C22.813 10.8479 22.8062 11.9491 22.1368 12.6185C21.6012 13.1541 20.7384 13.1384 20.099 12.7325C19.4521 12.3219 18.7317 12.0318 17.9726 11.8808C16.7623 11.6401 15.5079 11.7636 14.3679 12.2358C13.2279 12.708 12.2535 13.5077 11.5679 14.5337C10.8824 15.5597 10.5165 16.7659 10.5165 17.9999C10.5165 19.2338 10.8824 20.4401 11.5679 21.4661C12.2535 22.4921 13.2279 23.2917 14.3679 23.7639C15.5079 24.2361 16.7624 24.3597 17.9726 24.119C18.7317 23.968 19.4521 23.6779 20.099 23.2673C20.7384 22.8614 21.6012 22.8457 22.1368 23.3812C22.8062 24.0507 22.813 25.1518 22.0325 25.6876C20.9954 26.3995 19.8199 26.8976 18.5746 27.1453C16.7658 27.5051 14.8909 27.3204 13.1871 26.6147C11.4832 25.9089 10.0269 24.7138 9.00232 23.1803C7.97772 21.6469 7.43085 19.8441 7.43085 17.9999C7.43085 16.1557 7.97772 14.3528 9.00232 12.8194C10.0269 11.286 11.4832 10.0908 13.1871 9.38509C14.8909 8.67933 16.7658 8.49468 18.5746 8.85447C19.8199 9.10217 20.9954 9.60029 22.0325 10.3122Z" fill="white"/>
|
||||
<path d="M16.7554 22.5497C18.6418 22.5497 20.2601 21.4017 20.9497 19.7663H26.8025C27.778 19.7663 28.5689 18.9754 28.5689 17.9999C28.5689 17.0243 27.778 16.2335 26.8025 16.2335H20.9497C20.2601 14.598 18.6418 13.45 16.7554 13.45C14.2426 13.45 12.2056 15.4871 12.2056 17.9999C12.2056 20.5127 14.2426 22.5497 16.7554 22.5497Z" fill="white"/>
|
||||
<svg viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="36" height="36" fill="url(#paint0_linear_11_1494)"/>
|
||||
<path d="M26.7748 18.1758C27.6189 17.9788 28.1208 17.1095 27.8693 16.2799L26.0368 10.2341C25.6559 8.97762 23.9587 8.7822 23.3022 9.91926L22.4156 11.4549C20.9409 10.9764 19.3731 10.8134 17.8155 10.9871C15.5157 11.2437 13.3559 12.2212 11.6454 13.7798C9.93493 15.3384 8.76137 17.3983 8.29272 19.6644C7.92581 21.4386 8.00571 23.2681 8.51454 24.991C8.74918 25.7855 9.64845 26.1199 10.4035 25.7791L12.3335 24.908C13.0886 24.5672 13.4028 23.6768 13.2647 22.86C13.1444 22.1483 13.1562 21.4161 13.3041 20.7008C13.554 19.4922 14.1799 18.3936 15.0922 17.5624C16.0044 16.7311 17.1562 16.2098 18.3828 16.073C18.8382 16.0222 19.2953 16.0254 19.7449 16.0807L18.9827 17.4009C18.3262 18.538 19.344 19.9101 20.6226 19.6117L26.7748 18.1758Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_10829_15860" x1="18" y1="0" x2="5.5" y2="33" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#68C0FF"/>
|
||||
<stop offset="1" stop-color="#52A2FF"/>
|
||||
<linearGradient id="paint0_linear_11_1494" x1="18" y1="0" x2="5.5" y2="33" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FF8BFD"/>
|
||||
<stop offset="1" stop-color="#7394FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.1 KiB |
3
packages/web/components/common/Icon/icons/edgeAdd.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 10 9" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.09993 1.21182C6.09993 0.604303 5.60744 0.111816 4.99993 0.111816C4.39242 0.111816 3.89993 0.604303 3.89993 1.21182L3.89993 3.11182H1.9999C1.39239 3.11182 0.899902 3.6043 0.899902 4.21182C0.899902 4.81933 1.39239 5.31182 1.9999 5.31182H3.89993L3.89993 7.21182C3.89993 7.81933 4.39242 8.31182 4.99993 8.31182C5.60744 8.31182 6.09993 7.81933 6.09993 7.21182V5.31182H7.9999C8.60742 5.31182 9.0999 4.81933 9.0999 4.21182C9.0999 3.6043 8.60742 3.11182 7.9999 3.11182H6.09993V1.21182Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 603 B |
3
packages/web/components/common/Icon/icons/help.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.99968 2.55678C4.99348 2.55678 2.55648 4.99379 2.55648 7.99998C2.55648 11.0062 4.99348 13.4432 7.99968 13.4432C11.0059 13.4432 13.4429 11.0062 13.4429 7.99998C13.4429 4.99379 11.0059 2.55678 7.99968 2.55678ZM1.22314 7.99998C1.22314 4.25741 4.2571 1.22345 7.99968 1.22345C11.7423 1.22345 14.7762 4.25741 14.7762 7.99998C14.7762 11.7426 11.7423 14.7765 7.99968 14.7765C4.2571 14.7765 1.22314 11.7426 1.22314 7.99998ZM8.14797 5.62577C7.87651 5.57921 7.59732 5.63022 7.35986 5.76978C7.1224 5.90934 6.942 6.12843 6.8506 6.38825C6.72842 6.73558 6.34781 6.9181 6.00048 6.79591C5.65315 6.67373 5.47064 6.29312 5.59282 5.9458C5.78871 5.38894 6.17536 4.91937 6.68428 4.62027C7.19321 4.32117 7.79157 4.21184 8.37338 4.31163C8.9552 4.41143 9.48292 4.71392 9.86308 5.16552C10.2432 5.61702 10.4512 6.18845 10.4504 6.77862C10.4501 7.74318 9.73548 8.37516 9.23708 8.70743C8.96754 8.88713 8.70274 9.01905 8.50796 9.10562C8.40962 9.14933 8.32674 9.18252 8.26689 9.20532C8.23691 9.21674 8.21254 9.22562 8.19473 9.23195L8.17297 9.23957L8.16595 9.24197L8.16345 9.24282L8.16245 9.24315C8.16225 9.24322 8.16162 9.24343 7.9508 8.61097L8.16162 9.24343C7.81232 9.35986 7.43478 9.17109 7.31835 8.82179C7.20195 8.47261 7.39056 8.0952 7.73963 7.97863L7.74805 7.97567C7.75708 7.97246 7.77209 7.96701 7.79223 7.95934C7.83262 7.94395 7.89294 7.91987 7.96644 7.8872C8.11534 7.82103 8.30878 7.72383 8.49748 7.59803C8.91545 7.31938 9.11709 7.03509 9.11709 6.77801L9.1171 6.77702C9.11751 6.50159 9.02042 6.2349 8.84305 6.02419C8.66567 5.81347 8.41944 5.67234 8.14797 5.62577ZM7.33301 11.0549C7.33301 10.6867 7.63149 10.3883 7.99968 10.3883H8.00579C8.37398 10.3883 8.67246 10.6867 8.67246 11.0549C8.67246 11.4231 8.37398 11.7216 8.00579 11.7216H7.99968C7.63149 11.7216 7.33301 11.4231 7.33301 11.0549Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -1,6 +0,0 @@
|
||||
<svg t="1709471698048" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4242"
|
||||
width="128" height="128">
|
||||
<path
|
||||
d="M855.158154 945.664H168.999385c-28.081231 0-50.845538-22.843077-50.845539-51.003077V486.833231C118.153846 458.673231 129.457231 433.230769 157.538462 433.230769h708.923076c28.081231 0 39.502769 25.442462 39.50277 53.602462v407.827692c0 28.16-22.764308 51.003077-50.806154 51.003077z m-340.913231-376.595692a99.761231 99.761231 0 0 0-99.603692 99.958154c0 40.251077 23.827692 74.712615 57.974154 90.54523V827.076923a39.384615 39.384615 0 0 0 78.76923 0v-65.417846a99.879385 99.879385 0 0 0 62.424616-92.632615 99.761231 99.761231 0 0 0-99.564308-99.958154z m0.551385-396.524308c-104.841846 0-189.794462 81.329231-197.159385 184.123077H217.718154C229.060923 201.334154 358.321231 78.769231 516.489846 78.769231s287.428923 122.564923 298.732308 277.897846h-103.266462c-7.364923-102.793846-92.317538-184.123077-197.159384-184.123077z"
|
||||
fill="#3B9BF8" p-id="4243"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
7
packages/web/components/common/Image/MyImage.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Image, ImageProps } from '@chakra-ui/react';
|
||||
import { getWebReqUrl } from '../../../common/system/utils';
|
||||
const MyImage = (props: ImageProps) => {
|
||||
return <Image {...props} src={getWebReqUrl(props.src)} alt={props.alt || ''} />;
|
||||
};
|
||||
export default React.memo(MyImage);
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { PhotoProvider, PhotoView } from 'react-photo-view';
|
||||
import 'react-photo-view/dist/react-photo-view.css';
|
||||
import { Box, Image, ImageProps } from '@chakra-ui/react';
|
||||
import { ImageProps } from '@chakra-ui/react';
|
||||
import { useSystem } from '../../../hooks/useSystem';
|
||||
import Loading from '../MyLoading';
|
||||
import MyImage from './MyImage';
|
||||
|
||||
const MyPhotoView = ({ ...props }: ImageProps) => {
|
||||
const { isPc } = useSystem();
|
||||
@@ -15,7 +16,7 @@ const MyPhotoView = ({ ...props }: ImageProps) => {
|
||||
loadingElement={<Loading fixed={false} />}
|
||||
>
|
||||
<PhotoView src={props.src}>
|
||||
<Image cursor={'pointer'} {...props} />
|
||||
<MyImage cursor={'pointer'} {...props} />
|
||||
</PhotoView>
|
||||
</PhotoProvider>
|
||||
);
|
||||
|
||||
@@ -7,28 +7,50 @@ import {
|
||||
NumberInputProps
|
||||
} from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import MyIcon from '../../Icon';
|
||||
import { UseFormRegister } from 'react-hook-form';
|
||||
|
||||
type Props = Omit<NumberInputProps, 'onChange'> & {
|
||||
onChange: (e?: number) => any;
|
||||
onChange?: (e?: number) => any;
|
||||
placeholder?: string;
|
||||
register?: UseFormRegister<any>;
|
||||
name?: string;
|
||||
bg?: string;
|
||||
};
|
||||
|
||||
const MyNumberInput = (props: Props) => {
|
||||
const { register, name, onChange, placeholder, bg, ...restProps } = props;
|
||||
|
||||
return (
|
||||
<NumberInput
|
||||
{...props}
|
||||
{...restProps}
|
||||
onChange={(e) => {
|
||||
if (!onChange) return;
|
||||
if (isNaN(Number(e))) {
|
||||
props?.onChange();
|
||||
onChange();
|
||||
} else {
|
||||
props?.onChange(Number(e));
|
||||
onChange(Number(e));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<NumberInputField placeholder={props?.placeholder} />
|
||||
<NumberInputField
|
||||
bg={bg}
|
||||
placeholder={placeholder}
|
||||
{...(register && name
|
||||
? register(name, {
|
||||
required: props.isRequired,
|
||||
min: props.min,
|
||||
max: props.max
|
||||
})
|
||||
: {})}
|
||||
/>
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
<NumberIncrementStepper>
|
||||
<MyIcon name={'core/chat/chevronUp'} width={'12px'} />
|
||||
</NumberIncrementStepper>
|
||||
<NumberDecrementStepper>
|
||||
<MyIcon name={'core/chat/chevronDown'} width={'12px'} />
|
||||
</NumberDecrementStepper>
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,14 @@ const FormLabel = ({
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<Box color={'myGray.900'} fontSize={'sm'} position={'relative'} flexShrink={0} {...props}>
|
||||
<Box
|
||||
color={'myGray.900'}
|
||||
fontWeight={'medium'}
|
||||
fontSize={'sm'}
|
||||
position={'relative'}
|
||||
flexShrink={0}
|
||||
{...props}
|
||||
>
|
||||
{required && (
|
||||
<Box color={'red.600'} position={'absolute'} top={'-4px'} left={'-6px'}>
|
||||
*
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import MyIcon from '../Icon';
|
||||
import { Flex, Image, Box, CloseButton, FlexProps } from '@chakra-ui/react';
|
||||
import { Flex, Box, CloseButton, FlexProps } from '@chakra-ui/react';
|
||||
import { useLoading } from '../../../hooks/useLoading';
|
||||
import Avatar from '../Avatar';
|
||||
|
||||
type Props = FlexProps & {
|
||||
onClose: () => void;
|
||||
@@ -50,15 +50,7 @@ const CustomRightDrawer = ({
|
||||
py={'10px'}
|
||||
px={5}
|
||||
>
|
||||
{iconSrc && (
|
||||
<>
|
||||
{iconSrc.startsWith('/') ? (
|
||||
<Image mr={3} objectFit={'contain'} alt="" src={iconSrc} w={'20px'} />
|
||||
) : (
|
||||
<MyIcon mr={3} name={iconSrc as any} w={'20px'} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{iconSrc && <Avatar mr={3} w={'20px'} src={iconSrc} />}
|
||||
<Box flex={'1'} fontSize={'md'}>
|
||||
{title}
|
||||
</Box>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Box
|
||||
} from '@chakra-ui/react';
|
||||
import { useLoading } from '../../../hooks/useLoading';
|
||||
import Avatar from '../Avatar';
|
||||
|
||||
type Props = DrawerContentProps & {
|
||||
onClose: () => void;
|
||||
@@ -52,15 +53,7 @@ const MyRightDrawer = ({
|
||||
py={'10px'}
|
||||
px={5}
|
||||
>
|
||||
{iconSrc && (
|
||||
<>
|
||||
{iconSrc.startsWith('/') ? (
|
||||
<Image mr={3} objectFit={'contain'} alt="" src={iconSrc} w={'20px'} />
|
||||
) : (
|
||||
<MyIcon mr={3} name={iconSrc as any} w={'20px'} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{iconSrc && <Avatar mr={3} w={'20px'} src={iconSrc} />}
|
||||
<Box flex={'1'} fontSize={'md'}>
|
||||
{title}
|
||||
</Box>
|
||||
|
||||
@@ -148,7 +148,6 @@ const MyMenu = ({
|
||||
color={isOpen ? 'primary.600' : ''}
|
||||
w="fit-content"
|
||||
p="1"
|
||||
bgColor={isOpen ? 'myGray.50' : ''}
|
||||
h="fit-content"
|
||||
borderRadius="sm"
|
||||
>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useRef, useCallback, useState } from 'react';
|
||||
import { Button, useDisclosure, Box, Flex, useOutsideClick } from '@chakra-ui/react';
|
||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||
import { MultipleSelectProps } from './type';
|
||||
import React, { useRef, useCallback, useState, useMemo } from 'react';
|
||||
import { Button, useDisclosure, Box, Flex, useOutsideClick, Checkbox } from '@chakra-ui/react';
|
||||
import { ListItemType, MultipleArraySelectProps, MultipleSelectProps } from './type';
|
||||
import EmptyTip from '../EmptyTip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '../../common/Icon';
|
||||
|
||||
const MultipleRowSelect = ({
|
||||
export const MultipleRowSelect = ({
|
||||
placeholder,
|
||||
label,
|
||||
value = [],
|
||||
@@ -106,17 +106,25 @@ const MultipleRowSelect = ({
|
||||
<Button
|
||||
justifyContent={'space-between'}
|
||||
width={'100%'}
|
||||
rightIcon={<ChevronDownIcon />}
|
||||
variant={'whiteBase'}
|
||||
variant={'whitePrimaryOutline'}
|
||||
size={'lg'}
|
||||
fontSize={'sm'}
|
||||
px={3}
|
||||
outline={'none'}
|
||||
rightIcon={<MyIcon name={'core/chat/chevronDown'} w="1rem" color={'myGray.500'} />}
|
||||
_active={{
|
||||
transform: 'none'
|
||||
}}
|
||||
{...(isOpen
|
||||
? {
|
||||
boxShadow: '0px 0px 4px #A8DBFF',
|
||||
borderColor: 'primary.500'
|
||||
borderColor: 'primary.600',
|
||||
color: 'primary.700',
|
||||
boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)'
|
||||
}
|
||||
: {})}
|
||||
: {
|
||||
borderColor: 'myGray.200',
|
||||
boxShadow: 'none'
|
||||
})}
|
||||
{...styles}
|
||||
onClick={() => (isOpen ? onClose() : onOpenSelect())}
|
||||
>
|
||||
@@ -127,10 +135,194 @@ const MultipleRowSelect = ({
|
||||
position={'absolute'}
|
||||
{...(popDirection === 'top'
|
||||
? {
|
||||
bottom: '45px'
|
||||
transform: 'translateY(-105%)',
|
||||
top: '0'
|
||||
}
|
||||
: {
|
||||
top: '45px'
|
||||
transform: 'translateY(105%)',
|
||||
bottom: '0'
|
||||
})}
|
||||
py={2}
|
||||
bg={'white'}
|
||||
border={'1px solid #fff'}
|
||||
boxShadow={'5'}
|
||||
borderRadius={'md'}
|
||||
zIndex={1}
|
||||
minW={'100%'}
|
||||
w={'max-content'}
|
||||
>
|
||||
<Flex>
|
||||
<RenderList list={list} index={0} />
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const MultipleRowArraySelect = ({
|
||||
placeholder,
|
||||
label,
|
||||
value = [],
|
||||
list,
|
||||
emptyTip,
|
||||
maxH = 300,
|
||||
onSelect,
|
||||
popDirection = 'bottom',
|
||||
styles
|
||||
}: MultipleArraySelectProps) => {
|
||||
const { t } = useTranslation();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
const [navigationPath, setNavigationPath] = useState<string[]>([]);
|
||||
|
||||
const formatValue = useMemo(() => {
|
||||
return Array.isArray(value) ? value : [];
|
||||
}, [value]);
|
||||
|
||||
// Close when clicking outside
|
||||
useOutsideClick({
|
||||
ref: ref,
|
||||
handler: onClose
|
||||
});
|
||||
|
||||
const RenderList = useCallback(
|
||||
({ index, list }: { index: number; list: MultipleSelectProps['list'] }) => {
|
||||
const currentNavValue = navigationPath[index];
|
||||
const selectedIndex = list.findIndex((item) => item.value === currentNavValue);
|
||||
const children = list[selectedIndex]?.children || [];
|
||||
const hasChildren = list.some((item) => item.children && item.children?.length > 0);
|
||||
|
||||
const handleSelect = (item: ListItemType) => {
|
||||
// Has children, set parent value
|
||||
if (hasChildren) {
|
||||
const newPath = [...navigationPath];
|
||||
newPath[index] = item.value;
|
||||
setNavigationPath(newPath);
|
||||
} else {
|
||||
const parentValue = navigationPath[0];
|
||||
const newValues = [...formatValue];
|
||||
const newValue = [parentValue, item.value];
|
||||
|
||||
if (newValues.some((v) => v[0] === parentValue && v[1] === item.value)) {
|
||||
onSelect(newValues.filter((v) => !(v[0] === parentValue && v[1] === item.value)));
|
||||
} else {
|
||||
onSelect([...newValues, newValue]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
className="nowheel"
|
||||
flex={'1 0 auto'}
|
||||
px={2}
|
||||
borderLeft={index !== 0 ? 'base' : 'none'}
|
||||
maxH={`${maxH}px`}
|
||||
overflowY={'auto'}
|
||||
whiteSpace={'nowrap'}
|
||||
>
|
||||
{list.map((item) => {
|
||||
const isSelected = item.value === currentNavValue;
|
||||
const showCheckbox = !hasChildren;
|
||||
const isChecked =
|
||||
showCheckbox &&
|
||||
formatValue.some((v) => v[1] === item.value && v[0] === navigationPath[0]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
key={item.value}
|
||||
py={2}
|
||||
cursor={'pointer'}
|
||||
px={2}
|
||||
borderRadius={'md'}
|
||||
_hover={{
|
||||
bg: 'primary.50',
|
||||
color: 'primary.600'
|
||||
}}
|
||||
onClick={() => handleSelect(item)}
|
||||
{...(isSelected ? { color: 'primary.600' } : {})}
|
||||
>
|
||||
{showCheckbox && (
|
||||
<Checkbox
|
||||
isChecked={isChecked}
|
||||
icon={<MyIcon name={'common/check'} w={'12px'} />}
|
||||
mr={1}
|
||||
/>
|
||||
)}
|
||||
<Box>{item.label}</Box>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
{list.length === 0 && (
|
||||
<EmptyTip
|
||||
text={emptyTip ?? t('common:common.MultipleRowSelect.No data')}
|
||||
pt={1}
|
||||
pb={3}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
{children.length > 0 && <RenderList list={children} index={index + 1} />}
|
||||
</>
|
||||
);
|
||||
},
|
||||
[navigationPath, formatValue, onSelect]
|
||||
);
|
||||
|
||||
const onOpenSelect = useCallback(() => {
|
||||
setNavigationPath([]);
|
||||
onOpen();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box ref={ref} position={'relative'}>
|
||||
<Button
|
||||
width={'100%'}
|
||||
variant={'whitePrimaryOutline'}
|
||||
size={'lg'}
|
||||
fontSize={'sm'}
|
||||
px={3}
|
||||
outline={'none'}
|
||||
rightIcon={<MyIcon name={'core/chat/chevronDown'} w="1rem" color={'myGray.500'} />}
|
||||
iconSpacing={2}
|
||||
h={'auto'}
|
||||
_active={{
|
||||
transform: 'none'
|
||||
}}
|
||||
_hover={{
|
||||
borderColor: 'primary.500'
|
||||
}}
|
||||
{...(isOpen
|
||||
? {
|
||||
borderColor: 'primary.600',
|
||||
color: 'primary.700',
|
||||
boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)'
|
||||
}
|
||||
: {
|
||||
borderColor: 'myGray.200',
|
||||
boxShadow: 'none'
|
||||
})}
|
||||
{...styles}
|
||||
onClick={() => (isOpen ? onClose() : onOpenSelect())}
|
||||
className="nowheel"
|
||||
>
|
||||
<Box w={'100%'} textAlign={'left'}>
|
||||
{label ?? placeholder}
|
||||
</Box>
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
{...(popDirection === 'top'
|
||||
? {
|
||||
transform: 'translateY(-105%)',
|
||||
top: '0'
|
||||
}
|
||||
: {
|
||||
transform: 'translateY(105%)',
|
||||
bottom: '0'
|
||||
})}
|
||||
py={2}
|
||||
bg={'white'}
|
||||
|
||||
@@ -59,10 +59,11 @@ const MySelect = <T = any,>(
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
_hover: {
|
||||
backgroundColor: 'myWhite.600'
|
||||
backgroundColor: 'myGray.100',
|
||||
color: 'primary.700'
|
||||
},
|
||||
_notLast: {
|
||||
mb: 2
|
||||
mb: 1
|
||||
}
|
||||
};
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
@@ -107,16 +108,19 @@ const MySelect = <T = any,>(
|
||||
ref={ButtonRef}
|
||||
width={width}
|
||||
px={3}
|
||||
rightIcon={<ChevronDownIcon />}
|
||||
variant={'whitePrimary'}
|
||||
rightIcon={<MyIcon name={'core/chat/chevronDown'} w={4} color={'myGray.500'} />}
|
||||
variant={'whitePrimaryOutline'}
|
||||
size={'lg'}
|
||||
fontSize={'sm'}
|
||||
textAlign={'left'}
|
||||
_active={{
|
||||
transform: 'none'
|
||||
}}
|
||||
{...(isOpen
|
||||
? {
|
||||
boxShadow: '0px 0px 4px #A8DBFF',
|
||||
borderColor: 'primary.500'
|
||||
boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)',
|
||||
borderColor: 'primary.600',
|
||||
color: 'primary.700'
|
||||
}
|
||||
: {})}
|
||||
{...props}
|
||||
@@ -157,7 +161,7 @@ const MySelect = <T = any,>(
|
||||
{...(value === item.value
|
||||
? {
|
||||
ref: SelectedItemRef,
|
||||
color: 'primary.600',
|
||||
color: 'primary.700',
|
||||
bg: 'myGray.100'
|
||||
}
|
||||
: {
|
||||
|
||||
@@ -4,9 +4,9 @@ type ListItemType = {
|
||||
value: any;
|
||||
children?: ListItemType[];
|
||||
};
|
||||
export type MultipleSelectProps<T = any> = {
|
||||
export type MultipleSelectProps = {
|
||||
label?: string | React.ReactNode;
|
||||
value: any[];
|
||||
value?: any[];
|
||||
placeholder?: string;
|
||||
list: ListItemType[];
|
||||
emptyTip?: string;
|
||||
@@ -15,3 +15,7 @@ export type MultipleSelectProps<T = any> = {
|
||||
styles?: ButtonProps;
|
||||
popDirection?: 'top' | 'bottom';
|
||||
};
|
||||
export type MultipleArraySelectProps = Omit<MultipleSelectProps, 'value'> & {
|
||||
value?: any[][];
|
||||
onSelect: (val: any[][]) => void;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import MyTooltip from '.';
|
||||
import { IconProps, QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { IconProps } from '@chakra-ui/icons';
|
||||
import MyIcon from '../Icon';
|
||||
|
||||
type Props = IconProps & {
|
||||
label?: string | React.ReactNode;
|
||||
@@ -9,7 +10,7 @@ type Props = IconProps & {
|
||||
const QuestionTip = ({ label, maxW, ...props }: Props) => {
|
||||
return (
|
||||
<MyTooltip label={label} maxW={maxW}>
|
||||
<QuestionOutlineIcon w={'0.9rem'} {...props} />
|
||||
<MyIcon name={'help' as any} w={'16px'} color={'myGray.500'} {...props} />
|
||||
</MyTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -72,11 +72,11 @@ const LightRowTabs = <ValueType = string,>({
|
||||
_hover={{
|
||||
color: activeColor
|
||||
}}
|
||||
fontWeight={'medium'}
|
||||
{...(value === item.value
|
||||
? {
|
||||
color: activeColor,
|
||||
cursor: 'default',
|
||||
fontWeight: 'bold',
|
||||
borderBottomColor: activeColor
|
||||
}
|
||||
: {
|
||||
|
||||
@@ -2,9 +2,10 @@ import React, { useCallback, useRef, useState } from 'react';
|
||||
import Editor, { Monaco, loader } from '@monaco-editor/react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import MyIcon from '../../Icon';
|
||||
import { getWebReqUrl } from '../../../../common/system/utils';
|
||||
|
||||
loader.config({
|
||||
paths: { vs: '/js/monaco-editor.0.45.0/vs' }
|
||||
paths: { vs: getWebReqUrl('/js/monaco-editor.0.45.0/vs') }
|
||||
});
|
||||
|
||||
type EditorVariablePickerType = {
|
||||
|
||||
@@ -4,9 +4,10 @@ import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import MyIcon from '../../Icon';
|
||||
import { useToast } from '../../../../hooks/useToast';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { getWebReqUrl } from '../../../../common/system/utils';
|
||||
|
||||
loader.config({
|
||||
paths: { vs: '/js/monaco-editor.0.45.0/vs' }
|
||||
paths: { vs: getWebReqUrl('/js/monaco-editor.0.45.0/vs') }
|
||||
});
|
||||
|
||||
type EditorVariablePickerType = {
|
||||
|
||||
@@ -105,10 +105,10 @@ export default function Editor({
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
py={3}
|
||||
px={4}
|
||||
py={2}
|
||||
px={3}
|
||||
pointerEvents={'none'}
|
||||
overflow={'overlay'}
|
||||
overflow={'hidden'}
|
||||
>
|
||||
<Box
|
||||
color={'myGray.400'}
|
||||
@@ -146,12 +146,12 @@ export default function Editor({
|
||||
<Box
|
||||
zIndex={10}
|
||||
position={'absolute'}
|
||||
bottom={0}
|
||||
bottom={-1}
|
||||
right={2}
|
||||
cursor={'pointer'}
|
||||
onClick={onOpenModal}
|
||||
>
|
||||
<MyIcon name={'common/fullScreenLight'} w={'14px'} color={'myGray.600'} />
|
||||
<MyIcon name={'common/fullScreenLight'} w={'14px'} color={'myGray.500'} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -9,6 +9,45 @@
|
||||
|
||||
font-size: var(--chakra-fontSizes-sm);
|
||||
overflow-y: auto;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--chakra-colors-primary-300);
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
color: var(--chakra-colors-myGray-100);
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: var(--chakra-colors-myGray-200) !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--chakra-colors-myGray-250) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.contentEditable_isFlow {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 1px solid var(--chakra-colors-myGray-200);
|
||||
border-radius: var(--chakra-radii-sm);
|
||||
padding: 6px 8px;
|
||||
font-size: var(--chakra-fontSizes-sm);
|
||||
overflow-y: auto;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--chakra-colors-primary-300);
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
color: var(--chakra-colors-myGray-100);
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: var(--chakra-colors-myGray-200) !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--chakra-colors-myGray-250) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.contentEditable:focus {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, Button, ModalBody, ModalFooter, useDisclosure } from '@chakra-ui/react';
|
||||
import { Button, ModalBody, ModalFooter, useDisclosure } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { editorStateToText } from './utils';
|
||||
import Editor from './Editor';
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import { Box, Button, Image } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
export default function ComfirmVar({
|
||||
newVariables,
|
||||
onCancel,
|
||||
onConfirm
|
||||
}: {
|
||||
newVariables: string[];
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
background={'rgba(35, 56, 118, 0.2)'}
|
||||
rounded={'sm'}
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
/>
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'50%'}
|
||||
left={'50%'}
|
||||
transform={'translate(-50%, -50%)'}
|
||||
w={'70%'}
|
||||
h={'70%'}
|
||||
bg={'white'}
|
||||
rounded={'lg'}
|
||||
boxShadow={'0 2px 4px rgba(0, 0, 0, 0.1)'}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
justifyContent={'space-between'}
|
||||
pb={4}
|
||||
>
|
||||
<Box display={'flex'} mt={4} mr={4}>
|
||||
<Box
|
||||
w={'36px'}
|
||||
h={'36px'}
|
||||
minW={'36px'}
|
||||
boxShadow={'0 4px 8px rgba(0, 0, 0, 0.1)'}
|
||||
display={'flex'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
rounded={'md'}
|
||||
border={'1px solid rgba(0, 0, 0, 0.1)'}
|
||||
mx={4}
|
||||
>
|
||||
<Image alt={''} src={'/imgs/workflow/variable.png'} objectFit={'contain'} w={'20px'} />
|
||||
</Box>
|
||||
<Box>{t('common:undefined_var')}</Box>
|
||||
</Box>
|
||||
<Box
|
||||
ml={16}
|
||||
mt={4}
|
||||
fontSize={'sm'}
|
||||
color={'rgb(28,100,242)'}
|
||||
display={'flex'}
|
||||
whiteSpace={'wrap'}
|
||||
>
|
||||
{newVariables.map((item, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
display={'flex'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
bg={'rgb(237,242,250)'}
|
||||
px={1}
|
||||
h={6}
|
||||
rounded={'md'}
|
||||
mr={2}
|
||||
>
|
||||
<span>
|
||||
<span style={{ opacity: '60%' }}>{`{{`}</span>
|
||||
<span>{item}</span>
|
||||
<span style={{ opacity: '60%' }}>{`}}`}</span>
|
||||
</span>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
<Box>
|
||||
<Box display={'flex'} justifyContent={'flex-end'} mt={4} mr={4}>
|
||||
<Button size={'sm'} variant={'ghost'} onClick={onCancel}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button size={'sm'} variant={'primary'} ml={4} onClick={onConfirm}>
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -150,12 +150,20 @@ const NodeInputSelect = ({
|
||||
trigger="click"
|
||||
Button={
|
||||
<Button
|
||||
size={'xs'}
|
||||
leftIcon={<MyIcon name={renderTypeData.icon as any} w={'0.8rem'} color={'primary.600'} />}
|
||||
rightIcon={<MyIcon name={'common/select'} w={'0.8rem'} color={'myGray.500'} />}
|
||||
leftIcon={
|
||||
<MyIcon name={renderTypeData.icon as any} w={'14px'} color={'primary.600'} mr={-0.5} />
|
||||
}
|
||||
rightIcon={<MyIcon name={'common/select'} w={'0.8rem'} color={'myGray.500'} ml={-1} />}
|
||||
variant={'grayBase'}
|
||||
border={theme.borders.base}
|
||||
borderRadius={'xs'}
|
||||
borderColor={'myGray.200'}
|
||||
borderRadius={'sm'}
|
||||
px={'10px'}
|
||||
py={'6px'}
|
||||
fontSize={'mini'}
|
||||
color={'myGray.600'}
|
||||
h={'28px'}
|
||||
bg={'myGray.100'}
|
||||
>
|
||||
<Box fontWeight={'medium'}>{renderTypeData.title}</Box>
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"Role_setting": "Role setting",
|
||||
"Run": "Execute",
|
||||
"Team Tags Set": "Team tags",
|
||||
"Team_Tags": "Team tags",
|
||||
"ai_settings": "AI Configuration",
|
||||
"all_apps": "All Applications",
|
||||
"app.Version name": "Version Name",
|
||||
@@ -27,6 +30,7 @@
|
||||
"cron.every_month": "Run Monthly",
|
||||
"cron.every_week": "Run Weekly",
|
||||
"cron.interval": "Run at Intervals",
|
||||
"dataset_search_tool_description": "Call the \"Semantic Search\" and \"Full-text Search\" capabilities to find reference content that may be related to the problem from the \"Knowledge Base\". \nPrioritize calling this tool to assist in answering user questions.",
|
||||
"day": "Day",
|
||||
"document_quote": "Document Reference",
|
||||
"document_quote_tip": "Usually used to accept user-uploaded document content (requires document parsing), and can also be used to reference other string data.",
|
||||
@@ -37,6 +41,7 @@
|
||||
"export_config_successful": "Configuration copied, some sensitive information automatically filtered. Please check for any remaining sensitive data.",
|
||||
"export_configs": "Export Configurations",
|
||||
"feedback_count": "User Feedback",
|
||||
"file_quote_link": "Files",
|
||||
"file_recover": "File will overwrite current content",
|
||||
"file_upload": "File Upload",
|
||||
"file_upload_tip": "Once enabled, documents/images can be uploaded. Documents are retained for 7 days, images for 15 days. Using this feature may incur additional costs. To ensure a good experience, please choose an AI model with a larger context length when using this feature.",
|
||||
@@ -44,7 +49,7 @@
|
||||
"go_to_chat": "Go to Conversation",
|
||||
"go_to_run": "Go to Execution",
|
||||
"image_upload": "Image Upload",
|
||||
"image_upload_tip": "Please ensure to select a vision model that can process images.",
|
||||
"image_upload_tip": "How to activate model image recognition capabilities",
|
||||
"import_configs": "Import Configurations",
|
||||
"import_configs_failed": "Import configuration failed, please ensure the configuration is correct!",
|
||||
"import_configs_success": "Import Successful",
|
||||
@@ -58,7 +63,7 @@
|
||||
"intro": "A comprehensive model application orchestration system that offers out-of-the-box data processing and model invocation capabilities. It allows for rapid Dataset construction and workflow orchestration through Flow visualization, enabling complex Dataset scenarios!",
|
||||
"llm_not_support_vision": "This model does not support image recognition",
|
||||
"llm_use_vision": "Enable Image Recognition",
|
||||
"llm_use_vision_tip": "Once image recognition is enabled, this model will automatically receive images uploaded from the 'dialog box' and image links in 'user questions'.",
|
||||
"llm_use_vision_tip": "After clicking on the model selection, you can see whether the model supports image recognition and the ability to control whether to start image recognition. \nAfter starting image recognition, the model will read the image content in the file link, and if the user question is less than 500 words, it will automatically parse the image in the user question.",
|
||||
"logs_chat_user": "user",
|
||||
"logs_empty": "No logs yet~",
|
||||
"logs_message_total": "Total Messages",
|
||||
@@ -71,6 +76,7 @@
|
||||
"month.unit": "Day",
|
||||
"move_app": "Move Application",
|
||||
"not_json_file": "Please select a JSON file",
|
||||
"open_vision_function_tip": "Models with icon switches have image recognition capabilities. \nAfter being turned on, the model will parse the pictures in the file link and automatically parse the pictures in the user's question (user question ≤ 500 words).",
|
||||
"or_drag_JSON": "or drag in JSON file",
|
||||
"paste_config": "Paste Configuration",
|
||||
"permission.des.manage": "Based on write permissions, you can configure publishing channels, view conversation logs, and assign permissions to the application.",
|
||||
@@ -125,14 +131,14 @@
|
||||
"type.Simple bot": "Simple App",
|
||||
"type.Workflow bot": "Workflow",
|
||||
"upload_file_max_amount": "Maximum File Quantity",
|
||||
"upload_file_max_amount_tip": "1. The maximum number of files that can be uploaded at one time.\n2. The maximum number of files remembered by the chat window: each round of dialogue will automatically retrieve files from history, files beyond the range will be forgotten.",
|
||||
"upload_file_max_amount_tip": "Maximum number of files uploaded in a single round of conversation",
|
||||
"variable.select type_desc": "You can define a global variable that does not need to be filled in by the user.\n\nThe value of this variable can come from the API interface, the Query of the shared link, or assigned through the [Variable Update] module.",
|
||||
"variable.textarea_type_desc": "Allows users to input up to 4000 characters in the dialogue box.",
|
||||
"version.Revert success": "Revert Successful",
|
||||
"version_back": "Revert to Original State",
|
||||
"version_copy": "Duplicate",
|
||||
"version_initial_copy": "Duplicate - Original State",
|
||||
"vision_model_title": "Enable Image Recognition",
|
||||
"vision_model_title": "Image recognition ability",
|
||||
"week.Friday": "Friday",
|
||||
"week.Monday": "Monday",
|
||||
"week.Saturday": "Saturday",
|
||||
@@ -149,7 +155,7 @@
|
||||
"workflow.read_files": "Document Parsing",
|
||||
"workflow.read_files_result": "Document Parsing Result",
|
||||
"workflow.read_files_result_desc": "Original document text, consisting of file names and document content, separated by hyphens between multiple files.",
|
||||
"workflow.read_files_tip": "Parse all uploaded documents in the dialogue and return the corresponding document content.",
|
||||
"workflow.read_files_tip": "Parse the documents uploaded in this round of dialogue and return the corresponding document content",
|
||||
"workflow.select_description": "Description Text",
|
||||
"workflow.select_description_placeholder": "For example: \nAre there tomatoes in the fridge?",
|
||||
"workflow.select_description_tip": "You can add a description text to explain the meaning of each option to the user.",
|
||||
@@ -159,4 +165,4 @@
|
||||
"workflow.user_file_input_desc": "Links to documents and images uploaded by users.",
|
||||
"workflow.user_select": "User Selection",
|
||||
"workflow.user_select_tip": "This module can configure multiple options for selection during the dialogue. Different options can lead to different workflow branches."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"AI_input_is_empty": "The content passed to the AI node is empty",
|
||||
"Delete_all": "Clear All Lexicon",
|
||||
"LLM_model_response_empty": "The model flow response is empty, please check whether the model flow output is normal.",
|
||||
"chat_history": "Conversation History",
|
||||
"chat_input_guide_lexicon_is_empty": "Lexicon not configured yet",
|
||||
"citations": "{{num}} References",
|
||||
@@ -12,6 +14,7 @@
|
||||
"contextual_preview": "Contextual Preview {{num}} Items",
|
||||
"csv_input_lexicon_tip": "Only CSV batch import is supported, click to download the template",
|
||||
"custom_input_guide_url": "Custom Lexicon URL",
|
||||
"dataset_quote_type error": "Knowledge base reference type is wrong, correct type: { datasetId: string }[]",
|
||||
"delete_all_input_guide_confirm": "Are you sure you want to clear the input guide lexicon?",
|
||||
"empty_directory": "This directory is empty~",
|
||||
"file_amount_over": "Exceeded maximum file quantity {{max}}",
|
||||
@@ -29,15 +32,18 @@
|
||||
"multiple_AI_conversations": "Multiple AI Conversations",
|
||||
"new_input_guide_lexicon": "New Lexicon",
|
||||
"no_workflow_response": "No workflow data",
|
||||
"not_select_file": "No file selected",
|
||||
"plugins_output": "Plugin Output",
|
||||
"question_tip": "From top to bottom, the response order of each module",
|
||||
"response.dataset_concat_length": "Combined total",
|
||||
"response.node_inputs": "Node Inputs",
|
||||
"select": "Select",
|
||||
"select_file": "Upload File",
|
||||
"select_file_img": "Upload file / image",
|
||||
"select_img": "Upload Image",
|
||||
"stream_output": "Stream Output",
|
||||
"unsupported_file_type": "Unsupported file types",
|
||||
"upload": "Upload",
|
||||
"view_citations": "View References",
|
||||
"web_site_sync": "Web Site Sync"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,8 +69,12 @@
|
||||
"code_error.system_error.community_version_num_limit": "Exceeded Open Source Version Limit, Please Upgrade to Commercial Version: https://tryfastgpt.ai",
|
||||
"code_error.team_error.ai_points_not_enough": "Insufficient AI Points",
|
||||
"code_error.team_error.app_amount_not_enough": "Application Limit Reached",
|
||||
"code_error.team_error.cannot_delete_default_group": "Cannot delete default group",
|
||||
"code_error.team_error.dataset_amount_not_enough": "Dataset Limit Reached",
|
||||
"code_error.team_error.dataset_size_not_enough": "Insufficient Dataset Capacity, Please Expand",
|
||||
"code_error.team_error.group_name_duplicate": "Duplicate group name",
|
||||
"code_error.team_error.group_name_empty": "Group name cannot be empty",
|
||||
"code_error.team_error.group_not_exist": "Group does not exist",
|
||||
"code_error.team_error.over_size": "error.team.overSize",
|
||||
"code_error.team_error.plugin_amount_not_enough": "Plugin Limit Reached",
|
||||
"code_error.team_error.re_rank_not_enough": "Unauthorized to Use Re-Rank",
|
||||
@@ -184,6 +188,7 @@
|
||||
"common.Update Successful": "Updated Successfully",
|
||||
"common.Username": "Username",
|
||||
"common.Waiting": "Waiting",
|
||||
"common.Error": "Error",
|
||||
"common.Warning": "Warning",
|
||||
"common.Website": "Website",
|
||||
"common.all_result": "Full Results",
|
||||
@@ -569,10 +574,12 @@
|
||||
"core.dataset.import.Select source": "Select Source",
|
||||
"core.dataset.import.Source name": "Source Name",
|
||||
"core.dataset.import.Sources list": "Source List",
|
||||
"core.dataset.import.Continue upload": "Continue upload",
|
||||
"core.dataset.import.Upload complete": "Upload complete",
|
||||
"core.dataset.import.Start upload": "Start Upload",
|
||||
"core.dataset.import.Total files": "Total {{total}} Files",
|
||||
"core.dataset.import.Training mode": "Training Mode",
|
||||
"core.dataset.import.Upload data": "Upload Data",
|
||||
"core.dataset.import.Upload data": "Confirm Upload",
|
||||
"core.dataset.import.Upload file progress": "File Upload Progress",
|
||||
"core.dataset.import.Upload status": "Status",
|
||||
"core.dataset.import.Web link": "Web Link",
|
||||
@@ -1112,7 +1119,6 @@
|
||||
"tag_list": "Tag List",
|
||||
"team_tag": "Team Tag",
|
||||
"textarea_variable_picker_tip": "Enter \"/\" to select a variable",
|
||||
"undefined_var": "Referenced an undefined variable, add it automatically?",
|
||||
"unit.character": "Character",
|
||||
"unit.minute": "Minute",
|
||||
"unusable_variable": "No Usable Variables",
|
||||
@@ -1197,5 +1203,7 @@
|
||||
"user.type": "Type",
|
||||
"verification": "Verification",
|
||||
"xx_search_result": "{{key}} Search Results",
|
||||
"yes": "Yes"
|
||||
"yes": "Yes",
|
||||
"yesterday": "yesterday",
|
||||
"yesterday_detail_time": "Yesterday {{time}}"
|
||||
}
|
||||
|
||||