perf: ery extension and fix filter same embedding result (#3833)

* perf: ery extension and fix filter same embedding result

* fix: extract node too long

* perf: ui

* perf: not chatId will auto save

* fix: laf

* fix: member load

* feat: add completions unstream error response

* feat: add completions unstream error response

* updat emodel provider
This commit is contained in:
Archer 2025-02-19 22:16:43 +08:00 committed by GitHub
parent 8604cbd021
commit 6762723b10
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 317 additions and 257 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

View File

@ -7,6 +7,12 @@ toc: true
weight: 852 weight: 852
--- ---
# 如何获取 AppId
可在应用详情的路径里获取 AppId。
![](/imgs/appid.png)
# 发起对话 # 发起对话
{{% alert icon="🤖 " context="success" %}} {{% alert icon="🤖 " context="success" %}}
@ -102,8 +108,8 @@ curl --location --request POST 'http://localhost:3000/api/v1/chat/completions' \
{{% alert context="info" %}} {{% alert context="info" %}}
- headers.Authorization: Bearer {{apikey}} - headers.Authorization: Bearer {{apikey}}
- chatId: string | undefined 。 - chatId: string | undefined 。
- 为 `undefined` 时(不传入),不使用 FastGpt 提供的上下文功能,完全通过传入的 messages 构建上下文。 不会将你的记录存储到数据库中,你也无法在记录汇总中查阅到。 - 为 `undefined` 时(不传入),不使用 FastGpt 提供的上下文功能,完全通过传入的 messages 构建上下文。
- 为`非空字符串`时,意味着使用 chatId 进行对话,自动从 FastGpt 数据库取历史记录,并使用 messages 数组最后一个内容作为用户问题。请自行确保 chatId 唯一长度小于250通常可以是自己系统的对话框ID。 - 为`非空字符串`时,意味着使用 chatId 进行对话,自动从 FastGpt 数据库取历史记录,并使用 messages 数组最后一个内容作为用户问题,其余 message 会被忽略。请自行确保 chatId 唯一长度小于250通常可以是自己系统的对话框ID。
- messages: 结构与 [GPT接口](https://platform.openai.com/docs/api-reference/chat/object) chat模式一致。 - messages: 结构与 [GPT接口](https://platform.openai.com/docs/api-reference/chat/object) chat模式一致。
- responseChatItemId: string | undefined 。如果传入,则会将该值作为本次对话的响应消息的 IDFastGPT 会自动将该 ID 存入数据库。请确保,在当前`chatId`下,`responseChatItemId`是唯一的。 - responseChatItemId: string | undefined 。如果传入,则会将该值作为本次对话的响应消息的 IDFastGPT 会自动将该 ID 存入数据库。请确保,在当前`chatId`下,`responseChatItemId`是唯一的。
- detail: 是否返回中间值(模块状态,响应的完整结果等),`stream模式`下会通过`event`进行区分,`非stream模式`结果保存在`responseData`中。 - detail: 是否返回中间值(模块状态,响应的完整结果等),`stream模式`下会通过`event`进行区分,`非stream模式`结果保存在`responseData`中。

View File

@ -1,6 +1,6 @@
--- ---
title: 'Api Key 使用与鉴权' title: 'OpenAPI 介绍'
description: 'FastGPT Api Key 使用与鉴权' description: 'FastGPT OpenAPI 介绍'
icon: 'key' icon: 'key'
draft: false draft: false
toc: true toc: true
@ -27,6 +27,7 @@ FastGPT 的 API Key **有 2 类**,一类是全局通用的 key (无法直接
| --------------------- | --------------------- | | --------------------- | --------------------- |
| ![](/imgs/fastgpt-api2.jpg) | ![](/imgs/fastgpt-api1.jpg) | | ![](/imgs/fastgpt-api2.jpg) | ![](/imgs/fastgpt-api1.jpg) |
## 基本配置 ## 基本配置
OpenAPI 中,所有的接口都通过 Header.Authorization 进行鉴权。 OpenAPI 中,所有的接口都通过 Header.Authorization 进行鉴权。

View File

@ -17,19 +17,37 @@ weight: 802
- 更新 fastgpt-pro 商业版镜像 tag: v4.8.22-alpha - 更新 fastgpt-pro 商业版镜像 tag: v4.8.22-alpha
- Sandbox 镜像无需更新 - Sandbox 镜像无需更新
### 3. 运行升级脚本
仅商业版,并提供 Saas 服务的用户需要运行该升级脚本。
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`{{host}} 替换成**FastGPT 域名**。
```bash
curl --location --request POST 'https://{{host}}/api/admin/initv4822' \
--header 'rootkey: {{rootkey}}' \
--header 'Content-Type: application/json'
```
会迁移联系方式到对应用户表中。
## 🚀 新增内容 ## 🚀 新增内容
1. AI 对话节点解析 `<think></think>` 标签内容作为思考链,便于各类模型进行思考链输出。需主动开启模型输出思考。 1. AI 对话节点解析 `<think></think>` 标签内容作为思考链,便于各类模型进行思考链输出。需主动开启模型输出思考。
2. ppio 模型提供商 by @saikidev 2. 对话 API 优化,无论是否传递 chatId都会保存对话日志。未传递 chatId则随机生成一个 chatId 来进行存储。
3. ppio 模型提供商 by
## ⚙️ 优化 ## ⚙️ 优化
1. 模型未配置时提示,减少冲突提示。 1. 模型未配置时提示,减少冲突提示。
2. 使用记录代码。 2. 使用记录代码。
3. 内容提取节点,字段描述过长时换行。同时修改其输出名用 key而不是 description。
4. 团队管理交互。
5. 对话接口,非流响应,增加报错字段。
## 🐛 修复 ## 🐛 修复
1. 思考内容未进入到输出 Tokens.
1. 思考内容未进入到输出 Tokens.
2. 思考链流输出时,有时与正文顺序偏差。 2. 思考链流输出时,有时与正文顺序偏差。
3. API 调用工作流,如果传递的图片不支持 Head 检测时,图片会被过滤。已增加该类错误检测,避免被错误过滤。 3. API 调用工作流,如果传递的图片不支持 Head 检测时,图片会被过滤。已增加该类错误检测,避免被错误过滤。
4. 模板市场部分模板错误。 4. 模板市场部分模板错误。
@ -38,3 +56,6 @@ weight: 802
7. 切换团队时未刷新成员列表 7. 切换团队时未刷新成员列表
8. list 接口在联查 member 时,存在空指针可能性。 8. list 接口在联查 member 时,存在空指针可能性。
9. 工作流基础节点无法升级。 9. 工作流基础节点无法升级。
10. 向量检索结果未去重。
11. 用户选择节点无法正常连线。
12. 对话记录保存时source 未正常记录。

View File

@ -72,11 +72,6 @@ export const ModelProviderList: ModelProviderType[] = [
name: 'Groq', name: 'Groq',
avatar: 'model/groq' avatar: 'model/groq'
}, },
{
id: 'AliCloud',
name: i18nT('common:model_alicloud'),
avatar: 'model/alicloud'
},
{ {
id: 'Qwen', id: 'Qwen',
name: i18nT('common:model_qwen'), name: i18nT('common:model_qwen'),
@ -87,6 +82,11 @@ export const ModelProviderList: ModelProviderType[] = [
name: i18nT('common:model_doubao'), name: i18nT('common:model_doubao'),
avatar: 'model/doubao' avatar: 'model/doubao'
}, },
{
id: 'DeepSeek',
name: 'DeepSeek',
avatar: 'model/deepseek'
},
{ {
id: 'ChatGLM', id: 'ChatGLM',
name: i18nT('common:model_chatglm'), name: i18nT('common:model_chatglm'),
@ -97,11 +97,6 @@ export const ModelProviderList: ModelProviderType[] = [
name: i18nT('common:model_ernie'), name: i18nT('common:model_ernie'),
avatar: 'model/ernie' avatar: 'model/ernie'
}, },
{
id: 'DeepSeek',
name: 'DeepSeek',
avatar: 'model/deepseek'
},
{ {
id: 'Moonshot', id: 'Moonshot',
name: i18nT('common:model_moonshot'), name: i18nT('common:model_moonshot'),
@ -163,6 +158,11 @@ export const ModelProviderList: ModelProviderType[] = [
name: i18nT('common:model_moka'), name: i18nT('common:model_moka'),
avatar: 'model/moka' avatar: 'model/moka'
}, },
{
id: 'AliCloud',
name: i18nT('common:model_alicloud'),
avatar: 'model/alicloud'
},
{ {
id: 'Siliconflow', id: 'Siliconflow',
name: i18nT('common:model_siliconflow'), name: i18nT('common:model_siliconflow'),

View File

@ -1,7 +1,7 @@
import { connectionMongo, getMongoModel } from '../../common/mongo'; import { connectionMongo, getMongoModel } from '../../common/mongo';
const { Schema } = connectionMongo; const { Schema } = connectionMongo;
import { ChatSchema as ChatType } from '@fastgpt/global/core/chat/type.d'; import { ChatSchema as ChatType } from '@fastgpt/global/core/chat/type.d';
import { ChatSourceMap } from '@fastgpt/global/core/chat/constants'; import { ChatSourceEnum, ChatSourceMap } from '@fastgpt/global/core/chat/constants';
import { import {
TeamCollectionName, TeamCollectionName,
TeamMemberCollectionName TeamMemberCollectionName
@ -52,11 +52,10 @@ const ChatSchema = new Schema({
}, },
source: { source: {
type: String, type: String,
required: true required: true,
}, enum: Object.values(ChatSourceEnum)
sourceName: {
type: String
}, },
sourceName: String,
shareId: { shareId: {
type: String type: String
}, },

View File

@ -1,6 +1,10 @@
import type { AIChatItemType, UserChatItemType } from '@fastgpt/global/core/chat/type.d'; import type { AIChatItemType, UserChatItemType } from '@fastgpt/global/core/chat/type.d';
import { MongoApp } from '../app/schema'; import { MongoApp } from '../app/schema';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import {
ChatItemValueTypeEnum,
ChatRoleEnum,
ChatSourceEnum
} from '@fastgpt/global/core/chat/constants';
import { MongoChatItem } from './chatItemSchema'; import { MongoChatItem } from './chatItemSchema';
import { MongoChat } from './chatSchema'; import { MongoChat } from './chatSchema';
import { addLog } from '../../common/system/log'; import { addLog } from '../../common/system/log';
@ -22,8 +26,8 @@ type Props = {
variables?: Record<string, any>; variables?: Record<string, any>;
isUpdateUseTime: boolean; isUpdateUseTime: boolean;
newTitle: string; newTitle: string;
source: string; source: `${ChatSourceEnum}`;
sourceName: string; sourceName?: string;
shareId?: string; shareId?: string;
outLinkUid?: string; outLinkUid?: string;
content: [UserChatItemType & { dataId?: string }, AIChatItemType & { dataId?: string }]; content: [UserChatItemType & { dataId?: string }, AIChatItemType & { dataId?: string }];

View File

@ -383,6 +383,7 @@ export async function searchDatasetData(
).lean() ).lean()
]); ]);
const set = new Map<string, number>();
const formatResult = results const formatResult = results
.map((item, index) => { .map((item, index) => {
const collection = collections.find((col) => String(col._id) === String(item.collectionId)); const collection = collections.find((col) => String(col._id) === String(item.collectionId));
@ -398,8 +399,6 @@ export async function searchDatasetData(
return; return;
} }
const score = item?.score || 0;
const result: SearchDataResponseItemType = { const result: SearchDataResponseItemType = {
id: String(data._id), id: String(data._id),
updateTime: data.updateTime, updateTime: data.updateTime,
@ -409,12 +408,24 @@ export async function searchDatasetData(
datasetId: String(data.datasetId), datasetId: String(data.datasetId),
collectionId: String(data.collectionId), collectionId: String(data.collectionId),
...getCollectionSourceData(collection), ...getCollectionSourceData(collection),
score: [{ type: SearchScoreTypeEnum.embedding, value: score, index }] score: [{ type: SearchScoreTypeEnum.embedding, value: item?.score || 0, index }]
}; };
return result; return result;
}) })
.filter(Boolean) as SearchDataResponseItemType[]; .filter((item) => {
if (!item) return false;
if (set.has(item.id)) return false;
set.set(item.id, 1);
return true;
})
.map((item, index) => {
if (!item) return;
return {
...item,
score: item.score.map((item) => ({ ...item, index }))
};
}) as SearchDataResponseItemType[];
return { return {
embeddingRecallResults: formatResult, embeddingRecallResults: formatResult,
@ -717,11 +728,12 @@ export const defaultSearchDatasetData = async ({
? getLLMModel(datasetSearchExtensionModel) ? getLLMModel(datasetSearchExtensionModel)
: undefined; : undefined;
const { concatQueries, rewriteQuery, aiExtensionResult } = await datasetSearchQueryExtension({ const { concatQueries, extensionQueries, rewriteQuery, aiExtensionResult } =
query, await datasetSearchQueryExtension({
extensionModel, query,
extensionBg: datasetSearchExtensionBg extensionModel,
}); extensionBg: datasetSearchExtensionBg
});
const result = await searchDatasetData({ const result = await searchDatasetData({
...props, ...props,
@ -736,7 +748,7 @@ export const defaultSearchDatasetData = async ({
model: aiExtensionResult.model, model: aiExtensionResult.model,
inputTokens: aiExtensionResult.inputTokens, inputTokens: aiExtensionResult.inputTokens,
outputTokens: aiExtensionResult.outputTokens, outputTokens: aiExtensionResult.outputTokens,
query: concatQueries.join('\n') query: extensionQueries.join('\n')
} }
: undefined : undefined
}; };

View File

@ -72,12 +72,15 @@ Human: ${query}
if (result.extensionQueries?.length === 0) return; if (result.extensionQueries?.length === 0) return;
return result; return result;
})(); })();
const extensionQueries = filterSamQuery(aiExtensionResult?.extensionQueries || []);
if (aiExtensionResult) { if (aiExtensionResult) {
queries = filterSamQuery(queries.concat(aiExtensionResult.extensionQueries)); queries = filterSamQuery(queries.concat(extensionQueries));
rewriteQuery = queries.join('\n'); rewriteQuery = queries.join('\n');
} }
return { return {
extensionQueries,
concatQueries: queries, concatQueries: queries,
rewriteQuery, rewriteQuery,
aiExtensionResult aiExtensionResult

View File

@ -1182,7 +1182,6 @@
"support.wallet.usage.Audio Speech": "Voice Playback", "support.wallet.usage.Audio Speech": "Voice Playback",
"support.wallet.usage.Bill Module": "Billing Module", "support.wallet.usage.Bill Module": "Billing Module",
"support.wallet.usage.Duration": "Duration (seconds)", "support.wallet.usage.Duration": "Duration (seconds)",
"support.wallet.usage.Extension result": "Question Optimization Result",
"support.wallet.usage.Module name": "Module Name", "support.wallet.usage.Module name": "Module Name",
"support.wallet.usage.Source": "Source", "support.wallet.usage.Source": "Source",
"support.wallet.usage.Text Length": "Text Length", "support.wallet.usage.Text Length": "Text Length",

View File

@ -1185,7 +1185,6 @@
"support.wallet.usage.Audio Speech": "语音播放", "support.wallet.usage.Audio Speech": "语音播放",
"support.wallet.usage.Bill Module": "扣费模块", "support.wallet.usage.Bill Module": "扣费模块",
"support.wallet.usage.Duration": "时长(秒)", "support.wallet.usage.Duration": "时长(秒)",
"support.wallet.usage.Extension result": "问题优化结果",
"support.wallet.usage.Module name": "模块名", "support.wallet.usage.Module name": "模块名",
"support.wallet.usage.Source": "来源", "support.wallet.usage.Source": "来源",
"support.wallet.usage.Text Length": "文本长度", "support.wallet.usage.Text Length": "文本长度",

View File

@ -1181,7 +1181,6 @@
"support.wallet.usage.Audio Speech": "語音播放", "support.wallet.usage.Audio Speech": "語音播放",
"support.wallet.usage.Bill Module": "計費模組", "support.wallet.usage.Bill Module": "計費模組",
"support.wallet.usage.Duration": "時長(秒)", "support.wallet.usage.Duration": "時長(秒)",
"support.wallet.usage.Extension result": "問題最佳化結果",
"support.wallet.usage.Module name": "模組名稱", "support.wallet.usage.Module name": "模組名稱",
"support.wallet.usage.Source": "來源", "support.wallet.usage.Source": "來源",
"support.wallet.usage.Text Length": "文字長度", "support.wallet.usage.Text Length": "文字長度",

View File

@ -78,7 +78,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
isUpdateNotification && isUpdateNotification &&
feConfigs?.bind_notification_method && feConfigs?.bind_notification_method &&
feConfigs?.bind_notification_method.length > 0 && feConfigs?.bind_notification_method.length > 0 &&
!userInfo?.team.notificationAccount && !userInfo?.contact &&
!!userInfo?.team.permission.isOwner; !!userInfo?.team.permission.isOwner;
useMount(() => { useMount(() => {

View File

@ -250,7 +250,7 @@ export const WholeResponseContent = ({
value={`${activeModule.queryExtensionResult.inputTokens}/${activeModule.queryExtensionResult.outputTokens}`} value={`${activeModule.queryExtensionResult.inputTokens}/${activeModule.queryExtensionResult.outputTokens}`}
/> />
<Row <Row
label={t('common:support.wallet.usage.Extension result')} label={t('chat:query_extension_result')}
value={activeModule.queryExtensionResult.query} value={activeModule.queryExtensionResult.query}
/> />
</> </>
@ -259,10 +259,7 @@ export const WholeResponseContent = ({
label={t('common:core.chat.response.Extension model')} label={t('common:core.chat.response.Extension model')}
value={activeModule?.extensionModel} value={activeModule?.extensionModel}
/> />
<Row <Row label={t('chat:query_extension_result')} value={`${activeModule?.extensionResult}`} />
label={t('common:support.wallet.usage.Extension result')}
value={`${activeModule?.extensionResult}`}
/>
{activeModule.quoteList && activeModule.quoteList.length > 0 && ( {activeModule.quoteList && activeModule.quoteList.length > 0 && (
<Row <Row
label={t('common:core.chat.response.module quoteList')} label={t('common:core.chat.response.module quoteList')}

View File

@ -130,7 +130,7 @@ const QuoteItem = ({
<Box> <Box>
{t(SearchScoreTypeMap[score.primaryScore.type]?.label as any)} {t(SearchScoreTypeMap[score.primaryScore.type]?.label as any)}
{SearchScoreTypeMap[score.primaryScore.type]?.showScore {SearchScoreTypeMap[score.primaryScore.type]?.showScore
? ` ${score.primaryScore.value.toFixed(4)}` ? ` ${score.primaryScore.value?.toFixed(4)}`
: ''} : ''}
</Box> </Box>
</Flex> </Flex>

View File

@ -43,10 +43,10 @@ function MemberItemCard({
{isChecked !== undefined && <Checkbox isChecked={isChecked} pointerEvents="none" />} {isChecked !== undefined && <Checkbox isChecked={isChecked} pointerEvents="none" />}
<Avatar src={avatar} w="1.5rem" borderRadius={'50%'} /> <Avatar src={avatar} w="1.5rem" borderRadius={'50%'} />
<VStack w="full" gap={0}> <Box w="full">
<Box w="full">{name}</Box> <Box fontSize={'sm'}>{name}</Box>
<Box w="full">{orgs && orgs.length > 0 && <OrgTags orgs={orgs} />}</Box> <Box lineHeight={1}>{orgs && orgs.length > 0 && <OrgTags orgs={orgs} />}</Box>
</VStack> </Box>
{permission && <PermissionTags permission={permission} />} {permission && <PermissionTags permission={permission} />}
{onDelete !== undefined && ( {onDelete !== undefined && (
<MyIcon <MyIcon

View File

@ -126,7 +126,7 @@ function MemberModal({
const [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]); const [selectedMemberIdList, setSelectedMembers] = useState<string[]>([]);
const filterMembers = useMemo(() => { const filterMembers = useMemo(() => {
if (searchText) { if (searchText) {
return searchedData?.members; return searchedData?.members || [];
} }
if (!searchText && filterClass !== 'member' && filterClass !== 'org') return []; if (!searchText && filterClass !== 'member' && filterClass !== 'org') return [];
if (currentOrg && filterClass === 'org') { if (currentOrg && filterClass === 'org') {
@ -320,122 +320,124 @@ function MemberModal({
</Box> </Box>
)} )}
<ScrollData {(filterClass === 'org' || filterClass === 'member') && (
flexDirection={'column'} <ScrollData
gap={1} flexDirection={'column'}
userSelect={'none'} gap={1}
height={'fit-content'} userSelect={'none'}
> height={'fit-content'}
{filterOrgs?.map((org) => { >
const onChange = () => { {filterOrgs?.map((org) => {
setSelectedOrgIdList((state) => { const onChange = () => {
if (state.includes(org._id)) { setSelectedOrgIdList((state) => {
return state.filter((v) => v !== org._id); if (state.includes(org._id)) {
} return state.filter((v) => v !== org._id);
return [...state, org._id]; }
}); return [...state, org._id];
}; });
const collaborator = collaboratorList?.find((v) => v.orgId === org._id); };
return ( const collaborator = collaboratorList?.find((v) => v.orgId === org._id);
<HStack return (
justifyContent="space-between" <HStack
key={org._id} justifyContent="space-between"
py="2" key={org._id}
px="3" py="2"
borderRadius="sm" px="3"
alignItems="center" borderRadius="sm"
_hover={HoverBoxStyle} alignItems="center"
onClick={onChange} _hover={HoverBoxStyle}
> onClick={onChange}
<Checkbox >
isChecked={selectedOrgIdList.includes(org._id)} <Checkbox
pointerEvents="none" isChecked={selectedOrgIdList.includes(org._id)}
/> pointerEvents="none"
<MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} /> />
<HStack ml="2" w="full" gap="5px"> <MyAvatar src={org.avatar} w="1.5rem" borderRadius={'50%'} />
<Text>{org.name}</Text> <HStack ml="2" w="full" gap="5px">
<Text>{org.name}</Text>
{org.count && (
<>
<Tag size="sm" my="auto">
{org.count}
</Tag>
</>
)}
</HStack>
<PermissionTags permission={collaborator?.permission.value} />
{org.count && ( {org.count && (
<> <MyIcon
<Tag size="sm" my="auto"> name="core/chat/chevronRight"
{org.count} w="16px"
</Tag> p="4px"
</> rounded={'6px'}
_hover={{
bgColor: 'myGray.200'
}}
onClick={(e) => {
setParentPath(getOrgChildrenPath(org));
e.stopPropagation();
}}
/>
)} )}
</HStack> </HStack>
<PermissionTags permission={collaborator?.permission.value} /> );
{org.count && ( })}
<MyIcon {filterMembers?.map((member) => {
name="core/chat/chevronRight" const onChange = () => {
w="16px" setSelectedMembers((state) => {
p="4px" if (state.includes(member.tmbId)) {
rounded={'6px'} return state.filter((v) => v !== member.tmbId);
_hover={{ }
bgColor: 'myGray.200' return [...state, member.tmbId];
}} });
onClick={(e) => { };
setParentPath(getOrgChildrenPath(org)); const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId);
e.stopPropagation(); const memberOrgs = orgs.filter((org) =>
}} org.members.find((v) => String(v.tmbId) === String(member.tmbId))
/> );
)} const memberPathIds = memberOrgs.map((org) =>
</HStack> (org.path + '/' + org.pathId).split('/').slice(0)
); );
})} const memberOrgNames = memberPathIds.map((pathIds) =>
{filterMembers?.map((member) => { pathIds.map((id) => orgs.find((v) => v.pathId === id)?.name).join('/')
const onChange = () => { );
setSelectedMembers((state) => { return (
if (state.includes(member.tmbId)) { <MemberItemCard
return state.filter((v) => v !== member.tmbId); avatar={member.avatar}
} key={member.tmbId}
return [...state, member.tmbId]; name={member.memberName}
}); permission={collaborator?.permission.value}
}; onChange={onChange}
const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId); isChecked={selectedMemberIdList.includes(member.tmbId)}
const memberOrgs = orgs.filter((org) => orgs={memberOrgNames}
org.members.find((v) => String(v.tmbId) === String(member.tmbId)) />
); );
const memberPathIds = memberOrgs.map((org) => })}
(org.path + '/' + org.pathId).split('/').slice(0) </ScrollData>
); )}
const memberOrgNames = memberPathIds.map((pathIds) => {filterGroups?.map((group) => {
pathIds.map((id) => orgs.find((v) => v.pathId === id)?.name).join('/') const onChange = () => {
); setSelectedGroupIdList((state) => {
return ( if (state.includes(group._id)) {
<MemberItemCard return state.filter((v) => v !== group._id);
avatar={member.avatar} }
key={member.tmbId} return [...state, group._id];
name={member.memberName} });
permission={collaborator?.permission.value} };
onChange={onChange} const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
isChecked={selectedMemberIdList.includes(member.tmbId)} return (
orgs={memberOrgNames} <MemberItemCard
/> avatar={group.avatar}
); key={group._id}
})} name={
{filterGroups?.map((group) => { group.name === DefaultGroupName ? userInfo?.team.teamName ?? '' : group.name
const onChange = () => { }
setSelectedGroupIdList((state) => { permission={collaborator?.permission.value}
if (state.includes(group._id)) { onChange={onChange}
return state.filter((v) => v !== group._id); isChecked={selectedGroupIdList.includes(group._id)}
} />
return [...state, group._id]; );
}); })}
};
const collaborator = collaboratorList?.find((v) => v.groupId === group._id);
return (
<MemberItemCard
avatar={group.avatar}
key={group._id}
name={
group.name === DefaultGroupName ? userInfo?.team.teamName ?? '' : group.name
}
permission={collaborator?.permission.value}
onChange={onChange}
isChecked={selectedGroupIdList.includes(group._id)}
/>
);
})}
</ScrollData>
</Flex> </Flex>
</Flex> </Flex>

View File

@ -18,12 +18,18 @@ function OrgTags({ orgs, type = 'simple' }: { orgs: string[]; type?: 'simple' |
} }
> >
{type === 'simple' ? ( {type === 'simple' ? (
<Box fontSize="sm" fontWeight={400} w="full" color="myGray.500" whiteSpace={'nowrap'}> <Box
className="textEllipsis"
fontSize="xs"
fontWeight={400}
w="full"
color="myGray.400"
whiteSpace={'nowrap'}
>
{orgs {orgs
.map((org) => org.split('/').pop()) .map((org) => org.split('/').pop())
.join(', ') .join(', ')
.slice(0, 30)} .slice(0, 30)}
{orgs.length > 1 && '...'}
</Box> </Box>
) : ( ) : (
<Flex direction="row" gap="1" p="2" alignItems={'start'} wrap={'wrap'}> <Flex direction="row" gap="1" p="2" alignItems={'start'} wrap={'wrap'}>

View File

@ -194,19 +194,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
{t('account_team:user_team_invite_member')} {t('account_team:user_team_invite_member')}
</Button> </Button>
)} )}
{!userInfo?.team.permission.isOwner && ( {userInfo?.team.permission.isOwner && isSyncMember && (
<Button
variant={'whitePrimary'}
size="md"
borderRadius={'md'}
ml={3}
leftIcon={<MyIcon name={'support/account/loginoutLight'} w={'14px'} />}
onClick={() => openLeaveConfirm(onLeaveTeam)()}
>
{t('account_team:user_team_leave_team')}
</Button>
)}
{userInfo?.team.permission.hasManagePer && (
<Button <Button
variant={'whitePrimary'} variant={'whitePrimary'}
size="md" size="md"
@ -223,6 +211,18 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
{t('account_team:export_members')} {t('account_team:export_members')}
</Button> </Button>
)} )}
{!userInfo?.team.permission.isOwner && (
<Button
variant={'whitePrimary'}
size="md"
borderRadius={'md'}
ml={3}
leftIcon={<MyIcon name={'support/account/loginoutLight'} w={'14px'} />}
onClick={() => openLeaveConfirm(onLeaveTeam)()}
>
{t('account_team:user_team_leave_team')}
</Button>
)}
</HStack> </HStack>
</Flex> </Flex>

View File

@ -87,7 +87,7 @@ function PermissionManage({
const groupList = collaboratorList.filter( const groupList = collaboratorList.filter(
(item) => (item) =>
Object.keys(item).includes('groupId') && Object.keys(item).includes('groupId') &&
(!searchKey || searchResult?.groups.find((group) => group.groupId === item.groupId)) (!searchKey || searchResult?.groups.find((group) => group._id === item.groupId))
); );
const orgList = collaboratorList.filter( const orgList = collaboratorList.filter(
(item) => (item) =>

View File

@ -96,6 +96,7 @@ const ExtractFieldModal = ({
<Input <Input
bg={'myGray.50'} bg={'myGray.50'}
placeholder="name/age/sql" placeholder="name/age/sql"
maxLength={20}
{...register('key', { required: true })} {...register('key', { required: true })}
/> />
</Flex> </Flex>

View File

@ -35,6 +35,7 @@ import IOTitle from '../../components/IOTitle';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context'; import { WorkflowContext } from '../../../context';
import MyIconButton from '@fastgpt/web/components/common/Icon/button'; import MyIconButton from '@fastgpt/web/components/common/Icon/button';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
const NodeExtract = ({ data, selected }: NodeProps<FlowNodeItemType>) => { const NodeExtract = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { inputs, outputs, nodeId } = data; const { inputs, outputs, nodeId } = data;
@ -71,7 +72,7 @@ const NodeExtract = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
</Button> </Button>
</Flex> </Flex>
<TableContainer borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'} mt={2}> <TableContainer borderRadius={'md'} overflow={'auto'} borderWidth={'1px'} mt={2}>
<Table variant={'workflow'}> <Table variant={'workflow'}>
<Thead> <Thead>
<Tr> <Tr>
@ -85,19 +86,23 @@ const NodeExtract = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
{extractKeys.map((item, index) => ( {extractKeys.map((item, index) => (
<Tr key={index}> <Tr key={index}>
<Td> <Td>
<Flex alignItems={'center'}> <Flex alignItems={'center'} maxW={'300px'} className={'textEllipsis'}>
<MyIcon name={'checkCircle'} w={'14px'} mr={1} color={'myGray.600'} /> <MyIcon name={'checkCircle'} w={'14px'} mr={1} color={'myGray.600'} />
{item.key} {item.key}
</Flex> </Flex>
</Td> </Td>
<Td>{item.desc}</Td> <Td>
<Box maxW={'300px'} whiteSpace={'pre-wrap'}>
{item.desc}
</Box>
</Td>
<Td> <Td>
{item.required ? ( {item.required ? (
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} /> <MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
</Flex> </Flex>
) : ( ) : (
'' '-'
)} )}
</Td> </Td>
<Td> <Td>
@ -197,7 +202,7 @@ const NodeExtract = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const newOutput: FlowNodeOutputItemType = { const newOutput: FlowNodeOutputItemType = {
id: getNanoid(), id: getNanoid(),
key: data.key, key: data.key,
label: `${t('common:extraction_results')}-${data.desc}`, label: `${t('common:extraction_results')}-${data.key}`,
valueType: data.valueType || WorkflowIOValueTypeEnum.string, valueType: data.valueType || WorkflowIOValueTypeEnum.string,
type: FlowNodeOutputTypeEnum.static type: FlowNodeOutputTypeEnum.static
}; };

View File

@ -11,6 +11,9 @@ async function handler(req: NextApiRequest, _res: NextApiResponse) {
await authCert({ req, authRoot: true }); await authCert({ req, authRoot: true });
const users = await MongoUser.find(); const users = await MongoUser.find();
const teams = await MongoTeam.find(); const teams = await MongoTeam.find();
console.log('Total users:', users.length);
let success = 0;
for await (const user of users) { for await (const user of users) {
try { try {
const team = teams.find((team) => String(team.ownerId) === String(user._id)); const team = teams.find((team) => String(team.ownerId) === String(user._id));
@ -18,6 +21,7 @@ async function handler(req: NextApiRequest, _res: NextApiResponse) {
user.contact = team.notificationAccount; user.contact = team.notificationAccount;
} }
await user.save(); await user.save();
console.log('Success:', ++success);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }

View File

@ -6,12 +6,12 @@ import { ApiRequestProps } from '@fastgpt/service/type/next';
import { syncCollection } from '@fastgpt/service/core/dataset/collection/utils'; import { syncCollection } from '@fastgpt/service/core/dataset/collection/utils';
/* /*
Collection sync Collection sync
1. Check collection type: link, api dataset collection 1. Check collection type: link, api dataset collection
2. Get collection and raw text 2. Get collection and raw text
3. Check whether the original text is the same: skip if same 3. Check whether the original text is the same: skip if same
4. Create new collection 4. Create new collection
5. Delete old collection 5. Delete old collection
*/ */
export type CollectionSyncBody = { export type CollectionSyncBody = {
collectionId: string; collectionId: string;
@ -27,6 +27,7 @@ async function handler(req: ApiRequestProps<CollectionSyncBody>) {
const { collection } = await authDatasetCollection({ const { collection } = await authDatasetCollection({
req, req,
authToken: true, authToken: true,
authApiKey: true,
collectionId, collectionId,
per: WritePermissionVal per: WritePermissionVal
}); });

View File

@ -16,13 +16,13 @@ async function handler(
const user = await getUserDetail({ tmbId }); const user = await getUserDetail({ tmbId });
// Remove sensitive information // Remove sensitive information
if (user.team.lafAccount) { // if (user.team.lafAccount) {
user.team.lafAccount = { // user.team.lafAccount = {
appid: user.team.lafAccount.appid, // appid: user.team.lafAccount.appid,
token: '', // token: '',
pat: '' // pat: ''
}; // };
} // }
if (user.team.openaiAccount) { if (user.team.openaiAccount) {
user.team.openaiAccount = { user.team.openaiAccount = {
key: '', key: '',

View File

@ -302,65 +302,64 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
})(); })();
// save chat // save chat
if (chatId) { const isOwnerUse = !shareId && !spaceTeamId && String(tmbId) === String(app.tmbId);
const isOwnerUse = !shareId && !spaceTeamId && String(tmbId) === String(app.tmbId); const source = (() => {
const source = (() => { if (shareId) {
if (shareId) { return ChatSourceEnum.share;
return ChatSourceEnum.share;
}
if (authType === 'apikey') {
return ChatSourceEnum.api;
}
if (spaceTeamId) {
return ChatSourceEnum.team;
}
return ChatSourceEnum.online;
})();
const isInteractiveRequest = !!getLastInteractiveValue(histories);
const { text: userInteractiveVal } = chatValue2RuntimePrompt(userQuestion.value);
const newTitle = isPlugin
? variables.cTime ?? getSystemTime(timezone)
: getChatTitleFromChatMessage(userQuestion);
const aiResponse: AIChatItemType & { dataId?: string } = {
dataId: responseChatItemId,
obj: ChatRoleEnum.AI,
value: assistantResponses,
[DispatchNodeResponseKeyEnum.nodeResponse]: flowResponses
};
if (isInteractiveRequest) {
await updateInteractiveChat({
chatId,
appId: app._id,
userInteractiveVal,
aiResponse,
newVariables
});
} else {
await saveChat({
chatId,
appId: app._id,
teamId,
tmbId: tmbId,
nodes,
appChatConfig: chatConfig,
variables: newVariables,
isUpdateUseTime: isOwnerUse && source === ChatSourceEnum.online, // owner update use time
newTitle,
shareId,
outLinkUid: outLinkUserId,
source: source,
sourceName: sourceName || '',
content: [userQuestion, aiResponse],
metadata: {
originIp,
...metadata
}
});
} }
if (authType === 'apikey') {
return ChatSourceEnum.api;
}
if (spaceTeamId) {
return ChatSourceEnum.team;
}
return ChatSourceEnum.online;
})();
const isInteractiveRequest = !!getLastInteractiveValue(histories);
const { text: userInteractiveVal } = chatValue2RuntimePrompt(userQuestion.value);
const newTitle = isPlugin
? variables.cTime ?? getSystemTime(timezone)
: getChatTitleFromChatMessage(userQuestion);
const aiResponse: AIChatItemType & { dataId?: string } = {
dataId: responseChatItemId,
obj: ChatRoleEnum.AI,
value: assistantResponses,
[DispatchNodeResponseKeyEnum.nodeResponse]: flowResponses
};
const saveChatId = chatId || getNanoid(24);
if (isInteractiveRequest) {
await updateInteractiveChat({
chatId: saveChatId,
appId: app._id,
userInteractiveVal,
aiResponse,
newVariables
});
} else {
await saveChat({
chatId: saveChatId,
appId: app._id,
teamId,
tmbId: tmbId,
nodes,
appChatConfig: chatConfig,
variables: newVariables,
isUpdateUseTime: isOwnerUse && source === ChatSourceEnum.online, // owner update use time
newTitle,
shareId,
outLinkUid: outLinkUserId,
source,
sourceName: sourceName || '',
content: [userQuestion, aiResponse],
metadata: {
originIp,
...metadata
}
});
} }
addLog.info(`completions running time: ${(Date.now() - startTime) / 1000}s`); addLog.info(`completions running time: ${(Date.now() - startTime) / 1000}s`);
@ -407,9 +406,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
return assistantResponses; return assistantResponses;
})(); })();
const error = flowResponses[flowResponses.length - 1]?.error;
res.json({ res.json({
...(detail ? { responseData: feResponseData, newVariables } : {}), ...(detail ? { responseData: feResponseData, newVariables } : {}),
error,
id: chatId || '', id: chatId || '',
model: '', model: '',
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 1 }, usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 1 },