diff --git a/src/state/session.ts b/src/state/session.ts new file mode 100644 index 0000000..28ec4bd --- /dev/null +++ b/src/state/session.ts @@ -0,0 +1,26 @@ +import { mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import type { Message } from "../types"; + +const SESSION_DIR = join(process.env.HOME ?? "~", ".paikea", "sessions"); + +export interface Session { + id: string; + date: string; + model: string; + messages: Message[]; +} + +function ensureSessionDir() { + mkdirSync(SESSION_DIR, { recursive: true }); +} + +export function saveSession(session: Session) { + ensureSessionDir(); + const filePath = join(SESSION_DIR, `${session.id}.json`); + writeFileSync(filePath, JSON.stringify(session, null, 2)); +} + +export function createSessionId(): string { + return new Date().toISOString().replace(/[:.]/g, "-"); +} diff --git a/src/state/store.ts b/src/state/store.ts new file mode 100644 index 0000000..fe596bb --- /dev/null +++ b/src/state/store.ts @@ -0,0 +1,98 @@ +import type { + AgentStep, + AppState, + Message, + Step, + ThinkingState, +} from "../types"; + +type Listener = () => void; + +class Store { + private state: AppState = { + model: null, + models: [], + messages: [], + steps: [], + thinking: { active: false, content: "", tokens: 0 }, + result: "", + prompt: "", + scrollOffset: 0, + thinkingVisible: true, + streaming: false, + agentSteps: [], + toolCallsStreaming: new Map(), + uiMode: "normal", + toolbarIndex: 0, + paletteIndex: 0, + suggestions: [], + suggestionIndex: -1, + }; + + private listeners: Listener[] = []; + + get(): AppState { + return this.state; + } + + set(partial: Partial) { + this.state = { ...this.state, ...partial }; + this.notify(); + } + + subscribe(fn: Listener): () => void { + this.listeners.push(fn); + return () => { + this.listeners = this.listeners.filter((l) => l !== fn); + }; + } + + private notify() { + for (const fn of this.listeners) fn(); + } + + addMessage(msg: Message) { + this.state.messages.push(msg); + this.notify(); + } + + setThinking(thinking: Partial) { + this.state.thinking = { ...this.state.thinking, ...thinking }; + this.notify(); + } + + appendResult(text: string) { + this.state.result += text; + this.notify(); + } + + clearResult() { + this.state.result = ""; + this.notify(); + } + + setSteps(steps: Step[]) { + this.state.steps = steps; + this.notify(); + } + + addAgentStep(step: AgentStep) { + this.state.agentSteps.push(step); + this.notify(); + } + + updateAgentStep(id: string, update: Partial) { + const step = this.state.agentSteps.find((s) => s.id === id); + if (step) { + Object.assign(step, update); + this.notify(); + } + } + + clearAgentSteps() { + this.state.agentSteps = []; + this.notify(); + } +} + +export const store = new Store();