v4.6.5 (#620)
64
Dockerfile
@@ -1,57 +1,81 @@
|
||||
# Install dependencies only when needed
|
||||
FROM node:18.15-alpine AS deps
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat && npm install -g pnpm
|
||||
# --------- install dependence -----------
|
||||
FROM node:18.17-alpine AS mainDeps
|
||||
WORKDIR /app
|
||||
|
||||
ARG name
|
||||
ARG proxy
|
||||
|
||||
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
||||
RUN apk add --no-cache libc6-compat && npm install -g pnpm@8.6.0
|
||||
# if proxy exists, set proxy
|
||||
RUN [ -z "$proxy" ] || pnpm config set registry https://registry.npm.taobao.org
|
||||
|
||||
# copy packages and one project
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY ./packages ./packages
|
||||
COPY ./projects/$name/package.json ./projects/$name/package.json
|
||||
|
||||
RUN [ -f pnpm-lock.yaml ] || (echo "Lockfile not found." && exit 1)
|
||||
|
||||
RUN pnpm install
|
||||
RUN pnpm i
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM node:18.15-alpine AS builder
|
||||
# --------- install dependence -----------
|
||||
FROM node:18.17-alpine AS workerDeps
|
||||
WORKDIR /app
|
||||
|
||||
ARG proxy
|
||||
|
||||
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
||||
RUN apk add --no-cache libc6-compat && npm install -g pnpm@8.6.0
|
||||
# if proxy exists, set proxy
|
||||
RUN [ -z "$proxy" ] || pnpm config set registry https://registry.npm.taobao.org
|
||||
|
||||
COPY ./worker /app/worker
|
||||
RUN cd /app/worker && pnpm i --production --ignore-workspace
|
||||
|
||||
# --------- builder -----------
|
||||
FROM node:18.17-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
ARG name
|
||||
ARG proxy
|
||||
|
||||
# copy common node_modules and one project node_modules
|
||||
COPY package.json pnpm-workspace.yaml ./
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY --from=deps /app/packages ./packages
|
||||
COPY --from=mainDeps /app/node_modules ./node_modules
|
||||
COPY --from=mainDeps /app/packages ./packages
|
||||
COPY ./projects/$name ./projects/$name
|
||||
COPY --from=deps /app/projects/$name/node_modules ./projects/$name/node_modules
|
||||
COPY --from=mainDeps /app/projects/$name/node_modules ./projects/$name/node_modules
|
||||
|
||||
# Uncomment the following line in case you want to disable telemetry during the build.
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
RUN npm install -g pnpm
|
||||
RUN pnpm --filter=$name run build
|
||||
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
||||
|
||||
FROM node:18.15-alpine AS runner
|
||||
RUN apk add --no-cache libc6-compat && npm install -g pnpm@8.6.0
|
||||
RUN pnpm --filter=$name build
|
||||
|
||||
# --------- runner -----------
|
||||
FROM node:18.17-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ARG name
|
||||
ARG proxy
|
||||
|
||||
# create user and use it
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
RUN [ -z "$proxy" ] || sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
|
||||
RUN apk add --no-cache curl ca-certificates \
|
||||
&& update-ca-certificates
|
||||
|
||||
# copy running files
|
||||
COPY --from=builder /app/projects/$name/public ./projects/$name/public
|
||||
COPY --from=builder /app/projects/$name/next.config.js ./projects/$name/next.config.js
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/projects/$name/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/projects/$name/.next/static ./projects/$name/.next/static
|
||||
COPY --from=builder /app/projects/$name/public /app/projects/$name/public
|
||||
COPY --from=builder /app/projects/$name/next.config.js /app/projects/$name/next.config.js
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/projects/$name/.next/standalone /app/
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/projects/$name/.next/static /app/projects/$name/.next/static
|
||||
# copy package.json to version file
|
||||
COPY --from=builder /app/projects/$name/package.json ./package.json
|
||||
# copy woker
|
||||
COPY --from=workerDeps /app/worker /app/worker
|
||||
|
||||
ENV NODE_ENV production
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
BIN
docSite/assets/imgs/customfeedback1.png
Normal file
After Width: | Height: | Size: 459 KiB |
BIN
docSite/assets/imgs/customfeedback2.png
Normal file
After Width: | Height: | Size: 599 KiB |
BIN
docSite/assets/imgs/customfeedback3.png
Normal file
After Width: | Height: | Size: 267 KiB |
BIN
docSite/assets/imgs/customfeedback4.png
Normal file
After Width: | Height: | Size: 252 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 162 KiB |
35
docSite/content/docs/workflow/modules/custom_feedback.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: "自定义反馈"
|
||||
description: "自定义反馈模块介绍"
|
||||
icon: "feedback"
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 354
|
||||
---
|
||||
|
||||
该模块为临时模块,后续会针对该模块进行更全面的设计。
|
||||
|
||||
## 特点
|
||||
|
||||
- 可重复添加
|
||||
- 无外部输入
|
||||
- 自动执行
|
||||
|
||||
|
||||
| | |
|
||||
| --------------------- | --------------------- |
|
||||
|  |  |
|
||||
|  |  |
|
||||
|
||||
|
||||
## 介绍
|
||||
|
||||
自定义反馈模块,可以为你的对话增加一个反馈标记,从而方便在后台更好的分析对话的数据。
|
||||
|
||||
在调试模式下,不会记录反馈内容,而是直接提示: `自动反馈测试: 反馈内容`。
|
||||
|
||||
在对话模式(对话、分享窗口、带 chatId 的 API 调用)时,会将反馈内容记录到对话日志中。(会延迟60s记录)
|
||||
|
||||
## 作用
|
||||
|
||||
自定义反馈模块的功能类似于程序开发的`埋点`,便于你观测的对话中的数据。
|
@@ -1,19 +0,0 @@
|
||||
---
|
||||
title: "历史记录"
|
||||
description: "FastGPT 历史记录模块介绍"
|
||||
icon: "history"
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 354
|
||||
---
|
||||
|
||||
# 特点
|
||||
|
||||
- 可重复添加(防止复杂编排时线太乱,重复添加可以更美观)
|
||||
- 无外部输入
|
||||
- 流程入口
|
||||
- 自动执行
|
||||
|
||||
每次对话时,会从数据库取最多 n 条聊天记录作为上下文。注意,不是指本轮对话最多 n 条上下文,本轮对话还包括:提示词、限定词、引用内容和问题。
|
||||
|
||||

|
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "HTTP 模块"
|
||||
title: "新 HTTP 模块"
|
||||
description: "FastGPT HTTP 模块介绍"
|
||||
icon: "http"
|
||||
draft: false
|
||||
@@ -19,86 +19,233 @@ weight: 355
|
||||
|
||||
## 介绍
|
||||
|
||||
HTTP 模块会向对应的地址发送一个 POST 请求(Body 中携带 JSON 类型的参数,具体的参数可自定义),并接收一个 JSON 响应值,字段也是自定义。如上图中,我们定义了一个入参:「提取的字段」(定义的 key 为 appointment,类型为 string)和一个出参:「提取结果」(定义的 key 为 response,类型为 string)。
|
||||
HTTP 模块会向对应的地址发送一个 `POST/GET` 请求,携带部分`系统参数`及`自定义参数`,并接收一个 JSON 响应值,字段也是自定义。
|
||||
|
||||
那么,这个请求的命令为:
|
||||
- 你还可以通过 JSON 传入自定义的请求头。
|
||||
- POST 请求中,数据会被放置在 `body` 中。
|
||||
- GET 请求中,数据会被放置在 `query` 中。
|
||||
- 在出入参数中,你都可以通过 xxx.xxx 来代表嵌套的对象。
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://xxxx.laf.dev/appointment-lab' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"appointment":"{\"name\":\"小明\",\"time\":\"2023/08/16 15:00\",\"labname\":\"子良A323\"}"
|
||||
}'
|
||||
```
|
||||
## 参数结构
|
||||
|
||||
响应为:
|
||||
### 系统参数说明
|
||||
|
||||
- appId: 应用的ID
|
||||
- chatId: 当前对话的ID,测试模式下不存在。
|
||||
- responseChatItemId: 当前对话中,响应的消息ID,测试模式下不存在。
|
||||
- variables: 当前对话的全局变量。
|
||||
- data: 自定义传递的参数。
|
||||
|
||||
### 嵌套对象使用
|
||||
|
||||
**入参**
|
||||
|
||||
假设我们设计了`3个`输入。
|
||||
|
||||
- user.name (string)
|
||||
- user.age (number)
|
||||
- type (string)
|
||||
|
||||
最终组成的对象为:
|
||||
|
||||
```json
|
||||
{
|
||||
"response": "您已经有一个预约记录了,每人仅能同时预约一个实验室:\n 姓名:小明\n 时间: 2023/08/15 15:00\n 实验室: 子良A323\n "
|
||||
"user": {
|
||||
"name": "",
|
||||
"age": ""
|
||||
},
|
||||
"type": ""
|
||||
}
|
||||
```
|
||||
|
||||
**出参**
|
||||
|
||||
假设接口的输出结构为:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "测试",
|
||||
"data":{
|
||||
"name": "name",
|
||||
"age": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
那么,自定出参的`key`可以设置为:
|
||||
|
||||
- message (string)
|
||||
- data.name (string)
|
||||
- data.age (number)
|
||||
|
||||
|
||||
## POST 示例
|
||||
|
||||
**自定义入参**
|
||||
|
||||
- user.name (string)
|
||||
- user.age (number)
|
||||
- type (string)
|
||||
|
||||
**自定义出参**
|
||||
|
||||
- message (string)
|
||||
- data.name (string)
|
||||
- data.age (number)
|
||||
|
||||
那么,这个模块发出的请求则是:
|
||||
|
||||
{{< tabs tabTotal="2" >}}
|
||||
{{< tab tabName="POST 请求示例" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'http://xxxx.com' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"appId": "65782f7ffae5f7854ed4498b",
|
||||
"chatId": "xxxx",
|
||||
"responseChatItemId": "xxxx",
|
||||
"variables": {
|
||||
"cTime": "2023-12-18 13:45:46"
|
||||
},
|
||||
"data": {
|
||||
"user": {
|
||||
"name": "",
|
||||
"age": ""
|
||||
},
|
||||
"type": ""
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="POST响应" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "message",
|
||||
"data": {
|
||||
"name": "name",
|
||||
"age": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
## GET 示例
|
||||
|
||||
GET 中,不推荐使用嵌套参数,否则会出现奇怪的问题。此外,GET 请求中,FastGPT 会将参数扁平化,不会将自定义参单独抽到 data 中,同时全局变量也会扁平化,因此需要注意字段 key 是否冲突。
|
||||
|
||||
**自定义入参**
|
||||
|
||||
- name (string)
|
||||
- age (number)
|
||||
- type (string)
|
||||
|
||||
**自定义出参**
|
||||
|
||||
- message (string)
|
||||
- name (string)
|
||||
- age (number)
|
||||
|
||||
那么,这个模块发出的请求则是:
|
||||
|
||||
{{< tabs tabTotal="2" >}}
|
||||
{{< tab tabName="GET 请求示例" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```bash
|
||||
curl --location --request GET 'http://xxx.com/test?name&age&type&appId=65782f7ffae5f7854ed4498b&chatId=xxxx&responseChatItemId=xxxx&cTime=2023-12-18 13:45:46'
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="GET 响应" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "message",
|
||||
"data": {
|
||||
"name": "name",
|
||||
"age": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
|
||||
## laf 对接 HTTP 示例
|
||||
|
||||
{{% alert context="warning" %}}
|
||||
如果你不想额外部署服务,可以使用 [Laf](https://laf.dev/) 来快速开发上线接口,即写即发,无需部署。
|
||||
|
||||
下面是在 Laf 上编写的一个请求示例:
|
||||
{{% /alert %}}
|
||||
|
||||
下面是在 Laf 编写的 POST 请求示例:
|
||||
|
||||
```ts
|
||||
import cloud from '@lafjs/cloud';
|
||||
const db = cloud.database();
|
||||
import cloud from '@lafjs/cloud'
|
||||
const db = cloud.database()
|
||||
|
||||
type RequestType = {
|
||||
appId: string;
|
||||
data: {
|
||||
appointment: string;
|
||||
action: 'post' | 'delete' | 'put' | 'get'
|
||||
}
|
||||
}
|
||||
|
||||
export default async function (ctx: FunctionContext) {
|
||||
const { appointment } = ctx.body;
|
||||
const { name, time, labname } = JSON.parse(appointment);
|
||||
try {
|
||||
// 从 body 中获取参数
|
||||
const { appId, data: { appointment, action } } = ctx.body as RequestType
|
||||
|
||||
const parseBody = JSON.parse(appointment)
|
||||
if (action === 'get') {
|
||||
return await getRecord(parseBody)
|
||||
}
|
||||
if (action === 'post') {
|
||||
return await createRecord(parseBody)
|
||||
}
|
||||
if (action === 'put') {
|
||||
return await putRecord(parseBody)
|
||||
}
|
||||
if (action === 'delete') {
|
||||
return await removeRecord(parseBody)
|
||||
}
|
||||
|
||||
const missData = [];
|
||||
if (!name) missData.push('你的姓名');
|
||||
if (!time) missData.push('需要预约的时间');
|
||||
if (!labname) missData.push('实验室名称');
|
||||
|
||||
if (missData.length > 0) {
|
||||
return {
|
||||
response: `请提供: ${missData.join('、')}`
|
||||
};
|
||||
}
|
||||
|
||||
const { data: record } = await db
|
||||
.collection('LabAppointment')
|
||||
.where({
|
||||
name,
|
||||
status: 'unStart'
|
||||
})
|
||||
.getOne();
|
||||
|
||||
if (record) {
|
||||
response: "异常"
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
response: `您已经有一个预约记录了,每人仅能同时预约一个实验室:
|
||||
姓名:${record.name}
|
||||
时间: ${record.time}
|
||||
实验室: ${record.labname}
|
||||
`
|
||||
};
|
||||
response: "异常"
|
||||
}
|
||||
}
|
||||
|
||||
await db.collection('LabAppointment').add({
|
||||
name,
|
||||
time,
|
||||
labname,
|
||||
status: 'unStart'
|
||||
});
|
||||
|
||||
return {
|
||||
response: `预约成功。
|
||||
姓名:${name}
|
||||
时间: ${time}
|
||||
实验室: ${labname}
|
||||
`
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 作用
|
||||
|
||||
基于 HTTP 模块可以无限扩展,比如操作数据库、执行联网搜索、发送邮箱等等。如果你有有趣的案例,欢迎提交 PR 到 [编排案例](/docs/workflow/examples)
|
||||
通过 HTTP 模块你可以无限扩展,比如:
|
||||
- 操作数据库
|
||||
- 调用外部数据源
|
||||
- 执行联网搜索
|
||||
- 发送邮箱
|
||||
- ....
|
||||
|
||||
|
||||
## 相关示例
|
||||
|
||||
- [谷歌搜索](/docs/workflow/examples/google_search/)
|
||||
- [实验室预约(操作数据库)](/docs/workflow/examples/lab_appointment/)
|
19
package.json
@@ -5,27 +5,24 @@
|
||||
"scripts": {
|
||||
"prepare": "husky install",
|
||||
"format-code": "prettier --config \"./.prettierrc.js\" --write \"./**/src/**/*.{ts,tsx,scss}\"",
|
||||
"format-doc": "zhlint --dir ./docSite *.md --fix"
|
||||
"format-doc": "zhlint --dir ./docSite *.md --fix",
|
||||
"postinstall": "sh ./scripts/postinstall.sh"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/multer": "^1.4.10",
|
||||
"husky": "^8.0.3",
|
||||
"i18next": "^22.5.1",
|
||||
"lint-staged": "^13.2.1",
|
||||
"next-i18next": "^13.3.0",
|
||||
"prettier": "^3.0.3",
|
||||
"react-i18next": "^12.3.1",
|
||||
"zhlint": "^0.7.1"
|
||||
"zhlint": "^0.7.1",
|
||||
"i18next": "^22.5.1",
|
||||
"next-i18next": "^13.3.0",
|
||||
"react-i18next": "^12.3.1"
|
||||
},
|
||||
"lint-staged": {
|
||||
"./**/**/*.{ts,tsx,scss}": "npm run format-code",
|
||||
"./**/**/*.md": "npm run format-doc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"multer": "1.4.5-lts.1",
|
||||
"openai": "4.16.1"
|
||||
"node": ">=18.0.0",
|
||||
"pnpm": ">=8.6.0"
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,3 @@
|
||||
import axios from 'axios';
|
||||
import { UrlFetchParams, UrlFetchResponse } from './api.d';
|
||||
import { htmlToMarkdown } from '../string/markdown';
|
||||
import * as cheerio from 'cheerio';
|
||||
|
||||
export const formatFileSize = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B';
|
||||
|
||||
@@ -12,91 +7,3 @@ export const formatFileSize = (bytes: number): string => {
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
export const cheerioToHtml = ({
|
||||
fetchUrl,
|
||||
$,
|
||||
selector
|
||||
}: {
|
||||
fetchUrl: string;
|
||||
$: cheerio.CheerioAPI;
|
||||
selector?: string;
|
||||
}) => {
|
||||
// get origin url
|
||||
const originUrl = new URL(fetchUrl).origin;
|
||||
|
||||
// remove i element
|
||||
$('i,script').remove();
|
||||
|
||||
// remove empty a element
|
||||
$('a')
|
||||
.filter((i, el) => {
|
||||
return $(el).text().trim() === '' && $(el).children().length === 0;
|
||||
})
|
||||
.remove();
|
||||
|
||||
// if link,img startWith /, add origin url
|
||||
$('a').each((i, el) => {
|
||||
const href = $(el).attr('href');
|
||||
if (href && href.startsWith('/')) {
|
||||
$(el).attr('href', originUrl + href);
|
||||
}
|
||||
});
|
||||
$('img').each((i, el) => {
|
||||
const src = $(el).attr('src');
|
||||
if (src && src.startsWith('/')) {
|
||||
$(el).attr('src', originUrl + src);
|
||||
}
|
||||
});
|
||||
|
||||
const html = $(selector || 'body')
|
||||
.map((item, dom) => {
|
||||
return $(dom).html();
|
||||
})
|
||||
.get()
|
||||
.join('\n');
|
||||
|
||||
return html;
|
||||
};
|
||||
export const urlsFetch = async ({
|
||||
urlList,
|
||||
selector
|
||||
}: UrlFetchParams): Promise<UrlFetchResponse> => {
|
||||
urlList = urlList.filter((url) => /^(http|https):\/\/[^ "]+$/.test(url));
|
||||
|
||||
const response = (
|
||||
await Promise.all(
|
||||
urlList.map(async (url) => {
|
||||
try {
|
||||
const fetchRes = await axios.get(url, {
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
const $ = cheerio.load(fetchRes.data);
|
||||
|
||||
const md = htmlToMarkdown(
|
||||
cheerioToHtml({
|
||||
fetchUrl: url,
|
||||
$,
|
||||
selector
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
url,
|
||||
content: md
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error, 'fetch error');
|
||||
|
||||
return {
|
||||
url,
|
||||
content: ''
|
||||
};
|
||||
}
|
||||
})
|
||||
)
|
||||
).filter((item) => item.content);
|
||||
|
||||
return response;
|
||||
};
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { simpleText } from './tools';
|
||||
import { NodeHtmlMarkdown } from 'node-html-markdown';
|
||||
|
||||
/* Delete redundant text in markdown */
|
||||
export const simpleMarkdownText = (rawText: string) => {
|
||||
@@ -27,75 +26,11 @@ export const simpleMarkdownText = (rawText: string) => {
|
||||
|
||||
// Remove headings and code blocks front spaces
|
||||
['####', '###', '##', '#', '```', '~~~'].forEach((item, i) => {
|
||||
const isMarkdown = i <= 3;
|
||||
const reg = new RegExp(`\\n\\s*${item}`, 'g');
|
||||
if (reg.test(rawText)) {
|
||||
rawText = rawText.replace(
|
||||
new RegExp(`(\\n)\\s*(${item})`, 'g'),
|
||||
isMarkdown ? '\n$1$2' : '$1$2'
|
||||
);
|
||||
rawText = rawText.replace(new RegExp(`(\\n)\\s*(${item})`, 'g'), '$1$2');
|
||||
}
|
||||
});
|
||||
|
||||
return rawText.trim();
|
||||
};
|
||||
|
||||
/* html string to markdown */
|
||||
export const htmlToMarkdown = (html?: string | null) => {
|
||||
if (!html) return '';
|
||||
|
||||
const surround = (source: string, surroundStr: string) => `${surroundStr}${source}${surroundStr}`;
|
||||
|
||||
const nhm = new NodeHtmlMarkdown(
|
||||
{
|
||||
codeFence: '```',
|
||||
codeBlockStyle: 'fenced',
|
||||
ignore: ['i', 'script']
|
||||
},
|
||||
{
|
||||
code: ({ node, parent, options: { codeFence, codeBlockStyle }, visitor }) => {
|
||||
const isCodeBlock = ['PRE', 'WRAPPED-PRE'].includes(parent?.tagName!);
|
||||
|
||||
if (!isCodeBlock) {
|
||||
return {
|
||||
spaceIfRepeatingChar: true,
|
||||
noEscape: true,
|
||||
postprocess: ({ content }) => {
|
||||
// Find longest occurring sequence of running backticks and add one more (so content is escaped)
|
||||
const delimiter =
|
||||
'`' + (content.match(/`+/g)?.sort((a, b) => b.length - a.length)?.[0] || '');
|
||||
const padding = delimiter.length > 1 ? ' ' : '';
|
||||
|
||||
return surround(surround(content, padding), delimiter);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/* Handle code block */
|
||||
if (codeBlockStyle === 'fenced') {
|
||||
const language =
|
||||
node.getAttribute('class')?.match(/language-(\S+)/)?.[1] ||
|
||||
parent?.getAttribute('class')?.match(/language-(\S+)/)?.[1] ||
|
||||
'';
|
||||
|
||||
return {
|
||||
noEscape: true,
|
||||
prefix: `${codeFence}${language}\n`,
|
||||
postfix: `\n${codeFence}\n`,
|
||||
childTranslators: visitor.instance.codeBlockTranslators
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
noEscape: true,
|
||||
postprocess: ({ content }) => content.replace(/^/gm, ' '),
|
||||
childTranslators: visitor.instance.codeBlockTranslators
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const markdown = nhm.translate(html).trim();
|
||||
|
||||
return simpleMarkdownText(markdown);
|
||||
};
|
||||
|
@@ -13,12 +13,13 @@ export const splitText2Chunks = (props: {
|
||||
chunkLen: number;
|
||||
overlapRatio?: number;
|
||||
customReg?: string[];
|
||||
countTokens?: boolean;
|
||||
}): {
|
||||
chunks: string[];
|
||||
tokens: number;
|
||||
overlapRatio?: number;
|
||||
} => {
|
||||
let { text = '', chunkLen, overlapRatio = 0.2, customReg = [] } = props;
|
||||
let { text = '', chunkLen, overlapRatio = 0.2, customReg = [], countTokens = true } = props;
|
||||
const splitMarker = 'SPLIT_HERE_SPLIT_HERE';
|
||||
const codeBlockMarker = 'CODE_BLOCK_LINE_MARKER';
|
||||
const overlapLen = Math.round(chunkLen * overlapRatio);
|
||||
@@ -233,7 +234,9 @@ export const splitText2Chunks = (props: {
|
||||
mdTitle: ''
|
||||
}).map((chunk) => chunk.replaceAll(codeBlockMarker, '\n')); // restore code block
|
||||
|
||||
const tokens = chunks.reduce((sum, chunk) => sum + countPromptTokens(chunk, 'system'), 0);
|
||||
const tokens = countTokens
|
||||
? chunks.reduce((sum, chunk) => sum + countPromptTokens(chunk, 'system'), 0)
|
||||
: 0;
|
||||
|
||||
return {
|
||||
chunks,
|
||||
|
3
packages/global/core/chat/type.d.ts
vendored
@@ -40,7 +40,7 @@ export type ChatItemSchema = {
|
||||
value: string;
|
||||
userGoodFeedback?: string;
|
||||
userBadFeedback?: string;
|
||||
robotBadFeedback?: string;
|
||||
customFeedbacks?: string[];
|
||||
adminFeedback?: AdminFbkType;
|
||||
[ModuleOutputKeyEnum.responseData]?: ChatHistoryItemResType[];
|
||||
};
|
||||
@@ -60,6 +60,7 @@ export type ChatItemType = {
|
||||
value: any;
|
||||
userGoodFeedback?: string;
|
||||
userBadFeedback?: string;
|
||||
customFeedbacks?: ChatItemSchema['customFeedbacks'];
|
||||
adminFeedback?: ChatItemSchema['feedback'];
|
||||
[ModuleOutputKeyEnum.responseData]?: ChatHistoryItemResType[];
|
||||
};
|
||||
|
2
packages/global/core/module/api.d.ts
vendored
@@ -5,12 +5,14 @@ export type SelectedDatasetType = { datasetId: string; vectorModel: VectorModelI
|
||||
export type HttpBodyType<T = any> = {
|
||||
appId: string;
|
||||
chatId?: string;
|
||||
responseChatItemId?: string;
|
||||
variables: Record<string, any>;
|
||||
data: T;
|
||||
};
|
||||
export type HttpQueryType = {
|
||||
appId: string;
|
||||
chatId?: string;
|
||||
responseChatItemId?: string;
|
||||
variables: Record<string, any>;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
1
packages/global/core/plugin/type.d.ts
vendored
@@ -23,5 +23,6 @@ export type PluginTemplateType = {
|
||||
name: string;
|
||||
avatar: string;
|
||||
intro: string;
|
||||
showStatus?: boolean;
|
||||
modules: ModuleItemType[];
|
||||
};
|
||||
|
@@ -2,17 +2,14 @@
|
||||
"name": "@fastgpt/global",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.5.1",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"dayjs": "^1.11.7",
|
||||
"openai": "4.23.0",
|
||||
"encoding": "^0.1.13",
|
||||
"js-tiktoken": "^1.0.7",
|
||||
"node-html-markdown": "^1.3.0",
|
||||
"openai": "^4.20.1",
|
||||
"axios": "^1.5.1",
|
||||
"timezones-list": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.8.5",
|
||||
"@types/turndown": "^5.0.4"
|
||||
"@types/node": "^20.8.5"
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,8 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.8.5"
|
||||
"@types/node": "^20.8.5",
|
||||
"@fastgpt/global": "workspace:*",
|
||||
"@fastgpt/service": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
@@ -71,7 +71,7 @@ instance.interceptors.response.use(responseSuccess, (err) => Promise.reject(err)
|
||||
|
||||
export function request(url: string, data: any, config: ConfigType, method: Method): any {
|
||||
if (!global.systemEnv || !global.systemEnv?.pluginBaseUrl) {
|
||||
console.log('未部署商业版接口');
|
||||
console.log('未部署商业版接口', url);
|
||||
return Promise.reject('The The request was denied...');
|
||||
}
|
||||
|
||||
|
95
packages/service/common/string/cheerio.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { UrlFetchParams, UrlFetchResponse } from '@fastgpt/global/common/file/api';
|
||||
import * as cheerio from 'cheerio';
|
||||
import axios from 'axios';
|
||||
import { htmlToMarkdown } from './markdown';
|
||||
|
||||
export const cheerioToHtml = ({
|
||||
fetchUrl,
|
||||
$,
|
||||
selector
|
||||
}: {
|
||||
fetchUrl: string;
|
||||
$: cheerio.CheerioAPI;
|
||||
selector?: string;
|
||||
}) => {
|
||||
// get origin url
|
||||
const originUrl = new URL(fetchUrl).origin;
|
||||
|
||||
const selectDom = $(selector || 'body');
|
||||
|
||||
// remove i element
|
||||
selectDom.find('i,script').remove();
|
||||
|
||||
// remove empty a element
|
||||
selectDom
|
||||
.find('a')
|
||||
.filter((i, el) => {
|
||||
return $(el).text().trim() === '' && $(el).children().length === 0;
|
||||
})
|
||||
.remove();
|
||||
|
||||
// if link,img startWith /, add origin url
|
||||
selectDom.find('a').each((i, el) => {
|
||||
const href = $(el).attr('href');
|
||||
if (href && href.startsWith('/')) {
|
||||
$(el).attr('href', originUrl + href);
|
||||
}
|
||||
});
|
||||
selectDom.find('img').each((i, el) => {
|
||||
const src = $(el).attr('src');
|
||||
if (src && src.startsWith('/')) {
|
||||
$(el).attr('src', originUrl + src);
|
||||
}
|
||||
});
|
||||
|
||||
const html = selectDom
|
||||
.map((item, dom) => {
|
||||
return $(dom).html();
|
||||
})
|
||||
.get()
|
||||
.join('\n');
|
||||
|
||||
return html;
|
||||
};
|
||||
export const urlsFetch = async ({
|
||||
urlList,
|
||||
selector
|
||||
}: UrlFetchParams): Promise<UrlFetchResponse> => {
|
||||
urlList = urlList.filter((url) => /^(http|https):\/\/[^ "]+$/.test(url));
|
||||
|
||||
const response = (
|
||||
await Promise.all(
|
||||
urlList.map(async (url) => {
|
||||
try {
|
||||
const fetchRes = await axios.get(url, {
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
const $ = cheerio.load(fetchRes.data);
|
||||
|
||||
const md = await htmlToMarkdown(
|
||||
cheerioToHtml({
|
||||
fetchUrl: url,
|
||||
$,
|
||||
selector
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
url,
|
||||
content: md
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error, 'fetch error');
|
||||
|
||||
return {
|
||||
url,
|
||||
content: ''
|
||||
};
|
||||
}
|
||||
})
|
||||
)
|
||||
).filter((item) => item.content);
|
||||
|
||||
return response;
|
||||
};
|
23
packages/service/common/string/markdown.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { simpleMarkdownText } from '@fastgpt/global/common/string/markdown';
|
||||
import { Worker } from 'worker_threads';
|
||||
import { getWorkerPath } from './utils';
|
||||
|
||||
/* html string to markdown */
|
||||
export const htmlToMarkdown = (html?: string | null) =>
|
||||
new Promise<string>((resolve, reject) => {
|
||||
if (!html) return resolve('');
|
||||
|
||||
const start = Date.now();
|
||||
|
||||
// worker
|
||||
const worker = new Worker(getWorkerPath('html2md'));
|
||||
|
||||
worker.on('message', (md: string) => {
|
||||
resolve(simpleMarkdownText(md));
|
||||
});
|
||||
worker.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
worker.postMessage(html);
|
||||
});
|
9
packages/service/common/string/utils.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export const getWorkerPath = (name: string) => {
|
||||
// @ts-ignore
|
||||
const isSubModule = !!global?.systemConfig;
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
return isProd
|
||||
? `/app/worker/${name}.js`
|
||||
: `../../${isSubModule ? 'FastGPT/' : ''}/worker/${name}.js`;
|
||||
};
|
@@ -61,8 +61,8 @@ const ChatItemSchema = new Schema({
|
||||
userBadFeedback: {
|
||||
type: String
|
||||
},
|
||||
robotBadFeedback: {
|
||||
type: String
|
||||
customFeedbacks: {
|
||||
type: [String]
|
||||
},
|
||||
adminFeedback: {
|
||||
type: {
|
||||
@@ -86,7 +86,7 @@ try {
|
||||
ChatItemSchema.index({ chatId: 1 });
|
||||
ChatItemSchema.index({ userGoodFeedback: 1 });
|
||||
ChatItemSchema.index({ userBadFeedback: 1 });
|
||||
ChatItemSchema.index({ robotBadFeedback: 1 });
|
||||
ChatItemSchema.index({ customFeedbacks: 1 });
|
||||
ChatItemSchema.index({ adminFeedback: 1 });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { MongoChatItem } from './chatItemSchema';
|
||||
import { addLog } from '../../common/system/log';
|
||||
|
||||
export async function getChatItems({
|
||||
chatId,
|
||||
@@ -20,3 +21,29 @@ export async function getChatItems({
|
||||
|
||||
return { history };
|
||||
}
|
||||
|
||||
export const addCustomFeedbacks = async ({
|
||||
chatId,
|
||||
chatItemId,
|
||||
feedbacks
|
||||
}: {
|
||||
chatId?: string;
|
||||
chatItemId?: string;
|
||||
feedbacks: string[];
|
||||
}) => {
|
||||
if (!chatId || !chatItemId) return;
|
||||
|
||||
try {
|
||||
await MongoChatItem.findOneAndUpdate(
|
||||
{
|
||||
chatId,
|
||||
dataId: chatItemId
|
||||
},
|
||||
{
|
||||
$push: { customFeedbacks: { $each: feedbacks } }
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
addLog.error('addCustomFeedbacks error', error);
|
||||
}
|
||||
};
|
||||
|
@@ -4,7 +4,7 @@ import type { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder
|
||||
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
|
||||
import { splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
|
||||
import { MongoDatasetTraining } from '../training/schema';
|
||||
import { urlsFetch } from '@fastgpt/global/common/file/tools';
|
||||
import { urlsFetch } from '../../../common/string/cheerio';
|
||||
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
|
||||
/**
|
||||
@@ -105,7 +105,8 @@ export const loadingOneChunkCollection = async ({
|
||||
// split data
|
||||
const { chunks } = splitText2Chunks({
|
||||
text: newRawText,
|
||||
chunkLen: collection.chunkSize || 512
|
||||
chunkLen: collection.chunkSize || 512,
|
||||
countTokens: false
|
||||
});
|
||||
|
||||
// insert to training queue
|
||||
|
@@ -44,6 +44,7 @@ const getPluginTemplateById = async (id: string): Promise<PluginTemplateType> =>
|
||||
name: item.name,
|
||||
avatar: item.avatar,
|
||||
intro: item.intro,
|
||||
showStatus: true,
|
||||
source: PluginSourceEnum.personal,
|
||||
modules: item.modules,
|
||||
templateType: ModuleTemplateTypeEnum.personalPlugin
|
||||
@@ -67,7 +68,7 @@ export async function getPluginPreviewModule({
|
||||
avatar: plugin.avatar,
|
||||
name: plugin.name,
|
||||
intro: plugin.intro,
|
||||
showStatus: true,
|
||||
showStatus: plugin.showStatus,
|
||||
...plugin2ModuleIO(plugin.id, plugin.modules)
|
||||
};
|
||||
}
|
||||
|
@@ -3,22 +3,24 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@fastgpt/global": "workspace:*",
|
||||
"axios": "^1.5.1",
|
||||
"cookie": "^0.5.0",
|
||||
"encoding": "^0.1.13",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"mongoose": "^7.0.2",
|
||||
"nanoid": "^4.0.1",
|
||||
"dayjs": "^1.11.7",
|
||||
"next": "13.5.2",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"axios": "^1.5.1",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"nextjs-cors": "^2.1.2",
|
||||
"pg": "^8.10.0",
|
||||
"tunnel": "^0.0.6",
|
||||
"dayjs": "^1.11.7"
|
||||
"tunnel": "^0.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cookie": "^0.5.2",
|
||||
"@types/jsonwebtoken": "^9.0.3",
|
||||
"@types/node": "^20.8.5",
|
||||
"@types/multer": "^1.4.10",
|
||||
"@types/pg": "^8.6.6",
|
||||
"@types/tunnel": "^0.0.4"
|
||||
}
|
||||
|
@@ -31,3 +31,11 @@ export async function authCertOrShareId({
|
||||
canWrite: false
|
||||
};
|
||||
}
|
||||
|
||||
/* auth the request from local service */
|
||||
export const authRequestFromLocal = ({ req }: AuthModeType) => {
|
||||
const host = `${process.env.HOSTNAME || 'localhost'}:${process.env.PORT || 3000}`;
|
||||
if (host !== req.headers.host) {
|
||||
return Promise.reject('Invalid request');
|
||||
}
|
||||
};
|
||||
|
@@ -1,8 +1,6 @@
|
||||
{
|
||||
"name": "@fastgpt/web",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.5.1"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {}
|
||||
}
|
||||
|
3819
pnpm-lock.yaml
generated
@@ -6,10 +6,10 @@
|
||||
module.exports = {
|
||||
i18n: {
|
||||
defaultLocale: 'zh',
|
||||
locales: ['en', 'zh', 'zh-Hans', 'zh-CN'],
|
||||
localeDetection: false
|
||||
locales: ['en', 'zh'],
|
||||
localeDetection: true
|
||||
},
|
||||
localePath:
|
||||
typeof window === 'undefined' ? require('path').resolve('./public/locales') : '/locales',
|
||||
typeof window === 'undefined' ? require('path').resolve('./public/locales') : '/public/locales',
|
||||
reloadOnPrerender: process.env.NODE_ENV === 'development'
|
||||
};
|
||||
|
@@ -45,7 +45,7 @@ const nextConfig = {
|
||||
},
|
||||
transpilePackages: ['@fastgpt/*'],
|
||||
experimental: {
|
||||
serverComponentsExternalPackages: ['mongoose', 'winston', 'winston-mongodb', 'pg'],
|
||||
serverComponentsExternalPackages: ['mongoose', 'pg'],
|
||||
outputFileTracingRoot: path.join(__dirname, '../../')
|
||||
}
|
||||
};
|
||||
|
@@ -10,12 +10,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/anatomy": "^2.2.1",
|
||||
"@chakra-ui/icons": "^2.0.17",
|
||||
"@chakra-ui/react": "^2.7.0",
|
||||
"@chakra-ui/icons": "^2.1.1",
|
||||
"@chakra-ui/next-js": "^2.1.5",
|
||||
"@chakra-ui/react": "^2.8.1",
|
||||
"@chakra-ui/styled-system": "^2.9.1",
|
||||
"@chakra-ui/system": "^2.5.8",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@chakra-ui/system": "^2.6.1",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@fastgpt/plugins": "workspace:*",
|
||||
"@fastgpt/global": "workspace:*",
|
||||
"@fastgpt/service": "workspace:*",
|
||||
@@ -23,33 +24,25 @@
|
||||
"@node-rs/jieba": "^1.7.2",
|
||||
"@tanstack/react-query": "^4.24.10",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"axios": "^1.5.1",
|
||||
"date-fns": "^2.30.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"downloadjs": "^1.4.7",
|
||||
"echarts": "^5.4.1",
|
||||
"next": "13.5.2",
|
||||
"echarts-gl": "^2.0.9",
|
||||
"formidable": "^2.1.1",
|
||||
"framer-motion": "^9.0.6",
|
||||
"hyperdown": "^2.4.29",
|
||||
"i18next": "^22.5.1",
|
||||
"immer": "^9.0.19",
|
||||
"jschardet": "^3.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"mammoth": "^1.6.0",
|
||||
"mermaid": "^10.2.3",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"nanoid": "^4.0.1",
|
||||
"next": "13.5.2",
|
||||
"next-i18next": "^13.3.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"papaparse": "^5.4.1",
|
||||
"react": "18.2.0",
|
||||
"react-day-picker": "^8.7.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.43.1",
|
||||
"react-i18next": "^12.3.1",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"reactflow": "^11.7.4",
|
||||
@@ -59,28 +52,28 @@
|
||||
"remark-math": "^5.1.1",
|
||||
"request-ip": "^3.3.0",
|
||||
"sass": "^1.58.3",
|
||||
"zustand": "^4.3.5"
|
||||
"zustand": "^4.3.5",
|
||||
"i18next": "^22.5.1",
|
||||
"next-i18next": "^13.3.0",
|
||||
"react-i18next": "^12.3.1",
|
||||
"axios": "^1.5.1",
|
||||
"nanoid": "^4.0.1",
|
||||
"dayjs": "^1.11.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@svgr/webpack": "^6.5.1",
|
||||
"@types/downloadjs": "^1.4.3",
|
||||
"@types/formidable": "^2.0.5",
|
||||
"@types/js-cookie": "^3.0.3",
|
||||
"@types/jsonwebtoken": "^9.0.3",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/multer": "^1.4.10",
|
||||
"@types/node": "^20.8.5",
|
||||
"@types/papaparse": "^5.3.7",
|
||||
"@types/react": "18.0.28",
|
||||
"@types/react-dom": "18.0.11",
|
||||
"@types/react": "18.2.0",
|
||||
"@types/react-dom": "18.2.0",
|
||||
"@types/react-syntax-highlighter": "^15.5.6",
|
||||
"@types/request-ip": "^0.0.37",
|
||||
"eslint": "8.34.0",
|
||||
"eslint-config-next": "13.1.6",
|
||||
"typescript": "4.9.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"pnpm": ">=8.6.0"
|
||||
}
|
||||
}
|
||||
|
1
projects/app/public/imgs/module/customFeedback.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?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="1702637232008" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6146" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M261.688889 194.218667A67.470222 67.470222 0 1 1 194.218667 261.688889 67.584 67.584 0 0 1 261.688889 194.218667M261.688889 136.533333a125.155556 125.155556 0 1 0 125.155555 125.155556 125.155556 125.155556 0 0 0-125.155555-125.155556zM614.4 444.529778A67.470222 67.470222 0 1 1 547.271111 512a67.584 67.584 0 0 1 67.128889-67.470222M614.4 386.844444a125.155556 125.155556 0 1 0 125.155556 125.155556 125.155556 125.155556 0 0 0-125.155556-125.155556zM408.120889 694.840889A67.470222 67.470222 0 1 1 340.650667 762.311111a67.470222 67.470222 0 0 1 67.470222-67.470222m0-57.685333a125.155556 125.155556 0 1 0 125.155555 125.155555 125.155556 125.155556 0 0 0-125.155555-125.155555z" fill="#1DCCA1" p-id="6147"></path><path d="M489.016889 736.142222h375.011555v52.337778H489.016889zM325.632 788.48h-42.894222C171.804444 788.48 113.777778 711.452444 113.777778 635.335111a143.36 143.36 0 0 1 43.235555-103.651555c30.833778-30.037333 74.296889-45.511111 125.724445-45.511112h242.119111v52.337778H282.737778c-80.554667 0-116.622222 48.810667-116.622222 97.166222s36.522667 100.807111 116.622222 100.807112h42.894222zM743.537778 538.168889h-40.618667v-52.337778h40.618667c78.620444 0 114.346667-51.541333 114.346666-99.555555s-35.384889-98.417778-114.346666-98.417778H337.123556v-52.337778h406.414222C853.333333 235.52 910.222222 311.409778 910.222222 386.844444a147.911111 147.911111 0 0 1-42.780444 104.903112 168.732444 168.732444 0 0 1-123.904 46.421333z" fill="#1DCCA1" p-id="6148"></path><path d="M910.222222 762.311111l-136.533333 102.4V659.911111l136.533333 102.4z" fill="#1DCCA1" p-id="6149"></path></svg>
|
After Width: | Height: | Size: 1.9 KiB |
@@ -247,6 +247,10 @@
|
||||
"Save and out": "Save out",
|
||||
"UnSave": "UnSave"
|
||||
},
|
||||
"feedback": {
|
||||
"Custom feedback": "Custom feedback",
|
||||
"close custom feedback": "Close Feedback"
|
||||
},
|
||||
"logs": {
|
||||
"Source And Time": "Source & Time"
|
||||
},
|
||||
|
@@ -247,6 +247,10 @@
|
||||
"Save and out": "保存并退出",
|
||||
"UnSave": "不保存"
|
||||
},
|
||||
"feedback": {
|
||||
"Custom feedback": "自定义反馈",
|
||||
"close custom feedback": "关闭反馈"
|
||||
},
|
||||
"logs": {
|
||||
"Source And Time": "来源 & 时间"
|
||||
},
|
||||
|
@@ -18,7 +18,8 @@ type pluginType = {
|
||||
name: string;
|
||||
avatar: string;
|
||||
intro: string;
|
||||
modules: 直接从高级编排导出配置复制过来;
|
||||
showStatus?: boolean; // 是否需要展示组件运行状态
|
||||
modules: []; //直接从高级编排导出配置复制过来;
|
||||
};
|
||||
```
|
||||
|
||||
|
319
projects/app/public/pluginTemplates/customFeedback.json
Normal file
@@ -0,0 +1,319 @@
|
||||
{
|
||||
"author": "FastGPT Team",
|
||||
"templateType": "other",
|
||||
"name": "自定义反馈",
|
||||
"avatar": "/imgs/module/customFeedback.svg",
|
||||
"intro": "该模块被触发时,会给当前的对话记录增加一条反馈。可用于自动记录对话效果等。",
|
||||
"showStatus": false,
|
||||
"modules": [
|
||||
{
|
||||
"moduleId": "w90mfp",
|
||||
"name": "定义插件输入",
|
||||
"avatar": "/imgs/module/input.png",
|
||||
"flowType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 515.1887815471657,
|
||||
"y": -169.04905809653783
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "defaultFeedback",
|
||||
"valueType": "string",
|
||||
"label": "默认反馈内容",
|
||||
"type": "textarea",
|
||||
"required": false,
|
||||
"description": "",
|
||||
"edit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": true,
|
||||
"dataType": true,
|
||||
"inputType": true
|
||||
},
|
||||
"connected": true
|
||||
},
|
||||
{
|
||||
"key": "customFeedback",
|
||||
"valueType": "string",
|
||||
"label": "自定义反馈内容",
|
||||
"type": "target",
|
||||
"required": false,
|
||||
"description": "",
|
||||
"edit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": true,
|
||||
"dataType": true,
|
||||
"inputType": true
|
||||
},
|
||||
"connected": true
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"key": "defaultFeedback",
|
||||
"valueType": "string",
|
||||
"label": "默认反馈内容",
|
||||
"type": "source",
|
||||
"edit": true,
|
||||
"targets": [
|
||||
{
|
||||
"moduleId": "49de3g",
|
||||
"key": "defaultFeedback"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "customFeedback",
|
||||
"valueType": "string",
|
||||
"label": "自定义反馈内容",
|
||||
"type": "source",
|
||||
"edit": true,
|
||||
"targets": [
|
||||
{
|
||||
"moduleId": "49de3g",
|
||||
"key": "customFeedback"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "49de3g",
|
||||
"name": "HTTP模块",
|
||||
"avatar": "/imgs/module/http.png",
|
||||
"flowType": "httpRequest",
|
||||
"showStatus": true,
|
||||
"position": {
|
||||
"x": 1086.8929621216014,
|
||||
"y": -451.7550009773506
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "switch",
|
||||
"type": "target",
|
||||
"label": "core.module.input.label.switch",
|
||||
"valueType": "any",
|
||||
"showTargetInApp": true,
|
||||
"showTargetInPlugin": true,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpMethod",
|
||||
"type": "select",
|
||||
"valueType": "string",
|
||||
"label": "core.module.input.label.Http Request Method",
|
||||
"value": "POST",
|
||||
"list": [
|
||||
{
|
||||
"label": "GET",
|
||||
"value": "GET"
|
||||
},
|
||||
{
|
||||
"label": "POST",
|
||||
"value": "POST"
|
||||
}
|
||||
],
|
||||
"required": true,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpReqUrl",
|
||||
"type": "input",
|
||||
"valueType": "string",
|
||||
"label": "core.module.input.label.Http Request Url",
|
||||
"description": "core.module.input.description.Http Request Url",
|
||||
"placeholder": "https://api.ai.com/getInventory",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"value": "/api/plugins/customFeedback",
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpHeader",
|
||||
"type": "textarea",
|
||||
"valueType": "string",
|
||||
"label": "core.module.input.label.Http Request Header",
|
||||
"description": "core.module.input.description.Http Request Header",
|
||||
"placeholder": "core.module.input.description.Http Request Header",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"value": "",
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "DYNAMIC_INPUT_KEY",
|
||||
"type": "target",
|
||||
"valueType": "any",
|
||||
"label": "core.module.inputType.dynamicTargetInput",
|
||||
"description": "core.module.input.description.dynamic input",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": true,
|
||||
"hideInApp": true,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"valueType": "string",
|
||||
"label": "defaultFeedback",
|
||||
"type": "target",
|
||||
"required": true,
|
||||
"description": "",
|
||||
"edit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": true,
|
||||
"dataType": true
|
||||
},
|
||||
"connected": true,
|
||||
"key": "defaultFeedback"
|
||||
},
|
||||
{
|
||||
"key": "customFeedback",
|
||||
"valueType": "string",
|
||||
"label": "customFeedback",
|
||||
"type": "target",
|
||||
"required": true,
|
||||
"description": "",
|
||||
"edit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": true,
|
||||
"dataType": true
|
||||
},
|
||||
"connected": true
|
||||
},
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"type": "addInputParam",
|
||||
"valueType": "any",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": true,
|
||||
"dataType": true
|
||||
},
|
||||
"defaultEditField": {
|
||||
"label": "",
|
||||
"key": "",
|
||||
"description": "",
|
||||
"inputType": "target",
|
||||
"valueType": "string",
|
||||
"required": true
|
||||
},
|
||||
"connected": false
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"key": "finish",
|
||||
"label": "core.module.output.label.running done",
|
||||
"description": "core.module.output.description.running done",
|
||||
"valueType": "boolean",
|
||||
"type": "source",
|
||||
"targets": []
|
||||
},
|
||||
{
|
||||
"key": "system_addOutputParam",
|
||||
"type": "addOutputParam",
|
||||
"valueType": "any",
|
||||
"label": "",
|
||||
"targets": [],
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"dataType": true
|
||||
},
|
||||
"defaultEditField": {
|
||||
"label": "",
|
||||
"key": "",
|
||||
"description": "",
|
||||
"outputType": "source",
|
||||
"valueType": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"valueType": "string",
|
||||
"label": "response",
|
||||
"description": "",
|
||||
"edit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"dataType": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"moduleId": "s15f3v",
|
||||
"key": "text"
|
||||
}
|
||||
],
|
||||
"key": "response"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "s15f3v",
|
||||
"name": "指定回复",
|
||||
"avatar": "/imgs/module/reply.png",
|
||||
"flowType": "answerNode",
|
||||
"position": {
|
||||
"x": 1705.6337348182756,
|
||||
"y": -37.53826066726282
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "switch",
|
||||
"type": "target",
|
||||
"label": "core.module.input.label.switch",
|
||||
"valueType": "any",
|
||||
"showTargetInApp": true,
|
||||
"showTargetInPlugin": true,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "text",
|
||||
"type": "textarea",
|
||||
"valueType": "any",
|
||||
"label": "回复的内容",
|
||||
"description": "可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串",
|
||||
"placeholder": "可以使用 \\n 来实现连续换行。\n可以通过外部模块输入实现回复,外部模块输入时会覆盖当前填写的内容。\n如传入非字符串类型数据将会自动转成字符串",
|
||||
"showTargetInApp": true,
|
||||
"showTargetInPlugin": true,
|
||||
"connected": true
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"key": "finish",
|
||||
"label": "core.module.output.label.running done",
|
||||
"description": "core.module.output.description.running done",
|
||||
"valueType": "boolean",
|
||||
"type": "source",
|
||||
"targets": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@@ -4,6 +4,7 @@
|
||||
"name": "core.module.template.textEditor",
|
||||
"avatar": "/imgs/module/textEditor.svg",
|
||||
"intro": "core.module.template.textEditor intro",
|
||||
"showStatus": false,
|
||||
"modules": [
|
||||
{
|
||||
"moduleId": "w90mfp",
|
@@ -4,6 +4,7 @@
|
||||
"name": "core.module.template.TFSwitch",
|
||||
"avatar": "/imgs/module/tfSwitch.svg",
|
||||
"intro": "core.module.template.TFSwitch intro",
|
||||
"showStatus": false,
|
||||
"modules": [
|
||||
{
|
||||
"moduleId": "w90mfp",
|
@@ -2,7 +2,7 @@ import { useSpeech } from '@/web/common/hooks/useSpeech';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { Box, Flex, Image, Spinner, Textarea } from '@chakra-ui/react';
|
||||
import React, { useRef, useEffect, useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyTooltip from '../MyTooltip';
|
||||
import MyIcon from '../Icon';
|
||||
import styles from './index.module.scss';
|
||||
@@ -216,7 +216,7 @@ ${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
|
||||
pl={5}
|
||||
alignItems={'center'}
|
||||
bg={'white'}
|
||||
color={'myBlue.600'}
|
||||
color={'blue.500'}
|
||||
visibility={isSpeaking && isTransCription ? 'visible' : 'hidden'}
|
||||
>
|
||||
<Spinner size={'sm'} mr={4} />
|
||||
@@ -244,7 +244,7 @@ ${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
rounded={'md'}
|
||||
color={'myBlue.600'}
|
||||
color={'blue.500'}
|
||||
top={0}
|
||||
left={0}
|
||||
bottom={0}
|
||||
@@ -260,7 +260,7 @@ ${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
|
||||
h={'16px'}
|
||||
color={'myGray.700'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
_hover={{ color: 'blue.500' }}
|
||||
position={'absolute'}
|
||||
bg={'white'}
|
||||
right={'-8px'}
|
||||
@@ -396,7 +396,7 @@ ${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
|
||||
name={isSpeaking ? 'core/chat/stopSpeechFill' : 'core/chat/recordFill'}
|
||||
width={['20px', '22px']}
|
||||
height={['20px', '22px']}
|
||||
color={'myBlue.600'}
|
||||
color={'blue.500'}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
@@ -415,7 +415,7 @@ ${images.map((img) => JSON.stringify({ src: img.src })).join('\n')}
|
||||
h={['28px', '32px']}
|
||||
w={['28px', '32px']}
|
||||
borderRadius={'md'}
|
||||
bg={isSpeaking || isChatting ? '' : !havInput ? '#E5E5E5' : 'myBlue.600'}
|
||||
bg={isSpeaking || isChatting ? '' : !havInput ? '#E5E5E5' : 'blue.500'}
|
||||
cursor={havInput ? 'pointer' : 'not-allowed'}
|
||||
lineHeight={1}
|
||||
onClick={() => {
|
||||
|
@@ -105,7 +105,7 @@ const QuoteModal = ({
|
||||
className="hover-data"
|
||||
display={'none'}
|
||||
alignItems={'center'}
|
||||
color={'myBlue.600'}
|
||||
color={'blue.500'}
|
||||
href={`/dataset/detail?datasetId=${item.datasetId}¤tTab=dataCard&collectionId=${item.collectionId}`}
|
||||
>
|
||||
{t('core.dataset.Go Dataset')}
|
||||
@@ -164,7 +164,7 @@ const QuoteModal = ({
|
||||
cursor={'pointer'}
|
||||
color={'myGray.600'}
|
||||
_hover={{
|
||||
color: 'myBlue.700'
|
||||
color: 'blue.600'
|
||||
}}
|
||||
onClick={() => onclickEdit(item)}
|
||||
/>
|
||||
|
@@ -147,7 +147,7 @@ const ResponseTags = ({
|
||||
name="common/routePushLight"
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
_hover={{ color: 'blue.500' }}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
|
@@ -35,7 +35,7 @@ const SelectMarkCollection = ({
|
||||
const theme = useTheme();
|
||||
const [selectedDatasetId, setSelectedDatasetId] = useState<string>();
|
||||
const [selectedDatasetCollectionIds, setSelectedDatasetCollectionIds] = useState<string[]>([]);
|
||||
const { paths, parentId, setParentId, datasets, isFetching } = useDatasetSelect();
|
||||
const { paths, setParentId, datasets, isFetching } = useDatasetSelect();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -70,7 +70,7 @@ const SelectMarkCollection = ({
|
||||
}}
|
||||
{...(selected
|
||||
? {
|
||||
bg: 'myBlue.300'
|
||||
bg: 'blue.200'
|
||||
}
|
||||
: {})}
|
||||
onClick={() => {
|
||||
|
@@ -27,7 +27,8 @@ import {
|
||||
BoxProps,
|
||||
FlexProps,
|
||||
Image,
|
||||
Textarea
|
||||
Textarea,
|
||||
Checkbox
|
||||
} from '@chakra-ui/react';
|
||||
import { feConfigs } from '@/web/common/system/staticData';
|
||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
@@ -43,7 +44,11 @@ import { useRouter } from 'next/router';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { updateChatAdminFeedback, updateChatUserFeedback } from '@/web/core/chat/api';
|
||||
import {
|
||||
closeCustomFeedback,
|
||||
updateChatAdminFeedback,
|
||||
updateChatUserFeedback
|
||||
} from '@/web/core/chat/api';
|
||||
import type { AdminMarkType } from './SelectMarkCollection';
|
||||
|
||||
import MyIcon from '@/components/Icon';
|
||||
@@ -63,6 +68,7 @@ import { splitGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import type { AppTTSConfigType } from '@fastgpt/global/core/module/type.d';
|
||||
import MessageInput from './MessageInput';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import ChatBoxDivider from '../core/chat/Divider';
|
||||
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
|
||||
|
||||
@@ -492,7 +498,7 @@ const ChatBox = (
|
||||
const colorMap = {
|
||||
loading: 'myGray.700',
|
||||
running: '#67c13b',
|
||||
finish: 'myBlue.600'
|
||||
finish: 'blue.500'
|
||||
};
|
||||
if (!isChatting) return;
|
||||
const chatContent = chatHistory[chatHistory.length - 1];
|
||||
@@ -660,7 +666,7 @@ const ChatBox = (
|
||||
<Card
|
||||
className="markdown"
|
||||
{...MessageCardStyle}
|
||||
bg={'myBlue.300'}
|
||||
bg={'blue.200'}
|
||||
borderRadius={'8px 0 8px 8px'}
|
||||
textAlign={'left'}
|
||||
>
|
||||
@@ -853,16 +859,56 @@ const ChatBox = (
|
||||
|
||||
<ResponseTags responseData={item.responseData} isShare={!!shareId} />
|
||||
|
||||
{/* custom feedback */}
|
||||
{item.customFeedbacks && item.customFeedbacks.length > 0 && (
|
||||
<Box>
|
||||
<ChatBoxDivider
|
||||
icon={'core/app/customFeedback'}
|
||||
text={t('core.app.feedback.Custom feedback')}
|
||||
/>
|
||||
{item.customFeedbacks.map((text, i) => (
|
||||
<Box key={`${text}${i}`}>
|
||||
<MyTooltip label={t('core.app.feedback.close custom feedback')}>
|
||||
<Checkbox
|
||||
onChange={(e) => {
|
||||
if (e.target.checked && appId && chatId && item.dataId) {
|
||||
closeCustomFeedback({
|
||||
appId,
|
||||
chatId,
|
||||
chatItemId: item.dataId,
|
||||
index: i
|
||||
});
|
||||
// update dom
|
||||
setChatHistory((state) =>
|
||||
state.map((chatItem) =>
|
||||
chatItem.dataId === item.dataId
|
||||
? {
|
||||
...chatItem,
|
||||
customFeedbacks: chatItem.customFeedbacks?.filter(
|
||||
(item, index) => index !== i
|
||||
)
|
||||
}
|
||||
: chatItem
|
||||
)
|
||||
);
|
||||
}
|
||||
console.log(e);
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</Checkbox>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
{/* admin mark content */}
|
||||
{showMarkIcon && item.adminFeedback && (
|
||||
<Box>
|
||||
<Flex alignItems={'center'} py={2}>
|
||||
<MyIcon name={'core/app/markLight'} w={'14px'} color={'myGray.900'} />
|
||||
<Box ml={2} color={'myGray.500'}>
|
||||
{t('chat.Admin Mark Content')}
|
||||
</Box>
|
||||
<Box h={'1px'} bg={'myGray.300'} flex={'1'} />
|
||||
</Flex>
|
||||
<ChatBoxDivider
|
||||
icon="core/app/markLight"
|
||||
text={t('chat.Admin Mark Content')}
|
||||
/>
|
||||
<Box whiteSpace={'pre'}>{`${item.adminFeedback.q || ''}${
|
||||
item.adminFeedback.a ? `\n${item.adminFeedback.a}` : ''
|
||||
}`}</Box>
|
||||
@@ -942,7 +988,10 @@ const ChatBox = (
|
||||
setAdminMarkData={(e) => setAdminMarkData({ ...e, chatItemId: adminMarkData.chatItemId })}
|
||||
onClose={() => setAdminMarkData(undefined)}
|
||||
onSuccess={(adminFeedback) => {
|
||||
if (!appId || !chatId || !adminMarkData.chatItemId) return;
|
||||
updateChatAdminFeedback({
|
||||
appId,
|
||||
chatId,
|
||||
chatItemId: adminMarkData.chatItemId,
|
||||
...adminFeedback
|
||||
});
|
||||
@@ -1089,7 +1138,7 @@ function ChatAvatar({ src, type }: { src?: string; type: 'Human' | 'AI' }) {
|
||||
borderRadius={'lg'}
|
||||
border={theme.borders.base}
|
||||
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
||||
bg={type === 'Human' ? 'white' : 'myBlue.100'}
|
||||
bg={type === 'Human' ? 'white' : 'blue.50'}
|
||||
>
|
||||
<Avatar src={src} w={'100%'} h={'100%'} />
|
||||
</Box>
|
||||
@@ -1170,7 +1219,7 @@ function ChatController({
|
||||
<MyIcon
|
||||
{...controlIconStyle}
|
||||
name={'copy'}
|
||||
_hover={{ color: 'myBlue.700' }}
|
||||
_hover={{ color: 'blue.600' }}
|
||||
onClick={() => copyData(chat.value)}
|
||||
/>
|
||||
</MyTooltip>
|
||||
|
@@ -0,0 +1,12 @@
|
||||
<?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="1702637232008"
|
||||
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6146"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128">
|
||||
<path
|
||||
d="M261.688889 194.218667A67.470222 67.470222 0 1 1 194.218667 261.688889 67.584 67.584 0 0 1 261.688889 194.218667M261.688889 136.533333a125.155556 125.155556 0 1 0 125.155555 125.155556 125.155556 125.155556 0 0 0-125.155555-125.155556zM614.4 444.529778A67.470222 67.470222 0 1 1 547.271111 512a67.584 67.584 0 0 1 67.128889-67.470222M614.4 386.844444a125.155556 125.155556 0 1 0 125.155556 125.155556 125.155556 125.155556 0 0 0-125.155556-125.155556zM408.120889 694.840889A67.470222 67.470222 0 1 1 340.650667 762.311111a67.470222 67.470222 0 0 1 67.470222-67.470222m0-57.685333a125.155556 125.155556 0 1 0 125.155555 125.155555 125.155556 125.155556 0 0 0-125.155555-125.155555z"
|
||||
fill="#1DCCA1" p-id="6147"></path>
|
||||
<path
|
||||
d="M489.016889 736.142222h375.011555v52.337778H489.016889zM325.632 788.48h-42.894222C171.804444 788.48 113.777778 711.452444 113.777778 635.335111a143.36 143.36 0 0 1 43.235555-103.651555c30.833778-30.037333 74.296889-45.511111 125.724445-45.511112h242.119111v52.337778H282.737778c-80.554667 0-116.622222 48.810667-116.622222 97.166222s36.522667 100.807111 116.622222 100.807112h42.894222zM743.537778 538.168889h-40.618667v-52.337778h40.618667c78.620444 0 114.346667-51.541333 114.346666-99.555555s-35.384889-98.417778-114.346666-98.417778H337.123556v-52.337778h406.414222C853.333333 235.52 910.222222 311.409778 910.222222 386.844444a147.911111 147.911111 0 0 1-42.780444 104.903112 168.732444 168.732444 0 0 1-123.904 46.421333z"
|
||||
fill="#1DCCA1" p-id="6148"></path>
|
||||
<path d="M910.222222 762.311111l-136.533333 102.4V659.911111l136.533333 102.4z" fill="#1DCCA1" p-id="6149"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
@@ -124,7 +124,8 @@ const iconPaths = {
|
||||
'common/confirm/deleteTip': () => import('./icons/common/confirm/deleteTip.svg'),
|
||||
'common/confirm/commonTip': () => import('./icons/common/confirm/commonTip.svg'),
|
||||
'common/routePushLight': () => import('./icons/common/routePushLight.svg'),
|
||||
'common/viewLight': () => import('./icons/common/viewLight.svg')
|
||||
'common/viewLight': () => import('./icons/common/viewLight.svg'),
|
||||
'core/app/customFeedback': () => import('./icons/core/app/customFeedback.svg')
|
||||
};
|
||||
|
||||
export type IconName = keyof typeof iconPaths;
|
||||
|
@@ -129,7 +129,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
{...itemStyles}
|
||||
{...(item.activeLink.includes(router.pathname)
|
||||
? {
|
||||
color: 'myBlue.700',
|
||||
color: 'blue.600',
|
||||
bg: 'white !important',
|
||||
boxShadow: '1px 1px 10px rgba(0,0,0,0.2)'
|
||||
}
|
||||
|
@@ -25,9 +25,9 @@ const Loading = ({
|
||||
justifyContent={'center'}
|
||||
flexDirection={'column'}
|
||||
>
|
||||
<Spinner thickness="4px" speed="0.65s" emptyColor="myGray.100" color="myBlue.600" size="xl" />
|
||||
<Spinner thickness="4px" speed="0.65s" emptyColor="myGray.100" color="blue.500" size="xl" />
|
||||
{text && (
|
||||
<Box mt={2} color="myBlue.700" fontWeight={'bold'}>
|
||||
<Box mt={2} color="blue.600" fontWeight={'bold'}>
|
||||
{text}
|
||||
</Box>
|
||||
)}
|
||||
|
@@ -23,7 +23,7 @@ function MyLink(e: any) {
|
||||
<Box as={'li'} mb={1}>
|
||||
<Box
|
||||
as={'span'}
|
||||
color={'blue.600'}
|
||||
color={'blue.500'}
|
||||
textDecoration={'underline'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
|
@@ -77,7 +77,7 @@ const QuestionGuide = ({ text }: { text: string }) => {
|
||||
name={'core/chat/sendLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
_hover={{ color: 'blue.500' }}
|
||||
onClick={() => eventBus.emit(EventNameEnum.sendQuestion, { text })}
|
||||
/>
|
||||
</MyTooltip>
|
||||
|
@@ -123,7 +123,7 @@ const MermaidBlock = ({ code }: { code: string }) => {
|
||||
position={'absolute'}
|
||||
color={'myGray.600'}
|
||||
_hover={{
|
||||
color: 'myBlue.700'
|
||||
color: 'blue.600'
|
||||
}}
|
||||
right={0}
|
||||
top={0}
|
||||
|
@@ -1,20 +1,23 @@
|
||||
.waitingAnimation::after {
|
||||
.waitingAnimation > :last-child::after {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
width: 4px;
|
||||
width: 3px;
|
||||
height: 14px;
|
||||
transform: translate(4px, 2px) scaleY(1.3);
|
||||
background-color: var(--chakra-colors-chakra-body-text);
|
||||
background-color: var(--chakra-colors-blue-700);
|
||||
animation: blink 0.6s infinite;
|
||||
}
|
||||
|
||||
.animation {
|
||||
> :last-child::after {
|
||||
height: 20px;
|
||||
|
||||
&::after {
|
||||
display: inline-block;
|
||||
content: '';
|
||||
width: 4px;
|
||||
width: 3px;
|
||||
height: 14px;
|
||||
transform: translate(4px, 2px) scaleY(1.3);
|
||||
background-color: var(--chakra-colors-chakra-body-text);
|
||||
background-color: var(--chakra-colors-blue-700);
|
||||
animation: blink 0.6s infinite;
|
||||
}
|
||||
}
|
||||
|
@@ -96,10 +96,10 @@ function A({ children, ...props }: any) {
|
||||
name={'core/chat/quoteSign'}
|
||||
transform={'translateY(-2px)'}
|
||||
w={'18px'}
|
||||
color={'myBlue.600'}
|
||||
color={'blue.500'}
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'myBlue.800'
|
||||
color: 'blue.700'
|
||||
}}
|
||||
onClick={() => getFileAndOpen(props.href)}
|
||||
/>
|
||||
@@ -131,7 +131,7 @@ const Markdown = ({ source, isChatting = false }: { source: string; isChatting?:
|
||||
return (
|
||||
<ReactMarkdown
|
||||
className={`markdown ${styles.markdown}
|
||||
${isChatting ? (source === '' ? styles.waitingAnimation : styles.animation) : ''}
|
||||
${isChatting ? `${formatSource ? styles.waitingAnimation : styles.animation}` : ''}
|
||||
`}
|
||||
remarkPlugins={[RemarkGfm, RemarkMath, RemarkBreaks]}
|
||||
rehypePlugins={[RehypeKatex]}
|
||||
|
@@ -41,7 +41,7 @@ const MyMenu = ({ width, offset = [0, 10], Button, menuList }: Props) => {
|
||||
e.stopPropagation();
|
||||
item.onClick && item.onClick();
|
||||
}}
|
||||
color={item.isActive ? 'myBlue.600' : ''}
|
||||
color={item.isActive ? 'blue.500' : ''}
|
||||
whiteSpace={'pre-wrap'}
|
||||
>
|
||||
{item.child}
|
||||
|
@@ -32,7 +32,7 @@ const PromptTemplate = ({
|
||||
cursor={'pointer'}
|
||||
{...(item.title === selectTemplateTitle?.title
|
||||
? {
|
||||
bg: 'myBlue.100'
|
||||
bg: 'blue.50'
|
||||
}
|
||||
: {})}
|
||||
onClick={() => setSelectTemplateTitle(item)}
|
||||
|
@@ -62,7 +62,7 @@ const MySelect = (
|
||||
{...(isOpen
|
||||
? {
|
||||
boxShadow: '0px 0px 4px #A8DBFF',
|
||||
borderColor: 'myBlue.600'
|
||||
borderColor: 'blue.500'
|
||||
}
|
||||
: {})}
|
||||
{...props}
|
||||
@@ -93,7 +93,7 @@ const MySelect = (
|
||||
{...menuItemStyles}
|
||||
{...(value === item.value
|
||||
? {
|
||||
color: 'myBlue.600',
|
||||
color: 'blue.500',
|
||||
bg: 'myWhite.300'
|
||||
}
|
||||
: {})}
|
||||
|
@@ -44,9 +44,9 @@ const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) =>
|
||||
alignItems={'center'}
|
||||
{...(activeId === item.id
|
||||
? {
|
||||
bg: ' myBlue.300 !important',
|
||||
bg: ' blue.200 !important',
|
||||
fontWeight: 'bold',
|
||||
color: 'myBlue.700 ',
|
||||
color: 'blue.600 ',
|
||||
cursor: 'default'
|
||||
}
|
||||
: {
|
||||
|
@@ -69,7 +69,7 @@ const MySlider = ({
|
||||
<SliderMark
|
||||
value={value}
|
||||
textAlign="center"
|
||||
bg="myBlue.600"
|
||||
bg="blue.500"
|
||||
color="white"
|
||||
px={1}
|
||||
minW={'18px'}
|
||||
@@ -95,9 +95,9 @@ const MySlider = ({
|
||||
right: '-3px'
|
||||
}}
|
||||
>
|
||||
<SliderFilledTrack bg={'myBlue.600'} />
|
||||
<SliderFilledTrack bg={'blue.500'} />
|
||||
</SliderTrack>
|
||||
<SliderThumb border={'3px solid'} borderColor={'myBlue.600'}></SliderThumb>
|
||||
<SliderThumb border={'3px solid'} borderColor={'blue.500'}></SliderThumb>
|
||||
</Slider>
|
||||
);
|
||||
};
|
||||
|
@@ -55,10 +55,10 @@ const Tabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
|
||||
whiteSpace={'nowrap'}
|
||||
{...(activeId === item.id
|
||||
? {
|
||||
color: 'myBlue.700',
|
||||
color: 'blue.600',
|
||||
cursor: 'default',
|
||||
fontWeight: 'bold',
|
||||
borderBottomColor: 'myBlue.700'
|
||||
borderBottomColor: 'blue.600'
|
||||
}
|
||||
: {
|
||||
cursor: 'pointer'
|
||||
|
@@ -10,9 +10,9 @@ const Tag = ({ children, colorSchema = 'blue', ...props }: Props) => {
|
||||
const theme = useMemo(() => {
|
||||
const map = {
|
||||
blue: {
|
||||
borderColor: 'myBlue.600',
|
||||
borderColor: 'blue.500',
|
||||
bg: '#F2FBFF',
|
||||
color: 'myBlue.700'
|
||||
color: 'blue.600'
|
||||
},
|
||||
green: {
|
||||
borderColor: '#67c13b',
|
||||
|
@@ -43,13 +43,13 @@ const MyRadio = ({
|
||||
position={'relative'}
|
||||
{...(value === item.value
|
||||
? {
|
||||
borderColor: 'myBlue.500',
|
||||
bg: 'myBlue.100'
|
||||
borderColor: 'blue.400',
|
||||
bg: 'blue.50'
|
||||
}
|
||||
: {
|
||||
bg: 'myWhite.300',
|
||||
_hover: {
|
||||
borderColor: 'myBlue.500'
|
||||
borderColor: 'blue.400'
|
||||
}
|
||||
})}
|
||||
_after={{
|
||||
@@ -66,7 +66,7 @@ const MyRadio = ({
|
||||
...(value === item.value
|
||||
? {
|
||||
border: '5px solid',
|
||||
borderColor: 'myBlue.700'
|
||||
borderColor: 'blue.600'
|
||||
}
|
||||
: {
|
||||
border: '2px solid',
|
||||
|
@@ -52,7 +52,7 @@ const TagTextarea = ({ defaultValues, onUpdate, ...props }: Props) => {
|
||||
bg={'myWhite.600'}
|
||||
{...(focus && {
|
||||
boxShadow: '0px 0px 4px #A8DBFF',
|
||||
borderColor: 'myBlue.600'
|
||||
borderColor: 'blue.500'
|
||||
})}
|
||||
{...props}
|
||||
onClick={() => {
|
||||
|
@@ -3,8 +3,7 @@ import MyModal from '@/components/MyModal';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import React, { Dispatch, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import ParentPaths from '@/components/common/ParentPaths';
|
||||
|
||||
type PathItemType = {
|
||||
@@ -53,13 +52,12 @@ const DatasetSelectContainer = ({
|
||||
}
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
h={'80vh'}
|
||||
w={'100%'}
|
||||
maxW={['90vw', '900px']}
|
||||
isCentered
|
||||
>
|
||||
<Flex flexDirection={'column'} h={'90vh'}>
|
||||
<Box flex={'1 0 0'}>{children}</Box>
|
||||
</Flex>
|
||||
{children}
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
@@ -66,7 +66,7 @@ const AIChatSettingsModal = ({
|
||||
fontSize: ['sm', 'md']
|
||||
};
|
||||
const selectTemplateBtn: BoxProps = {
|
||||
color: 'myBlue.600',
|
||||
color: 'blue.500',
|
||||
cursor: 'pointer'
|
||||
};
|
||||
|
||||
|
@@ -82,7 +82,7 @@ export const DatasetSelectModal = ({
|
||||
p={3}
|
||||
border={theme.borders.base}
|
||||
boxShadow={'sm'}
|
||||
bg={'myBlue.300'}
|
||||
bg={'blue.200'}
|
||||
>
|
||||
<Flex alignItems={'center'} h={'38px'}>
|
||||
<Avatar src={item.avatar} w={['24px', '28px']}></Avatar>
|
||||
|
@@ -12,10 +12,7 @@ import type {
|
||||
FlowModuleItemType,
|
||||
FlowModuleTemplateType
|
||||
} from '@fastgpt/global/core/module/type.d';
|
||||
import type {
|
||||
FlowNodeOutputTargetItemType,
|
||||
FlowNodeChangeProps
|
||||
} from '@fastgpt/global/core/module/node/type';
|
||||
import type { FlowNodeChangeProps } from '@fastgpt/global/core/module/node/type';
|
||||
import React, {
|
||||
type SetStateAction,
|
||||
type Dispatch,
|
||||
|
@@ -60,7 +60,7 @@ const SelectAppModal = ({
|
||||
cursor={'pointer'}
|
||||
{...(selectedApps.includes(app._id)
|
||||
? {
|
||||
bg: 'myBlue.200',
|
||||
bg: 'blue.100',
|
||||
onClick: () => {
|
||||
setSelectedApps(selectedApps.filter((e) => e !== app._id));
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ const ButtonEdge = (
|
||||
targetPosition
|
||||
});
|
||||
|
||||
const edgeStyle = {
|
||||
const edgeStyle: React.CSSProperties = {
|
||||
...style,
|
||||
...(selected
|
||||
? {
|
||||
@@ -63,6 +63,7 @@ const ButtonEdge = (
|
||||
color={'black'}
|
||||
cursor={'pointer'}
|
||||
border={'1px solid #fff'}
|
||||
zIndex={selected ? 1000 : 0}
|
||||
_hover={{
|
||||
boxShadow: '0 0 6px 2px rgba(0, 0, 0, 0.08)'
|
||||
}}
|
||||
@@ -71,7 +72,7 @@ const ButtonEdge = (
|
||||
<MyIcon
|
||||
name="closeSolid"
|
||||
w={'100%'}
|
||||
color={selected ? 'myBlue.800' : 'myGray.500'}
|
||||
color={selected ? 'blue.700' : 'myGray.500'}
|
||||
></MyIcon>
|
||||
</Flex>
|
||||
</EdgeLabelRenderer>
|
||||
|
@@ -65,7 +65,7 @@ const NodePluginInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
mr={3}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
_hover={{ color: 'blue.500' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
inputType: item.type,
|
||||
|
@@ -84,7 +84,7 @@ const NodePluginOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
_hover={{ color: 'blue.500' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
inputType: item.type,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { EditNodeFieldType, FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
onChangeNode,
|
||||
useFlowProviderStore,
|
||||
@@ -81,7 +81,7 @@ const InputLabel = ({
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
_hover={{ color: 'blue.500' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
inputType: type,
|
||||
|
@@ -41,7 +41,7 @@ const OutputLabel = ({
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
mr={3}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
_hover={{ color: 'blue.500' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
key: outputKey,
|
||||
|
@@ -96,8 +96,10 @@ const Container = React.memo(function Container(props: Props) {
|
||||
minZoom={0.1}
|
||||
maxZoom={1.5}
|
||||
defaultEdgeOptions={{
|
||||
animated: true
|
||||
animated: true,
|
||||
zIndex: 0
|
||||
}}
|
||||
elevateEdgesOnSelect
|
||||
connectionLineStyle={{ strokeWidth: 2, stroke: '#5A646Es' }}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
|
@@ -88,7 +88,7 @@ const ApiKeyTable = ({ tips, appId }: { tips: string; appId?: string }) => {
|
||||
href={feConfigs.openAPIDocUrl || getDocPath('/docs/development/openapi')}
|
||||
target={'_blank'}
|
||||
ml={1}
|
||||
color={'myBlue.600'}
|
||||
color={'blue.500'}
|
||||
>
|
||||
查看文档
|
||||
</Link>
|
||||
|
@@ -156,7 +156,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
<MyIcon
|
||||
name={'common/addCircleLight'}
|
||||
w={['16px', '18px']}
|
||||
color={'myBlue.600'}
|
||||
color={'blue.500'}
|
||||
cursor={'pointer'}
|
||||
/>
|
||||
}
|
||||
@@ -177,7 +177,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
gap={3}
|
||||
{...(userInfo?.team?.teamId === team.teamId
|
||||
? {
|
||||
bg: 'myBlue.300'
|
||||
bg: 'blue.200'
|
||||
}
|
||||
: {
|
||||
_hover: {
|
||||
@@ -198,7 +198,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
{team.teamName}
|
||||
</Box>
|
||||
{userInfo?.team?.teamId === team.teamId ? (
|
||||
<MyIcon name={'common/tickFill'} w={'16px'} color={'myBlue.600'} />
|
||||
<MyIcon name={'common/tickFill'} w={'16px'} color={'blue.500'} />
|
||||
) : (
|
||||
<Button size={'xs'} variant={'base'} onClick={() => onSwitchTeam(team.teamId)}>
|
||||
{t('user.team.Check Team')}
|
||||
@@ -235,7 +235,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
ml={2}
|
||||
cursor={'pointer'}
|
||||
_hover={{
|
||||
color: 'myBlue.600'
|
||||
color: 'blue.500'
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!userInfo?.team) return;
|
||||
@@ -260,7 +260,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
size="sm"
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<MyIcon name={'common/inviteLight'} w={'14px'} color={'myBlue.600'} />}
|
||||
leftIcon={<MyIcon name={'common/inviteLight'} w={'14px'} color={'blue.500'} />}
|
||||
onClick={() => {
|
||||
if (userInfo.team.maxSize <= members.length) {
|
||||
toast({
|
||||
@@ -283,11 +283,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={
|
||||
<MyIcon
|
||||
name={'support/account/loginoutLight'}
|
||||
w={'14px'}
|
||||
color={'myBlue.600'}
|
||||
/>
|
||||
<MyIcon name={'support/account/loginoutLight'} w={'14px'} color={'blue.500'} />
|
||||
}
|
||||
onClick={() => {
|
||||
openLeaveConfirm(() => onLeaveTeam(userInfo?.team?.teamId))();
|
||||
@@ -339,7 +335,7 @@ const TeamManageModal = ({ onClose }: { onClose: () => void }) => {
|
||||
name={'edit'}
|
||||
cursor={'pointer'}
|
||||
w="14px"
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
_hover={{ color: 'blue.500' }}
|
||||
/>
|
||||
</MenuButton>
|
||||
}
|
||||
|
10
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -12,6 +12,7 @@ export type GetChatSpeechProps = {
|
||||
export type InitChatProps = {
|
||||
appId?: string;
|
||||
chatId?: string;
|
||||
loadCustomFeedbacks?: boolean;
|
||||
};
|
||||
export type InitOutLinkChatProps = {
|
||||
chatId?: string;
|
||||
@@ -74,5 +75,14 @@ export type DeleteChatItemProps = {
|
||||
};
|
||||
|
||||
export type AdminUpdateFeedbackParams = AdminFbkType & {
|
||||
appId: string;
|
||||
chatId: string;
|
||||
chatItemId: string;
|
||||
};
|
||||
|
||||
export type CloseCustomFeedbackParams = {
|
||||
appId: string;
|
||||
chatId: string;
|
||||
chatItemId: string;
|
||||
index: number;
|
||||
};
|
||||
|
@@ -288,7 +288,7 @@ async function initPgData() {
|
||||
const limit = 10;
|
||||
// add column
|
||||
try {
|
||||
await Promise.all([
|
||||
await Promise.allSettled([
|
||||
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ADD COLUMN team_id VARCHAR(50);`),
|
||||
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ADD COLUMN tmb_id VARCHAR(50);`),
|
||||
PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN user_id DROP NOT NULL;`)
|
||||
|
@@ -2,79 +2,15 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import multer from 'multer';
|
||||
import path from 'path';
|
||||
import { uploadFile } from '@fastgpt/service/common/file/gridfs/controller';
|
||||
import { BucketNameEnum } from '@fastgpt/global/common/file/constants';
|
||||
|
||||
const nanoid = customAlphabet('1234567890abcdef', 12);
|
||||
|
||||
type FileType = {
|
||||
fieldname: string;
|
||||
originalname: string;
|
||||
encoding: string;
|
||||
mimetype: string;
|
||||
filename: string;
|
||||
path: string;
|
||||
size: number;
|
||||
};
|
||||
import { getUploadModel } from '@fastgpt/service/common/file/upload/multer';
|
||||
|
||||
/**
|
||||
* Creates the multer uploader
|
||||
*/
|
||||
const maxSize = 500 * 1024 * 1024;
|
||||
class UploadModel {
|
||||
uploader = multer({
|
||||
limits: {
|
||||
fieldSize: maxSize
|
||||
},
|
||||
preservePath: true,
|
||||
storage: multer.diskStorage({
|
||||
filename: (_req, file, cb) => {
|
||||
const { ext } = path.parse(decodeURIComponent(file.originalname));
|
||||
cb(null, nanoid() + ext);
|
||||
}
|
||||
})
|
||||
}).any();
|
||||
|
||||
async doUpload(req: NextApiRequest, res: NextApiResponse) {
|
||||
return new Promise<{
|
||||
files: FileType[];
|
||||
bucketName: `${BucketNameEnum}`;
|
||||
metadata: Record<string, any>;
|
||||
}>((resolve, reject) => {
|
||||
// @ts-ignore
|
||||
this.uploader(req, res, (error) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
resolve({
|
||||
...req.body,
|
||||
files:
|
||||
// @ts-ignore
|
||||
req.files?.map((file) => ({
|
||||
...file,
|
||||
originalname: decodeURIComponent(file.originalname)
|
||||
})) || [],
|
||||
metadata: (() => {
|
||||
if (!req.body?.metadata) return {};
|
||||
try {
|
||||
return JSON.parse(req.body.metadata);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
return {};
|
||||
}
|
||||
})()
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const upload = new UploadModel();
|
||||
const upload = getUploadModel({
|
||||
maxSize: 500 * 1024 * 1024
|
||||
});
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
@@ -82,6 +18,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
const { userId, teamId, tmbId } = await authCert({ req, authToken: true });
|
||||
const { files, bucketName, metadata } = await upload.doUpload(req, res);
|
||||
|
||||
if (!bucketName) {
|
||||
throw new Error('bucketName is empty');
|
||||
}
|
||||
|
||||
const upLoadResults = await Promise.all(
|
||||
files.map((file) =>
|
||||
uploadFile({
|
||||
|
@@ -4,7 +4,7 @@ import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { UrlFetchParams, UrlFetchResponse } from '@fastgpt/global/common/file/api.d';
|
||||
import { urlsFetch } from '@fastgpt/global/common/file/tools';
|
||||
import { urlsFetch } from '@fastgpt/service/common/string/cheerio';
|
||||
|
||||
const fetchContent = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
try {
|
||||
|
@@ -53,7 +53,7 @@ function simpleChatTemplate({
|
||||
key: 'userChatInput',
|
||||
type: 'systemInput',
|
||||
label: '用户问题',
|
||||
connected: true
|
||||
connected: false
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
@@ -95,7 +95,7 @@ function simpleChatTemplate({
|
||||
label: '对话模型',
|
||||
required: true,
|
||||
value: formData.aiSettings.model,
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'temperature',
|
||||
@@ -115,7 +115,7 @@ function simpleChatTemplate({
|
||||
value: 10
|
||||
}
|
||||
],
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'maxToken',
|
||||
@@ -135,7 +135,7 @@ function simpleChatTemplate({
|
||||
value: 4000
|
||||
}
|
||||
],
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'isResponseAnswerText',
|
||||
@@ -143,21 +143,21 @@ function simpleChatTemplate({
|
||||
label: '返回AI内容',
|
||||
valueType: 'boolean',
|
||||
value: true,
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'quoteTemplate',
|
||||
type: 'hidden',
|
||||
label: '引用内容模板',
|
||||
valueType: 'string',
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
type: 'hidden',
|
||||
label: '引用内容提示词',
|
||||
valueType: 'string',
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'aiSettings',
|
||||
@@ -176,7 +176,7 @@ function simpleChatTemplate({
|
||||
placeholder:
|
||||
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
|
||||
value: formData.aiSettings.systemPrompt,
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'quoteQA',
|
||||
@@ -191,7 +191,7 @@ function simpleChatTemplate({
|
||||
type: 'target',
|
||||
label: 'core.module.input.label.chat history',
|
||||
valueType: 'chatHistory',
|
||||
connected: true,
|
||||
connected: false,
|
||||
value: 8
|
||||
},
|
||||
{
|
||||
@@ -292,21 +292,21 @@ function datasetTemplate({
|
||||
value: formData.dataset.datasets,
|
||||
type: FlowNodeInputTypeEnum.custom,
|
||||
label: '关联的知识库',
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'similarity',
|
||||
value: 0.1,
|
||||
type: FlowNodeInputTypeEnum.slider,
|
||||
label: '相关度',
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'limit',
|
||||
value: 2000,
|
||||
type: FlowNodeInputTypeEnum.slider,
|
||||
label: '单次搜索上限',
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'switch',
|
||||
@@ -403,7 +403,7 @@ function datasetTemplate({
|
||||
label: '对话模型',
|
||||
required: true,
|
||||
value: formData.aiSettings.model,
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'temperature',
|
||||
@@ -423,7 +423,7 @@ function datasetTemplate({
|
||||
value: 10
|
||||
}
|
||||
],
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'maxToken',
|
||||
@@ -443,7 +443,7 @@ function datasetTemplate({
|
||||
value: 4000
|
||||
}
|
||||
],
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'isResponseAnswerText',
|
||||
@@ -451,21 +451,21 @@ function datasetTemplate({
|
||||
label: '返回AI内容',
|
||||
valueType: 'boolean',
|
||||
value: true,
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'quoteTemplate',
|
||||
type: 'hidden',
|
||||
label: '引用内容模板',
|
||||
valueType: 'string',
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
type: 'hidden',
|
||||
label: '引用内容提示词',
|
||||
valueType: 'string',
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'aiSettings',
|
||||
@@ -484,7 +484,7 @@ function datasetTemplate({
|
||||
placeholder:
|
||||
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}',
|
||||
value: formData.aiSettings.systemPrompt,
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'quoteQA',
|
||||
@@ -499,7 +499,7 @@ function datasetTemplate({
|
||||
type: 'target',
|
||||
label: 'core.module.input.label.chat history',
|
||||
valueType: 'chatHistory',
|
||||
connected: true,
|
||||
connected: false,
|
||||
value: 8
|
||||
},
|
||||
{
|
||||
|
@@ -39,49 +39,49 @@ function chatModelInput(formData: AppSimpleEditFormType): FlowNodeInputItemType[
|
||||
value: formData.aiSettings.model,
|
||||
type: 'custom',
|
||||
label: '对话模型',
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'temperature',
|
||||
value: formData.aiSettings.temperature,
|
||||
type: 'slider',
|
||||
label: '温度',
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'maxToken',
|
||||
value: formData.aiSettings.maxToken,
|
||||
type: 'custom',
|
||||
label: '回复上限',
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'systemPrompt',
|
||||
value: formData.aiSettings.systemPrompt || '',
|
||||
type: 'textarea',
|
||||
label: '系统提示词',
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: ModuleInputKeyEnum.aiChatIsResponseText,
|
||||
value: true,
|
||||
type: 'hidden',
|
||||
label: '返回AI内容',
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'quoteTemplate',
|
||||
value: formData.aiSettings.quoteTemplate || '',
|
||||
type: 'hidden',
|
||||
label: '引用内容模板',
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
value: formData.aiSettings.quotePrompt || '',
|
||||
type: 'hidden',
|
||||
label: '引用内容提示词',
|
||||
connected: true
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'switch',
|
||||
@@ -93,7 +93,7 @@ function chatModelInput(formData: AppSimpleEditFormType): FlowNodeInputItemType[
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: 'core.module.input.label.chat history',
|
||||
connected: true,
|
||||
connected: false,
|
||||
value: 6
|
||||
},
|
||||
{
|
||||
@@ -118,9 +118,9 @@ function simpleChatTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
inputs: [
|
||||
{
|
||||
key: 'userChatInput',
|
||||
connected: true,
|
||||
connected: false,
|
||||
label: '用户问题',
|
||||
type: 'target'
|
||||
type: 'systemInput'
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
@@ -179,8 +179,8 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
{
|
||||
key: 'userChatInput',
|
||||
label: '用户问题',
|
||||
type: 'target',
|
||||
connected: true
|
||||
type: 'systemInput',
|
||||
connected: false
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
@@ -319,7 +319,7 @@ function datasetTemplate(formData: AppSimpleEditFormType): ModuleItemType[] {
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
label: '回复的内容',
|
||||
connected: true
|
||||
connected: false
|
||||
}
|
||||
],
|
||||
outputs: [],
|
||||
|
@@ -78,12 +78,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
}
|
||||
},
|
||||
robotBadFeedbackCount: {
|
||||
customFeedbacksCount: {
|
||||
$size: {
|
||||
$filter: {
|
||||
input: '$chatitems',
|
||||
as: 'item',
|
||||
cond: { $ifNull: ['$$item.robotBadFeedback', false] }
|
||||
cond: { $gt: [{ $size: { $ifNull: ['$$item.customFeedbacks', []] } }, 0] }
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -102,7 +102,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
$sort: {
|
||||
userBadFeedbackCount: -1,
|
||||
userGoodFeedbackCount: -1,
|
||||
robotBadFeedbackCount: -1,
|
||||
customFeedbacksCount: -1,
|
||||
updateTime: -1
|
||||
}
|
||||
},
|
||||
@@ -118,7 +118,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
messageCount: { $size: '$chatitems' },
|
||||
userGoodFeedbackCount: 1,
|
||||
userBadFeedbackCount: 1,
|
||||
robotBadFeedbackCount: 1,
|
||||
customFeedbacksCount: 1,
|
||||
markCount: 1
|
||||
}
|
||||
}
|
||||
|
@@ -4,22 +4,29 @@ import { connectToDatabase } from '@/service/mongo';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import type { AdminUpdateFeedbackParams } from '@/global/core/chat/api.d';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { autChatCrud } from '@/service/support/permission/auth/chat';
|
||||
|
||||
/* 初始化我的聊天框,需要身份验证 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { chatItemId, datasetId, dataId, q, a } = req.body as AdminUpdateFeedbackParams;
|
||||
const { appId, chatId, chatItemId, datasetId, dataId, q, a } =
|
||||
req.body as AdminUpdateFeedbackParams;
|
||||
|
||||
if (!chatItemId || !datasetId || !dataId || !q) {
|
||||
throw new Error('missing parameter');
|
||||
}
|
||||
|
||||
const { userId } = await authCert({ req, authToken: true });
|
||||
await autChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
chatId,
|
||||
per: 'r'
|
||||
});
|
||||
|
||||
await MongoChatItem.findOneAndUpdate(
|
||||
{
|
||||
userId,
|
||||
dataId: chatItemId
|
||||
},
|
||||
{
|
||||
|
47
projects/app/src/pages/api/core/chat/feedback/closeCustom.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import type {
|
||||
AdminUpdateFeedbackParams,
|
||||
CloseCustomFeedbackParams
|
||||
} from '@/global/core/chat/api.d';
|
||||
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
|
||||
import { autChatCrud } from '@/service/support/permission/auth/chat';
|
||||
|
||||
/* 初始化我的聊天框,需要身份验证 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { appId, chatId, chatItemId, index } = req.body as CloseCustomFeedbackParams;
|
||||
|
||||
if (!chatItemId || !appId || !chatId || !chatItemId) {
|
||||
throw new Error('missing parameter');
|
||||
}
|
||||
|
||||
await autChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
chatId,
|
||||
per: 'r'
|
||||
});
|
||||
await authCert({ req, authToken: true });
|
||||
|
||||
await MongoChatItem.findOneAndUpdate(
|
||||
{ dataId: chatItemId },
|
||||
{ $unset: { [`customFeedbacks.${index}`]: 1 } }
|
||||
);
|
||||
await MongoChatItem.findOneAndUpdate(
|
||||
{ dataId: chatItemId },
|
||||
{ $pull: { customFeedbacks: null } }
|
||||
);
|
||||
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
@@ -14,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
try {
|
||||
await connectToDatabase();
|
||||
|
||||
let { appId, chatId } = req.query as InitChatProps;
|
||||
let { appId, chatId, loadCustomFeedbacks } = req.query as InitChatProps;
|
||||
|
||||
if (!appId) {
|
||||
return jsonRes(res, {
|
||||
@@ -43,7 +43,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const { history } = await getChatItems({
|
||||
chatId,
|
||||
limit: 30,
|
||||
field: `dataId obj value adminFeedback userBadFeedback userGoodFeedback robotBadFeedback ${ModuleOutputKeyEnum.responseData}`
|
||||
field: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${
|
||||
ModuleOutputKeyEnum.responseData
|
||||
} ${loadCustomFeedbacks ? 'customFeedbacks' : ''}`
|
||||
});
|
||||
|
||||
jsonRes<InitChatResponse>(res, {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { HttpBodyType } from '@fastgpt/global/core/module/api.d';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { authRequestFromLocal } from '@fastgpt/service/support/permission/auth/common';
|
||||
|
||||
type Props = HttpBodyType<{
|
||||
input: string;
|
||||
@@ -13,6 +14,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
data: { input, rule = '' }
|
||||
} = req.body as Props;
|
||||
|
||||
await authRequestFromLocal({ req });
|
||||
|
||||
const result = (() => {
|
||||
if (typeof input === 'string') {
|
||||
const defaultReg: any[] = [
|
||||
|
52
projects/app/src/pages/api/plugins/customFeedback/index.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { HttpBodyType } from '@fastgpt/global/core/module/api.d';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { addCustomFeedbacks } from '@fastgpt/service/core/chat/controller';
|
||||
import { authRequestFromLocal } from '@fastgpt/service/support/permission/auth/common';
|
||||
|
||||
type Props = HttpBodyType<{
|
||||
defaultFeedback: string;
|
||||
customFeedback: string;
|
||||
}>;
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const {
|
||||
chatId,
|
||||
responseChatItemId: chatItemId,
|
||||
data: { defaultFeedback, customFeedback }
|
||||
} = req.body as Props;
|
||||
|
||||
await authRequestFromLocal({ req });
|
||||
|
||||
const feedback = customFeedback || defaultFeedback;
|
||||
|
||||
if (!feedback) {
|
||||
return res.json({
|
||||
response: ''
|
||||
});
|
||||
}
|
||||
|
||||
// wait the chat finish
|
||||
setTimeout(() => {
|
||||
addCustomFeedbacks({
|
||||
chatId,
|
||||
chatItemId,
|
||||
feedbacks: [feedback]
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
if (!chatId || !chatItemId) {
|
||||
return res.json({
|
||||
response: `\\n\\n**自动反馈调试**: ${feedback}\\n\\n`
|
||||
});
|
||||
}
|
||||
|
||||
return res.json({
|
||||
response: ''
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
res.status(500).send(getErrText(err));
|
||||
}
|
||||
}
|
@@ -2,6 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { HttpBodyType } from '@fastgpt/global/core/module/api.d';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import { authRequestFromLocal } from '@fastgpt/service/support/permission/auth/common';
|
||||
|
||||
type Props = HttpBodyType<{
|
||||
text: string;
|
||||
@@ -14,6 +15,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
data: { text, ...obj }
|
||||
} = req.body as Props;
|
||||
|
||||
await authRequestFromLocal({ req });
|
||||
|
||||
const textResult = replaceVariable(text, obj);
|
||||
|
||||
res.json({
|
||||
|
@@ -195,12 +195,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
// get and concat history
|
||||
const { history } = await getChatItems({ chatId, limit: 30, field: `dataId obj value` });
|
||||
const concatHistories = history.concat(chatMessages);
|
||||
const responseChatItemId: string | undefined = messages[messages.length - 1].dataId;
|
||||
|
||||
/* start flow controller */
|
||||
const { responseData, answerText } = await dispatchModules({
|
||||
res,
|
||||
appId: String(app._id),
|
||||
chatId,
|
||||
responseChatItemId,
|
||||
modules: app.modules,
|
||||
user,
|
||||
teamId: user.team.teamId,
|
||||
@@ -237,7 +239,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
content: [
|
||||
question,
|
||||
{
|
||||
dataId: messages[messages.length - 1].dataId,
|
||||
dataId: responseChatItemId,
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: answerText,
|
||||
responseData
|
||||
|
@@ -29,6 +29,7 @@ import Tag from '@/components/Tag';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import DateRangePicker, { type DateRangeType } from '@/components/DateRangePicker';
|
||||
import { addDays } from 'date-fns';
|
||||
import MyBox from '@/components/common/MyBox';
|
||||
|
||||
const Logs = ({ appId }: { appId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -96,6 +97,7 @@ const Logs = ({ appId }: { appId: string }) => {
|
||||
<Th>{t('app.Logs Title')}</Th>
|
||||
<Th>{t('app.Logs Message Total')}</Th>
|
||||
<Th>{t('app.Feedback Count')}</Th>
|
||||
<Th>{t('core.app.feedback.Custom feedback')}</Th>
|
||||
<Th>{t('app.Mark Count')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
@@ -158,7 +160,9 @@ const Logs = ({ appId }: { appId: string }) => {
|
||||
{item.userBadFeedbackCount}
|
||||
</Flex>
|
||||
)}
|
||||
{!item?.userGoodFeedbackCount && !item?.userBadFeedbackCount && <>-</>}
|
||||
</Td>
|
||||
<Td>{item.customFeedbacksCount || '-'}</Td>
|
||||
<Td>{item.markCount}</Td>
|
||||
</Tr>
|
||||
))}
|
||||
@@ -208,7 +212,7 @@ const Logs = ({ appId }: { appId: string }) => {
|
||||
|
||||
export default React.memo(Logs);
|
||||
|
||||
function DetailLogsModal({
|
||||
const DetailLogsModal = ({
|
||||
appId,
|
||||
chatId,
|
||||
onClose
|
||||
@@ -216,14 +220,14 @@ function DetailLogsModal({
|
||||
appId: string;
|
||||
chatId: string;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
}) => {
|
||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||
const { isPc } = useSystemStore();
|
||||
const theme = useTheme();
|
||||
|
||||
const { data: chat } = useQuery(
|
||||
const { data: chat, isFetching } = useQuery(
|
||||
['getChatDetail', chatId],
|
||||
() => getInitChatInfo({ appId, chatId }),
|
||||
() => getInitChatInfo({ appId, chatId, loadCustomFeedbacks: true }),
|
||||
{
|
||||
onSuccess(res) {
|
||||
const history = res.history.map((item) => ({
|
||||
@@ -249,9 +253,11 @@ function DetailLogsModal({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
zIndex={3}
|
||||
<MyBox
|
||||
isLoading={isFetching}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
zIndex={3}
|
||||
position={['fixed', 'absolute']}
|
||||
top={[0, '2%']}
|
||||
right={0}
|
||||
@@ -325,8 +331,8 @@ function DetailLogsModal({
|
||||
chatId={chatId}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyBox>
|
||||
<Box zIndex={2} position={'fixed'} top={0} left={0} bottom={0} right={0} onClick={onClose} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@@ -189,7 +189,7 @@ const SelectUsingWayModal = ({ share, onClose }: { share: OutLinkSchema; onClose
|
||||
w={'16px'}
|
||||
color={'myGray.600'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
_hover={{ color: 'blue.500' }}
|
||||
onClick={() => {
|
||||
copyData(wayMap[getValues('usingWay')].code);
|
||||
}}
|
||||
|
@@ -77,7 +77,7 @@ const Share = ({ appId }: { appId: string }) => {
|
||||
</Box>
|
||||
<Button
|
||||
variant={'base'}
|
||||
colorScheme={'myBlue'}
|
||||
colorScheme={'blue'}
|
||||
size={['sm', 'md']}
|
||||
{...(shareChatList.length >= 10
|
||||
? {
|
||||
|
@@ -548,7 +548,7 @@ function Settings({ appId }: { appId: string }) {
|
||||
mt={2}
|
||||
px={5}
|
||||
py={4}
|
||||
bg={'myBlue.100'}
|
||||
bg={'blue.50'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Flex alignItems={'center'} py={2}>
|
||||
|
@@ -139,7 +139,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
>
|
||||
<IconButton
|
||||
mr={3}
|
||||
icon={<MyIcon name={'backFill'} w={'18px'} color={'myBlue.600'} />}
|
||||
icon={<MyIcon name={'backFill'} w={'18px'} color={'blue.500'} />}
|
||||
bg={'white'}
|
||||
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
|
||||
h={'28px'}
|
||||
|
@@ -61,7 +61,7 @@ const ShareModelList = ({
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
color={model.isCollection ? 'myBlue.700' : 'blackAlpha.700'}
|
||||
color={model.isCollection ? 'blue.600' : 'blackAlpha.700'}
|
||||
onClick={() => onclickCollection(model._id)}
|
||||
>
|
||||
<MyIcon
|
||||
|
@@ -154,7 +154,7 @@ const ChatHistorySlider = ({
|
||||
variant={'base'}
|
||||
flex={1}
|
||||
h={'100%'}
|
||||
color={'myBlue.700'}
|
||||
color={'blue.600'}
|
||||
borderRadius={'xl'}
|
||||
leftIcon={<MyIcon name={'chat'} w={'16px'} />}
|
||||
overflow={'hidden'}
|
||||
@@ -201,8 +201,8 @@ const ChatHistorySlider = ({
|
||||
bg={item.top ? '#E6F6F6 !important' : ''}
|
||||
{...(item.id === activeChatId
|
||||
? {
|
||||
backgroundColor: 'myBlue.100 !important',
|
||||
color: 'myBlue.700'
|
||||
backgroundColor: 'blue.50 !important',
|
||||
color: 'blue.600'
|
||||
}
|
||||
: {
|
||||
onClick: () => {
|
||||
@@ -290,8 +290,8 @@ const ChatHistorySlider = ({
|
||||
alignItems={'center'}
|
||||
{...(item._id === appId
|
||||
? {
|
||||
backgroundColor: 'myBlue.100 !important',
|
||||
color: 'myBlue.700'
|
||||
backgroundColor: 'blue.50 !important',
|
||||
color: 'blue.600'
|
||||
}
|
||||
: {
|
||||
onClick: () => {
|
||||
@@ -325,7 +325,7 @@ const ChatHistorySlider = ({
|
||||
>
|
||||
<IconButton
|
||||
mr={3}
|
||||
icon={<MyIcon name={'backFill'} w={'18px'} color={'myBlue.600'} />}
|
||||
icon={<MyIcon name={'backFill'} w={'18px'} color={'blue.500'} />}
|
||||
bg={'white'}
|
||||
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
|
||||
h={'28px'}
|
||||
|
@@ -28,7 +28,7 @@ const SliderApps = ({ appId }: { appId: string }) => {
|
||||
>
|
||||
<IconButton
|
||||
mr={3}
|
||||
icon={<MyIcon name={'backFill'} w={'18px'} color={'myBlue.600'} />}
|
||||
icon={<MyIcon name={'backFill'} w={'18px'} color={'blue.500'} />}
|
||||
bg={'white'}
|
||||
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
|
||||
h={'28px'}
|
||||
|
@@ -311,7 +311,7 @@ const CollectionCard = () => {
|
||||
target="_blank"
|
||||
mr={2}
|
||||
textDecoration={'underline'}
|
||||
color={'myBlue.700'}
|
||||
color={'blue.600'}
|
||||
>
|
||||
{datasetDetail.websiteConfig.url}
|
||||
</Link>
|
||||
@@ -371,7 +371,7 @@ const CollectionCard = () => {
|
||||
Button={
|
||||
<MenuButton
|
||||
_hover={{
|
||||
color: 'myBlue.600'
|
||||
color: 'blue.500'
|
||||
}}
|
||||
fontSize={['sm', 'md']}
|
||||
>
|
||||
@@ -381,7 +381,7 @@ const CollectionCard = () => {
|
||||
py={2}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
bg={'myBlue.600'}
|
||||
bg={'blue.500'}
|
||||
overflow={'hidden'}
|
||||
color={'white'}
|
||||
h={['28px', '35px']}
|
||||
@@ -489,7 +489,7 @@ const CollectionCard = () => {
|
||||
data-drag-id={
|
||||
collection.type === DatasetCollectionTypeEnum.folder ? collection._id : undefined
|
||||
}
|
||||
bg={dragTargetId === collection._id ? 'myBlue.200' : ''}
|
||||
bg={dragTargetId === collection._id ? 'blue.100' : ''}
|
||||
userSelect={'none'}
|
||||
onDragStart={(e) => {
|
||||
setDragStartId(collection._id);
|
||||
@@ -579,7 +579,7 @@ const CollectionCard = () => {
|
||||
h={'22px'}
|
||||
borderRadius={'md'}
|
||||
_hover={{
|
||||
color: 'myBlue.600',
|
||||
color: 'blue.500',
|
||||
'& .icon': {
|
||||
bg: 'myGray.100'
|
||||
}
|
||||
|