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. 模板市场部分模板错误。
@ -37,4 +55,7 @@ weight: 802
6. 对话日志导出,未兼容 sub path。 6. 对话日志导出,未兼容 sub path。
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 },