Skip to content

Commit c616655

Browse files
committed
🐛 Unify Agent save icon #1883
1 parent eca4c88 commit c616655

File tree

4 files changed

+104
-74
lines changed

4 files changed

+104
-74
lines changed

frontend/app/[locale]/agents/components/agent/AgentConfigModal.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,13 +1071,13 @@ export default function AgentConfigModal({
10711071
const tooltipText = getButtonTitle();
10721072
return (
10731073
tooltipText ||
1074-
t("businessLogic.config.button.saveToAgentPool")
1074+
t("common.save")
10751075
);
10761076
}
1077-
return t("businessLogic.config.button.saveToAgentPool");
1077+
return t("common.save");
10781078
})()}
10791079
>
1080-
{t("businessLogic.config.button.saveToAgentPool")}
1080+
{t("common.save")}
10811081
</Button>
10821082
) : (
10831083
<Button

frontend/components/ui/markdownRenderer.tsx

Lines changed: 101 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,102 @@ const convertLatexDelimiters = (content: string): string => {
423423
);
424424
};
425425

426+
// Video component with error handling - defined outside to prevent re-creation on each render
427+
interface VideoWithErrorHandlingProps {
428+
src: string;
429+
alt?: string | null;
430+
props?: React.VideoHTMLAttributes<HTMLVideoElement>;
431+
}
432+
433+
const VideoWithErrorHandling: React.FC<VideoWithErrorHandlingProps> = React.memo(({ src, alt, props = {} }) => {
434+
const { t } = useTranslation("common");
435+
const [hasError, setHasError] = React.useState(false);
436+
437+
if (hasError) {
438+
return (
439+
<div className="markdown-media-error">
440+
<div className="markdown-media-error-message">
441+
{t("chatStreamMessage.videoLinkUnavailable", {
442+
defaultValue: "This video link is unavailable",
443+
})}
444+
</div>
445+
{alt && (
446+
<div className="markdown-media-error-caption">{alt}</div>
447+
)}
448+
</div>
449+
);
450+
}
451+
452+
return (
453+
<figure className="markdown-video-wrapper">
454+
<video
455+
className="markdown-video"
456+
controls
457+
preload="metadata"
458+
playsInline
459+
src={src}
460+
onError={() => setHasError(true)}
461+
{...props}
462+
>
463+
{t("chatStreamMessage.videoNotSupported", {
464+
defaultValue: "Sorry, your browser does not support embedded videos.",
465+
})}
466+
</video>
467+
{alt ? (
468+
<figcaption className="markdown-video-caption">{alt}</figcaption>
469+
) : null}
470+
</figure>
471+
);
472+
}, (prevProps, nextProps) => {
473+
// Custom comparison function to prevent unnecessary re-renders
474+
// Only compare src and alt, props object reference may change but content is the same
475+
return prevProps.src === nextProps.src &&
476+
prevProps.alt === nextProps.alt;
477+
});
478+
479+
VideoWithErrorHandling.displayName = "VideoWithErrorHandling";
480+
481+
// Image component with error handling - defined outside to prevent re-creation on each render
482+
interface ImageWithErrorHandlingProps {
483+
src: string;
484+
alt?: string | null;
485+
}
486+
487+
const ImageWithErrorHandling: React.FC<ImageWithErrorHandlingProps> = React.memo(({ src, alt }) => {
488+
const { t } = useTranslation("common");
489+
const [hasError, setHasError] = React.useState(false);
490+
491+
if (hasError) {
492+
return (
493+
<div className="markdown-media-error">
494+
<div className="markdown-media-error-message">
495+
{t("chatStreamMessage.imageLinkUnavailable", {
496+
defaultValue: "This image link is unavailable",
497+
})}
498+
</div>
499+
{alt && (
500+
<div className="markdown-media-error-caption">{alt}</div>
501+
)}
502+
</div>
503+
);
504+
}
505+
506+
return (
507+
<img
508+
src={src}
509+
alt={alt ?? undefined}
510+
className="markdown-img"
511+
onError={() => setHasError(true)}
512+
/>
513+
);
514+
}, (prevProps, nextProps) => {
515+
// Custom comparison function to prevent unnecessary re-renders
516+
return prevProps.src === nextProps.src &&
517+
prevProps.alt === nextProps.alt;
518+
});
519+
520+
ImageWithErrorHandling.displayName = "ImageWithErrorHandling";
521+
426522
export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
427523
content,
428524
className,
@@ -517,47 +613,7 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
517613
return renderMediaFallback(src, alt);
518614
}
519615

