feat: support to adjust writing style (#290)

* feat: implment backend for adjust report style

* feat: add web part

* fix test cases

* fix: fix typing

---------

Co-authored-by: Henry Li <henry1943@163.com>
This commit is contained in:
DanielWalnut
2025-06-07 20:48:39 +08:00
committed by GitHub
parent cda3870add
commit 0e22c373af
14 changed files with 411 additions and 7 deletions

View File

@@ -9,6 +9,7 @@ import { Detective } from "~/components/deer-flow/icons/detective";
import MessageInput, {
type MessageInputRef,
} from "~/components/deer-flow/message-input";
import { ReportStyleDialog } from "~/components/deer-flow/report-style-dialog";
import { Tooltip } from "~/components/deer-flow/tooltip";
import { Button } from "~/components/ui/button";
import type { Option, Resource } from "~/core/messages";
@@ -104,7 +105,7 @@ export function InputBox({
/>
</div>
<div className="flex items-center px-4 py-2">
<div className="flex grow">
<div className="flex grow gap-2">
<Tooltip
className="max-w-60"
title={
@@ -133,6 +134,7 @@ export function InputBox({
<Detective /> Investigation
</Button>
</Tooltip>
<ReportStyleDialog />
</div>
<div className="flex shrink-0 items-center gap-2">
<Tooltip title={responding ? "Stop" : "Send"}>

View File

@@ -25,7 +25,6 @@ import type { Tab } from "./types";
const generalFormSchema = z.object({
autoAcceptedPlan: z.boolean(),
enableBackgroundInvestigation: z.boolean(),
maxPlanIterations: z.number().min(1, {
message: "Max plan iterations must be at least 1.",
}),
@@ -35,6 +34,9 @@ const generalFormSchema = z.object({
maxSearchResults: z.number().min(1, {
message: "Max search results must be at least 1.",
}),
// Others
enableBackgroundInvestigation: z.boolean(),
reportStyle: z.enum(["academic", "popular_science", "news", "social_media"]),
});
export const GeneralTab: Tab = ({

View File

@@ -0,0 +1,45 @@
export function ReportStyle({ className }: { className?: string }) {
return (
<svg
className={className}
version="1.1"
width="800px"
height="800px"
viewBox="0 0 24 24"
fill="none"
>
<g fill="currentcolor">
<path
d="M4 4C4 3.44772 4.44772 3 5 3H19C19.5523 3 20 3.44772 20 4V20C20 20.5523 19.5523 21 19 21H5C4.44772 21 4 20.5523 4 20V4Z"
stroke="currentColor"
strokeWidth="2"
fill="none"
/>
<path
d="M8 7H16"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M8 11H16"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
<path
d="M8 15H12"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
<circle
cx="16"
cy="15"
r="2"
fill="currentColor"
/>
</g>
</svg>
);
}

View File

@@ -0,0 +1,128 @@
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
// SPDX-License-Identifier: MIT
import { useState } from "react";
import { Check, FileText, Newspaper, Users, GraduationCap } from "lucide-react";
import { Button } from "~/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog";
import { setReportStyle, useSettingsStore } from "~/core/store";
import { cn } from "~/lib/utils";
import { Tooltip } from "./tooltip";
const REPORT_STYLES = [
{
value: "academic" as const,
label: "Academic",
description: "Formal, objective, and analytical with precise terminology",
icon: GraduationCap,
},
{
value: "popular_science" as const,
label: "Popular Science",
description: "Engaging and accessible for general audience",
icon: FileText,
},
{
value: "news" as const,
label: "News",
description: "Factual, concise, and impartial journalistic style",
icon: Newspaper,
},
{
value: "social_media" as const,
label: "Social Media",
description: "Concise, attention-grabbing, and shareable",
icon: Users,
},
];
export function ReportStyleDialog() {
const [open, setOpen] = useState(false);
const currentStyle = useSettingsStore((state) => state.general.reportStyle);
const handleStyleChange = (
style: "academic" | "popular_science" | "news" | "social_media",
) => {
setReportStyle(style);
setOpen(false);
};
const currentStyleConfig =
REPORT_STYLES.find((style) => style.value === currentStyle) ||
REPORT_STYLES[0]!;
const CurrentIcon = currentStyleConfig.icon;
return (
<Dialog open={open} onOpenChange={setOpen}>
<Tooltip
className="max-w-60"
title={
<div>
<h3 className="mb-2 font-bold">
Writing Style: {currentStyleConfig.label}
</h3>
<p>
Choose the writing style for your research reports. Different
styles are optimized for different audiences and purposes.
</p>
</div>
}
>
<DialogTrigger asChild>
<Button
className="!border-brand !text-brand rounded-2xl"
variant="outline"
>
<CurrentIcon className="h-4 w-4" /> {currentStyleConfig.label}
</Button>
</DialogTrigger>
</Tooltip>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>Choose Writing Style</DialogTitle>
<DialogDescription>
Select the writing style for your research reports. Each style is
optimized for different audiences and purposes.
</DialogDescription>
</DialogHeader>
<div className="grid gap-3 py-4">
{REPORT_STYLES.map((style) => {
const Icon = style.icon;
const isSelected = currentStyle === style.value;
return (
<button
key={style.value}
className={cn(
"hover:bg-accent flex items-start gap-3 rounded-lg border p-4 text-left transition-colors",
isSelected && "border-primary bg-accent",
)}
onClick={() => handleStyleChange(style.value)}
>
<Icon className="mt-0.5 h-5 w-5 shrink-0" />
<div className="flex-1 space-y-1">
<div className="flex items-center gap-2">
<h4 className="font-medium">{style.label}</h4>
{isSelected && <Check className="text-primary h-4 w-4" />}
</div>
<p className="text-muted-foreground text-sm">
{style.description}
</p>
</div>
</button>
);
})}
</div>
</DialogContent>
</Dialog>
);
}

View File

@@ -23,6 +23,7 @@ export async function* chatStream(
max_search_results?: number;
interrupt_feedback?: string;
enable_background_investigation: boolean;
report_style?: "academic" | "popular_science" | "news" | "social_media";
mcp_settings?: {
servers: Record<
string,

View File

@@ -14,6 +14,7 @@ const DEFAULT_SETTINGS: SettingsState = {
maxPlanIterations: 1,
maxStepNum: 3,
maxSearchResults: 3,
reportStyle: "academic",
},
mcp: {
servers: [],
@@ -27,6 +28,7 @@ export type SettingsState = {
maxPlanIterations: number;
maxStepNum: number;
maxSearchResults: number;
reportStyle: "academic" | "popular_science" | "news" | "social_media";
};
mcp: {
servers: MCPServerMetadata[];
@@ -125,6 +127,16 @@ export const getChatStreamSettings = () => {
};
};
export function setReportStyle(value: "academic" | "popular_science" | "news" | "social_media") {
useSettingsStore.setState((state) => ({
general: {
...state.general,
reportStyle: value,
},
}));
saveSettings();
}
export function setEnableBackgroundInvestigation(value: boolean) {
useSettingsStore.setState((state) => ({
general: {

View File

@@ -109,6 +109,7 @@ export async function sendMessage(
max_plan_iterations: settings.maxPlanIterations,
max_step_num: settings.maxStepNum,
max_search_results: settings.maxSearchResults,
report_style: settings.reportStyle,
mcp_settings: settings.mcpSettings,
},
options,