mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-03 14:22:13 +08:00
feat: add suggestions
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollBar,
|
||||
} from "@/components/ui/scroll-area";
|
||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Icon } from "@radix-ui/react-select";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ComponentProps } from "react";
|
||||
|
||||
export type SuggestionsProps = ComponentProps<typeof ScrollArea>;
|
||||
@@ -15,7 +14,7 @@ export const Suggestions = ({
|
||||
children,
|
||||
...props
|
||||
}: SuggestionsProps) => (
|
||||
<ScrollArea className="w-full overflow-x-auto whitespace-nowrap" {...props}>
|
||||
<ScrollArea className="overflow-x-auto whitespace-nowrap" {...props}>
|
||||
<div className={cn("flex w-max flex-nowrap items-center gap-2", className)}>
|
||||
{children}
|
||||
</div>
|
||||
@@ -24,32 +23,38 @@ export const Suggestions = ({
|
||||
);
|
||||
|
||||
export type SuggestionProps = Omit<ComponentProps<typeof Button>, "onClick"> & {
|
||||
suggestion: string;
|
||||
onClick?: (suggestion: string) => void;
|
||||
suggestion: React.ReactNode;
|
||||
icon?: LucideIcon;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export const Suggestion = ({
|
||||
suggestion,
|
||||
onClick,
|
||||
className,
|
||||
icon: Icon,
|
||||
variant = "outline",
|
||||
size = "sm",
|
||||
children,
|
||||
...props
|
||||
}: SuggestionProps) => {
|
||||
const handleClick = () => {
|
||||
onClick?.(suggestion);
|
||||
onClick?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={cn("cursor-pointer rounded-full px-4", className)}
|
||||
className={cn(
|
||||
"text-muted-foreground cursor-pointer rounded-full px-4 text-xs font-normal",
|
||||
className,
|
||||
)}
|
||||
onClick={handleClick}
|
||||
size={size}
|
||||
type="button"
|
||||
variant={variant}
|
||||
{...props}
|
||||
>
|
||||
{Icon && <Icon className="size-4" />}
|
||||
{children || suggestion}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -6,13 +6,13 @@ import {
|
||||
GraduationCapIcon,
|
||||
LightbulbIcon,
|
||||
PaperclipIcon,
|
||||
PlusIcon,
|
||||
ZapIcon,
|
||||
} from "lucide-react";
|
||||
import { useCallback, useMemo, useState, type ComponentProps } from "react";
|
||||
|
||||
import {
|
||||
PromptInput,
|
||||
PromptInputActionAddAttachments,
|
||||
PromptInputActionMenu,
|
||||
PromptInputActionMenuContent,
|
||||
PromptInputActionMenuItem,
|
||||
@@ -26,11 +26,13 @@ import {
|
||||
PromptInputTextarea,
|
||||
PromptInputTools,
|
||||
usePromptInputAttachments,
|
||||
usePromptInputController,
|
||||
type PromptInputMessage,
|
||||
} from "@/components/ai-elements/prompt-input";
|
||||
import {
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { useModels } from "@/core/models/hooks";
|
||||
@@ -46,6 +48,13 @@ import {
|
||||
ModelSelectorName,
|
||||
ModelSelectorTrigger,
|
||||
} from "../ai-elements/model-selector";
|
||||
import { Suggestion, Suggestions } from "../ai-elements/suggestion";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "../ui/dropdown-menu";
|
||||
|
||||
import { Tooltip } from "./tooltip";
|
||||
|
||||
@@ -338,6 +347,11 @@ export function InputBox({
|
||||
/>
|
||||
</PromptInputTools>
|
||||
</PromptInputFooter>
|
||||
{isNewThread && (
|
||||
<div className="absolute right-0 -bottom-12 left-0 z-0 flex items-center justify-center">
|
||||
<SuggestionList />
|
||||
</div>
|
||||
)}
|
||||
{!isNewThread && (
|
||||
<div className="bg-background absolute right-0 -bottom-[17px] left-0 z-0 h-4"></div>
|
||||
)}
|
||||
@@ -345,6 +359,67 @@ export function InputBox({
|
||||
);
|
||||
}
|
||||
|
||||
function SuggestionList() {
|
||||
const { t } = useI18n();
|
||||
const { textInput } = usePromptInputController();
|
||||
const handleSuggestionClick = useCallback(
|
||||
(prompt: string | undefined) => {
|
||||
if (!prompt) return;
|
||||
textInput.setInput(prompt);
|
||||
setTimeout(() => {
|
||||
const textarea = document.querySelector<HTMLTextAreaElement>(
|
||||
"textarea[name='message']",
|
||||
);
|
||||
if (textarea) {
|
||||
const selStart = prompt.indexOf("[");
|
||||
const selEnd = prompt.indexOf("]");
|
||||
if (selStart !== -1 && selEnd !== -1) {
|
||||
textarea.setSelectionRange(selStart, selEnd + 1);
|
||||
textarea.focus();
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
},
|
||||
[textInput],
|
||||
);
|
||||
return (
|
||||
<Suggestions className="w-fit">
|
||||
{t.inputBox.suggestions.map((suggestion) => (
|
||||
<Suggestion
|
||||
key={suggestion.suggestion}
|
||||
icon={suggestion.icon}
|
||||
suggestion={suggestion.suggestion}
|
||||
onClick={() => handleSuggestionClick(suggestion.prompt)}
|
||||
/>
|
||||
))}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Suggestion icon={PlusIcon} suggestion={t.common.create} />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuGroup>
|
||||
{t.inputBox.suggestionsCreate.map((suggestion, index) =>
|
||||
"type" in suggestion && suggestion.type === "separator" ? (
|
||||
<DropdownMenuSeparator key={index} />
|
||||
) : (
|
||||
!("type" in suggestion) && (
|
||||
<DropdownMenuItem
|
||||
key={suggestion.suggestion}
|
||||
onClick={() => handleSuggestionClick(suggestion.prompt)}
|
||||
>
|
||||
{suggestion.icon && <suggestion.icon className="size-4" />}
|
||||
{suggestion.suggestion}
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
),
|
||||
)}
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</Suggestions>
|
||||
);
|
||||
}
|
||||
|
||||
function AddAttachmentsButton({ className }: { className?: string }) {
|
||||
const { t } = useI18n();
|
||||
const attachments = usePromptInputAttachments();
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
import {
|
||||
CompassIcon,
|
||||
GraduationCapIcon,
|
||||
ImageIcon,
|
||||
MicroscopeIcon,
|
||||
PenLineIcon,
|
||||
ShapesIcon,
|
||||
SparklesIcon,
|
||||
VideoIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
import type { Translations } from "./types";
|
||||
|
||||
export const enUS: Translations = {
|
||||
@@ -29,6 +40,7 @@ export const enUS: Translations = {
|
||||
cancel: "Cancel",
|
||||
save: "Save",
|
||||
install: "Install",
|
||||
create: "Create",
|
||||
},
|
||||
|
||||
// Welcome
|
||||
@@ -62,6 +74,55 @@ export const enUS: Translations = {
|
||||
proModeDescription:
|
||||
"Reasoning, planning and executing, get more accurate results, may take more time",
|
||||
searchModels: "Search models...",
|
||||
suggestions: [
|
||||
{
|
||||
suggestion: "Write",
|
||||
prompt: "Write a blog post about the latest trends on [topic]",
|
||||
icon: PenLineIcon,
|
||||
},
|
||||
{
|
||||
suggestion: "Research",
|
||||
prompt:
|
||||
"Conduct a deep dive research on [topic], and summarize the findings.",
|
||||
icon: MicroscopeIcon,
|
||||
},
|
||||
{
|
||||
suggestion: "Collect",
|
||||
prompt: "Collect data from [source] and create a report.",
|
||||
icon: ShapesIcon,
|
||||
},
|
||||
{
|
||||
suggestion: "Learn",
|
||||
prompt: "Learn about [topic] and create a tutorial.",
|
||||
icon: GraduationCapIcon,
|
||||
},
|
||||
],
|
||||
suggestionsCreate: [
|
||||
{
|
||||
suggestion: "Webpage",
|
||||
prompt: "Create a webpage about [topic]",
|
||||
icon: CompassIcon,
|
||||
},
|
||||
{
|
||||
suggestion: "Image",
|
||||
prompt: "Create an image about [topic]",
|
||||
icon: ImageIcon,
|
||||
},
|
||||
{
|
||||
suggestion: "Video",
|
||||
prompt: "Create a video about [topic]",
|
||||
icon: VideoIcon,
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
},
|
||||
{
|
||||
suggestion: "Skill",
|
||||
prompt:
|
||||
"We're going to build a new skill step by step with `skill-creator`. To start, what do you want this skill to do?",
|
||||
icon: SparklesIcon,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Sidebar
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
|
||||
export interface Translations {
|
||||
// Locale meta
|
||||
locale: {
|
||||
@@ -27,6 +29,7 @@ export interface Translations {
|
||||
cancel: string;
|
||||
save: string;
|
||||
install: string;
|
||||
create: string;
|
||||
};
|
||||
|
||||
// Welcome
|
||||
@@ -56,6 +59,21 @@ export interface Translations {
|
||||
proMode: string;
|
||||
proModeDescription: string;
|
||||
searchModels: string;
|
||||
suggestions: {
|
||||
suggestion: string;
|
||||
prompt: string;
|
||||
icon: LucideIcon;
|
||||
}[];
|
||||
suggestionsCreate: (
|
||||
| {
|
||||
suggestion: string;
|
||||
prompt: string;
|
||||
icon: LucideIcon;
|
||||
}
|
||||
| {
|
||||
type: "separator";
|
||||
}
|
||||
)[];
|
||||
};
|
||||
|
||||
// Sidebar
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
import {
|
||||
CompassIcon,
|
||||
GraduationCapIcon,
|
||||
ImageIcon,
|
||||
MicroscopeIcon,
|
||||
PenLineIcon,
|
||||
ShapesIcon,
|
||||
SparklesIcon,
|
||||
VideoIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
import type { Translations } from "./types";
|
||||
|
||||
export const zhCN: Translations = {
|
||||
@@ -29,6 +40,7 @@ export const zhCN: Translations = {
|
||||
cancel: "取消",
|
||||
save: "保存",
|
||||
install: "安装",
|
||||
create: "创建",
|
||||
},
|
||||
|
||||
// Welcome
|
||||
@@ -60,6 +72,54 @@ export const zhCN: Translations = {
|
||||
proMode: "专业",
|
||||
proModeDescription: "思考、计划再执行,获得更精准的结果,可能需要更多时间",
|
||||
searchModels: "搜索模型...",
|
||||
suggestions: [
|
||||
{
|
||||
suggestion: "写作",
|
||||
prompt: "撰写一篇关于[主题]的博客文章",
|
||||
icon: PenLineIcon,
|
||||
},
|
||||
{
|
||||
suggestion: "研究",
|
||||
prompt: "深入浅出的研究一下[主题],并总结发现。",
|
||||
icon: MicroscopeIcon,
|
||||
},
|
||||
{
|
||||
suggestion: "收集",
|
||||
prompt: "从[来源]收集数据并创建报告。",
|
||||
icon: ShapesIcon,
|
||||
},
|
||||
{
|
||||
suggestion: "学习",
|
||||
prompt: "学习关于[主题]并创建教程。",
|
||||
icon: GraduationCapIcon,
|
||||
},
|
||||
],
|
||||
suggestionsCreate: [
|
||||
{
|
||||
suggestion: "网页",
|
||||
prompt: "生成一个关于[主题]的网页",
|
||||
icon: CompassIcon,
|
||||
},
|
||||
{
|
||||
suggestion: "图片",
|
||||
prompt: "生成一个关于[主题]的图片",
|
||||
icon: ImageIcon,
|
||||
},
|
||||
{
|
||||
suggestion: "视频",
|
||||
prompt: "生成一个关于[主题]的视频",
|
||||
icon: VideoIcon,
|
||||
},
|
||||
{
|
||||
type: "separator",
|
||||
},
|
||||
{
|
||||
suggestion: "技能",
|
||||
prompt:
|
||||
"我们一起用 skill-creator 技能来创建一个技能吧。先问问我希望这个技能能做什么。",
|
||||
icon: SparklesIcon,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Sidebar
|
||||
|
||||
Reference in New Issue
Block a user