520-
const VideoWithErrorHandling = () => {
521-
const [hasError, setHasError] = React.useState(false);
522-
523-
if (hasError) {
524-
return (
525-
<div className="markdown-media-error">
526-
<div className="markdown-media-error-message">
527-
{t("chatStreamMessage.videoLinkUnavailable", {
528-
defaultValue: "This video link is unavailable",
529-
})}
530-
</div>
531-
{alt && (
532-
<div className="markdown-media-error-caption">{alt}</div>
533-
)}
534-
</div>
535-
);
536-
}
537-
538-
return (
539-
<figure className="markdown-video-wrapper">
540-
<video
541-
className="markdown-video"
542-
controls
543-
preload="metadata"
544-
playsInline
545-
src={src}
546-
onError={() => setHasError(true)}
547-
{...props}
548-
>
549-
{t("chatStreamMessage.videoNotSupported", {
550-
defaultValue: "Sorry, your browser does not support embedded videos.",
551-
})}
552-
</video>
553-
{alt ? (
554-
<figcaption className="markdown-video-caption">{alt}</figcaption>
555-
) : null}
556-
</figure>
557-
);
558-
};
559-
560-
return <VideoWithErrorHandling />;
616+
return <VideoWithErrorHandling key={src} src={src} alt={alt} props={props} />;
561617
};
562618

563619
// Modified processText function logic
@@ -861,35 +917,11 @@ export const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
861917
return renderVideoElement({ src, alt });
862918
}
863919

864-
const ImageWithErrorHandling = () => {
865-
const [hasError, setHasError] = React.useState(false);
866-
867-
if (hasError) {
868-
return (
869-
<div className="markdown-media-error">
870-
<div className="markdown-media-error-message">
871-
{t("chatStreamMessage.imageLinkUnavailable", {
872-
defaultValue: "This image link is unavailable",
873-
})}
874-
</div>
875-
{alt && (
876-
<div className="markdown-media-error-caption">{alt}</div>
877-
)}
878-
</div>
879-
);
880-
}
920+
if (!src || typeof src !== "string") {
921+
return null;
922+
}
881923

882-
return (
883-
<img
884-
src={src}
885-
alt={alt}
886-
className="markdown-img"
887-
onError={() => setHasError(true)}
888-
/>
889-
);
890-
};
891-
892-
return <ImageWithErrorHandling />;
924+
return <ImageWithErrorHandling key={src} src={src} alt={alt} />;
893925
},
894926
// Video
895927
video: ({ children, ...props }: any) => {

frontend/public/locales/en/common.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,6 @@
690690
"businessLogic.config.maxSteps": "Max Steps of Agent Run",
691691
"businessLogic.config.button.generatePrompt": "Generate",
692692
"businessLogic.config.button.generating": "Generating...",
693-
"businessLogic.config.button.saveToAgentPool": "Save to Agent Pool",
694693
"businessLogic.config.modal.deleteTitle": "Confirm Delete",
695694
"businessLogic.config.modal.deleteContent": "Are you sure you want to delete this agent? This action cannot be undone.",
696695
"businessLogic.config.modal.button.cancel": "Cancel",

frontend/public/locales/zh/common.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,6 @@
690690
"businessLogic.config.maxSteps": "Agent运行最大步骤数",
691691
"businessLogic.config.button.generatePrompt": "生成智能体",
692692
"businessLogic.config.button.generating": "智能生成提示词中...",
693-
"businessLogic.config.button.saveToAgentPool": "保存到Agent池",
694693
"businessLogic.config.modal.deleteTitle": "确认删除",
695694
"businessLogic.config.modal.deleteContent": "确定要删除Agent {{name}} 吗?此操作不可恢复。",
696695
"businessLogic.config.modal.button.cancel": "取消",

0 commit comments

Comments
 (0)