tidaldb/applications/iknowyou/lib/store.ts
jordan 98bdc18a49 feat: add iknowyou app + complete M8 replication extensions + Aeries agents/skills
- 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>
2026-02-24 21:09:11 -07:00

192 lines
4.8 KiB
TypeScript

import { create } from "zustand";
import { persist } from "zustand/middleware";
import type { ChatState, Conversation } from "./types";
function genId(): string {
return crypto.randomUUID();
}
export const useChatStore = create<ChatState>()(
persist(
(set, get) => ({
messages: [],
isStreaming: false,
error: null,
conversations: [],
activeConversationId: null,
personId: crypto.randomUUID(),
// --- Messages ---
addUserMessage: (content: string) => {
const id = genId();
const now = Date.now();
set((s) => {
const updated = {
messages: [
...s.messages,
{ id, role: "user" as const, content, timestamp: now },
],
error: null,
};
// Update conversation title from first user message
if (s.activeConversationId) {
const conv = s.conversations.find(
(c) => c.id === s.activeConversationId
);
if (conv && conv.title === "New conversation") {
return {
...updated,
conversations: s.conversations.map((c) =>
c.id === s.activeConversationId
? {
...c,
title: content.slice(0, 60),
lastMessageAt: now,
}
: c
),
};
}
return {
...updated,
conversations: s.conversations.map((c) =>
c.id === s.activeConversationId
? { ...c, lastMessageAt: now }
: c
),
};
}
return updated;
});
return id;
},
startStreaming: () => {
const id = genId();
set((s) => ({
messages: [
...s.messages,
{
id,
role: "assistant" as const,
content: "",
timestamp: Date.now(),
},
],
isStreaming: true,
error: null,
}));
return id;
},
appendToken: (id: string, token: string) => {
set((s) => ({
messages: s.messages.map((m) =>
m.id === id ? { ...m, content: m.content + token } : m
),
}));
},
finishStreaming: (id: string) => {
set((s) => ({
messages: s.messages.map((m) =>
m.id === id ? { ...m, timestamp: Date.now() } : m
),
isStreaming: false,
}));
},
setError: (error: string | null) => {
set({ error, isStreaming: false });
},
clearMessages: () => {
set({ messages: [], error: null, isStreaming: false });
},
// --- Conversations ---
createConversation: () => {
const id = genId();
const now = Date.now();
const conv: Conversation = {
id,
title: "New conversation",
createdAt: now,
lastMessageAt: now,
};
set((s) => ({
conversations: [conv, ...s.conversations],
activeConversationId: id,
messages: [],
error: null,
isStreaming: false,
}));
return id;
},
switchConversation: (id: string) => {
const { activeConversationId } = get();
if (id === activeConversationId) return;
set({
activeConversationId: id,
messages: [],
error: null,
isStreaming: false,
});
},
setMessages: (msgs) => {
set({ messages: msgs });
},
updateConversationTitle: (id: string, title: string) => {
set((s) => ({
conversations: s.conversations.map((c) =>
c.id === id ? { ...c, title: title.slice(0, 60) } : c
),
}));
},
deleteConversation: (id: string) => {
set((s) => {
const filtered = s.conversations.filter((c) => c.id !== id);
const wasActive = s.activeConversationId === id;
return {
conversations: filtered,
activeConversationId: wasActive
? filtered[0]?.id ?? null
: s.activeConversationId,
messages: wasActive ? [] : s.messages,
};
});
},
switchPerson: () => {
const newId = genId();
set({
personId: newId,
conversations: [],
activeConversationId: null,
messages: [],
error: null,
isStreaming: false,
});
return newId;
},
}),
{
name: "aeries-conversations",
partialize: (state) => ({
conversations: state.conversations,
activeConversationId: state.activeConversationId,
personId: state.personId,
}),
}
)
);