chat history

This commit is contained in:
2026-03-01 13:25:25 +10:00
parent ff6e946529
commit 70f8c00e5e
4 changed files with 268 additions and 55 deletions

View File

@@ -63,11 +63,6 @@
line-height: 1.4;
}
/* Push Clear button to the far right inside the tab bar */
.chat-tabs .chat-clear-btn {
margin-left: auto;
}
.chat-header {
display: flex;
align-items: center;
@@ -82,22 +77,111 @@
flex-shrink: 0;
}
.chat-clear-btn {
/* ── Chat sub-header (History link + New Chat button) ────────────── */
.chat-subheader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 5px 10px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.chat-history-link {
background: none;
border: none;
color: var(--text-muted);
font-size: 10px;
font-weight: 700;
letter-spacing: 0.06em;
padding: 3px 0;
cursor: pointer;
transition: color 0.15s;
text-decoration: underline;
text-underline-offset: 2px;
text-decoration-color: transparent;
}
.chat-history-link:hover,
.chat-history-link-active {
color: var(--text-primary);
text-decoration-color: currentColor;
}
.chat-new-btn {
background: none;
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text-muted);
font-size: 10px;
padding: 2px 7px;
font-weight: 700;
letter-spacing: 0.06em;
padding: 3px 8px;
cursor: pointer;
transition: color 0.15s, border-color 0.15s;
}
.chat-clear-btn:hover {
.chat-new-btn:hover {
color: var(--text-primary);
border-color: var(--text-muted);
}
/* ── History list ────────────────────────────────────────────────── */
.chat-history-list {
flex: 1;
overflow-y: auto;
padding: 8px;
display: flex;
flex-direction: column;
gap: 2px;
}
.chat-history-item {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 3px;
padding: 9px 11px;
border-radius: 6px;
background: none;
border: 1px solid transparent;
cursor: pointer;
text-align: left;
width: 100%;
transition: background 0.12s, border-color 0.12s;
}
.chat-history-item:hover {
background: var(--message-bg);
}
.chat-history-item-active {
border-color: var(--border);
background: var(--message-bg);
}
.chat-history-time {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--accent);
}
.chat-history-summary {
font-size: 12px;
color: var(--text-muted);
line-height: 1.45;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.chat-history-item-active .chat-history-summary {
color: var(--text-secondary);
}
/* ── Pending attachment indicator (between tab bar and messages) ─── */
.chat-attachment-indicator {
display: flex;

View File

@@ -9,9 +9,23 @@ import './Chat.css'
type TabId = 'chat' | 'feedback'
function formatSessionTime(createdAt: number): string {
const date = new Date(createdAt)
const now = new Date()
const isToday = date.toDateString() === now.toDateString()
const yesterday = new Date(now)
yesterday.setDate(yesterday.getDate() - 1)
const isYesterday = date.toDateString() === yesterday.toDateString()
if (isToday) return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })
if (isYesterday) return 'Yesterday'
return date.toLocaleDateString([], { month: 'short', day: 'numeric' })
}
export function ChatPanel(): JSX.Element {
const {
chatHistory,
chatSessionsByFile,
activeSessionIdByFile,
isAILoading,
aiError,
activeFileContent,
@@ -26,14 +40,19 @@ export function ChatPanel(): JSX.Element {
setAIError,
setAnnotations,
linkAnnotationsToMessage,
clearChat
newChat,
setActiveSession
} = useEditorStore()
const [tab, setTab] = useState<TabId>('chat')
const [showHistory, setShowHistory] = useState(false)
const [pendingAttachmentCount, setPendingAttachmentCount] = useState(0)
const scrollRef = useRef<HTMLDivElement>(null)
const prevAnnotationCountRef = useRef(annotations.length)
// Collapse history when switching files
useEffect(() => { setShowHistory(false) }, [activeFilePath])
// Auto-switch to Feedback tab the first time annotations appear (0 → >0)
useEffect(() => {
const prev = prevAnnotationCountRef.current
@@ -103,6 +122,12 @@ export function ChatPanel(): JSX.Element {
const allAnnotationsForFile: TextAnnotation[] =
(activeFilePath ? annotationsByFile[activeFilePath]?.annotations : undefined) ?? []
const sessions = (activeFilePath ? chatSessionsByFile[activeFilePath] : undefined) ?? []
const activeSessionId = activeFilePath ? activeSessionIdByFile[activeFilePath] : undefined
// Subheader is shown in the chat tab once there is at least one session
const showSubheader = tab === 'chat' && sessions.length > 0
return (
<div className="chat-panel">
{/* ── Tab bar ── */}
@@ -122,15 +147,25 @@ export function ChatPanel(): JSX.Element {
<span className="chat-tab-badge">{annotations.length}</span>
)}
</button>
{/* Clear button floated right, only visible in Chat tab */}
{tab === 'chat' && chatHistory.length > 0 && (
<button className="chat-clear-btn" onClick={clearChat} title="Clear conversation">
Clear
</button>
)}
</div>
{/* ── Chat sub-header: History link + New Chat button ── */}
{showSubheader && (
<div className="chat-subheader">
<button
className={`chat-history-link${showHistory ? ' chat-history-link-active' : ''}`}
onClick={() => setShowHistory(v => !v)}
>
{showHistory ? 'Back to chat' : `History${sessions.length > 1 ? ` (${sessions.length})` : ''}`}
</button>
{!showHistory && chatHistory.length > 0 && (
<button className="chat-new-btn" onClick={newChat} title="Start a new conversation">
+ New Chat
</button>
)}
</div>
)}
{/* ── Pending-attachment indicator ── */}
{pendingAttachmentCount > 0 && (
<div className="chat-attachment-indicator">
@@ -142,6 +177,29 @@ export function ChatPanel(): JSX.Element {
{/* ── Panel content ── */}
{tab === 'feedback' ? (
<FeedbackPanel />
) : showHistory ? (
<div className="chat-history-list">
{[...sessions].reverse().map((session) => {
const isActive = session.id === activeSessionId
const firstUserMsg = session.messages.find(m => m.role === 'user')
const summary = firstUserMsg
? firstUserMsg.content.slice(0, 80) + (firstUserMsg.content.length > 80 ? '…' : '')
: 'Empty conversation'
return (
<button
key={session.id}
className={`chat-history-item${isActive ? ' chat-history-item-active' : ''}`}
onClick={() => {
setActiveSession(session.id)
setShowHistory(false)
}}
>
<span className="chat-history-time">{formatSessionTime(session.createdAt)}</span>
<span className="chat-history-summary">{summary}</span>
</button>
)
})}
</div>
) : (
<>
<div className="chat-messages" ref={scrollRef}>