Merge branch 'dhk-1' into 'master'

样式修改

See merge request knowledge-base-bj/deerflow-server!29
This commit is contained in:
董宏凯 committed 2026-05-20 14:47:15 +00:00
commit 628bbe14a8
24 files changed
+936 -154

No files matched your search

@@ -514,9 +514,15 @@ export function InputBox({
</div>
</div>
)}
<div className={cn(isNewThread && "gemini-gradient-border gemini-shadow")}>
<PromptInput
className={cn(
"rounded-[1.85rem] border border-[#eadfce] bg-white shadow-[0_24px_70px_-34px_rgba(120,83,42,0.32)] ring-1 ring-[#f6efe5] transition-all duration-300 ease-out *:data-[slot='input-group']:rounded-[1.85rem] dark:bg-slate-800 dark:border-slate-700 dark:ring-slate-700 dark:shadow-none",
"transition-all duration-300 ease-out *:data-[slot='input-group']:rounded-3xl",
// landing 态:透明,让外层 gemini-gradient-border 提供白卡 + 旋转描边
// 对话态:常规白卡 + 中性边框 + 轻阴影
isNewThread
? "rounded-3xl bg-transparent border-0 shadow-none ring-0"
: "rounded-3xl bg-background border border-border shadow-sm dark:bg-slate-800 dark:border-slate-700",
className,
)}
disabled={disabled}
@@ -531,7 +537,8 @@ export function InputBox({
<PromptInputBody className="absolute top-0 right-0 left-0 z-3">
<PromptInputTextarea
className={cn(
"size-full px-2 py-1 text-[15px] leading-7 placeholder:text-[#b7ab9a] dark:placeholder:text-slate-500",
"size-full px-2 py-1 text-[15px] leading-7 placeholder:text-muted-foreground/60",
isNewThread && "min-h-[110px]!",
)}
disabled={disabled}
placeholder={t.inputBox.placeholder}
@@ -539,7 +546,7 @@ export function InputBox({
defaultValue={initialValue}
/>
</PromptInputBody>
<PromptInputFooter className="flex items-center border-t border-[#f4ede4] dark:border-slate-700 px-2 pt-2.5 pb-1.5">
<PromptInputFooter className="flex items-center border-t-0 px-2 pt-1 pb-1.5">
<PromptInputTools className="gap-1.5">
{/* TODO: Add more connectors here
<PromptInputActionMenu>
@@ -550,7 +557,7 @@ export function InputBox({
/>
</PromptInputActionMenuContent>
</PromptInputActionMenu> */}
<AddAttachmentsButton className="rounded-full border border-[#eee3d5] bg-[#fffaf5] px-2.5! shadow-none hover:bg-white dark:border-slate-600 dark:bg-slate-700/50 dark:text-slate-300 dark:hover:bg-slate-700" />
<AddAttachmentsButton className="rounded-full border-0 bg-transparent px-2.5! shadow-none text-muted-foreground hover:bg-muted/70 hover:text-foreground dark:text-slate-400 dark:hover:bg-slate-700/70 dark:hover:text-slate-100" />
<Tooltip
content={
context.writing_mode
@@ -560,10 +567,10 @@ export function InputBox({
>
<PromptInputButton
className={cn(
"gap-1.5! rounded-full border px-3! shadow-none",
"gap-1.5! rounded-full px-3! shadow-none",
context.writing_mode
? "border-[#d8b878] bg-[#fff3cf] text-[#6d5625] hover:bg-[#ffedbd]"
: "border-[#eee3d5] bg-[#fffaf5] text-[#7e6a57] hover:bg-white",
? "border border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-100 dark:border-amber-700 dark:bg-amber-950/40 dark:text-amber-200"
: "border-0 bg-transparent text-muted-foreground hover:bg-muted/70 hover:text-foreground dark:text-slate-400 dark:hover:bg-slate-700/70 dark:hover:text-slate-100",
)}
onClick={handleWritingModeToggle}
>
@@ -582,7 +589,7 @@ export function InputBox({
: "flash"
}
>
<PromptInputActionMenuTrigger className="gap-1.5! rounded-full border border-[#eee3d5] bg-[#fffaf5] px-3! text-[#7e6a57] shadow-none hover:bg-white dark:border-slate-600 dark:bg-slate-700/50 dark:text-slate-300 dark:hover:bg-slate-700">
<PromptInputActionMenuTrigger className="gap-1.5! rounded-full border-0 bg-transparent px-3! text-muted-foreground shadow-none hover:bg-muted/70 hover:text-foreground dark:text-slate-400 dark:hover:bg-slate-700/70 dark:hover:text-slate-100">
<div>
{context.mode === "flash" && <ZapIcon className="size-3" />}
{context.mode === "thinking" && (
@@ -744,7 +751,7 @@ export function InputBox({
</PromptInputActionMenu>
{supportReasoningEffort && context.mode !== "flash" && (
<PromptInputActionMenu>
<PromptInputActionMenuTrigger className="gap-1.5! rounded-full border border-[#eee3d5] bg-[#fffaf5] px-3! text-[#7e6a57] shadow-none hover:bg-white dark:border-slate-600 dark:bg-slate-700/50 dark:text-slate-300 dark:hover:bg-slate-700">
<PromptInputActionMenuTrigger className="gap-1.5! rounded-full border-0 bg-transparent px-3! text-muted-foreground shadow-none hover:bg-muted/70 hover:text-foreground dark:text-slate-400 dark:hover:bg-slate-700/70 dark:hover:text-slate-100">
<div className="text-xs font-normal">
{t.inputBox.reasoningEffort}:
{context.reasoning_effort === "minimal" &&
@@ -873,17 +880,17 @@ export function InputBox({
onClick={promptPrefix.onToggle}
aria-pressed={promptPrefix.on}
className={cn(
"rounded-full border px-3 shadow-none transition-colors",
"rounded-full px-3 shadow-none transition-colors",
promptPrefix.on
? "border-[#d6a868] bg-[#fff3df] text-[#7a5417] hover:bg-[#ffe8c4]"
: "border-[#eee3d5] bg-[#fffaf5] text-[#8a7558] hover:bg-white",
? "border border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-100 dark:border-amber-700 dark:bg-amber-950/40 dark:text-amber-200"
: "border-0 bg-transparent text-muted-foreground hover:bg-muted/70 hover:text-foreground",
)}
>
<span className="max-w-[160px] truncate text-xs font-normal">
{promptPrefix.label}
</span>
{promptPrefix.on && (
<CheckIcon className="ml-1 size-3.5 text-[#7a5417]" />
<CheckIcon className="ml-1 size-3.5 text-amber-700 dark:text-amber-200" />
)}
</PromptInputButton>
</Tooltip>
@@ -893,7 +900,7 @@ export function InputBox({
onOpenChange={setModelDialogOpen}
>
<ModelSelectorTrigger asChild>
<PromptInputButton className="rounded-full border border-[#eee3d5] bg-[#fffaf5] px-3 shadow-none hover:bg-white dark:border-slate-600 dark:bg-slate-700/50 dark:text-slate-300 dark:hover:bg-slate-700">
<PromptInputButton className="rounded-full border-0 bg-transparent px-3 shadow-none text-muted-foreground hover:bg-muted/70 hover:text-foreground dark:text-slate-400 dark:hover:bg-slate-700/70 dark:hover:text-slate-100">
<div className="flex min-w-0 flex-col items-start text-left">
<ModelSelectorName className="text-xs font-normal">
{selectedModel?.display_name}
@@ -927,7 +934,7 @@ export function InputBox({
</ModelSelectorContent>
</ModelSelector>
<PromptInputSubmit
className="rounded-full border-[#e7d9c7] bg-white shadow-[0_10px_24px_-18px_rgba(120,83,42,0.34)] dark:bg-blue-500 dark:border-blue-500 dark:text-white dark:hover:bg-blue-400 dark:shadow-none"
className="rounded-full border-border bg-background shadow-sm hover:bg-muted dark:bg-blue-500 dark:border-blue-500 dark:text-white dark:hover:bg-blue-400 dark:shadow-none"
disabled={disabled}
variant="outline"
status={status}
@@ -935,9 +942,10 @@ export function InputBox({
</PromptInputTools>
</PromptInputFooter>
{!isNewThread && (
<div className="absolute right-0 -bottom-[17px] left-0 z-0 h-4 bg-[#fffdfa] dark:bg-background"></div>
<div className="absolute right-0 -bottom-[17px] left-0 z-0 h-4 bg-background"></div>
)}
</PromptInput>
</div>
<Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
<DialogContent>
@@ -1,6 +1,7 @@
import type { Message } from "@langchain/langgraph-sdk";
import {
BookOpenTextIcon,
ChevronDownIcon,
ChevronUp,
FolderOpenIcon,
GlobeIcon,
@@ -23,6 +24,18 @@ import {
} from "@/components/ai-elements/chain-of-thought";
import { CodeBlock } from "@/components/ai-elements/code-block";
import { Button } from "@/components/ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { useI18n } from "@/core/i18n/hooks";
import {
extractReasoningContentFromMessage,
@@ -208,19 +221,10 @@ function ToolCall({
if (typeof args.query === "string") {
label = t.toolCalls.searchOnWebFor(args.query);
}
const items = normalizeSearchResults(result);
return (
<ChainOfThoughtStep key={id} label={label} icon={SearchIcon}>
{Array.isArray(result) && (
<ChainOfThoughtSearchResults>
{result.map((item) => (
<ChainOfThoughtSearchResult key={item.url}>
<a href={item.url} target="_blank" rel="noopener noreferrer">
{item.title}
</a>
</ChainOfThoughtSearchResult>
))}
</ChainOfThoughtSearchResults>
)}
{items.length > 0 && <WebSearchResultsList results={items} />}
</ChainOfThoughtStep>
);
} else if (name === "image_search") {
@@ -496,3 +500,117 @@ function convertToSteps(messages: Message[]): CoTStep[] {
}
return steps;
}
type WebSearchItem = {
title?: string;
url?: string;
snippet?: string;
content?: string;
published_date?: string;
publishedDate?: string;
date?: string;
};
function normalizeSearchResults(
result: string | Record<string, unknown> | undefined,
): WebSearchItem[] {
if (Array.isArray(result)) {
return result as WebSearchItem[];
}
if (result && typeof result === "object") {
const nested = (result as { results?: unknown }).results;
if (Array.isArray(nested)) {
return nested as WebSearchItem[];
}
}
return [];
}
function WebSearchResultsList({ results }: { results: WebSearchItem[] }) {
const [open, setOpen] = useState(false);
return (
<Collapsible open={open} onOpenChange={setOpen} className="w-full">
<CollapsibleTrigger className="text-muted-foreground hover:text-foreground flex items-center gap-1 text-xs">
<ChevronDownIcon
className={cn(
"size-3.5 transition-transform duration-200",
open && "rotate-180",
)}
/>
<span>{results.length} 条检索结果</span>
</CollapsibleTrigger>
<CollapsibleContent className="mt-2 flex flex-col gap-2">
{results.map((item, idx) => (
<SearchResultCard key={item.url ?? idx} item={item} />
))}
</CollapsibleContent>
</Collapsible>
);
}
function SearchResultCard({ item }: { item: WebSearchItem }) {
const [open, setOpen] = useState(false);
const content = item.snippet ?? item.content;
const date = item.published_date ?? item.publishedDate ?? item.date;
let source: string | undefined;
if (item.url) {
try {
source = new URL(item.url).hostname.replace(/^www\./, "");
} catch {
source = undefined;
}
}
return (
<>
<button
type="button"
onClick={() => setOpen(true)}
className="bg-card hover:bg-accent block w-full cursor-pointer rounded-lg border p-3 text-left transition-colors"
>
{item.title && (
<div className="text-foreground line-clamp-1 text-sm font-medium">
{item.title}
</div>
)}
{(source || date) && (
<div className="text-muted-foreground mt-1 flex items-center gap-2 text-xs">
{source && <span className="truncate">{source}</span>}
{source && date && <span>·</span>}
{date && <span>{date}</span>}
</div>
)}
{content && (
<div className="text-muted-foreground mt-1.5 line-clamp-2 text-xs leading-relaxed">
{content}
</div>
)}
</button>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle className="pr-6 text-base leading-relaxed">
{item.title ?? "检索结果"}
</DialogTitle>
{(source || date) && (
<DialogDescription className="flex items-center gap-2 text-xs">
{source && <span>{source}</span>}
{source && date && <span>·</span>}
{date && <span>{date}</span>}
</DialogDescription>
)}
</DialogHeader>
{content && (
<div className="text-foreground max-h-[60vh] overflow-y-auto whitespace-pre-wrap text-sm leading-relaxed">
{content}
</div>
)}
{item.url && (
<div className="text-muted-foreground border-t pt-3 text-xs break-all">
{item.url}
</div>
)}
</DialogContent>
</Dialog>
</>
);
}
@@ -158,15 +158,15 @@ export function RecentChatList() {
}
return (
<>
<SidebarGroup className="px-2">
<SidebarGroupLabel className="text-muted-foreground/90 px-2 text-[11px] font-semibold tracking-wide uppercase">
<SidebarGroup className="px-2 pt-3 mt-2 border-t border-sidebar-border/60">
<SidebarGroupLabel className="text-muted-foreground/70 px-3 mb-1 text-[10px] font-semibold tracking-[0.12em] uppercase">
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY !== "true"
? t.sidebar.recentChats
: t.sidebar.demoChats}
</SidebarGroupLabel>
<SidebarGroupContent className="group-data-[collapsible=icon]:pointer-events-none group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0">
<SidebarMenu>
<div className="flex w-full flex-col gap-1">
<div className="flex w-full flex-col gap-0.5">
{visibleThreads.map((thread) => {
const isActive = pathOfThread(thread) === pathname;
return (
@@ -174,9 +174,17 @@ export function RecentChatList() {
key={thread.thread_id}
className="group/side-menu-item"
>
<SidebarMenuButton isActive={isActive} asChild>
<SidebarMenuButton
isActive={isActive}
asChild
className={
"rounded-lg h-7 px-3 text-[13px] text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/60 " +
"data-[active=true]:bg-white data-[active=true]:text-foreground data-[active=true]:shadow-[0_1px_2px_rgba(15,23,42,0.05)] " +
"dark:data-[active=true]:bg-slate-700/80 dark:data-[active=true]:text-slate-100 dark:data-[active=true]:shadow-none"
}
>
<Link
className="text-muted-foreground block w-full whitespace-nowrap group-hover/side-menu-item:overflow-hidden"
className="block w-full whitespace-nowrap group-hover/side-menu-item:overflow-hidden"
href={pathOfThread(thread)}
>
{titleOfThread(thread)}
@@ -36,12 +36,12 @@ export function Welcome({
className,
)}
>
<div className="text-2xl font-bold">
<div className="text-[26px] font-medium tracking-tight text-foreground/85">
{searchParams.get("mode") === "skill" ? (
`✨ ${t.welcome.createYourOwnSkill} ✨`
) : (
<div className="flex items-center gap-2">
<div className={cn("inline-block", !waved ? "animate-wave" : "")}>
<div className="flex items-center gap-2.5">
<div className={cn("inline-block text-2xl", !waved ? "animate-wave" : "")}>
{isUltra ? "🚀" : "👋"}
</div>
<AuroraText colors={colors}>{t.welcome.greeting}</AuroraText>
@@ -15,26 +15,43 @@ import { useI18n } from "@/core/i18n/hooks";
// import { BotIcon, BrainIcon, ListChecks, MessageSquarePlus, MessagesSquare, SparklesIcon, UsersIcon } from "lucide-react";
import { BotIcon, BrainIcon, CalendarClock, ListChecks, MessageSquarePlus, MessagesSquare, SparklesIcon, UsersIcon } from "lucide-react";
// 普通导航项:默认中性色,激活态与「新对话」一致——主色浅底 + 主色文字/图标
const NAV_ITEM_CLASS =
"text-sidebar-foreground/80 hover:text-primary rounded-xl h-9 px-3 transition-colors hover:bg-primary/10 " +
"data-[active=true]:bg-primary/15 data-[active=true]:text-primary data-[active=true]:shadow-none " +
"dark:hover:bg-primary/15 dark:data-[active=true]:bg-primary/25 dark:data-[active=true]:text-primary-foreground";
// 「新对话」专属:默认仅主色文字/图标(显眼但不是选中态),命中 /chats/new 时才上主色底
const NEW_CHAT_CLASS =
"rounded-xl h-9 px-3 text-primary transition-colors hover:bg-primary/10 hover:text-primary " +
"data-[active=true]:bg-primary/15 data-[active=true]:text-primary data-[active=true]:shadow-none " +
"dark:text-primary-foreground dark:hover:bg-primary/15 dark:data-[active=true]:bg-primary/25";
export function WorkspaceNavChatList() {
const { t } = useI18n();
const pathname = usePathname();
return (
<SidebarGroup className="px-2 pt-2">
<SidebarMenu>
<SidebarMenu className="gap-1">
<SidebarMenuItem>
<SidebarMenuButton
isActive={pathname === "/page/workspace/chats/new"}
asChild
className={NEW_CHAT_CLASS}
>
<Link className="text-sidebar-foreground" href="/page/workspace/chats/new">
<Link href="/page/workspace/chats/new">
<MessageSquarePlus />
<span>{t.sidebar.newChat}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton isActive={pathname === "/page/workspace/chats"} asChild>
<Link className="text-sidebar-foreground" href="/page/workspace/chats">
<SidebarMenuButton
isActive={pathname === "/page/workspace/chats"}
asChild
className={NAV_ITEM_CLASS}
>
<Link href="/page/workspace/chats">
<MessagesSquare />
<span>{t.sidebar.chats}</span>
</Link>
@@ -44,8 +61,9 @@ export function WorkspaceNavChatList() {
<SidebarMenuButton
isActive={pathname.startsWith("/page/workspace/agents")}
asChild
className={NAV_ITEM_CLASS}
>
<Link className="text-sidebar-foreground" href="/page/workspace/agents">
<Link href="/page/workspace/agents">
<BotIcon />
<span>{t.sidebar.agents}</span>
</Link>
@@ -55,8 +73,9 @@ export function WorkspaceNavChatList() {
<SidebarMenuButton
isActive={pathname.startsWith("/page/workspace/skills")}
asChild
className={NAV_ITEM_CLASS}
>
<Link className="text-sidebar-foreground" href="/page/workspace/skills">
<Link href="/page/workspace/skills">
<SparklesIcon />
<span>技能</span>
</Link>
@@ -66,8 +85,9 @@ export function WorkspaceNavChatList() {
<SidebarMenuButton
isActive={pathname.startsWith("/page/workspace/memory")}
asChild
className={NAV_ITEM_CLASS}
>
<Link className="text-sidebar-foreground" href="/page/workspace/memory">
<Link href="/page/workspace/memory">
<BrainIcon />
<span>记忆</span>
</Link>
@@ -77,8 +97,9 @@ export function WorkspaceNavChatList() {
<SidebarMenuButton
isActive={pathname === "/page/workspace/scheduled/tasks"}
asChild
className={NAV_ITEM_CLASS}
>
<Link className="text-sidebar-foreground" href="/page/workspace/scheduled/tasks">
<Link href="/page/workspace/scheduled/tasks">
<ListChecks />
<span>任务管理</span>
</Link>