- 非底部时显示悬浮回到底部按钮,点击平滑滚底并隐藏 - 仅在已处于底部附近时随新消息自动贴底,避免打断上翻阅读 - 按钮使用原生圆钮并相对输入区 max-w-4xl 定位,避免 AntD 按钮宽条问题 - 同步 lucide-react 依赖及流式代码块、全局样式等小改动 Made-with: Cursor
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
"axios": "^1.15.0",
|
"axios": "^1.15.0",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"katex": "^0.16.45",
|
"katex": "^0.16.45",
|
||||||
|
"lucide-react": "^1.9.0",
|
||||||
"react": "^19.2.4",
|
"react": "^19.2.4",
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { CheckOutlined, CopyOutlined } from "@ant-design/icons";
|
|
||||||
import { Button, message } from "antd";
|
import { Button, message } from "antd";
|
||||||
|
import { Check, Copy } from "lucide-react";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
import remarkMath from "remark-math";
|
import remarkMath from "remark-math";
|
||||||
@@ -47,7 +47,7 @@ function MarkdownPreBlock(props: { children: ReactNode }) {
|
|||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
icon={copied ? <CheckOutlined /> : <CopyOutlined />}
|
icon={copied ? <Check size={16} /> : <Copy size={16} />}
|
||||||
className="text-neutral-600 hover:bg-black/5! hover:text-neutral-800!"
|
className="text-neutral-600 hover:bg-black/5! hover:text-neutral-800!"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
void onCopy();
|
void onCopy();
|
||||||
|
|||||||
@@ -83,3 +83,9 @@ pre code.hljs {
|
|||||||
.ant-dropdown-menu-item-danger {
|
.ant-dropdown-menu-item-danger {
|
||||||
border-radius: 12px !important;
|
border-radius: 12px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 修正 lucide 图标在 AntD Button 中的基线偏移 */
|
||||||
|
.ant-btn .ant-btn-icon > .lucide {
|
||||||
|
display: block;
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
import type { CSSProperties } from "react";
|
import type { CSSProperties } from "react";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
||||||
import {
|
import {
|
||||||
ArrowUpOutlined,
|
ArrowUp,
|
||||||
CopyOutlined,
|
ChevronDown,
|
||||||
DeleteOutlined,
|
Copy,
|
||||||
EditOutlined,
|
Ellipsis,
|
||||||
LogoutOutlined,
|
LogOut,
|
||||||
MenuFoldOutlined,
|
MessageCirclePlus,
|
||||||
MenuUnfoldOutlined,
|
Paperclip,
|
||||||
MobileOutlined,
|
PanelLeftClose,
|
||||||
MoreOutlined,
|
PanelLeftOpen,
|
||||||
PaperClipOutlined,
|
Pencil,
|
||||||
PlusOutlined,
|
Pin,
|
||||||
QuestionCircleOutlined,
|
RotateCcw,
|
||||||
RedoOutlined,
|
Search,
|
||||||
SearchOutlined,
|
Settings,
|
||||||
ShareAltOutlined,
|
Share2,
|
||||||
SettingOutlined,
|
Smartphone,
|
||||||
ThunderboltOutlined,
|
Trash2,
|
||||||
VerticalAlignTopOutlined,
|
User,
|
||||||
UserOutlined,
|
Zap,
|
||||||
} from "@ant-design/icons";
|
CircleHelp,
|
||||||
|
} from "lucide-react";
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Button,
|
Button,
|
||||||
@@ -88,13 +89,7 @@ function subscribeUserProfile(onChange: () => void) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const INITIAL_MESSAGES: UiMessage[] = [
|
const INITIAL_MESSAGES: UiMessage[] = [];
|
||||||
{
|
|
||||||
id: "init-assistant",
|
|
||||||
role: "assistant",
|
|
||||||
content: "你好,我是 ChatOne 助手,有什么可以帮你?",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const STREAM_TIMEOUT_MS = 90000;
|
const STREAM_TIMEOUT_MS = 90000;
|
||||||
const MAX_STREAM_RETRY = 1;
|
const MAX_STREAM_RETRY = 1;
|
||||||
@@ -155,7 +150,9 @@ export default function HomePage() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const messageListRef = useRef<HTMLDivElement | null>(null);
|
const messageListRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const shouldStickToBottomRef = useRef(true);
|
||||||
const abortRef = useRef<AbortController | null>(null);
|
const abortRef = useRef<AbortController | null>(null);
|
||||||
|
const [showScrollToBottom, setShowScrollToBottom] = useState(false);
|
||||||
|
|
||||||
const [sessions, setSessions] = useState<ChatSession[]>([]);
|
const [sessions, setSessions] = useState<ChatSession[]>([]);
|
||||||
const [sessionsLoading, setSessionsLoading] = useState(false);
|
const [sessionsLoading, setSessionsLoading] = useState(false);
|
||||||
@@ -200,18 +197,18 @@ export default function HomePage() {
|
|||||||
const userMenuDividerStyle: CSSProperties = { margin: "10px 0" };
|
const userMenuDividerStyle: CSSProperties = { margin: "10px 0" };
|
||||||
|
|
||||||
const userMenuItems: MenuProps["items"] = [
|
const userMenuItems: MenuProps["items"] = [
|
||||||
{ key: "app", icon: <MobileOutlined />, label: "下载手机应用", style: userMenuItemStyle },
|
{ key: "app", icon: <Smartphone size={16} />, label: "下载手机应用", style: userMenuItemStyle },
|
||||||
{ key: "settings", icon: <SettingOutlined />, label: "系统设置", style: userMenuItemStyle },
|
{ key: "settings", icon: <Settings size={16} />, label: "系统设置", style: userMenuItemStyle },
|
||||||
{
|
{
|
||||||
key: "help",
|
key: "help",
|
||||||
icon: <QuestionCircleOutlined />,
|
icon: <CircleHelp size={16} />,
|
||||||
label: "帮助与反馈",
|
label: "帮助与反馈",
|
||||||
style: userMenuItemStyle,
|
style: userMenuItemStyle,
|
||||||
},
|
},
|
||||||
{ type: "divider", style: userMenuDividerStyle },
|
{ type: "divider", style: userMenuDividerStyle },
|
||||||
{
|
{
|
||||||
key: "logout",
|
key: "logout",
|
||||||
icon: <LogoutOutlined />,
|
icon: <LogOut size={16} />,
|
||||||
label: "退出登录",
|
label: "退出登录",
|
||||||
danger: true,
|
danger: true,
|
||||||
style: userMenuItemStyle,
|
style: userMenuItemStyle,
|
||||||
@@ -258,11 +255,42 @@ export default function HomePage() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const isMessageListAtBottom = useCallback(() => {
|
||||||
|
const el = messageListRef.current;
|
||||||
|
if (!el) return true;
|
||||||
|
// 允许少量像素误差,避免小数像素造成闪烁。
|
||||||
|
return el.scrollHeight - el.scrollTop - el.clientHeight <= 24;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const scrollMessageListToBottom = useCallback((behavior: ScrollBehavior = "smooth") => {
|
||||||
|
const el = messageListRef.current;
|
||||||
|
if (!el) return;
|
||||||
|
el.scrollTo({ top: el.scrollHeight, behavior });
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const el = messageListRef.current;
|
const el = messageListRef.current;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
el.scrollTop = el.scrollHeight;
|
const onScroll = () => {
|
||||||
}, [messages]);
|
const atBottom = isMessageListAtBottom();
|
||||||
|
shouldStickToBottomRef.current = atBottom;
|
||||||
|
setShowScrollToBottom(!atBottom);
|
||||||
|
};
|
||||||
|
onScroll();
|
||||||
|
el.addEventListener("scroll", onScroll, { passive: true });
|
||||||
|
return () => {
|
||||||
|
el.removeEventListener("scroll", onScroll);
|
||||||
|
};
|
||||||
|
}, [isMessageListAtBottom]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (shouldStickToBottomRef.current) {
|
||||||
|
scrollMessageListToBottom("auto");
|
||||||
|
setShowScrollToBottom(false);
|
||||||
|
} else {
|
||||||
|
setShowScrollToBottom(true);
|
||||||
|
}
|
||||||
|
}, [messages, scrollMessageListToBottom]);
|
||||||
|
|
||||||
const refreshSessionList = useCallback(async (): Promise<ChatSession[]> => {
|
const refreshSessionList = useCallback(async (): Promise<ChatSession[]> => {
|
||||||
if (!window.localStorage.getItem(ACCESS_TOKEN_KEY)) return [];
|
if (!window.localStorage.getItem(ACCESS_TOKEN_KEY)) return [];
|
||||||
@@ -567,7 +595,7 @@ export default function HomePage() {
|
|||||||
loading={sessionMutationBusy}
|
loading={sessionMutationBusy}
|
||||||
disabled={sessionsLoading}
|
disabled={sessionsLoading}
|
||||||
className="h-10! rounded-xl! border-[var(--ds-border)]! bg-[var(--ds-bg-main)]! text-[13px]! font-medium shadow-none hover:border-sky-300! hover:text-sky-600!"
|
className="h-10! rounded-xl! border-[var(--ds-border)]! bg-[var(--ds-bg-main)]! text-[13px]! font-medium shadow-none hover:border-sky-300! hover:text-sky-600!"
|
||||||
icon={<PlusOutlined />}
|
icon={<MessageCirclePlus size={14} strokeWidth={2} />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
void handleNewSession();
|
void handleNewSession();
|
||||||
}}
|
}}
|
||||||
@@ -600,9 +628,9 @@ export default function HomePage() {
|
|||||||
const active = sid === activeSessionId;
|
const active = sid === activeSessionId;
|
||||||
const itemMenu: MenuProps = {
|
const itemMenu: MenuProps = {
|
||||||
items: [
|
items: [
|
||||||
{ key: "rename", icon: <EditOutlined />, label: "重命名" },
|
{ key: "rename", icon: <Pencil size={16} />, label: "重命名" },
|
||||||
{ key: "pin", icon: <VerticalAlignTopOutlined />, label: "置顶" },
|
{ key: "pin", icon: <Pin size={16} />, label: "置顶" },
|
||||||
{ key: "delete", icon: <DeleteOutlined />, danger: true, label: "删除" },
|
{ key: "delete", icon: <Trash2 size={16} />, danger: true, label: "删除" },
|
||||||
],
|
],
|
||||||
onClick: ({ key, domEvent }) => {
|
onClick: ({ key, domEvent }) => {
|
||||||
domEvent.stopPropagation();
|
domEvent.stopPropagation();
|
||||||
@@ -614,7 +642,7 @@ export default function HomePage() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={sid}
|
key={sid}
|
||||||
className="mb-1 flex w-full items-center gap-1 rounded-xl pr-1 text-[14px] text-neutral-700 transition-colors hover:bg-black/5"
|
className="group mb-1.5 flex w-full items-center gap-1 rounded-2xl pr-1 text-[14px] text-neutral-700 transition-colors hover:bg-black/5"
|
||||||
style={{
|
style={{
|
||||||
background: active ? "var(--ds-active-item)" : undefined,
|
background: active ? "var(--ds-active-item)" : undefined,
|
||||||
color: active ? "var(--ds-accent)" : undefined,
|
color: active ? "var(--ds-accent)" : undefined,
|
||||||
@@ -625,23 +653,29 @@ export default function HomePage() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
void openSession(sid);
|
void openSession(sid);
|
||||||
}}
|
}}
|
||||||
className="flex min-h-12 min-w-0 flex-1 items-center rounded-xl px-3 py-3 text-left"
|
className="flex h-11 min-w-0 flex-1 cursor-pointer items-center rounded-2xl px-4 text-left"
|
||||||
>
|
>
|
||||||
{(!collapsed || isMobile) && <span className="truncate">{title}</span>}
|
{(!collapsed || isMobile) && (
|
||||||
|
<span className="min-w-0 flex-1 truncate text-[14px] leading-none">
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
{(!collapsed || isMobile) && (
|
{(!collapsed || isMobile) && (
|
||||||
|
<div className={`${active ? "flex" : "hidden group-hover:flex"}`}>
|
||||||
<Dropdown menu={itemMenu} trigger={["click"]} placement="bottomRight">
|
<Dropdown menu={itemMenu} trigger={["click"]} placement="bottomRight">
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
icon={<MoreOutlined />}
|
icon={<Ellipsis size={16} />}
|
||||||
loading={sessionActionBusyId === sid}
|
loading={sessionActionBusyId === sid}
|
||||||
className="text-neutral-500 hover:bg-black/5!"
|
className="!flex !h-8 !w-8 !items-center !justify-center rounded-xl text-neutral-500 hover:bg-black/5!"
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -650,7 +684,7 @@ export default function HomePage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="shrink-0 border-t border-[var(--ds-border)] p-3">
|
<div className="shrink-0 p-3">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
menu={{ items: userMenuItems, onClick: onUserMenuClick }}
|
menu={{ items: userMenuItems, onClick: onUserMenuClick }}
|
||||||
placement="topLeft"
|
placement="topLeft"
|
||||||
@@ -658,14 +692,14 @@ export default function HomePage() {
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`flex w-full items-center gap-2 rounded-xl bg-neutral-100 px-2 py-2 text-left transition-colors hover:bg-neutral-200/90 ${
|
className={`flex w-full cursor-pointer items-center gap-2 rounded-xl bg-neutral-100 px-2 py-2 text-left transition-colors hover:bg-neutral-200/90 ${
|
||||||
collapsed && !isMobile ? "justify-center py-2.5" : ""
|
collapsed && !isMobile ? "justify-center py-2.5" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
size={36}
|
size={36}
|
||||||
src={userProfile.avatar}
|
src={userProfile.avatar}
|
||||||
icon={!userProfile.avatar ? <UserOutlined /> : undefined}
|
icon={!userProfile.avatar ? <User size={18} /> : undefined}
|
||||||
className="shrink-0 bg-neutral-300"
|
className="shrink-0 bg-neutral-300"
|
||||||
/>
|
/>
|
||||||
{(!collapsed || isMobile) && (
|
{(!collapsed || isMobile) && (
|
||||||
@@ -673,7 +707,7 @@ export default function HomePage() {
|
|||||||
<span className="min-w-0 flex-1 truncate text-[13px] font-medium text-neutral-800">
|
<span className="min-w-0 flex-1 truncate text-[13px] font-medium text-neutral-800">
|
||||||
{userProfile.displayName}
|
{userProfile.displayName}
|
||||||
</span>
|
</span>
|
||||||
<MoreOutlined className="shrink-0 text-neutral-500" />
|
<Ellipsis size={16} className="shrink-0 text-neutral-500" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
@@ -696,10 +730,10 @@ export default function HomePage() {
|
|||||||
width={260}
|
width={260}
|
||||||
collapsedWidth={72}
|
collapsedWidth={72}
|
||||||
theme="light"
|
theme="light"
|
||||||
className="!min-h-0 !h-full !overflow-hidden !bg-[var(--ds-bg-sider)] !border-r !border-[var(--ds-border)]"
|
className="!min-h-0 !h-full !overflow-hidden !bg-[var(--ds-bg-sider)]"
|
||||||
>
|
>
|
||||||
<div className="flex h-full min-h-0 min-w-0 flex-col">
|
<div className="flex h-full min-h-0 min-w-0 flex-col">
|
||||||
<div className="flex h-14 shrink-0 items-center gap-2 border-b border-[var(--ds-border)] px-3">
|
<div className="flex h-14 shrink-0 items-center gap-2 px-3">
|
||||||
<img
|
<img
|
||||||
src="/logo.png"
|
src="/logo.png"
|
||||||
alt=""
|
alt=""
|
||||||
@@ -724,7 +758,7 @@ export default function HomePage() {
|
|||||||
style={{ background: "var(--ds-bg-main)" }}
|
style={{ background: "var(--ds-bg-main)" }}
|
||||||
>
|
>
|
||||||
<Header
|
<Header
|
||||||
className="!flex !h-14 !shrink-0 !items-center !justify-between !border-b !border-[var(--ds-border)] !bg-[var(--ds-bg-main)] !px-5"
|
className="!flex !h-14 !shrink-0 !items-center !justify-between !bg-[var(--ds-bg-main)] !px-5"
|
||||||
style={{ lineHeight: "56px" }}
|
style={{ lineHeight: "56px" }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@@ -733,11 +767,11 @@ export default function HomePage() {
|
|||||||
className="text-neutral-500 hover:bg-black/5!"
|
className="text-neutral-500 hover:bg-black/5!"
|
||||||
icon={
|
icon={
|
||||||
isMobile ? (
|
isMobile ? (
|
||||||
<MenuUnfoldOutlined />
|
<PanelLeftOpen size={16} />
|
||||||
) : collapsed ? (
|
) : collapsed ? (
|
||||||
<MenuUnfoldOutlined />
|
<PanelLeftOpen size={16} />
|
||||||
) : (
|
) : (
|
||||||
<MenuFoldOutlined />
|
<PanelLeftClose size={16} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -770,13 +804,16 @@ export default function HomePage() {
|
|||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
<Content className="flex min-h-0 flex-1 flex-col overflow-hidden !bg-[var(--ds-bg-main)]">
|
<Content className="flex min-h-0 flex-1 flex-col overflow-hidden !bg-[var(--ds-bg-main)]">
|
||||||
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
<div className="relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
||||||
<div
|
<div
|
||||||
ref={messageListRef}
|
ref={messageListRef}
|
||||||
className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden px-4 pt-6 pb-0 md:px-8 md:pt-6 md:pb-0"
|
className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden px-4 pt-6 pb-0 md:px-8 md:pt-6 md:pb-0"
|
||||||
>
|
>
|
||||||
<div className="mx-auto flex max-w-4xl flex-col gap-5">
|
<div className="mx-auto flex max-w-4xl flex-col gap-5">
|
||||||
{messages.map((item) => (
|
{!activeSessionId && messages.length === 0 ? (
|
||||||
|
<p className="px-4 text-[18px] text-neutral-800">你好,我是 ChatOne</p>
|
||||||
|
) : (
|
||||||
|
messages.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className={`flex ${item.role === "user" ? "justify-end" : "justify-start"}`}
|
className={`flex ${item.role === "user" ? "justify-end" : "justify-start"}`}
|
||||||
@@ -804,7 +841,7 @@ export default function HomePage() {
|
|||||||
key: "think",
|
key: "think",
|
||||||
label: (
|
label: (
|
||||||
<span className="text-[13px] text-neutral-600">
|
<span className="text-[13px] text-neutral-600">
|
||||||
<ThunderboltOutlined className="mr-1.5" />
|
<Zap size={14} className="mr-1.5" />
|
||||||
已思考(用时约 2 秒)
|
已思考(用时约 2 秒)
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -839,7 +876,7 @@ export default function HomePage() {
|
|||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
icon={<CopyOutlined />}
|
icon={<Copy size={16} />}
|
||||||
className="!px-1 text-neutral-500 hover:bg-black/5!"
|
className="!px-1 text-neutral-500 hover:bg-black/5!"
|
||||||
aria-label="复制回复"
|
aria-label="复制回复"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -851,7 +888,7 @@ export default function HomePage() {
|
|||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
icon={<RedoOutlined />}
|
icon={<RotateCcw size={16} />}
|
||||||
className="!px-1 text-neutral-500 hover:bg-black/5!"
|
className="!px-1 text-neutral-500 hover:bg-black/5!"
|
||||||
aria-label="重新生成"
|
aria-label="重新生成"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -863,7 +900,7 @@ export default function HomePage() {
|
|||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
icon={<ShareAltOutlined />}
|
icon={<Share2 size={16} />}
|
||||||
className="!px-1 text-neutral-500 hover:bg-black/5!"
|
className="!px-1 text-neutral-500 hover:bg-black/5!"
|
||||||
aria-label="分享"
|
aria-label="分享"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -879,7 +916,7 @@ export default function HomePage() {
|
|||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
icon={<CopyOutlined />}
|
icon={<Copy size={16} />}
|
||||||
className="text-neutral-500 hover:bg-black/5!"
|
className="text-neutral-500 hover:bg-black/5!"
|
||||||
aria-label="复制消息"
|
aria-label="复制消息"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -889,7 +926,7 @@ export default function HomePage() {
|
|||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
icon={<EditOutlined />}
|
icon={<Pencil size={16} />}
|
||||||
className="text-neutral-500 hover:bg-black/5!"
|
className="text-neutral-500 hover:bg-black/5!"
|
||||||
aria-label="编辑消息"
|
aria-label="编辑消息"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -900,12 +937,28 @@ export default function HomePage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="shrink-0 bg-[var(--ds-bg-main)] px-4 pt-0 md:px-8 md:pt-0">
|
<div className="shrink-0 bg-[var(--ds-bg-main)] px-4 pt-0 md:px-8 md:pt-0">
|
||||||
<div className="mx-auto max-w-4xl">
|
<div className="mx-auto max-w-4xl">
|
||||||
|
<div className="relative">
|
||||||
|
{showScrollToBottom && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label="滚动到底部"
|
||||||
|
className="absolute -top-14 right-3 z-10 flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full border border-neutral-200/90 bg-white text-neutral-900 shadow-md transition-colors hover:bg-neutral-50"
|
||||||
|
onClick={() => {
|
||||||
|
shouldStickToBottomRef.current = true;
|
||||||
|
setShowScrollToBottom(false);
|
||||||
|
scrollMessageListToBottom("smooth");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ChevronDown color="#666" size={18} strokeWidth={2} aria-hidden />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<div className="rounded-3xl border border-[var(--ds-border)] bg-neutral-50/50 p-3 shadow-sm">
|
<div className="rounded-3xl border border-[var(--ds-border)] bg-neutral-50/50 p-3 shadow-sm">
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
@@ -925,38 +978,38 @@ export default function HomePage() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setDeepThink((v) => !v)}
|
onClick={() => setDeepThink((v) => !v)}
|
||||||
className={`inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs transition-colors ${
|
className={`inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs transition-colors ${
|
||||||
deepThink
|
deepThink
|
||||||
? "border-sky-300 bg-sky-50 text-sky-700"
|
? "border-sky-300 bg-sky-50 text-sky-700"
|
||||||
: "border-[var(--ds-border)] bg-[var(--ds-bg-main)] text-neutral-600 hover:border-neutral-300"
|
: "border-[var(--ds-border)] bg-[var(--ds-bg-main)] text-neutral-600 hover:border-neutral-300"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<ThunderboltOutlined />
|
<Zap size={14} />
|
||||||
深度思考
|
深度思考
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setSmartSearch((v) => !v)}
|
onClick={() => setSmartSearch((v) => !v)}
|
||||||
className={`inline-flex items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs transition-colors ${
|
className={`inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs transition-colors ${
|
||||||
smartSearch
|
smartSearch
|
||||||
? "border-sky-300 bg-sky-50 text-sky-700"
|
? "border-sky-300 bg-sky-50 text-sky-700"
|
||||||
: "border-[var(--ds-border)] bg-[var(--ds-bg-main)] text-neutral-600 hover:border-neutral-300"
|
: "border-[var(--ds-border)] bg-[var(--ds-bg-main)] text-neutral-600 hover:border-neutral-300"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<SearchOutlined />
|
<Search size={14} />
|
||||||
智能搜索
|
智能搜索
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
icon={<PaperClipOutlined className="text-neutral-400" />}
|
icon={<Paperclip size={16} className="text-neutral-400" />}
|
||||||
className="text-neutral-400"
|
className="text-neutral-400"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
shape="circle"
|
shape="circle"
|
||||||
icon={<ArrowUpOutlined />}
|
icon={<ArrowUp size={16} />}
|
||||||
loading={isSending}
|
loading={isSending}
|
||||||
disabled={isSending || !inputValue.trim()}
|
disabled={isSending || !inputValue.trim()}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -970,6 +1023,7 @@ export default function HomePage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<p className="m-0 py-2 text-center text-[11px] text-neutral-500">
|
<p className="m-0 py-2 text-center text-[11px] text-neutral-500">
|
||||||
内容由 AI 生成,请注意核实
|
内容由 AI 生成,请注意核实
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -2932,6 +2932,11 @@ lru-cache@^5.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
yallist "^3.0.2"
|
yallist "^3.0.2"
|
||||||
|
|
||||||
|
lucide-react@^1.9.0:
|
||||||
|
version "1.9.0"
|
||||||
|
resolved "https://registry.npmmirror.com/lucide-react/-/lucide-react-1.9.0.tgz#3c8324e6b574131624c1869cbd38829d9c659627"
|
||||||
|
integrity sha512-6qVAmbgCjcJz7sAGSPSSJ++RAwjlK2XCbRrZKv63Ciko1KT8jX0//CXxgI3jg2HlJu8tADqdYlNDebmYjeoruA==
|
||||||
|
|
||||||
magic-string@^0.30.21:
|
magic-string@^0.30.21:
|
||||||
version "0.30.21"
|
version "0.30.21"
|
||||||
resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91"
|
resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91"
|
||||||
|
|||||||
Reference in New Issue
Block a user