Next.js app — chat interface, auth, settings, PWA manifest

This commit is contained in:
2026-04-26 02:05:09 -04:00
parent 241acf2b52
commit 996c4e19a7
12 changed files with 1161 additions and 122 deletions
+98
View File
@@ -0,0 +1,98 @@
'use client';
import { useEffect, useRef } from 'react';
import { useStore } from '@/lib/store';
import { renderMarkdown } from '@/lib/markdown';
export default function MessageList() {
const { messages, isLoading } = useStore();
const bottomRef = useRef<HTMLDivElement>(null);
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages, isLoading]);
if (!messages.length && !isLoading) {
return (
<div className="flex-1 flex flex-col items-center justify-center text-center px-8">
<h2 className="text-lg font-medium mb-2" style={{ color: 'var(--text2)' }}>
What are you working on?
</h2>
<p className="text-sm leading-relaxed max-w-sm" style={{ color: 'var(--text3)' }}>
Ask about your documents, projects, research, or anything else.
Your entire corpus is available.
</p>
</div>
);
}
return (
<div className="flex-1 overflow-y-auto px-4 py-6 flex flex-col gap-5">
{messages.map((m, i) => (
<div
key={i}
className={`flex flex-col max-w-3xl w-full ${
m.role === 'user' ? 'self-end items-end max-w-xl' : 'self-start items-start'
}`}
>
<div
className="text-xs mb-1"
style={{ color: 'var(--text3)', letterSpacing: '0.03em' }}
>
{m.role === 'user' ? 'you' : 'aaron ai'}
</div>
<div
className="px-4 py-3 leading-relaxed"
style={{
background: m.role === 'user' ? 'var(--user-bg)' : 'var(--accent-light)',
color: m.role === 'user' ? 'var(--text)' : 'var(--accent-text)',
border: m.role === 'assistant' ? '1px solid var(--accent-border)' : 'none',
borderRadius: m.role === 'user' ? '12px 12px 3px 12px' : '12px 12px 12px 3px',
fontSize: 'var(--font-size)',
}}
>
{m.role === 'assistant' ? (
<div
className="prose"
dangerouslySetInnerHTML={{ __html: renderMarkdown(m.content) }}
/>
) : (
<span style={{ whiteSpace: 'pre-wrap' }}>{m.content}</span>
)}
</div>
{m.sources && m.sources.length > 0 && (
<div className="text-xs mt-1.5 italic" style={{ color: 'var(--text3)' }}>
Sources: {[...new Set(m.sources)].join(', ')}
</div>
)}
</div>
))}
{isLoading && (
<div className="self-start flex flex-col items-start max-w-3xl">
<div className="text-xs mb-1" style={{ color: 'var(--text3)' }}>aaron ai</div>
<div
className="px-4 py-3 rounded-xl text-sm italic"
style={{
background: 'var(--accent-light)',
color: 'var(--text3)',
border: '1px solid var(--accent-border)',
animation: 'pulse 1.5s infinite',
}}
>
Thinking...
</div>
</div>
)}
<div ref={bottomRef} />
<style jsx>{`
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
`}</style>
</div>
);
}