Commit 870ea89b by Archer Committed by GitHub

perf: invalid quote filter (#7139)

* perf: invalid quote filter

* doc

* perf: user edit

* fxi: template

* fix: tempalte init
parent 9796796b
...@@ -28,6 +28,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initSandboxArchive' \ ...@@ -28,6 +28,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initSandboxArchive' \
2. 优化应用、知识库、文件和文件夹等长名称展示:超出宽度时自动省略,并在 hover 名称时展示完整内容。 2. 优化应用、知识库、文件和文件夹等长名称展示:超出宽度时自动省略,并在 hover 名称时展示完整内容。
3. 移除所有内置 LLM 请求中的 `temperature` 和 `max_tokens`,避免部分模型不兼容。 3. 移除所有内置 LLM 请求中的 `temperature` 和 `max_tokens`,避免部分模型不兼容。
4. 知识库训练出现错误时的提示,同时支持一键全部重试。 4. 知识库训练出现错误时的提示,同时支持一键全部重试。
5. 过滤掉无效的知识库引用角标。
## 🐛 修复 ## 🐛 修复
......
...@@ -25,8 +25,8 @@ ...@@ -25,8 +25,8 @@
"content/guide/build/publish/mcp_server.mdx": "2026-05-07T15:06:40+08:00", "content/guide/build/publish/mcp_server.mdx": "2026-05-07T15:06:40+08:00",
"content/guide/build/publish/official_account.en.mdx": "2026-05-07T15:06:40+08:00", "content/guide/build/publish/official_account.en.mdx": "2026-05-07T15:06:40+08:00",
"content/guide/build/publish/official_account.mdx": "2026-05-07T15:06:40+08:00", "content/guide/build/publish/official_account.mdx": "2026-05-07T15:06:40+08:00",
"content/guide/build/publish/openapi.en.mdx": "2026-05-07T15:06:40+08:00", "content/guide/build/publish/openapi.en.mdx": "2026-06-18T10:43:42+08:00",
"content/guide/build/publish/openapi.mdx": "2026-05-07T15:06:40+08:00", "content/guide/build/publish/openapi.mdx": "2026-06-18T10:43:42+08:00",
"content/guide/build/publish/wechat.en.mdx": "2026-05-07T15:06:40+08:00", "content/guide/build/publish/wechat.en.mdx": "2026-05-07T15:06:40+08:00",
"content/guide/build/publish/wechat.mdx": "2026-05-07T15:06:40+08:00", "content/guide/build/publish/wechat.mdx": "2026-05-07T15:06:40+08:00",
"content/guide/build/publish/wecom.en.mdx": "2026-05-07T15:06:40+08:00", "content/guide/build/publish/wecom.en.mdx": "2026-05-07T15:06:40+08:00",
...@@ -139,14 +139,14 @@ ...@@ -139,14 +139,14 @@
"content/guide/workspace/team/team_roles_permissions.mdx": "2026-05-07T15:06:40+08:00", "content/guide/workspace/team/team_roles_permissions.mdx": "2026-05-07T15:06:40+08:00",
"content/openapi/app.en.mdx": "2026-05-29T19:31:16+08:00", "content/openapi/app.en.mdx": "2026-05-29T19:31:16+08:00",
"content/openapi/app.mdx": "2026-05-29T19:31:16+08:00", "content/openapi/app.mdx": "2026-05-29T19:31:16+08:00",
"content/openapi/chat.en.mdx": "2026-05-29T19:31:16+08:00", "content/openapi/chat.en.mdx": "2026-06-18T10:43:42+08:00",
"content/openapi/chat.mdx": "2026-05-29T19:31:16+08:00", "content/openapi/chat.mdx": "2026-06-18T10:43:42+08:00",
"content/openapi/dataset.en.mdx": "2026-05-29T19:31:16+08:00", "content/openapi/dataset.en.mdx": "2026-05-29T19:31:16+08:00",
"content/openapi/dataset.mdx": "2026-05-29T19:31:16+08:00", "content/openapi/dataset.mdx": "2026-05-29T19:31:16+08:00",
"content/openapi/index.en.mdx": "2026-04-26T21:08:47+08:00", "content/openapi/index.en.mdx": "2026-04-26T21:08:47+08:00",
"content/openapi/index.mdx": "2026-04-26T21:08:47+08:00", "content/openapi/index.mdx": "2026-04-26T21:08:47+08:00",
"content/openapi/intro.en.mdx": "2026-05-29T21:03:14+08:00", "content/openapi/intro.en.mdx": "2026-06-18T10:43:42+08:00",
"content/openapi/intro.mdx": "2026-05-29T21:03:14+08:00", "content/openapi/intro.mdx": "2026-06-18T10:43:42+08:00",
"content/openapi/share.en.mdx": "2026-04-26T21:08:47+08:00", "content/openapi/share.en.mdx": "2026-04-26T21:08:47+08:00",
"content/openapi/share.mdx": "2026-04-26T21:08:47+08:00", "content/openapi/share.mdx": "2026-04-26T21:08:47+08:00",
"content/plugin/index.en.mdx": "2026-06-04T16:10:15+08:00", "content/plugin/index.en.mdx": "2026-06-04T16:10:15+08:00",
...@@ -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-17T17:02:34+08:00", "content/self-host/upgrading/4-15/41505.mdx": "2026-06-17T18:19:09+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",
......
...@@ -9,11 +9,48 @@ const getFileTemplates = async (): Promise<AppTemplateSchemaType[]> => { ...@@ -9,11 +9,48 @@ const getFileTemplates = async (): Promise<AppTemplateSchemaType[]> => {
return (await pluginClient.listWorkflows()) as AppTemplateSchemaType[]; return (await pluginClient.listWorkflows()) as AppTemplateSchemaType[];
}; };
const formatTemplateAvatar = (avatar?: string | null) => {
if (!avatar) {
return '';
}
if (avatar.startsWith('/') || avatar.startsWith('http://') || avatar.startsWith('https://')) {
return avatar;
}
return `/${avatar}`;
};
/**
* 提取系统模板允许由数据库覆盖的运营字段。
*
* 系统模板来自 plugin 服务,workflow、userGuide、type 等内容字段必须始终以 plugin 返回为准。
* 数据库只作为展示卡片、上下线、推荐和标签等运营配置层,避免后台保存的旧 workflow 覆盖新版模板。
*/
const pickPluginSystemTemplateEditableConfig = (config: AppTemplateSchemaType) => ({
...Object.fromEntries(
Object.entries({
name: config.name,
intro: config.intro,
avatar: formatTemplateAvatar(config.avatar),
tags: config.tags,
isActive: config.isActive,
isPromoted: config.isPromoted,
promoteTags: config.promoteTags,
hideTags: config.hideTags,
recommendText: config.recommendText,
isQuickTemplate: config.isQuickTemplate,
order: config.order
}).filter(([, value]) => value !== undefined)
)
});
const getAppTemplates = async () => { const getAppTemplates = async () => {
const originCommunityTemplates = await getFileTemplates(); const originCommunityTemplates = await getFileTemplates();
const communityTemplates = originCommunityTemplates.map((template) => { const communityTemplates = originCommunityTemplates.map((template) => {
return { return {
...template, ...template,
avatar: formatTemplateAvatar(template.avatar),
templateId: `${AppToolSourceEnum.community}-${template.templateId.split('.')[0]}` templateId: `${AppToolSourceEnum.community}-${template.templateId.split('.')[0]}`
}; };
}); });
...@@ -26,8 +63,9 @@ const getAppTemplates = async () => { ...@@ -26,8 +63,9 @@ const getAppTemplates = async () => {
if (config) { if (config) {
return { return {
...config,
...template, ...template,
...config ...pickPluginSystemTemplateEditableConfig(config)
}; };
} }
...@@ -72,5 +110,13 @@ export const getAppTemplatesAndLoadThem = async (refresh = false) => { ...@@ -72,5 +110,13 @@ export const getAppTemplatesAndLoadThem = async (refresh = false) => {
}; };
export const isCommercialTemaplte = (templateId: string) => { export const isCommercialTemaplte = (templateId: string) => {
return templateId.startsWith(AppToolSourceEnum.commercial); return templateId.startsWith(`${AppToolSourceEnum.commercial}-`);
};
/**
* 判断模板是否来自 fastgpt-plugin 的系统模板列表。
* 系统模板的 workflow 和 userGuide 由 plugin 服务维护,本地数据库只保存展示和上下线等覆盖配置。
*/
export const isPluginSystemTemplate = (templateId: string) => {
return templateId.startsWith(`${AppToolSourceEnum.community}-`);
}; };
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { AppToolSourceEnum } from '@fastgpt/global/core/app/tool/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
const mocks = vi.hoisted(() => ({
listWorkflows: vi.fn(),
findTemplates: vi.fn()
}));
vi.mock('@fastgpt/service/thirdProvider/fastgptPlugin', () => ({
pluginClient: {
listWorkflows: mocks.listWorkflows
}
}));
vi.mock('@fastgpt/service/core/app/templates/templateSchema', () => ({
MongoAppTemplate: {
find: mocks.findTemplates
}
}));
import { getAppTemplatesAndLoadThem } from '@fastgpt/service/core/app/templates/register';
beforeEach(() => {
vi.clearAllMocks();
global.appTemplates = [];
global.templatesRefreshTime = 0;
});
describe('getAppTemplatesAndLoadThem', () => {
it('keeps plugin workflow fields authoritative while applying editable database config', async () => {
const pluginWorkflow = {
nodes: [
{
nodeId: 'plugin-start',
flowNodeType: FlowNodeTypeEnum.workflowStart,
name: 'Plugin start',
inputs: [],
outputs: []
}
],
edges: [],
chatConfig: {
welcomeText: 'plugin welcome'
}
};
const staleDbWorkflow = {
nodes: [
{
nodeId: 'stale-db-node',
flowNodeType: FlowNodeTypeEnum.workflowStart,
name: 'Stale DB node',
inputs: [
{
key: 'questionGuide',
valueType: 'hidden',
renderTypeList: ['hidden'],
label: 'core.app.Question Guide'
}
],
outputs: []
}
],
edges: [],
chatConfig: {
welcomeText: 'stale db welcome'
}
};
mocks.listWorkflows.mockResolvedValue([
{
templateId: 'githubIssue',
name: 'Plugin name',
intro: 'Plugin intro',
avatar: 'core/app/templates/githubIssue',
tags: ['plugin-tag'],
type: AppTypeEnum.workflow,
author: 'plugin author',
userGuide: {
type: 'link',
content: 'https://plugin.example.com'
},
workflow: pluginWorkflow,
order: 10
}
]);
mocks.findTemplates.mockReturnValue({
lean: vi.fn().mockResolvedValue([
{
templateId: `${AppToolSourceEnum.community}-githubIssue`,
name: 'DB name',
intro: 'DB intro',
avatar: 'db-avatar',
tags: ['db-tag'],
type: AppTypeEnum.simple,
isActive: false,
isPromoted: true,
recommendText: 'DB recommend',
userGuide: {
type: 'link',
content: 'https://db.example.com'
},
workflow: staleDbWorkflow,
order: 1
}
])
});
const [template] = await getAppTemplatesAndLoadThem(true);
expect(template).toMatchObject({
templateId: `${AppToolSourceEnum.community}-githubIssue`,
name: 'DB name',
intro: 'DB intro',
avatar: '/db-avatar',
tags: ['db-tag'],
type: AppTypeEnum.workflow,
isActive: false,
isPromoted: true,
recommendText: 'DB recommend',
order: 1
});
expect(template?.workflow).toEqual(pluginWorkflow);
expect(template?.userGuide).toEqual({
type: 'link',
content: 'https://plugin.example.com'
});
});
});
Subproject commit 860cc780847d747e7fdd24433e95887cdb341812 Subproject commit 465869779095ca30d14a2852c220dd2d896a6c67
...@@ -30,6 +30,7 @@ export type AProps = { ...@@ -30,6 +30,7 @@ export type AProps = {
chatId: string; chatId: string;
chatItemDataId: string; chatItemDataId: string;
} & OutLinkChatAuthProps; } & OutLinkChatAuthProps;
allowedCitationIds?: Set<string>;
onOpenCiteModal?: (e?: { onOpenCiteModal?: (e?: {
collectionId?: string; collectionId?: string;
sourceId?: string; sourceId?: string;
...@@ -202,6 +203,7 @@ const CiteLink = React.memo(function CiteLink({ ...@@ -202,6 +203,7 @@ const CiteLink = React.memo(function CiteLink({
const A = ({ const A = ({
children, children,
chatAuthData, chatAuthData,
allowedCitationIds,
onOpenCiteModal, onOpenCiteModal,
showAnimation, showAnimation,
...props ...props
...@@ -222,6 +224,11 @@ const A = ({ ...@@ -222,6 +224,11 @@ const A = ({
(props.href?.startsWith('CITE') || props.href?.startsWith('QUOTE')) && (props.href?.startsWith('CITE') || props.href?.startsWith('QUOTE')) &&
typeof content === 'string' typeof content === 'string'
) { ) {
console.log(allowedCitationIds, allowedCitationIds?.has(content));
if (allowedCitationIds && !allowedCitationIds.has(content)) {
return null;
}
return ( return (
<CiteLink <CiteLink
id={content} id={content}
......
...@@ -51,7 +51,7 @@ function MarkdownCodeRenderer(props: any) { ...@@ -51,7 +51,7 @@ function MarkdownCodeRenderer(props: any) {
} }
function MarkdownLinkRenderer(props: any) { function MarkdownLinkRenderer(props: any) {
const { showAnimation, chatAuthData, onOpenCiteModal } = useContext( const { showAnimation, chatAuthData, allowedCitationIds, onOpenCiteModal } = useContext(
MarkdownRendererRuntimeContext MarkdownRendererRuntimeContext
); );
...@@ -60,6 +60,7 @@ function MarkdownLinkRenderer(props: any) { ...@@ -60,6 +60,7 @@ function MarkdownLinkRenderer(props: any) {
{...props} {...props}
showAnimation={showAnimation} showAnimation={showAnimation}
chatAuthData={chatAuthData} chatAuthData={chatAuthData}
allowedCitationIds={allowedCitationIds}
onOpenCiteModal={onOpenCiteModal} onOpenCiteModal={onOpenCiteModal}
/> />
); );
...@@ -100,6 +101,7 @@ const MarkdownRender = ({ ...@@ -100,6 +101,7 @@ const MarkdownRender = ({
autoPreviewHtmlCodeBlock, autoPreviewHtmlCodeBlock,
chatAuthData, chatAuthData,
allowedCitationIds,
onOpenCiteModal onOpenCiteModal
}: Props) => { }: Props) => {
const renderContextValue = useMemo( const renderContextValue = useMemo(
...@@ -108,9 +110,17 @@ const MarkdownRender = ({ ...@@ -108,9 +110,17 @@ const MarkdownRender = ({
autoPreviewHtmlCodeBlock, autoPreviewHtmlCodeBlock,
markdownClassName: className, markdownClassName: className,
chatAuthData, chatAuthData,
allowedCitationIds,
onOpenCiteModal onOpenCiteModal
}), }),
[autoPreviewHtmlCodeBlock, chatAuthData, className, onOpenCiteModal, showAnimation] [
allowedCitationIds,
autoPreviewHtmlCodeBlock,
chatAuthData,
className,
onOpenCiteModal,
showAnimation
]
); );
const formatSource = useMemo(() => { const formatSource = useMemo(() => {
......
...@@ -6,6 +6,7 @@ export type MarkdownRendererRuntimeContextValue = { ...@@ -6,6 +6,7 @@ export type MarkdownRendererRuntimeContextValue = {
autoPreviewHtmlCodeBlock?: boolean; autoPreviewHtmlCodeBlock?: boolean;
markdownClassName?: string; markdownClassName?: string;
chatAuthData?: AProps['chatAuthData']; chatAuthData?: AProps['chatAuthData'];
allowedCitationIds?: AProps['allowedCitationIds'];
onOpenCiteModal?: AProps['onOpenCiteModal']; onOpenCiteModal?: AProps['onOpenCiteModal'];
}; };
......
...@@ -24,6 +24,7 @@ type AIChatBubbleContentProps = { ...@@ -24,6 +24,7 @@ type AIChatBubbleContentProps = {
responseData?: ChatHistoryItemResType[]; responseData?: ChatHistoryItemResType[];
isLastChild: boolean; isLastChild: boolean;
isChatting: boolean; isChatting: boolean;
allowedCitationIds?: Set<string>;
onOpenCiteModal: (e?: OnOpenCiteModalProps) => void; onOpenCiteModal: (e?: OnOpenCiteModalProps) => void;
}; };
...@@ -33,6 +34,7 @@ const AIChatBubbleContent = ({ ...@@ -33,6 +34,7 @@ const AIChatBubbleContent = ({
dataId, dataId,
isLastChild, isLastChild,
isChatting, isChatting,
allowedCitationIds,
onOpenCiteModal onOpenCiteModal
}: AIChatBubbleContentProps) => { }: AIChatBubbleContentProps) => {
const renderValue = ({ const renderValue = ({
...@@ -67,6 +69,7 @@ const AIChatBubbleContent = ({ ...@@ -67,6 +69,7 @@ const AIChatBubbleContent = ({
isLastChild={isLastChild} isLastChild={isLastChild}
isChatting={isChatting} isChatting={isChatting}
onOpenCiteModal={onOpenCiteModal} onOpenCiteModal={onOpenCiteModal}
allowedCitationIds={allowedCitationIds}
wrapProcessing={wrapProcessing} wrapProcessing={wrapProcessing}
showProcessing={showProcessing} showProcessing={showProcessing}
showFoldableProcessing={showFoldableProcessing} showFoldableProcessing={showFoldableProcessing}
......
...@@ -28,6 +28,7 @@ type AIChatBubbleProps = { ...@@ -28,6 +28,7 @@ type AIChatBubbleProps = {
isChatting: boolean; isChatting: boolean;
loadingText?: string; loadingText?: string;
questionGuides: string[]; questionGuides: string[];
allowedCitationIds?: Set<string>;
onOpenCiteModal: (e?: OnOpenCiteModalProps) => void; onOpenCiteModal: (e?: OnOpenCiteModalProps) => void;
chatControllerProps: ChatControllerProps; chatControllerProps: ChatControllerProps;
children?: React.ReactNode; children?: React.ReactNode;
...@@ -42,6 +43,7 @@ const AIChatBubble = ({ ...@@ -42,6 +43,7 @@ const AIChatBubble = ({
isChatting, isChatting,
loadingText, loadingText,
questionGuides, questionGuides,
allowedCitationIds,
onOpenCiteModal, onOpenCiteModal,
chatControllerProps, chatControllerProps,
children children
...@@ -79,6 +81,7 @@ const AIChatBubble = ({ ...@@ -79,6 +81,7 @@ const AIChatBubble = ({
dataId={chat.dataId} dataId={chat.dataId}
isLastChild={isLastChild && isLastValueGroup} isLastChild={isLastChild && isLastValueGroup}
isChatting={isChatting} isChatting={isChatting}
allowedCitationIds={allowedCitationIds}
onOpenCiteModal={onOpenCiteModal} onOpenCiteModal={onOpenCiteModal}
/> />
{showNoOutputTip && ( {showNoOutputTip && (
......
...@@ -80,6 +80,12 @@ const ChatItem = (props: Props) => { ...@@ -80,6 +80,12 @@ const ChatItem = (props: Props) => {
const statisticalChatItem = useMemoEnhance(() => addStatisticalDataToHistoryItem(chat), [chat]); const statisticalChatItem = useMemoEnhance(() => addStatisticalDataToHistoryItem(chat), [chat]);
const quoteList: SearchDataResponseQuoteListItemType[] = statisticalChatItem.totalQuoteList ?? []; const quoteList: SearchDataResponseQuoteListItemType[] = statisticalChatItem.totalQuoteList ?? [];
const allowedCitationIds = useMemoEnhance(() => {
const sourceQuoteList = statisticalChatItem.totalQuoteList;
if (!sourceQuoteList) return;
return new Set(sourceQuoteList.map((item) => item.id).filter((id): id is string => !!id));
}, [statisticalChatItem.totalQuoteList]);
const { errorText } = statisticalChatItem; const { errorText } = statisticalChatItem;
const inlineErrorInfo = useMemo(() => { const inlineErrorInfo = useMemo(() => {
if (!chat.errorMsg && !errorText) return; if (!chat.errorMsg && !errorText) return;
...@@ -315,6 +321,7 @@ const ChatItem = (props: Props) => { ...@@ -315,6 +321,7 @@ const ChatItem = (props: Props) => {
<HumanChatBubble <HumanChatBubble
chatValue={value as UserChatItemValueItemType[]} chatValue={value as UserChatItemValueItemType[]}
chatTime={i === splitAiResponseResults.length - 1 ? chat.time : undefined} chatTime={i === splitAiResponseResults.length - 1 ? chat.time : undefined}
canEdit={!isChatting}
onEditSubmit={onEditSubmit} onEditSubmit={onEditSubmit}
> >
{renderCommonFooter()} {renderCommonFooter()}
...@@ -341,6 +348,7 @@ const ChatItem = (props: Props) => { ...@@ -341,6 +348,7 @@ const ChatItem = (props: Props) => {
isChatting={isChatting} isChatting={isChatting}
loadingText={showRunningStatus ? statusBoxData?.name : undefined} loadingText={showRunningStatus ? statusBoxData?.name : undefined}
questionGuides={questionGuides} questionGuides={questionGuides}
allowedCitationIds={allowedCitationIds}
onOpenCiteModal={onOpenCiteModal} onOpenCiteModal={onOpenCiteModal}
chatControllerProps={{ chatControllerProps={{
...props, ...props,
......
...@@ -10,6 +10,7 @@ type HumanChatBubbleActionsProps = { ...@@ -10,6 +10,7 @@ type HumanChatBubbleActionsProps = {
chatText: string; chatText: string;
chatTime?: Date; chatTime?: Date;
onEdit: () => void; onEdit: () => void;
canEdit?: boolean;
isAlwaysVisible?: boolean; isAlwaysVisible?: boolean;
}; };
...@@ -17,6 +18,7 @@ const HumanChatBubbleActions = ({ ...@@ -17,6 +18,7 @@ const HumanChatBubbleActions = ({
chatText, chatText,
chatTime, chatTime,
onEdit, onEdit,
canEdit = true,
isAlwaysVisible = false isAlwaysVisible = false
}: HumanChatBubbleActionsProps) => { }: HumanChatBubbleActionsProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
...@@ -58,6 +60,7 @@ const HumanChatBubbleActions = ({ ...@@ -58,6 +60,7 @@ const HumanChatBubbleActions = ({
onClick={() => copyData(chatText)} onClick={() => copyData(chatText)}
/> />
</MyTooltip> </MyTooltip>
{canEdit && (
<MyTooltip label={t('common:Edit')}> <MyTooltip label={t('common:Edit')}>
<MyIcon <MyIcon
w={'16px'} w={'16px'}
...@@ -69,6 +72,7 @@ const HumanChatBubbleActions = ({ ...@@ -69,6 +72,7 @@ const HumanChatBubbleActions = ({
onClick={onEdit} onClick={onEdit}
/> />
</MyTooltip> </MyTooltip>
)}
</Flex> </Flex>
</Flex> </Flex>
); );
......
...@@ -14,6 +14,7 @@ type HumanChatBubbleProps = { ...@@ -14,6 +14,7 @@ type HumanChatBubbleProps = {
chatValue: UserChatItemValueItemType[]; chatValue: UserChatItemValueItemType[];
chatTime?: Date; chatTime?: Date;
onEditSubmit?: (input: ChatBoxInputType) => void | Promise<void>; onEditSubmit?: (input: ChatBoxInputType) => void | Promise<void>;
canEdit?: boolean;
children?: React.ReactNode; children?: React.ReactNode;
}; };
...@@ -23,7 +24,13 @@ type HumanChatBubbleProps = { ...@@ -23,7 +24,13 @@ type HumanChatBubbleProps = {
* 用户气泡的宽度、附件单列展示和 hover 操作区与 AI 气泡完全隔离,避免影响流式 * 用户气泡的宽度、附件单列展示和 hover 操作区与 AI 气泡完全隔离,避免影响流式
* AI 回复的原有宽度计算。编辑按钮当前只提供 UI 入口,后续需要接入消息编辑接口。 * AI 回复的原有宽度计算。编辑按钮当前只提供 UI 入口,后续需要接入消息编辑接口。
*/ */
const HumanChatBubble = ({ chatValue, chatTime, onEditSubmit, children }: HumanChatBubbleProps) => { const HumanChatBubble = ({
chatValue,
chatTime,
onEditSubmit,
canEdit = true,
children
}: HumanChatBubbleProps) => {
const { isPc } = useSystem(); const { isPc } = useSystem();
const { text: chatText = '', files = [] } = useMemo( const { text: chatText = '', files = [] } = useMemo(
() => formatChatValue2InputType(chatValue), () => formatChatValue2InputType(chatValue),
...@@ -79,6 +86,7 @@ const HumanChatBubble = ({ chatValue, chatTime, onEditSubmit, children }: HumanC ...@@ -79,6 +86,7 @@ const HumanChatBubble = ({ chatValue, chatTime, onEditSubmit, children }: HumanC
chatText={chatText} chatText={chatText}
chatTime={chatTime} chatTime={chatTime}
isAlwaysVisible={!isPc} isAlwaysVisible={!isPc}
canEdit={canEdit}
onEdit={() => setIsEditing(true)} onEdit={() => setIsEditing(true)}
/> />
</Box> </Box>
......
...@@ -15,12 +15,14 @@ const RenderText = React.memo(function RenderText({ ...@@ -15,12 +15,14 @@ const RenderText = React.memo(function RenderText({
text, text,
chatItemDataId, chatItemDataId,
onOpenCiteModal, onOpenCiteModal,
allowedCitationIds,
isDisabled isDisabled
}: { }: {
showAnimation: boolean; showAnimation: boolean;
text: string; text: string;
chatItemDataId: string; chatItemDataId: string;
onOpenCiteModal?: (e?: OnOpenCiteModalProps) => void; onOpenCiteModal?: (e?: OnOpenCiteModalProps) => void;
allowedCitationIds?: Set<string>;
isDisabled?: boolean; isDisabled?: boolean;
}) { }) {
const appId = useContextSelector(WorkflowRuntimeContext, (v) => v.appId); const appId = useContextSelector(WorkflowRuntimeContext, (v) => v.appId);
...@@ -47,6 +49,7 @@ const RenderText = React.memo(function RenderText({ ...@@ -47,6 +49,7 @@ const RenderText = React.memo(function RenderText({
source={source} source={source}
showAnimation={showAnimation} showAnimation={showAnimation}
chatAuthData={chatAuthData} chatAuthData={chatAuthData}
allowedCitationIds={allowedCitationIds}
onOpenCiteModal={onOpenCiteModal} onOpenCiteModal={onOpenCiteModal}
isDisabled={isDisabled} isDisabled={isDisabled}
autoPreviewHtmlCodeBlock autoPreviewHtmlCodeBlock
......
...@@ -30,6 +30,7 @@ const AIResponseBox = ({ ...@@ -30,6 +30,7 @@ const AIResponseBox = ({
isLastChild, isLastChild,
isChatting, isChatting,
onOpenCiteModal, onOpenCiteModal,
allowedCitationIds,
wrapProcessing = true, wrapProcessing = true,
showProcessing = true, showProcessing = true,
showFoldableProcessing: showFoldableProcessingProp = true, showFoldableProcessing: showFoldableProcessingProp = true,
...@@ -45,6 +46,7 @@ const AIResponseBox = ({ ...@@ -45,6 +46,7 @@ const AIResponseBox = ({
isLastChild: boolean; isLastChild: boolean;
isChatting: boolean; isChatting: boolean;
onOpenCiteModal?: (e?: OnOpenCiteModalProps) => void; onOpenCiteModal?: (e?: OnOpenCiteModalProps) => void;
allowedCitationIds?: Set<string>;
wrapProcessing?: boolean; wrapProcessing?: boolean;
showProcessing?: boolean; showProcessing?: boolean;
showFoldableProcessing?: boolean; showFoldableProcessing?: boolean;
...@@ -146,6 +148,7 @@ const AIResponseBox = ({ ...@@ -146,6 +148,7 @@ const AIResponseBox = ({
showAnimation={isChatting && isLastResponseValue} showAnimation={isChatting && isLastResponseValue}
text={textContent} text={textContent}
onOpenCiteModal={onOpenCiteModal} onOpenCiteModal={onOpenCiteModal}
allowedCitationIds={allowedCitationIds}
isDisabled={disableStreamingInteraction} isDisabled={disableStreamingInteraction}
/> />
); );
......
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