feat: implement enhance prompt (#294)

* feat: implement enhance prompt

* add unit test

* fix prompt

* fix: fix eslint and compiling issues

* feat: add border-beam animation

* fix: fix importing issues

---------

Co-authored-by: Henry Li <henry1943@163.com>
This commit is contained in:
DanielWalnut
2025-06-08 19:41:59 +08:00
committed by GitHub
parent 8081a14c21
commit 1cd6aa0ece
19 changed files with 1100 additions and 4 deletions

View File

@@ -1,9 +1,10 @@
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
// SPDX-License-Identifier: MIT
import { MagicWandIcon } from "@radix-ui/react-icons";
import { AnimatePresence, motion } from "framer-motion";
import { ArrowUp, X } from "lucide-react";
import { useCallback, useRef } from "react";
import { useCallback, useRef, useState } from "react";
import { Detective } from "~/components/deer-flow/icons/detective";
import MessageInput, {
@@ -11,7 +12,9 @@ import MessageInput, {
} from "~/components/deer-flow/message-input";
import { ReportStyleDialog } from "~/components/deer-flow/report-style-dialog";
import { Tooltip } from "~/components/deer-flow/tooltip";
import { BorderBeam } from "~/components/magicui/border-beam";
import { Button } from "~/components/ui/button";
import { enhancePrompt } from "~/core/api";
import type { Option, Resource } from "~/core/messages";
import {
setEnableBackgroundInvestigation,
@@ -44,10 +47,16 @@ export function InputBox({
const backgroundInvestigation = useSettingsStore(
(state) => state.general.enableBackgroundInvestigation,
);
const reportStyle = useSettingsStore((state) => state.general.reportStyle);
const containerRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<MessageInputRef>(null);
const feedbackRef = useRef<HTMLDivElement>(null);
// Enhancement state
const [isEnhancing, setIsEnhancing] = useState(false);
const [isEnhanceAnimating, setIsEnhanceAnimating] = useState(false);
const [currentPrompt, setCurrentPrompt] = useState("");
const handleSendMessage = useCallback(
(message: string, resources: Array<Resource>) => {
if (responding) {
@@ -62,12 +71,50 @@ export function InputBox({
resources,
});
onRemoveFeedback?.();
// Clear enhancement animation after sending
setIsEnhanceAnimating(false);
}
}
},
[responding, onCancel, onSend, feedback, onRemoveFeedback],
);
const handleEnhancePrompt = useCallback(async () => {
if (currentPrompt.trim() === "" || isEnhancing) {
return;
}
setIsEnhancing(true);
setIsEnhanceAnimating(true);
try {
const enhancedPrompt = await enhancePrompt({
prompt: currentPrompt,
report_style: reportStyle.toUpperCase(),
});
// Add a small delay for better UX
await new Promise((resolve) => setTimeout(resolve, 500));
// Update the input with the enhanced prompt with animation
if (inputRef.current) {
inputRef.current.setContent(enhancedPrompt);
setCurrentPrompt(enhancedPrompt);
}
// Keep animation for a bit longer to show the effect
setTimeout(() => {
setIsEnhanceAnimating(false);
}, 1000);
} catch (error) {
console.error("Failed to enhance prompt:", error);
setIsEnhanceAnimating(false);
// Could add toast notification here
} finally {
setIsEnhancing(false);
}
}, [currentPrompt, isEnhancing, reportStyle]);
return (
<div
className={cn(
@@ -97,11 +144,61 @@ export function InputBox({
/>
</motion.div>
)}
{isEnhanceAnimating && (
<motion.div
className="pointer-events-none absolute inset-0 z-20"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
>
<div className="relative h-full w-full">
{/* Sparkle effect overlay */}
<motion.div
className="absolute inset-0 rounded-[24px] bg-gradient-to-r from-blue-500/10 via-purple-500/10 to-blue-500/10"
animate={{
background: [
"linear-gradient(45deg, rgba(59, 130, 246, 0.1), rgba(147, 51, 234, 0.1), rgba(59, 130, 246, 0.1))",
"linear-gradient(225deg, rgba(147, 51, 234, 0.1), rgba(59, 130, 246, 0.1), rgba(147, 51, 234, 0.1))",
"linear-gradient(45deg, rgba(59, 130, 246, 0.1), rgba(147, 51, 234, 0.1), rgba(59, 130, 246, 0.1))",
],
}}
transition={{ duration: 2, repeat: Infinity }}
/>
{/* Floating sparkles */}
{[...Array(6)].map((_, i) => (
<motion.div
key={i}
className="absolute h-2 w-2 rounded-full bg-blue-400"
style={{
left: `${20 + i * 12}%`,
top: `${30 + (i % 2) * 40}%`,
}}
animate={{
y: [-10, -20, -10],
opacity: [0, 1, 0],
scale: [0.5, 1, 0.5],
}}
transition={{
duration: 1.5,
repeat: Infinity,
delay: i * 0.2,
}}
/>
))}
</div>
</motion.div>
)}
</AnimatePresence>
<MessageInput
className={cn("h-24 px-4 pt-5", feedback && "pt-9")}
className={cn(
"h-24 px-4 pt-5",
feedback && "pt-9",
isEnhanceAnimating && "transition-all duration-500",
)}
ref={inputRef}
onEnter={handleSendMessage}
onChange={setCurrentPrompt}
/>
</div>
<div className="flex items-center px-4 py-2">
@@ -137,6 +234,26 @@ export function InputBox({
<ReportStyleDialog />
</div>
<div className="flex shrink-0 items-center gap-2">
<Tooltip title="Enhance prompt with AI">
<Button
variant="ghost"
size="icon"
className={cn(
"hover:bg-accent h-10 w-10",
isEnhancing && "animate-pulse",
)}
onClick={handleEnhancePrompt}
disabled={isEnhancing || currentPrompt.trim() === ""}
>
{isEnhancing ? (
<div className="flex h-10 w-10 items-center justify-center">
<div className="bg-foreground h-3 w-3 animate-bounce rounded-full opacity-70" />
</div>
) : (
<MagicWandIcon className="text-brand" />
)}
</Button>
</Tooltip>
<Tooltip title={responding ? "Stop" : "Send"}>
<Button
variant="outline"
@@ -155,6 +272,21 @@ export function InputBox({
</Tooltip>
</div>
</div>
{isEnhancing && (
<>
<BorderBeam
duration={5}
size={250}
className="from-transparent via-red-500 to-transparent"
/>
<BorderBeam
duration={5}
delay={3}
size={250}
className="from-transparent via-blue-500 to-transparent"
/>
</>
)}
</div>
);
}