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;
|
onChange: (sessionId: string, options?: { highlightMessageId?: string }) => void;
|
||||||
refreshTick?: number;
|
refreshTick?: number;
|
||||||
theme?: 'light' | 'dark';
|
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 { message } = AntApp.useApp();
|
||||||
const [tab, setTab] = useState<'active' | 'archived'>('active');
|
const [tab, setTab] = useState<'active' | 'archived'>('active');
|
||||||
const [active, setActive] = useState<ChatSession[]>([]);
|
const [active, setActive] = useState<ChatSession[]>([]);
|
||||||
|
|
@ -269,9 +270,11 @@ export default function SessionSidebar({ agentId, activeSessionId, onChange, ref
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
<Button type="primary" onClick={handleNew} style={{ marginBottom: 8, background: c.primaryBtn }} block>
|
{showNewButton && (
|
||||||
➕ 新对话
|
<Button type="primary" onClick={handleNew} style={{ marginBottom: 8, background: c.primaryBtn }} block>
|
||||||
</Button>
|
➕ 新对话
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder="搜索会话与消息…"
|
placeholder="搜索会话与消息…"
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ export function NewChatIcon(props: SVGProps<SVGSVGElement>) {
|
||||||
<path
|
<path
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="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"
|
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>
|
</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 type { Agent, ModelOverrides } from '../../../api';
|
||||||
import McpResourcesDrawer from '../../../components/McpResourcesDrawer';
|
import McpResourcesDrawer from '../../../components/McpResourcesDrawer';
|
||||||
import SessionSidebar from '../../../components/SessionSidebar';
|
import SessionSidebar from '../../../components/SessionSidebar';
|
||||||
|
|
@ -142,12 +142,25 @@ export default function ChatDrawers(props: {
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
<Drawer
|
<Drawer
|
||||||
title="历史对话"
|
title="聊天记录"
|
||||||
placement="right"
|
placement="right"
|
||||||
width={320}
|
width={320}
|
||||||
onClose={() => setHistoryDrawerOpen(false)}
|
onClose={() => setHistoryDrawerOpen(false)}
|
||||||
open={historyDrawerOpen}
|
open={historyDrawerOpen}
|
||||||
styles={{ body: { padding: 0 } }}
|
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 ? (
|
{agentId ? (
|
||||||
<SessionSidebar
|
<SessionSidebar
|
||||||
|
|
@ -159,6 +172,7 @@ export default function ChatDrawers(props: {
|
||||||
setHistoryDrawerOpen(false);
|
setHistoryDrawerOpen(false);
|
||||||
}}
|
}}
|
||||||
refreshTick={sessionRefresh}
|
refreshTick={sessionRefresh}
|
||||||
|
showNewButton={false}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Empty description="未选择 Agent" />
|
<Empty description="未选择 Agent" />
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ export default function ChatInput(props: {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="chat-input-card">
|
<div className="chat-input-card-wrap">
|
||||||
<div className="chat-input-actions">
|
<div className="chat-input-actions">
|
||||||
<Tooltip title="历史记录">
|
<Tooltip title="历史记录">
|
||||||
<Button size="small" type="text" className="chat-input-action-btn" icon={<HistoryIcon />} onClick={onOpenHistory}>
|
<Button size="small" type="text" className="chat-input-action-btn" icon={<HistoryIcon />} onClick={onOpenHistory}>
|
||||||
|
|
@ -71,82 +71,84 @@ export default function ChatInput(props: {
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="新增会话">
|
<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>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div className="chat-input-stack">
|
<div className="chat-input-card">
|
||||||
<Input.TextArea
|
<div className="chat-input-stack">
|
||||||
value={input}
|
<Input.TextArea
|
||||||
onChange={(e) => setInput(e.target.value)}
|
value={input}
|
||||||
placeholder="问我任何问题..."
|
onChange={(e) => setInput(e.target.value)}
|
||||||
autoSize={{ minRows: 3, maxRows: 10 }}
|
placeholder="问我任何问题..."
|
||||||
onKeyDown={(e) => {
|
autoSize={{ minRows: 3, maxRows: 10 }}
|
||||||
if (e.key !== 'Enter') return;
|
onKeyDown={(e) => {
|
||||||
if ((e as any).isComposing) return;
|
if (e.key !== 'Enter') return;
|
||||||
|
if ((e as any).isComposing) return;
|
||||||
|
|
||||||
if (e.metaKey || e.ctrlKey) {
|
if (e.metaKey || e.ctrlKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const el = e.currentTarget;
|
const el = e.currentTarget;
|
||||||
const start = el.selectionStart ?? input.length;
|
const start = el.selectionStart ?? input.length;
|
||||||
const end = el.selectionEnd ?? input.length;
|
const end = el.selectionEnd ?? input.length;
|
||||||
const next = input.slice(0, start) + '\n' + input.slice(end);
|
const next = input.slice(0, start) + '\n' + input.slice(end);
|
||||||
setInput(next);
|
setInput(next);
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
el.selectionStart = el.selectionEnd = start + 1;
|
el.selectionStart = el.selectionEnd = start + 1;
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!e.shiftKey && !e.altKey) {
|
if (!e.shiftKey && !e.altKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
onSend();
|
onSend();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="chat-input-textarea"
|
className="chat-input-textarea"
|
||||||
disabled={sending}
|
disabled={sending}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="chat-input-toolbar">
|
<div className="chat-input-toolbar">
|
||||||
<div className="chat-input-toolbar-left">
|
<div className="chat-input-toolbar-left">
|
||||||
<Select
|
<Select
|
||||||
value={activeModelValue || undefined}
|
value={activeModelValue || undefined}
|
||||||
className="chat-model-select"
|
className="chat-model-select"
|
||||||
popupMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
options={modelOptions}
|
options={modelOptions}
|
||||||
suffixIcon={<DownOutlined className="chat-model-select-arrow" />}
|
suffixIcon={<DownOutlined className="chat-model-select-arrow" />}
|
||||||
placeholder="选择模型"
|
placeholder="选择模型"
|
||||||
onChange={onChangeModel}
|
onChange={onChangeModel}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Upload
|
<Upload
|
||||||
className="chat-upload"
|
className="chat-upload"
|
||||||
multiple
|
multiple
|
||||||
beforeUpload={(_f, files) => {
|
beforeUpload={(_f, files) => {
|
||||||
onAttach(files as File[]);
|
onAttach(files as File[]);
|
||||||
return false;
|
return false;
|
||||||
}}
|
}}
|
||||||
showUploadList={false}
|
showUploadList={false}
|
||||||
accept=".txt,.md,.markdown,.json,.csv,.pdf,.docx,.html,.htm,image/png,image/jpeg,image/webp,image/gif"
|
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 }} />} />
|
<Button type="text" className="chat-tool-button" icon={<PaperClipOutlined style={{ fontSize: 18 }} />} />
|
||||||
</Upload>
|
</Upload>
|
||||||
<Button type="text" className="chat-tool-button" icon={<BookOutlined style={{ fontSize: 18 }} />} onClick={onOpenTpl} />
|
<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>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -749,13 +749,16 @@ body {
|
||||||
padding: 14px 16px 12px;
|
padding: 14px 16px 12px;
|
||||||
min-height: 110px;
|
min-height: 110px;
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-card-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-input-actions {
|
.chat-input-actions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: -10px;
|
||||||
right: 12px;
|
right: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|
@ -772,6 +775,15 @@ body {
|
||||||
color: var(--color-text);
|
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 {
|
.chat-input-stack {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue