Commit b41b5411 by siigure Committed by GitHub

Fix/playground-visibility-config-layout (#7135)

* fix: enhance Playground visibility configuration with new grid layout and switch components

* refactor: update layout and styling for various components to improve responsiveness and usability

* perf: ui

* perf: tool response none

* doc

* doc

* hide auth switch in community version

---------

Co-authored-by: archer <545436317@qq.com>
parent c44a1a73
...@@ -21,6 +21,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initSandboxArchive' \ ...@@ -21,6 +21,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initSandboxArchive' \
5. 支持通过模型生成对话标题,需配置 `CHAT_TITLE_MODEL` 变量。 5. 支持通过模型生成对话标题,需配置 `CHAT_TITLE_MODEL` 变量。
6. 调整 Skill Edit 编辑交互。 6. 调整 Skill Edit 编辑交互。
7. HTTP 节点支持返回完整错误对象。 7. HTTP 节点支持返回完整错误对象。
8. agent 模式知识库搜索,支持权限过滤。
## ⚙️ 优化 ## ⚙️ 优化
...@@ -29,11 +30,13 @@ curl --location --request POST 'https://{{host}}/api/admin/initSandboxArchive' \ ...@@ -29,11 +30,13 @@ curl --location --request POST 'https://{{host}}/api/admin/initSandboxArchive' \
3. 移除所有内置 LLM 请求中的 `temperature` 和 `max_tokens`,避免部分模型不兼容。 3. 移除所有内置 LLM 请求中的 `temperature` 和 `max_tokens`,避免部分模型不兼容。
4. 知识库训练出现错误时的提示,同时支持一键全部重试。 4. 知识库训练出现错误时的提示,同时支持一键全部重试。
5. 过滤掉无效的知识库引用角标。 5. 过滤掉无效的知识库引用角标。
6. 工具运行空响应时候,自动补充 "none",避免部分模型报错。
## 🐛 修复 ## 🐛 修复
1. 修复 S3 私有对象 key 未绑定已鉴权资源时可能导致的跨资源文件访问风险。 1. 修复 S3 私有对象 key 未绑定已鉴权资源时可能导致的跨资源文件访问风险。
2. 工作流工具,array 和 object 类型,工具调用参数 schema 异常。 2. 工作流工具,array 和 object 类型,工具调用参数 schema 异常。
3. 发布渠道 - 门户,UI 偏移。
## 代码优化 ## 代码优化
......
...@@ -285,7 +285,7 @@ ...@@ -285,7 +285,7 @@
"content/self-host/upgrading/4-15/41504.en.mdx": "2026-06-10T19:02:59+08:00", "content/self-host/upgrading/4-15/41504.en.mdx": "2026-06-10T19:02:59+08:00",
"content/self-host/upgrading/4-15/41504.mdx": "2026-06-15T23:34:43+08:00", "content/self-host/upgrading/4-15/41504.mdx": "2026-06-15T23:34:43+08:00",
"content/self-host/upgrading/4-15/41505.en.mdx": "2026-06-12T20:47:04+08:00", "content/self-host/upgrading/4-15/41505.en.mdx": "2026-06-12T20:47:04+08:00",
"content/self-host/upgrading/4-15/41505.mdx": "2026-06-17T18:19:09+08:00", "content/self-host/upgrading/4-15/41505.mdx": "2026-06-18T14:56:07+08:00",
"content/self-host/upgrading/outdated/40.en.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/outdated/40.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/40.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/outdated/40.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/41.en.mdx": "2026-04-26T21:08:47+08:00", "content/self-host/upgrading/outdated/41.en.mdx": "2026-04-26T21:08:47+08:00",
......
...@@ -10,3 +10,4 @@ export * from './plan/updateTool'; ...@@ -10,3 +10,4 @@ export * from './plan/updateTool';
export * from './stop'; export * from './stop';
export * from './tools'; export * from './tools';
export * from './loop/unified'; export * from './loop/unified';
export * from './utils';
...@@ -23,6 +23,7 @@ import type { ...@@ -23,6 +23,7 @@ import type {
import { AgentUsageModuleName } from '../constants'; import { AgentUsageModuleName } from '../constants';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import { batchRun } from '@fastgpt/global/common/system/utils'; import { batchRun } from '@fastgpt/global/common/system/utils';
import { normalizeToolResponseContent } from '../utils';
type RunAgentCallProps<TChildrenResponse = unknown> = { type RunAgentCallProps<TChildrenResponse = unknown> = {
maxRunAgentTimes: number; maxRunAgentTimes: number;
...@@ -233,7 +234,7 @@ export const runAgentLoop = async <TChildrenResponse = unknown>({ ...@@ -233,7 +234,7 @@ export const runAgentLoop = async <TChildrenResponse = unknown>({
item.role === 'tool' && item.tool_call_id === childrenInteractiveParams.toolParams.toolCallId item.role === 'tool' && item.tool_call_id === childrenInteractiveParams.toolParams.toolCallId
? { ? {
...item, ...item,
content: response content: normalizeToolResponseContent(response)
} }
: item : item
); );
...@@ -461,7 +462,7 @@ export const runAgentLoop = async <TChildrenResponse = unknown>({ ...@@ -461,7 +462,7 @@ export const runAgentLoop = async <TChildrenResponse = unknown>({
const { toolFinalResponse, toolResponseCompress } = await (async () => { const { toolFinalResponse, toolResponseCompress } = await (async () => {
if (skipResponseCompress) { if (skipResponseCompress) {
return { return {
toolFinalResponse: response toolFinalResponse: normalizeToolResponseContent(response)
}; };
} }
...@@ -475,12 +476,13 @@ export const runAgentLoop = async <TChildrenResponse = unknown>({ ...@@ -475,12 +476,13 @@ export const runAgentLoop = async <TChildrenResponse = unknown>({
userKey userKey
}); });
const { compressed: compressed_context, usage: compressionUsage } = compressionResult; const { compressed: compressed_context, usage: compressionUsage } = compressionResult;
const normalizedCompressedContext = normalizeToolResponseContent(compressed_context);
if (compressionUsage) { if (compressionUsage) {
usagePush([compressionUsage]); usagePush([compressionUsage]);
return { return {
toolFinalResponse: compressed_context, toolFinalResponse: normalizedCompressedContext,
toolResponseCompress: { toolResponseCompress: {
response: compressed_context, response: normalizedCompressedContext,
usage: compressionUsage, usage: compressionUsage,
requestIds: compressionResult.requestIds ?? [], requestIds: compressionResult.requestIds ?? [],
seconds: +((Date.now() - compressStartTime) / 1000).toFixed(2) seconds: +((Date.now() - compressStartTime) / 1000).toFixed(2)
...@@ -489,7 +491,7 @@ export const runAgentLoop = async <TChildrenResponse = unknown>({ ...@@ -489,7 +491,7 @@ export const runAgentLoop = async <TChildrenResponse = unknown>({
} }
return { return {
toolFinalResponse: compressed_context toolFinalResponse: normalizedCompressedContext
}; };
})(); })();
......
...@@ -11,6 +11,7 @@ import { parsePlanAskToolCall } from '../plan/parser'; ...@@ -11,6 +11,7 @@ import { parsePlanAskToolCall } from '../plan/parser';
import { applyPlanUpdate } from '../plan/state'; import { applyPlanUpdate } from '../plan/state';
import { runStopGate } from '../stop'; import { runStopGate } from '../stop';
import { getToolsForUnifiedLoop, normalizeToolCatalog } from '../tools'; import { getToolsForUnifiedLoop, normalizeToolCatalog } from '../tools';
import { normalizeToolResponseContent } from '../utils';
import type { import type {
AgentLoopRuntime, AgentLoopRuntime,
AgentLoopToolExecutionResult, AgentLoopToolExecutionResult,
...@@ -163,7 +164,7 @@ export const runUnifiedAgentLoop = async ({ ...@@ -163,7 +164,7 @@ export const runUnifiedAgentLoop = async ({
{ {
role: ChatCompletionRequestMessageRoleEnum.Tool, role: ChatCompletionRequestMessageRoleEnum.Tool,
tool_call_id: input.pendingMainContext.askToolCallId, tool_call_id: input.pendingMainContext.askToolCallId,
content: input.userAnswer || '' content: normalizeToolResponseContent(input.userAnswer)
} as ChatCompletionMessageParam } as ChatCompletionMessageParam
] ]
: buildInitialMessages({ input, hasRuntimeTools }); : buildInitialMessages({ input, hasRuntimeTools });
......
/**
* 规范化会写入 LLM tool message 的工具响应。
* OpenAI 兼容接口通常不接受空 tool content;undefined 和空字符串统一兜底为 none。
*/
export const normalizeToolResponseContent = (response?: string) =>
response === '' || response === undefined ? 'none' : response;
...@@ -440,6 +440,115 @@ describe('runAgentLoop with mocked createLLMResponse', () => { ...@@ -440,6 +440,115 @@ describe('runAgentLoop with mocked createLLMResponse', () => {
}); });
}); });
it('normalizes empty tool response to none before feeding the next LLM request', async () => {
const onRunTool = vi.fn(async () => ({
response: '',
assistantMessages: [],
usages: []
}));
mockCreateLLMResponseQueue(createLLMResponseMock, [
toolCall({
id: 'call_search',
name: 'search',
args: {
q: 'FastGPT'
}
}),
text({ requestId: 'req_final', content: 'final answer' })
]);
const result = await runAgentLoop({
maxRunAgentTimes: 5,
body: {
model: 'gpt-4',
stream: true,
messages: [
{
role: ChatCompletionRequestMessageRoleEnum.User,
content: 'search FastGPT'
}
],
tools: [searchTool]
},
usagePush: vi.fn(),
isAborted: () => false,
onRunTool,
onRunInteractiveTool: vi.fn()
});
expect(createLLMResponseMock.mock.calls[1][0].body.messages).toContainEqual({
role: 'tool',
tool_call_id: 'call_search',
content: 'none'
});
expect(result.assistantMessages).toContainEqual({
role: 'tool',
tool_call_id: 'call_search',
content: 'none'
});
});
it('normalizes empty compressed tool response to none', async () => {
compressToolResponseMock.mockImplementation(async () => ({
compressed: ''
}));
const onAfterToolCall = vi.fn();
const onRunTool = vi.fn(async () => ({
response: 'raw response',
assistantMessages: [],
usages: []
}));
mockCreateLLMResponseQueue(createLLMResponseMock, [
toolCall({
id: 'call_search',
name: 'search',
args: {
q: 'FastGPT'
}
}),
text({ requestId: 'req_final', content: 'final answer' })
]);
const result = await runAgentLoop({
maxRunAgentTimes: 5,
body: {
model: 'gpt-4',
stream: true,
messages: [
{
role: ChatCompletionRequestMessageRoleEnum.User,
content: 'search FastGPT'
}
],
tools: [searchTool]
},
usagePush: vi.fn(),
isAborted: () => false,
onRunTool,
onRunInteractiveTool: vi.fn(),
onAfterToolCall
});
expect(onAfterToolCall).toHaveBeenCalledWith(
expect.objectContaining({
call: expect.objectContaining({ id: 'call_search' }),
response: 'none'
})
);
expect(createLLMResponseMock.mock.calls[1][0].body.messages).toContainEqual({
role: 'tool',
tool_call_id: 'call_search',
content: 'none'
});
expect(result.assistantMessages).toContainEqual({
role: 'tool',
tool_call_id: 'call_search',
content: 'none'
});
});
it('passes reasoning effort into context and tool response compression', async () => { it('passes reasoning effort into context and tool response compression', async () => {
compressRequestMessagesMock.mockImplementation(async ({ messages }) => ({ compressRequestMessagesMock.mockImplementation(async ({ messages }) => ({
messages messages
......
...@@ -787,6 +787,51 @@ describe('runUnifiedAgentLoop', () => { ...@@ -787,6 +787,51 @@ describe('runUnifiedAgentLoop', () => {
expect(createLLMResponseMock).toHaveBeenCalledTimes(2); expect(createLLMResponseMock).toHaveBeenCalledTimes(2);
}); });
it('normalizes empty ask_agent resume answer to none in tool message', async () => {
mockCreateLLMResponseQueue(createLLMResponseMock, [
text({
requestId: 'req_after_resume',
content: 'continued after empty answer'
})
]);
await runUnifiedAgentLoop({
runtime: createRuntime(),
input: {
messages: [],
pendingMainContext: {
messages: [
{
role: ChatCompletionRequestMessageRoleEnum.User,
content: 'Need clarification'
},
{
role: ChatCompletionRequestMessageRoleEnum.Assistant,
tool_calls: [
{
id: 'call_ask',
type: 'function',
function: {
name: 'ask_agent',
arguments: '{}'
}
}
]
}
],
askToolCallId: 'call_ask'
},
userAnswer: ''
}
});
expect(createLLMResponseMock.mock.calls[0][0].body.messages).toContainEqual({
role: 'tool',
tool_call_id: 'call_ask',
content: 'none'
});
});
it('keeps runtime-tool plan-update requirements across ask_agent resume', async () => { it('keeps runtime-tool plan-update requirements across ask_agent resume', async () => {
mockCreateLLMResponseQueue(createLLMResponseMock, [ mockCreateLLMResponseQueue(createLLMResponseMock, [
toolCall({ toolCall({
......
...@@ -438,26 +438,28 @@ const EditForm = ({ ...@@ -438,26 +438,28 @@ const EditForm = ({
<MyIcon name={'core/app/simpleMode/dataset'} w={'20px'} /> <MyIcon name={'core/app/simpleMode/dataset'} w={'20px'} />
<FormLabel ml={2}>{t('app:dataset')}</FormLabel> <FormLabel ml={2}>{t('app:dataset')}</FormLabel>
</Flex> </Flex>
<Flex alignItems={'center'} mr={2}> {feConfigs?.isPlus && (
<Box fontSize={'sm'} color={'myGray.600'} whiteSpace={'nowrap'}> <Flex alignItems={'center'} mr={2}>
{t('workflow:auth_tmb_id')} <Box fontSize={'sm'} color={'myGray.600'} whiteSpace={'nowrap'}>
</Box> {t('workflow:auth_tmb_id')}
<QuestionTip ml={1} label={t('workflow:auth_tmb_id_tip')} /> </Box>
<Switch <QuestionTip ml={1} label={t('workflow:auth_tmb_id_tip')} />
ml={2} <Switch
size={'sm'} ml={2}
isChecked={!!appForm.dataset.authTmbId} size={'sm'}
onChange={(e) => { isChecked={!!appForm.dataset.authTmbId}
setAppForm((state) => ({ onChange={(e) => {
...state, setAppForm((state) => ({
dataset: { ...state,
...state.dataset, dataset: {
authTmbId: e.target.checked ...state.dataset,
} authTmbId: e.target.checked
})); }
}} }));
/> }}
</Flex> />
</Flex>
)}
<Button <Button
variant={'transparentBase'} variant={'transparentBase'}
leftIcon={<MyIcon name={'edit'} w={'14px'} />} leftIcon={<MyIcon name={'edit'} w={'14px'} />}
......
...@@ -64,7 +64,7 @@ const AgentEdit = () => { ...@@ -64,7 +64,7 @@ const AgentEdit = () => {
); );
return ( return (
<Flex h={'100%'} flexDirection={'column'} px={[3, 0]} pr={[3, 3]}> <Flex h={'100%'} minH={0} flexDirection={'column'} px={[3, 0]} pr={[3, 3]}>
<Header <Header
appForm={appForm} appForm={appForm}
forbiddenSaveSnapshot={forbiddenSaveSnapshot} forbiddenSaveSnapshot={forbiddenSaveSnapshot}
...@@ -78,7 +78,15 @@ const AgentEdit = () => { ...@@ -78,7 +78,15 @@ const AgentEdit = () => {
{currentTab === TabEnum.appEdit ? ( {currentTab === TabEnum.appEdit ? (
<Edit appForm={appForm} setAppForm={setAppForm} setPast={setPast} /> <Edit appForm={appForm} setAppForm={setAppForm} setPast={setPast} />
) : ( ) : (
<Box flex={'1 0 0'} h={0} mt={[4, 0]} mb={[2, 4]}> <Box
flex={'1 0 0'}
h={0}
minH={0}
overflowY={'auto'}
overflowX={'hidden'}
mt={[4, 0]}
mb={[2, 4]}
>
{currentTab === TabEnum.publish && <PublishChannel />} {currentTab === TabEnum.publish && <PublishChannel />}
{currentTab === TabEnum.logs && <Logs />} {currentTab === TabEnum.logs && <Logs />}
</Box> </Box>
......
...@@ -320,26 +320,28 @@ const EditForm = ({ ...@@ -320,26 +320,28 @@ const EditForm = ({
<MyIcon name={'core/app/simpleMode/dataset'} w={'20px'} /> <MyIcon name={'core/app/simpleMode/dataset'} w={'20px'} />
<FormLabel ml={2}>{t('app:dataset')}</FormLabel> <FormLabel ml={2}>{t('app:dataset')}</FormLabel>
</Flex> </Flex>
<Flex alignItems={'center'} mr={2}> {feConfigs?.isPlus && (
<Box fontSize={'sm'} color={'myGray.600'} whiteSpace={'nowrap'}> <Flex alignItems={'center'} mr={2}>
{t('workflow:auth_tmb_id')} <Box fontSize={'sm'} color={'myGray.600'} whiteSpace={'nowrap'}>
</Box> {t('workflow:auth_tmb_id')}
<QuestionTip ml={1} label={t('workflow:auth_tmb_id_tip')} /> </Box>
<Switch <QuestionTip ml={1} label={t('workflow:auth_tmb_id_tip')} />
ml={2} <Switch
size={'sm'} ml={2}
isChecked={!!appForm.dataset.authTmbId} size={'sm'}
onChange={(e) => { isChecked={!!appForm.dataset.authTmbId}
setAppForm((state) => ({ onChange={(e) => {
...state, setAppForm((state) => ({
dataset: { ...state,
...state.dataset, dataset: {
authTmbId: e.target.checked ...state.dataset,
} authTmbId: e.target.checked
})); }
}} }));
/> }}
</Flex> />
</Flex>
)}
<Button <Button
variant={'transparentBase'} variant={'transparentBase'}
leftIcon={<MyIcon name={'edit'} w={'14px'} />} leftIcon={<MyIcon name={'edit'} w={'14px'} />}
......
...@@ -2,7 +2,6 @@ import React, { useState } from 'react'; ...@@ -2,7 +2,6 @@ import React, { useState } from 'react';
import Header from '../FormComponent/Header'; import Header from '../FormComponent/Header';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { AppContext, TabEnum } from '../../context'; import { AppContext, TabEnum } from '../../context';
import dynamic from 'next/dynamic';
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useSimpleAppSnapshots } from '../FormComponent/useSnapshots'; import { useSimpleAppSnapshots } from '../FormComponent/useSnapshots';
...@@ -70,7 +69,7 @@ const SimpleEdit = () => { ...@@ -70,7 +69,7 @@ const SimpleEdit = () => {
); );
return ( return (
<Flex h={'100%'} flexDirection={'column'} px={[3, 0]} pr={[3, 3]}> <Flex h={'100%'} minH={0} flexDirection={'column'} px={[3, 0]} pr={[3, 3]}>
<Header <Header
appForm={appForm} appForm={appForm}
forbiddenSaveSnapshot={forbiddenSaveSnapshot} forbiddenSaveSnapshot={forbiddenSaveSnapshot}
...@@ -84,7 +83,17 @@ const SimpleEdit = () => { ...@@ -84,7 +83,17 @@ const SimpleEdit = () => {
{currentTab === TabEnum.appEdit ? ( {currentTab === TabEnum.appEdit ? (
<Edit appForm={appForm} setAppForm={setAppForm} setPast={setPast} /> <Edit appForm={appForm} setAppForm={setAppForm} setPast={setPast} />
) : ( ) : (
<Box flex={'1 0 0'} h={0} mt={[4, 0]} mb={[2, 4]} bg={'white'} borderRadius={'lg'}> <Box
flex={'1 0 0'}
h={0}
minH={0}
overflowY={'auto'}
overflowX={'hidden'}
mt={[4, 0]}
mb={[2, 4]}
bg={'white'}
borderRadius={'lg'}
>
{currentTab === TabEnum.publish && <PublishChannel />} {currentTab === TabEnum.publish && <PublishChannel />}
{currentTab === TabEnum.logs && <Logs />} {currentTab === TabEnum.logs && <Logs />}
</Box> </Box>
......
...@@ -56,7 +56,17 @@ const WorkflowEdit = () => { ...@@ -56,7 +56,17 @@ const WorkflowEdit = () => {
{currentTab === TabEnum.appEdit ? ( {currentTab === TabEnum.appEdit ? (
<Flow /> <Flow />
) : ( ) : (
<Flex flexDirection={'column'} h={'100%'} mt={'72px'} px={4} pb={4} bg={'white'}> <Flex
flexDirection={'column'}
flex={1}
minH={0}
mt={'72px'}
px={4}
pb={4}
bg={'white'}
overflowY={'auto'}
overflowX={'hidden'}
>
{currentTab === TabEnum.publish && <PublishChannel />} {currentTab === TabEnum.publish && <PublishChannel />}
{currentTab === TabEnum.logs && <Logs />} {currentTab === TabEnum.logs && <Logs />}
</Flex> </Flex>
......
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Box, Flex, Switch } from '@chakra-ui/react'; import { Box, Flex, Grid, Switch } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useForm, useWatch } from 'react-hook-form'; import { useForm, useWatch } from 'react-hook-form';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
...@@ -35,7 +35,6 @@ const PlaygroundVisibilityConfig = ({ appId }: { appId: string }) => { ...@@ -35,7 +35,6 @@ const PlaygroundVisibilityConfig = ({ appId }: { appId: string }) => {
const showFullText = useWatch({ control, name: 'showFullText' }); const showFullText = useWatch({ control, name: 'showFullText' });
const canDownloadSource = useWatch({ control, name: 'canDownloadSource' }); const canDownloadSource = useWatch({ control, name: 'canDownloadSource' });
const showRunningStatus = useWatch({ control, name: 'showRunningStatus' }); const showRunningStatus = useWatch({ control, name: 'showRunningStatus' });
const showSkillReferences = useWatch({ control, name: 'showSkillReferences' });
const showWholeResponse = useWatch({ control, name: 'showWholeResponse' }); const showWholeResponse = useWatch({ control, name: 'showWholeResponse' });
const playgroundLink = useMemo(() => { const playgroundLink = useMemo(() => {
...@@ -76,8 +75,14 @@ const PlaygroundVisibilityConfig = ({ appId }: { appId: string }) => { ...@@ -76,8 +75,14 @@ const PlaygroundVisibilityConfig = ({ appId }: { appId: string }) => {
await saveConfig(values); await saveConfig(values);
}; };
const visibilityItemGridProps = {
templateColumns: { base: 'minmax(0, 1fr) auto', sm: '140px auto' } as const,
columnGap: '28px' as const,
alignItems: 'center' as const
};
return ( return (
<Flex flexDirection="column" h="100%"> <Flex flexDirection="column">
<Box fontSize={'sm'} fontWeight={'medium'} color={'myGray.900'} mb={3}> <Box fontSize={'sm'} fontWeight={'medium'} color={'myGray.900'} mb={3}>
{t('app:publish.playground_link')} {t('app:publish.playground_link')}
</Box> </Box>
...@@ -112,113 +117,112 @@ const PlaygroundVisibilityConfig = ({ appId }: { appId: string }) => { ...@@ -112,113 +117,112 @@ const PlaygroundVisibilityConfig = ({ appId }: { appId: string }) => {
{t('publish:private_config')} {t('publish:private_config')}
</Box> </Box>
<Flex flexDirection="column" gap={4} mt={4}> <Grid
<Flex gap={4} flexWrap={'wrap'}> mt={4}
<Flex alignItems={'center'}> w={'100%'}
<FormLabel fontSize={'12px'} flex={'0 0 127px'}> templateColumns={{ base: '1fr', sm: 'repeat(3, 1fr)' }}
{t('publish:show_node')} columnGap={6}
rowGap={4}
pb={4}
>
<Grid {...visibilityItemGridProps}>
<FormLabel fontSize={'12px'} mb={0} whiteSpace={{ base: 'normal', sm: 'nowrap' }}>
{t('publish:show_node')}
</FormLabel>
<Switch
flexShrink={0}
{...register('showRunningStatus', {
onChange: autoSave
})}
isChecked={showRunningStatus}
/>
</Grid>
<Grid {...visibilityItemGridProps}>
<Flex alignItems={'center'} gap={1}>
<FormLabel fontSize={'12px'} mb={0} whiteSpace={{ base: 'normal', sm: 'nowrap' }}>
{t('app:publish.show_whole_response')}
</FormLabel> </FormLabel>
<Switch <QuestionTip label={t('app:publish.show_whole_response_tip')} />
{...register('showRunningStatus', {
onChange: autoSave
})}
isChecked={showRunningStatus}
/>
</Flex>
<Flex alignItems={'center'}>
<Flex alignItems={'center'} flex={'0 0 127px'}>
<FormLabel fontSize={'12px'}>{t('app:publish.show_whole_response')}</FormLabel>
<QuestionTip ml={1} label={t('app:publish.show_whole_response_tip')} />
</Flex>
<Switch
{...register('showWholeResponse', {
onChange: autoSave
})}
isChecked={showWholeResponse}
/>
</Flex> </Flex>
</Flex> <Switch
<Flex gap={4} flexWrap={'wrap'}> flexShrink={0}
<Flex alignItems={'center'}> {...register('showWholeResponse', {
<Flex alignItems={'center'} flex={'0 0 127px'}> onChange: autoSave
<FormLabel fontSize={'12px'}> })}
{t('common:support.outlink.share.Response Quote')} isChecked={showWholeResponse}
</FormLabel> />
<QuestionTip ml={1} label={t('common:support.outlink.share.Response Quote tips')} /> </Grid>
</Flex>
<Switch <Box display={{ base: 'none', sm: 'block' }} />
{...register('showCite', {
onChange(e) { <Grid {...visibilityItemGridProps}>
if (!e.target.checked) { <Flex alignItems={'center'} gap={1}>
setValue('showFullText', false); <FormLabel fontSize={'12px'} mb={0} whiteSpace={{ base: 'normal', sm: 'nowrap' }}>
setValue('canDownloadSource', false); {t('common:support.outlink.share.Response Quote')}
} </FormLabel>
autoSave(); <QuestionTip label={t('common:support.outlink.share.Response Quote tips')} />
}
})}
isChecked={showCite}
/>
</Flex> </Flex>
<Flex alignItems={'center'}> <Switch
<Flex alignItems={'center'} flex={'0 0 127px'}> flexShrink={0}
<FormLabel fontSize={'12px'}>{t('common:core.app.share.Show full text')}</FormLabel> {...register('showCite', {
<QuestionTip ml={1} label={t('common:support.outlink.share.Show full text tips')} /> onChange(e) {
</Flex> if (!e.target.checked) {
<Switch setValue('showFullText', false);
{...register('showFullText', { setValue('canDownloadSource', false);
onChange(e) {
if (!e.target.checked) {
setValue('canDownloadSource', false);
} else {
setValue('showCite', true);
}
autoSave();
} }
})} autoSave();
isChecked={showFullText} }
/> })}
isChecked={showCite}
/>
</Grid>
<Grid {...visibilityItemGridProps}>
<Flex alignItems={'center'} gap={1}>
<FormLabel fontSize={'12px'} mb={0} whiteSpace={{ base: 'normal', sm: 'nowrap' }}>
{t('common:core.app.share.Show full text')}
</FormLabel>
<QuestionTip label={t('common:support.outlink.share.Show full text tips')} />
</Flex> </Flex>
<Flex alignItems={'center'}> <Switch
<Flex alignItems={'center'} flex={'0 0 127px'}> flexShrink={0}
<FormLabel fontSize={'12px'} fontWeight={'medium'}> {...register('showFullText', {
{t('common:core.app.share.Download source')} onChange(e) {
</FormLabel> if (!e.target.checked) {
<QuestionTip ml={1} label={t('common:support.outlink.share.Download source tips')} /> setValue('canDownloadSource', false);
</Flex> } else {
<Switch setValue('showCite', true);
{...register('canDownloadSource', {
onChange(e) {
if (e.target.checked) {
setValue('showFullText', true);
setValue('showCite', true);
}
autoSave();
} }
})} autoSave();
isChecked={canDownloadSource} }
/> })}
isChecked={showFullText}
/>
</Grid>
<Grid {...visibilityItemGridProps}>
<Flex alignItems={'center'} gap={1}>
<FormLabel fontSize={'12px'} mb={0} whiteSpace={{ base: 'normal', sm: 'nowrap' }}>
{t('common:core.app.share.Download source')}
</FormLabel>
<QuestionTip label={t('common:support.outlink.share.Download source tips')} />
</Flex> </Flex>
</Flex> <Switch
<Flex gap={4} flexWrap={'wrap'}> flexShrink={0}
<Flex alignItems={'center'}> {...register('canDownloadSource', {
<Flex alignItems={'center'} flex={'0 0 127px'}> onChange(e) {
<FormLabel fontSize={'12px'}>{t('publish:show_skill_reference')}</FormLabel> if (e.target.checked) {
<QuestionTip ml={1} label={t('publish:show_skill_reference_tips')} /> setValue('showFullText', true);
</Flex> setValue('showCite', true);
<Switch
{...register('showSkillReferences', {
onChange(e) {
if (e.target.checked) {
setValue('showRunningStatus', true);
}
autoSave();
} }
})} autoSave();
isChecked={showSkillReferences} }
/> })}
</Flex> isChecked={canDownloadSource}
</Flex> />
</Flex> </Grid>
</Grid>
</Flex> </Flex>
); );
}; };
......
import React, { useRef, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { Box, Flex } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant'; import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
...@@ -31,98 +31,95 @@ const OutLink = () => { ...@@ -31,98 +31,95 @@ const OutLink = () => {
const appId = useContextSelector(AppContext, (v) => v.appId); const appId = useContextSelector(AppContext, (v) => v.appId);
const publishList = useRef([ const publishList = useMemo(
{ () => [
icon: '/imgs/modal/shareFill.svg', {
title: t('common:core.app.Share link'), icon: '/imgs/modal/shareFill.svg',
desc: t('common:core.app.Share link desc'), title: t('common:core.app.Share link'),
value: PublishChannelEnum.share, desc: t('common:core.app.Share link desc'),
isProFn: false value: PublishChannelEnum.share,
}, isProFn: false
{ },
icon: 'support/outlink/apikeyFill', {
title: t('common:core.app.Api request'), icon: 'support/outlink/apikeyFill',
desc: t('common:core.app.Api request desc'), title: t('common:core.app.Api request'),
value: PublishChannelEnum.apikey, desc: t('common:core.app.Api request desc'),
isProFn: false value: PublishChannelEnum.apikey,
}, isProFn: false
...(feConfigs?.show_publish_wechat !== false },
? [ ...(feConfigs?.show_publish_wechat !== false
{ ? [
icon: 'core/app/publish/wechat', {
title: t('publish:wechat.bot'), icon: 'core/app/publish/wechat',
desc: t('publish:wechat.bot_desc'), title: t('publish:wechat.bot'),
value: PublishChannelEnum.wechat, desc: t('publish:wechat.bot_desc'),
isProFn: false value: PublishChannelEnum.wechat,
} isProFn: false
] }
: []), ]
...(feConfigs?.show_publish_feishu !== false && : []),
!userInfo?.tags?.includes(UserTagsSchema.enum.wecom) ...(feConfigs?.show_publish_feishu !== false &&
? [ !userInfo?.tags?.includes(UserTagsSchema.enum.wecom)
{ ? [
icon: 'core/app/publish/lark', {
title: t('publish:feishu_bot'), icon: 'core/app/publish/lark',
desc: t('publish:feishu_bot_desc'), title: t('publish:feishu_bot'),
value: PublishChannelEnum.feishu, desc: t('publish:feishu_bot_desc'),
isProFn: true value: PublishChannelEnum.feishu,
} isProFn: true
] }
: []), ]
...(feConfigs?.show_publish_dingtalk !== false && : []),
!userInfo?.tags?.includes(UserTagsSchema.enum.wecom) ...(feConfigs?.show_publish_dingtalk !== false &&
? [ !userInfo?.tags?.includes(UserTagsSchema.enum.wecom)
{ ? [
icon: 'common/dingtalkFill', {
title: t('publish:dingtalk.bot'), icon: 'common/dingtalkFill',
desc: t('publish:dingtalk.bot_desc'), title: t('publish:dingtalk.bot'),
value: PublishChannelEnum.dingtalk, desc: t('publish:dingtalk.bot_desc'),
isProFn: true value: PublishChannelEnum.dingtalk,
} isProFn: true
] }
: []), ]
...(feConfigs?.show_publish_wecom === true : []),
? [ ...(feConfigs?.show_publish_wecom === true
{ ? [
icon: 'core/app/publish/wecom', {
title: t('publish:wecom.bot'), icon: 'core/app/publish/wecom',
desc: t('publish:wecom.bot_desc'), title: t('publish:wecom.bot'),
value: PublishChannelEnum.wecom, desc: t('publish:wecom.bot_desc'),
isProFn: true value: PublishChannelEnum.wecom,
} isProFn: true
] }
: []), ]
...(feConfigs?.show_publish_offiaccount !== false : []),
? [ ...(feConfigs?.show_publish_offiaccount !== false
{ ? [
icon: 'core/app/publish/offiaccount', {
title: t('publish:official_account.name'), icon: 'core/app/publish/offiaccount',
desc: t('publish:official_account.desc'), title: t('publish:official_account.name'),
value: PublishChannelEnum.officialAccount, desc: t('publish:official_account.desc'),
isProFn: true value: PublishChannelEnum.officialAccount,
} isProFn: true
] }
: []), ]
: []),
{ {
icon: 'core/chat/sidebar/home', icon: 'core/chat/sidebar/home',
title: t('common:navbar.Chat'), title: t('common:navbar.Chat'),
desc: t('app:publish.chat_desc'), desc: t('app:publish.chat_desc'),
value: PublishChannelEnum.playground, value: PublishChannelEnum.playground,
isProFn: false isProFn: false
} }
]); ],
[t, feConfigs, userInfo?.tags]
);
const [linkType, setLinkType] = useState<PublishChannelEnum>(PublishChannelEnum.share); const [linkType, setLinkType] = useState<PublishChannelEnum>(PublishChannelEnum.share);
return ( return (
<Box <Box>
display={['block', 'flex']}
overflowY={'auto'}
overflowX={'hidden'}
h={'100%'}
flexDirection={'column'}
>
<Box mx={[4, 8]} py={[4, 6]} borderBottom={'1px solid'} borderColor={'myGray.150'}> <Box mx={[4, 8]} py={[4, 6]} borderBottom={'1px solid'} borderColor={'myGray.150'}>
<MyRadio <MyRadio
gridTemplateColumns={[ gridTemplateColumns={[
...@@ -133,10 +130,10 @@ const OutLink = () => { ...@@ -133,10 +130,10 @@ const OutLink = () => {
'repeat(4, 1fr)' 'repeat(4, 1fr)'
]} ]}
iconSize={'20px'} iconSize={'20px'}
list={publishList.current} list={publishList}
value={linkType} value={linkType}
onChange={(e) => { onChange={(e) => {
const config = publishList.current.find((v) => v.value === e)!; const config = publishList.find((v) => v.value === e)!;
if (!feConfigs.isPlus && config.isProFn) { if (!feConfigs.isPlus && config.isProFn) {
toast({ toast({
status: 'warning', status: 'warning',
...@@ -149,7 +146,7 @@ const OutLink = () => { ...@@ -149,7 +146,7 @@ const OutLink = () => {
/> />
</Box> </Box>
<Flex flexDirection={'column'} mt={2} px={[4, 8]} py={[4, 6]} flex={1}> <Box mt={2} px={[4, 8]} py={[4, 6]}>
{linkType === PublishChannelEnum.share && ( {linkType === PublishChannelEnum.share && (
<Link appId={appId} type={PublishChannelEnum.share} /> <Link appId={appId} type={PublishChannelEnum.share} />
)} )}
...@@ -160,7 +157,7 @@ const OutLink = () => { ...@@ -160,7 +157,7 @@ const OutLink = () => {
{linkType === PublishChannelEnum.officialAccount && <OffiAccount appId={appId} />} {linkType === PublishChannelEnum.officialAccount && <OffiAccount appId={appId} />}
{linkType === PublishChannelEnum.wechat && <Wechat appId={appId} />} {linkType === PublishChannelEnum.wechat && <Wechat appId={appId} />}
{linkType === PublishChannelEnum.playground && <Playground appId={appId} />} {linkType === PublishChannelEnum.playground && <Playground appId={appId} />}
</Flex> </Box>
</Box> </Box>
); );
}; };
......
...@@ -62,7 +62,17 @@ const WorkflowEdit = () => { ...@@ -62,7 +62,17 @@ const WorkflowEdit = () => {
{currentTab === TabEnum.appEdit ? ( {currentTab === TabEnum.appEdit ? (
<Flow /> <Flow />
) : ( ) : (
<Flex flexDirection={'column'} h={'100%'} mt={'72px'} px={4} pb={4} bg={'white'}> <Flex
flexDirection={'column'}
flex={1}
minH={0}
mt={'72px'}
px={4}
pb={4}
bg={'white'}
overflowY={'auto'}
overflowX={'hidden'}
>
{currentTab === TabEnum.publish && <PublishChannel />} {currentTab === TabEnum.publish && <PublishChannel />}
{currentTab === TabEnum.logs && <Logs />} {currentTab === TabEnum.logs && <Logs />}
</Flex> </Flex>
......
...@@ -845,7 +845,7 @@ const NodeAgent = ({ data, selected }: NodeProps<FlowNodeItemType>) => { ...@@ -845,7 +845,7 @@ const NodeAgent = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
<Box mb={5}> <Box mb={5}>
<Flex className="nodrag" cursor={'default'} alignItems={'center'}> <Flex className="nodrag" cursor={'default'} alignItems={'center'}>
<FormLabel color={'myGray.600'}>{t('common:core.dataset.Dataset')}</FormLabel> <FormLabel color={'myGray.600'}>{t('common:core.dataset.Dataset')}</FormLabel>
{authTmbIdInput && ( {feConfigs?.isPlus && authTmbIdInput && (
<Flex ml={2} alignItems={'center'}> <Flex ml={2} alignItems={'center'}>
<Box fontSize={'sm'} color={'myGray.600'} whiteSpace={'nowrap'}> <Box fontSize={'sm'} color={'myGray.600'} whiteSpace={'nowrap'}>
{t('workflow:auth_tmb_id')} {t('workflow:auth_tmb_id')}
......
...@@ -11,6 +11,7 @@ import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; ...@@ -11,6 +11,7 @@ import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { WorkflowActionsContext } from '@/pageComponents/app/detail/WorkflowComponents/context/workflowActionsContext'; import { WorkflowActionsContext } from '@/pageComponents/app/detail/WorkflowComponents/context/workflowActionsContext';
import DatasetCard from '@/components/core/app/DatasetCard'; import DatasetCard from '@/components/core/app/DatasetCard';
import { useSystemStore } from '@/web/common/system/useSystemStore';
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal')); const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
...@@ -133,6 +134,7 @@ export const SwitchAuthTmb = React.memo(function SwitchAuthTmb({ ...@@ -133,6 +134,7 @@ export const SwitchAuthTmb = React.memo(function SwitchAuthTmb({
nodeId nodeId
}: RenderInputProps) { }: RenderInputProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const { feConfigs } = useSystemStore();
const onChangeNode = useContextSelector(WorkflowActionsContext, (v) => v.onChangeNode); const onChangeNode = useContextSelector(WorkflowActionsContext, (v) => v.onChangeNode);
const authTmbIdInput = useMemo( const authTmbIdInput = useMemo(
...@@ -140,7 +142,7 @@ export const SwitchAuthTmb = React.memo(function SwitchAuthTmb({ ...@@ -140,7 +142,7 @@ export const SwitchAuthTmb = React.memo(function SwitchAuthTmb({
[inputs] [inputs]
); );
return authTmbIdInput ? ( return feConfigs?.isPlus && authTmbIdInput ? (
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<Box fontSize={'sm'}>{t('workflow:auth_tmb_id')}</Box> <Box fontSize={'sm'}>{t('workflow:auth_tmb_id')}</Box>
<QuestionTip label={t('workflow:auth_tmb_id_tip')} /> <QuestionTip label={t('workflow:auth_tmb_id_tip')} />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment