样式修改
This commit is contained in:
1 parent
97f8940b15
commit
eeee00f6cd
4 files changed
+135
-20
No files matched your search
@@ -514,6 +514,7 @@ export function InputBox({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div className={cn(isNewThread && "gemini-gradient-border gemini-shadow")}>
|
||||||
<PromptInput
|
<PromptInput
|
||||||
className={cn(
|
className={cn(
|
||||||
"transition-all duration-300 ease-out *:data-[slot='input-group']:rounded-3xl",
|
"transition-all duration-300 ease-out *:data-[slot='input-group']:rounded-3xl",
|
||||||
@@ -944,6 +945,7 @@ export function InputBox({
|
|||||||
<div className="absolute right-0 -bottom-[17px] left-0 z-0 h-4 bg-background"></div>
|
<div className="absolute right-0 -bottom-[17px] left-0 z-0 h-4 bg-background"></div>
|
||||||
)}
|
)}
|
||||||
</PromptInput>
|
</PromptInput>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
|
<Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Message } from "@langchain/langgraph-sdk";
|
import type { Message } from "@langchain/langgraph-sdk";
|
||||||
import {
|
import {
|
||||||
BookOpenTextIcon,
|
BookOpenTextIcon,
|
||||||
|
ChevronDownIcon,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
FolderOpenIcon,
|
FolderOpenIcon,
|
||||||
GlobeIcon,
|
GlobeIcon,
|
||||||
@@ -23,6 +24,18 @@ import {
|
|||||||
} from "@/components/ai-elements/chain-of-thought";
|
} from "@/components/ai-elements/chain-of-thought";
|
||||||
import { CodeBlock } from "@/components/ai-elements/code-block";
|
import { CodeBlock } from "@/components/ai-elements/code-block";
|
||||||
import { Button } from "@/components/ui/button";
|
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 { useI18n } from "@/core/i18n/hooks";
|
||||||
import {
|
import {
|
||||||
extractReasoningContentFromMessage,
|
extractReasoningContentFromMessage,
|
||||||
@@ -208,19 +221,10 @@ function ToolCall({
|
|||||||
if (typeof args.query === "string") {
|
if (typeof args.query === "string") {
|
||||||
label = t.toolCalls.searchOnWebFor(args.query);
|
label = t.toolCalls.searchOnWebFor(args.query);
|
||||||
}
|
}
|
||||||
|
const items = normalizeSearchResults(result);
|
||||||
return (
|
return (
|
||||||
<ChainOfThoughtStep key={id} label={label} icon={SearchIcon}>
|
<ChainOfThoughtStep key={id} label={label} icon={SearchIcon}>
|
||||||
{Array.isArray(result) && (
|
{items.length > 0 && <WebSearchResultsList results={items} />}
|
||||||
<ChainOfThoughtSearchResults>
|
|
||||||
{result.map((item) => (
|
|
||||||
<ChainOfThoughtSearchResult key={item.url}>
|
|
||||||
<a href={item.url} target="_blank" rel="noopener noreferrer">
|
|
||||||
{item.title}
|
|
||||||
</a>
|
|
||||||
</ChainOfThoughtSearchResult>
|
|
||||||
))}
|
|
||||||
</ChainOfThoughtSearchResults>
|
|
||||||
)}
|
|
||||||
</ChainOfThoughtStep>
|
</ChainOfThoughtStep>
|
||||||
);
|
);
|
||||||
} else if (name === "image_search") {
|
} else if (name === "image_search") {
|
||||||
@@ -496,3 +500,117 @@ function convertToSteps(messages: Message[]): CoTStep[] {
|
|||||||
}
|
}
|
||||||
return steps;
|
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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -21,11 +21,11 @@ const NAV_ITEM_CLASS =
|
|||||||
"data-[active=true]:bg-white data-[active=true]:text-foreground data-[active=true]:shadow-[0_1px_2px_rgba(15,23,42,0.06),0_1px_3px_rgba(15,23,42,0.04)] " +
|
"data-[active=true]:bg-white data-[active=true]:text-foreground data-[active=true]:shadow-[0_1px_2px_rgba(15,23,42,0.06),0_1px_3px_rgba(15,23,42,0.04)] " +
|
||||||
"dark:data-[active=true]:bg-slate-700/80 dark:data-[active=true]:text-slate-100 dark:data-[active=true]:shadow-none";
|
"dark:data-[active=true]:bg-slate-700/80 dark:data-[active=true]:text-slate-100 dark:data-[active=true]:shadow-none";
|
||||||
|
|
||||||
// 「新对话」专属:始终主色 chip,让它在导航里更显眼
|
// 「新对话」专属:默认仅主色文字/图标(显眼但不是选中态),命中 /chats/new 时才上主色底
|
||||||
const NEW_CHAT_CLASS =
|
const NEW_CHAT_CLASS =
|
||||||
"rounded-xl h-9 px-3 bg-primary/10 text-primary hover:bg-primary/15 hover:text-primary " +
|
"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 " +
|
"data-[active=true]:bg-primary/15 data-[active=true]:text-primary data-[active=true]:shadow-none " +
|
||||||
"dark:bg-primary/15 dark:text-primary-foreground dark:hover:bg-primary/25 dark:data-[active=true]:bg-primary/25";
|
"dark:text-primary-foreground dark:hover:bg-primary/15 dark:data-[active=true]:bg-primary/25";
|
||||||
|
|
||||||
export function WorkspaceNavChatList() {
|
export function WorkspaceNavChatList() {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|||||||
@@ -236,12 +236,7 @@ export default function ChatPage() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{mounted ? (
|
{mounted ? (
|
||||||
<div
|
<div className="w-full -translate-y-4">
|
||||||
className={cn(
|
|
||||||
"w-full -translate-y-4",
|
|
||||||
isLandingState && "gemini-gradient-border gemini-shadow",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<InputBox
|
<InputBox
|
||||||
className="w-full"
|
className="w-full"
|
||||||
isNewThread={isLandingState}
|
isNewThread={isLandingState}
|
||||||
|
|||||||
Reference in new issue
Block a user