- applications/iknowyou: new Next.js chat application with persona-aware conversations, briefing API, cohort logic, vLLM streaming, and sidebar navigation - tidal M8: add replication control plane (control.rs), tenant migration state machine (migration.rs), tenant/upgrade coordinators, cluster/fault test harnesses - tidal M8 tests: expand m8p2/m8p3/m8p4 test suites; add m8p5_multitenancy and m8_uat - tidal db: split replication_ops out of db/mod.rs (was 647 lines, now 574) - .claude: add kai-park, kaya-osei, mira-vasquez agents; add aeries-design-architect, aeries-fullstack-engineer, aeries-product-visionary skills - docs: update ROADMAP.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
83 lines
2.3 KiB
TypeScript
83 lines
2.3 KiB
TypeScript
"use client";
|
|
|
|
import { useChatStore } from "@/lib/store";
|
|
import { ConversationItem } from "./conversation-item";
|
|
import { PersonSwitcher } from "./person-switcher";
|
|
|
|
interface SidebarProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export function Sidebar({ isOpen, onClose }: SidebarProps) {
|
|
const conversations = useChatStore((s) => s.conversations);
|
|
const activeId = useChatStore((s) => s.activeConversationId);
|
|
const createConversation = useChatStore((s) => s.createConversation);
|
|
const switchConversation = useChatStore((s) => s.switchConversation);
|
|
|
|
const sorted = [...conversations].sort(
|
|
(a, b) => b.lastMessageAt - a.lastMessageAt
|
|
);
|
|
|
|
const handleNew = () => {
|
|
createConversation();
|
|
onClose();
|
|
};
|
|
|
|
const handleSwitch = (id: string) => {
|
|
switchConversation(id);
|
|
onClose();
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{/* Backdrop on mobile */}
|
|
{isOpen && (
|
|
<div
|
|
className="fixed inset-0 bg-black/40 z-30 md:hidden"
|
|
onClick={onClose}
|
|
/>
|
|
)}
|
|
|
|
<aside
|
|
className={`fixed md:static inset-y-0 left-0 z-40 w-64 bg-bg-surface border-r border-border flex flex-col transition-transform duration-200 ${
|
|
isOpen ? "translate-x-0" : "-translate-x-full md:translate-x-0"
|
|
}`}
|
|
>
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between px-4 py-3 border-b border-border">
|
|
<span className="text-text-muted text-xs font-medium tracking-wide uppercase">
|
|
aeries
|
|
</span>
|
|
<button
|
|
onClick={handleNew}
|
|
className="text-text-muted hover:text-text text-sm transition-colors px-2 py-1 rounded hover:bg-bg-hover"
|
|
>
|
|
+ new
|
|
</button>
|
|
</div>
|
|
|
|
{/* Person identity */}
|
|
<PersonSwitcher />
|
|
|
|
{/* Conversation list */}
|
|
<div className="flex-1 overflow-y-auto px-2 py-2 space-y-0.5">
|
|
{sorted.length === 0 && (
|
|
<p className="text-text-faint text-xs px-3 py-4 text-center">
|
|
no conversations yet
|
|
</p>
|
|
)}
|
|
{sorted.map((conv) => (
|
|
<ConversationItem
|
|
key={conv.id}
|
|
conversation={conv}
|
|
isActive={conv.id === activeId}
|
|
onClick={() => handleSwitch(conv.id)}
|
|
/>
|
|
))}
|
|
</div>
|
|
</aside>
|
|
</>
|
|
);
|
|
}
|