ui: refactor session sidebar styles and widen history drawer

main
sp mac bookpro 2605 2026-06-06 14:46:07 +08:00
parent b333600245
commit 1c2c7bd06a
3 changed files with 208 additions and 76 deletions

View File

@ -67,6 +67,19 @@ export default function SessionSidebar({ agentId, activeSessionId, onChange, ref
}; };
const list = tab === 'active' ? active : archived; const list = tab === 'active' ? active : archived;
const styleVars = {
['--ss-text' as any]: c.text,
['--ss-text-dim' as any]: c.textDim,
['--ss-text-active' as any]: c.textActive,
['--ss-bg-active' as any]: c.bgActive,
['--ss-border-active' as any]: c.borderActive,
['--ss-bg-hover' as any]: c.bgHover,
['--ss-bg-search' as any]: c.bgSearch,
['--ss-input-bg' as any]: c.inputBg,
['--ss-border' as any]: c.border,
['--ss-danger' as any]: c.dangerText,
['--ss-primary' as any]: c.primaryBtn ?? 'var(--color-brand)'
} as any;
// 防抖搜索 // 防抖搜索
useEffect(() => { useEffect(() => {
@ -165,16 +178,9 @@ export default function SessionSidebar({ agentId, activeSessionId, onChange, ref
<List.Item <List.Item
key={s.id} key={s.id}
onClick={() => (editing ? null : onChange(s.id))} onClick={() => (editing ? null : onChange(s.id))}
style={{ className={`session-sidebar-item${isActive ? ' active' : ''}`}
cursor: 'pointer',
padding: '8px 10px',
background: isActive ? c.bgActive : c.bgTransparent,
borderRadius: 6,
border: isActive ? `1px solid ${c.borderActive}` : '1px solid transparent',
marginBottom: 4
}}
> >
<div style={{ flex: 1, minWidth: 0 }}> <div className="session-sidebar-item-main">
{editing ? ( {editing ? (
<Input <Input
size="small" size="small"
@ -186,35 +192,17 @@ export default function SessionSidebar({ agentId, activeSessionId, onChange, ref
/> />
) : ( ) : (
<> <>
<div <div className={`session-sidebar-item-title${isActive ? ' active' : ''}`}>
style={{ <MessageOutlined /> {s.title}
fontSize: 13,
fontWeight: isActive ? 600 : 500,
color: isActive ? c.textActive : c.text,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}
>
💬 {s.title}
</div> </div>
<div <div className="session-sidebar-item-subtitle">
style={{
fontSize: 11,
color: c.textDim,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
marginTop: 2
}}
>
{s.lastPreview || '无消息'} · {dayjs(s.lastAt || s.updatedAt).format('MM-DD HH:mm')} {s.lastPreview || '无消息'} · {dayjs(s.lastAt || s.updatedAt).format('MM-DD HH:mm')}
</div> </div>
</> </>
)} )}
</div> </div>
{!editing && ( {!editing && (
<div style={{ display: 'flex', gap: 2, marginLeft: 6 }} onClick={(e) => e.stopPropagation()}> <div className="session-sidebar-item-actions" onClick={(e) => e.stopPropagation()}>
<Tooltip title="重命名"> <Tooltip title="重命名">
<Button <Button
type="text" type="text"
@ -266,10 +254,10 @@ export default function SessionSidebar({ agentId, activeSessionId, onChange, ref
const inSearchMode = searchQ.trim().length > 0; const inSearchMode = searchQ.trim().length > 0;
return ( return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}> <div className="session-sidebar" style={styleVars}>
{showNewButton && ( {showNewButton && (
<Button type="primary" onClick={handleNew} style={{ marginBottom: 8, background: c.primaryBtn }} block> <Button type="primary" onClick={handleNew} className="session-sidebar-new" block>
</Button> </Button>
)} )}
@ -278,14 +266,13 @@ export default function SessionSidebar({ agentId, activeSessionId, onChange, ref
allowClear allowClear
value={searchQ} value={searchQ}
onChange={(e) => setSearchQ(e.target.value)} onChange={(e) => setSearchQ(e.target.value)}
style={{ marginBottom: 8 }} className="session-sidebar-search"
styles={isDark ? { input: { background: c.inputBg, borderColor: c.border, color: c.text } } : undefined}
/> />
{inSearchMode ? ( {inSearchMode ? (
<div style={{ flex: 1, overflow: 'auto' }}> <div className="session-sidebar-scroll">
{searching ? ( {searching ? (
<div style={{ padding: 16, textAlign: 'center' }}> <div className="session-sidebar-loading">
<Spin size="small" /> <Spin size="small" />
</div> </div>
) : searchHits.length === 0 && searchSessionTitles.length === 0 ? ( ) : searchHits.length === 0 && searchSessionTitles.length === 0 ? (
@ -293,25 +280,17 @@ export default function SessionSidebar({ agentId, activeSessionId, onChange, ref
) : ( ) : (
<> <>
{searchSessionTitles.length > 0 && ( {searchSessionTitles.length > 0 && (
<div style={{ marginBottom: 8 }}> <div className="session-sidebar-search-section">
<div style={{ fontSize: 11, color: c.textDim, padding: '4px 8px' }}> <div className="session-sidebar-search-section-title">
📁 ({searchSessionTitles.length}) 📁 ({searchSessionTitles.length})
</div> </div>
{searchSessionTitles.map((s) => ( {searchSessionTitles.map((s) => (
<div <div
key={s.id} key={s.id}
onClick={() => onChange(s.id)} onClick={() => onChange(s.id)}
style={{ className={`session-sidebar-search-hit${activeSessionId === s.id ? ' active' : ''}`}
cursor: 'pointer',
padding: '6px 10px',
borderRadius: 6,
marginBottom: 2,
background: activeSessionId === s.id ? c.bgActive : c.bgSearch,
fontSize: 12,
color: c.text
}}
> >
💬 {s.title} <MessageOutlined /> {s.title}
{s.archived && <Tag style={{ marginLeft: 6 }}></Tag>} {s.archived && <Tag style={{ marginLeft: 6 }}></Tag>}
</div> </div>
))} ))}
@ -319,7 +298,7 @@ export default function SessionSidebar({ agentId, activeSessionId, onChange, ref
)} )}
{searchHits.length > 0 && ( {searchHits.length > 0 && (
<div> <div>
<div style={{ fontSize: 11, color: c.textDim, padding: '4px 8px' }}> <div className="session-sidebar-search-section-title">
🔎 ({searchHits.length}) 🔎 ({searchHits.length})
</div> </div>
{searchHits.map((h) => ( {searchHits.map((h) => (
@ -328,22 +307,15 @@ export default function SessionSidebar({ agentId, activeSessionId, onChange, ref
onClick={() => onClick={() =>
onChange(h.sessionId, { highlightMessageId: h.id }) onChange(h.sessionId, { highlightMessageId: h.id })
} }
style={{ className={`session-sidebar-search-msg-hit ${h.role === 'user' ? 'role-user' : 'role-assistant'}`}
cursor: 'pointer',
padding: 8,
borderRadius: 6,
marginBottom: 4,
background: c.bgSearch,
borderLeft: `3px solid ${h.role === 'user' ? '#6366f1' : '#10b981'}`
}}
> >
<div style={{ fontSize: 11, color: c.textDim, marginBottom: 2 }}> <div className="session-sidebar-search-msg-meta">
{h.role === 'user' ? '我' : 'AI'} · {h.sessionTitle} {h.role === 'user' ? '我' : 'AI'} · {h.sessionTitle}
{h.sessionArchived && <Tag style={{ marginLeft: 4 }}></Tag>} {h.sessionArchived && <Tag style={{ marginLeft: 4 }}></Tag>}
<span style={{ marginLeft: 6 }}>{dayjs(h.createdAt).format('MM-DD HH:mm')}</span> <span style={{ marginLeft: 6 }}>{dayjs(h.createdAt).format('MM-DD HH:mm')}</span>
</div> </div>
<div <div
style={{ fontSize: 12, color: c.text, lineHeight: 1.4 }} className="session-sidebar-search-msg-snippet"
dangerouslySetInnerHTML={{ __html: h.snippet }} dangerouslySetInnerHTML={{ __html: h.snippet }}
/> />
</div> </div>
@ -364,7 +336,7 @@ export default function SessionSidebar({ agentId, activeSessionId, onChange, ref
{ {
value: 'active', value: 'active',
label: ( label: (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}> <span className="session-sidebar-seg-label">
<MessageOutlined /> ({active.length}) <MessageOutlined /> ({active.length})
</span> </span>
) )
@ -372,13 +344,13 @@ export default function SessionSidebar({ agentId, activeSessionId, onChange, ref
{ {
value: 'archived', value: 'archived',
label: ( label: (
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}> <span className="session-sidebar-seg-label">
<InboxOutlined /> ({archived.length}) <InboxOutlined /> ({archived.length})
</span> </span>
) )
} }
]} ]}
style={{ marginBottom: 8 }} className="session-sidebar-seg"
/> />
{list.length === 0 ? ( {list.length === 0 ? (
<Empty description={tab === 'active' ? '无活跃会话' : '无归档会话'} image={Empty.PRESENTED_IMAGE_SIMPLE} /> <Empty description={tab === 'active' ? '无活跃会话' : '无归档会话'} image={Empty.PRESENTED_IMAGE_SIMPLE} />
@ -386,22 +358,12 @@ export default function SessionSidebar({ agentId, activeSessionId, onChange, ref
<List <List
size="small" size="small"
dataSource={list} dataSource={list}
style={{ flex: 1, overflow: 'auto' }} className="session-sidebar-list"
renderItem={renderSession} renderItem={renderSession}
/> />
)} )}
</> </>
)} )}
{/* 顶部全局样式 mark 标签高亮 */}
<style>{`
mark {
background: #fef3c7;
color: #b45309;
padding: 0 2px;
border-radius: 2px;
}
`}</style>
</div> </div>
); );
} }

View File

@ -144,7 +144,7 @@ export default function ChatDrawers(props: {
<Drawer <Drawer
title="聊天记录" title="聊天记录"
placement="right" placement="right"
width={320} width={420}
onClose={() => setHistoryDrawerOpen(false)} onClose={() => setHistoryDrawerOpen(false)}
open={historyDrawerOpen} open={historyDrawerOpen}
styles={{ body: { padding: 0 } }} styles={{ body: { padding: 0 } }}

View File

@ -870,6 +870,176 @@ body {
border-radius: 2px; border-radius: 2px;
} }
.session-sidebar {
width: 420px;
height: 100%;
display: flex;
flex-direction: column;
padding: 0 4px;
box-sizing: border-box;
}
.session-sidebar-new {
margin-bottom: 8px;
background: var(--ss-primary);
border: none;
}
.session-sidebar-search {
margin-bottom: 8px;
}
.session-sidebar .ant-input-affix-wrapper,
.session-sidebar .ant-input-affix-wrapper:hover,
.session-sidebar .ant-input-affix-wrapper-focused {
background: var(--ss-input-bg);
border-color: var(--ss-border);
}
.session-sidebar .ant-input {
color: var(--ss-text);
}
.session-sidebar-scroll {
flex: 1;
overflow: auto;
}
.session-sidebar-loading {
padding: 16px;
text-align: center;
}
.session-sidebar-search-section {
margin-bottom: 8px;
}
.session-sidebar-search-section-title {
font-size: 11px;
color: var(--ss-text-dim);
padding: 4px 8px;
}
.session-sidebar-search-hit {
cursor: pointer;
padding: 6px 10px;
border-radius: 6px;
margin-bottom: 2px;
background: var(--ss-bg-search);
font-size: 12px;
color: var(--ss-text);
}
.session-sidebar-search-hit.active {
background: var(--ss-bg-active);
}
.session-sidebar-search-msg-hit {
cursor: pointer;
padding: 8px;
border-radius: 6px;
margin-bottom: 4px;
background: var(--ss-bg-search);
border-left: 3px solid transparent;
}
.session-sidebar-search-msg-hit.role-user {
border-left-color: #6366f1;
}
.session-sidebar-search-msg-hit.role-assistant {
border-left-color: #10b981;
}
.session-sidebar-search-msg-meta {
font-size: 11px;
color: var(--ss-text-dim);
margin-bottom: 2px;
}
.session-sidebar-search-msg-snippet {
font-size: 12px;
color: var(--ss-text);
line-height: 1.4;
}
.session-sidebar-seg {
margin-bottom: 8px;
}
.session-sidebar-seg-label {
display: inline-flex;
align-items: center;
gap: 6px;
}
.session-sidebar-list {
flex: 1;
overflow: auto;
}
.session-sidebar-item {
cursor: pointer;
padding: 8px 10px;
background: transparent;
border-radius: 6px;
border: 1px solid transparent;
margin-bottom: 4px;
}
.session-sidebar-item:hover {
background: var(--ss-bg-hover);
}
.session-sidebar-item.active {
background: var(--ss-bg-active);
border-color: var(--ss-border-active);
}
.session-sidebar-item-main {
flex: 1;
min-width: 0;
}
.session-sidebar-item-title {
font-size: 13px;
font-weight: 500;
color: var(--ss-text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-flex;
align-items: center;
gap: 6px;
}
.session-sidebar-item-title.active {
font-weight: 600;
color: var(--ss-text-active);
}
.session-sidebar-item-subtitle {
font-size: 11px;
color: var(--ss-text-dim);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 2px;
}
.session-sidebar-item-actions {
display: flex;
gap: 2px;
margin-left: 6px;
}
.session-sidebar mark {
background: #fef3c7;
color: #b45309;
padding: 0 2px;
border-radius: 2px;
}
.chat-disclaimer { .chat-disclaimer {
text-align: center; text-align: center;
font-size: 11px; font-size: 11px;