chat: reposition session actions and refine history drawer
parent
3f5b737fbb
commit
d7bf0e5c9d
|
|
@ -9,9 +9,10 @@ interface Props {
|
|||
onChange: (sessionId: string, options?: { highlightMessageId?: string }) => void;
|
||||
refreshTick?: number;
|
||||
theme?: 'light' | 'dark';
|
||||
showNewButton?: boolean;
|
||||
}
|
||||
|
||||
export default function SessionSidebar({ agentId, activeSessionId, onChange, refreshTick, theme = 'light' }: Props) {
|
||||
export default function SessionSidebar({ agentId, activeSessionId, onChange, refreshTick, theme = 'light', showNewButton = true }: Props) {
|
||||
const { message } = AntApp.useApp();
|
||||
const [tab, setTab] = useState<'active' | 'archived'>('active');
|
||||
const [active, setActive] = useState<ChatSession[]>([]);
|
||||
|
|
@ -269,9 +270,11 @@ export default function SessionSidebar({ agentId, activeSessionId, onChange, ref
|
|||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<Button type="primary" onClick={handleNew} style={{ marginBottom: 8, background: c.primaryBtn }} block>
|
||||
➕ 新对话
|
||||
</Button>
|
||||
{showNewButton && (
|
||||
<Button type="primary" onClick={handleNew} style={{ marginBottom: 8, background: c.primaryBtn }} block>
|
||||
➕ 新对话
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Input.Search
|
||||
placeholder="搜索会话与消息…"
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ export function NewChatIcon(props: SVGProps<SVGSVGElement>) {
|
|||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
fill="var(--theme-icon-brand)"
|
||||
fill="currentColor"
|
||||
d="M128 22C69.458 22 22 69.458 22 128v96c0 5.523 4.477 10 10 10h96c58.542 0 106-47.458 106-106S186.542 22 128 22Zm0 56c5.523 0 10 4.477 10 10v30h30c5.523 0 10 4.477 10 10s-4.477 10-10 10h-30v30c0 5.523-4.477 10-10 10s-10-4.477-10-10v-30H88c-5.523 0-10-4.477-10-10s4.477-10 10-10h30V88c0-5.523 4.477-10 10-10Z"
|
||||
/>
|
||||
</g>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Button, Drawer, Empty, InputNumber, Slider } from 'antd';
|
||||
import { Button, Drawer, Dropdown, Empty, InputNumber, Slider } from 'antd';
|
||||
import type { Agent, ModelOverrides } from '../../../api';
|
||||
import McpResourcesDrawer from '../../../components/McpResourcesDrawer';
|
||||
import SessionSidebar from '../../../components/SessionSidebar';
|
||||
|
|
@ -142,12 +142,25 @@ export default function ChatDrawers(props: {
|
|||
</Drawer>
|
||||
|
||||
<Drawer
|
||||
title="历史对话"
|
||||
title="聊天记录"
|
||||
placement="right"
|
||||
width={320}
|
||||
onClose={() => setHistoryDrawerOpen(false)}
|
||||
open={historyDrawerOpen}
|
||||
styles={{ body: { padding: 0 } }}
|
||||
extra={
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
menu={{
|
||||
items: [
|
||||
{ key: 'select', label: '选择', disabled: true },
|
||||
{ key: 'clear', label: '清除所有对话', disabled: true }
|
||||
]
|
||||
}}
|
||||
>
|
||||
<Button type="text">⋯</Button>
|
||||
</Dropdown>
|
||||
}
|
||||
>
|
||||
{agentId ? (
|
||||
<SessionSidebar
|
||||
|
|
@ -159,6 +172,7 @@ export default function ChatDrawers(props: {
|
|||
setHistoryDrawerOpen(false);
|
||||
}}
|
||||
refreshTick={sessionRefresh}
|
||||
showNewButton={false}
|
||||
/>
|
||||
) : (
|
||||
<Empty description="未选择 Agent" />
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export default function ChatInput(props: {
|
|||
))}
|
||||
</div>
|
||||
|
||||
<div className="chat-input-card">
|
||||
<div className="chat-input-card-wrap">
|
||||
<div className="chat-input-actions">
|
||||
<Tooltip title="历史记录">
|
||||
<Button size="small" type="text" className="chat-input-action-btn" icon={<HistoryIcon />} onClick={onOpenHistory}>
|
||||
|
|
@ -71,82 +71,84 @@ export default function ChatInput(props: {
|
|||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="新增会话">
|
||||
<Button size="small" type="text" className="chat-input-action-btn" icon={<NewChatIcon />} onClick={onNewSession}>
|
||||
<Button size="small" type="text" className="chat-input-action-btn chat-input-action-btn-primary" icon={<NewChatIcon />} onClick={onNewSession}>
|
||||
新增会话
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="chat-input-stack">
|
||||
<Input.TextArea
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder="问我任何问题..."
|
||||
autoSize={{ minRows: 3, maxRows: 10 }}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== 'Enter') return;
|
||||
if ((e as any).isComposing) return;
|
||||
<div className="chat-input-card">
|
||||
<div className="chat-input-stack">
|
||||
<Input.TextArea
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder="问我任何问题..."
|
||||
autoSize={{ minRows: 3, maxRows: 10 }}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== 'Enter') return;
|
||||
if ((e as any).isComposing) return;
|
||||
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
const el = e.currentTarget;
|
||||
const start = el.selectionStart ?? input.length;
|
||||
const end = el.selectionEnd ?? input.length;
|
||||
const next = input.slice(0, start) + '\n' + input.slice(end);
|
||||
setInput(next);
|
||||
requestAnimationFrame(() => {
|
||||
el.selectionStart = el.selectionEnd = start + 1;
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (e.metaKey || e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
const el = e.currentTarget;
|
||||
const start = el.selectionStart ?? input.length;
|
||||
const end = el.selectionEnd ?? input.length;
|
||||
const next = input.slice(0, start) + '\n' + input.slice(end);
|
||||
setInput(next);
|
||||
requestAnimationFrame(() => {
|
||||
el.selectionStart = el.selectionEnd = start + 1;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.shiftKey && !e.altKey) {
|
||||
e.preventDefault();
|
||||
onSend();
|
||||
}
|
||||
}}
|
||||
className="chat-input-textarea"
|
||||
disabled={sending}
|
||||
/>
|
||||
if (!e.shiftKey && !e.altKey) {
|
||||
e.preventDefault();
|
||||
onSend();
|
||||
}
|
||||
}}
|
||||
className="chat-input-textarea"
|
||||
disabled={sending}
|
||||
/>
|
||||
|
||||
<div className="chat-input-toolbar">
|
||||
<div className="chat-input-toolbar-left">
|
||||
<Select
|
||||
value={activeModelValue || undefined}
|
||||
className="chat-model-select"
|
||||
popupMatchSelectWidth={false}
|
||||
options={modelOptions}
|
||||
suffixIcon={<DownOutlined className="chat-model-select-arrow" />}
|
||||
placeholder="选择模型"
|
||||
onChange={onChangeModel}
|
||||
/>
|
||||
<div className="chat-input-toolbar">
|
||||
<div className="chat-input-toolbar-left">
|
||||
<Select
|
||||
value={activeModelValue || undefined}
|
||||
className="chat-model-select"
|
||||
popupMatchSelectWidth={false}
|
||||
options={modelOptions}
|
||||
suffixIcon={<DownOutlined className="chat-model-select-arrow" />}
|
||||
placeholder="选择模型"
|
||||
onChange={onChangeModel}
|
||||
/>
|
||||
|
||||
<Upload
|
||||
className="chat-upload"
|
||||
multiple
|
||||
beforeUpload={(_f, files) => {
|
||||
onAttach(files as File[]);
|
||||
return false;
|
||||
}}
|
||||
showUploadList={false}
|
||||
accept=".txt,.md,.markdown,.json,.csv,.pdf,.docx,.html,.htm,image/png,image/jpeg,image/webp,image/gif"
|
||||
>
|
||||
<Button type="text" className="chat-tool-button" icon={<PaperClipOutlined style={{ fontSize: 18 }} />} />
|
||||
</Upload>
|
||||
<Button type="text" className="chat-tool-button" icon={<BookOutlined style={{ fontSize: 18 }} />} onClick={onOpenTpl} />
|
||||
<Upload
|
||||
className="chat-upload"
|
||||
multiple
|
||||
beforeUpload={(_f, files) => {
|
||||
onAttach(files as File[]);
|
||||
return false;
|
||||
}}
|
||||
showUploadList={false}
|
||||
accept=".txt,.md,.markdown,.json,.csv,.pdf,.docx,.html,.htm,image/png,image/jpeg,image/webp,image/gif"
|
||||
>
|
||||
<Button type="text" className="chat-tool-button" icon={<PaperClipOutlined style={{ fontSize: 18 }} />} />
|
||||
</Upload>
|
||||
<Button type="text" className="chat-tool-button" icon={<BookOutlined style={{ fontSize: 18 }} />} onClick={onOpenTpl} />
|
||||
</div>
|
||||
|
||||
{sending ? (
|
||||
<Button danger shape="circle" onClick={onStop} icon={<span className="chat-stop-icon" />} className="chat-send-button" />
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
shape="circle"
|
||||
onClick={onSend}
|
||||
icon={<ArrowUpOutlined />}
|
||||
disabled={!input.trim()}
|
||||
className="chat-send-button chat-send-button-primary"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{sending ? (
|
||||
<Button danger shape="circle" onClick={onStop} icon={<span className="chat-stop-icon" />} className="chat-send-button" />
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
shape="circle"
|
||||
onClick={onSend}
|
||||
icon={<ArrowUpOutlined />}
|
||||
disabled={!input.trim()}
|
||||
className="chat-send-button chat-send-button-primary"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -749,13 +749,16 @@ body {
|
|||
padding: 14px 16px 12px;
|
||||
min-height: 110px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.chat-input-card-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chat-input-actions {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 12px;
|
||||
top: -10px;
|
||||
right: 8px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
z-index: 2;
|
||||
|
|
@ -772,6 +775,15 @@ body {
|
|||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.chat-input-action-btn-primary {
|
||||
color: var(--color-brand);
|
||||
}
|
||||
|
||||
.chat-input-action-btn-primary:hover {
|
||||
color: var(--color-brand);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.chat-input-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
Loading…
Reference in New Issue