@recallkit/web 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/next-env.d.ts +6 -0
  2. package/next.config.ts +13 -0
  3. package/package.json +40 -0
  4. package/public/logo.png +0 -0
  5. package/public/textures/bg-scene.png +0 -0
  6. package/src/app/api/_lib/guards.ts +35 -0
  7. package/src/app/api/_lib/limits.ts +6 -0
  8. package/src/app/api/_lib/responses.ts +9 -0
  9. package/src/app/api/commit/complete/route.ts +112 -0
  10. package/src/app/api/commit/preview/route.ts +71 -0
  11. package/src/app/api/commit/route.ts +16 -0
  12. package/src/app/api/memory-cache/route.ts +50 -0
  13. package/src/app/api/pending/[id]/delete/route.ts +21 -0
  14. package/src/app/api/pending/[id]/route.ts +47 -0
  15. package/src/app/api/pending/route.ts +41 -0
  16. package/src/app/api/security.ts +25 -0
  17. package/src/app/api/status/route.ts +35 -0
  18. package/src/app/dashboard/page.tsx +57 -0
  19. package/src/app/drafts/page.tsx +5 -0
  20. package/src/app/globals.css +10 -0
  21. package/src/app/icon.png +0 -0
  22. package/src/app/layout.tsx +43 -0
  23. package/src/app/page.tsx +132 -0
  24. package/src/app/settings/page.tsx +76 -0
  25. package/src/components/ArrowRightIcon.tsx +25 -0
  26. package/src/components/CommitPreview.tsx +156 -0
  27. package/src/components/CopyValue.tsx +49 -0
  28. package/src/components/MemoryInbox.tsx +74 -0
  29. package/src/components/RetrievedMemories.tsx +36 -0
  30. package/src/components/TopNav.tsx +39 -0
  31. package/src/components/WalletConnectButton.tsx +68 -0
  32. package/src/components/inbox/EmptyInbox.tsx +20 -0
  33. package/src/components/inbox/InboxStats.tsx +41 -0
  34. package/src/components/inbox/MemoryCandidateList.tsx +90 -0
  35. package/src/components/inbox/MemoryCandidateRow.tsx +195 -0
  36. package/src/components/memory-cache/CachedMemoryList.tsx +47 -0
  37. package/src/components/memory-cache/EmptyCache.tsx +13 -0
  38. package/src/hooks/useCommitFlow.ts +55 -0
  39. package/src/hooks/useMemoryCache.ts +44 -0
  40. package/src/hooks/usePendingMemories.ts +137 -0
  41. package/src/hooks/useWallet.ts +69 -0
  42. package/src/lib/api.ts +71 -0
  43. package/src/lib/wallet.ts +88 -0
  44. package/src/services/commitMemories.ts +153 -0
  45. package/src/services/signerApi.ts +67 -0
  46. package/src/services/types.ts +22 -0
  47. package/src/stores/appStore.ts +18 -0
  48. package/src/stores/createStore.ts +41 -0
  49. package/src/stores/slices/memoryCacheSlice.ts +29 -0
  50. package/src/stores/slices/pendingMemorySlice.ts +21 -0
  51. package/src/stores/slices/walletSlice.ts +24 -0
  52. package/src/styles/base.css +61 -0
  53. package/src/styles/buttons.css +53 -0
  54. package/src/styles/data-display.css +485 -0
  55. package/src/styles/forms.css +86 -0
  56. package/src/styles/landing.css +75 -0
  57. package/src/styles/layout.css +111 -0
  58. package/src/styles/navigation.css +121 -0
  59. package/src/styles/overlays.css +65 -0
  60. package/src/styles/tokens.css +26 -0
  61. package/src/styles/utilities.css +358 -0
  62. package/src/utils/errors.ts +5 -0
  63. package/src/utils/format.ts +37 -0
  64. package/tsconfig.json +44 -0
@@ -0,0 +1,69 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect } from "react";
4
+ import { connectInjectedWallet, getGlmBalance, switchToArkivBraga } from "@/lib/wallet";
5
+ import { getWalletStatus, saveWalletOwner } from "@/services/signerApi";
6
+ import { appStore, useAppStore } from "@/stores/appStore";
7
+ import { readError } from "@/utils/errors";
8
+
9
+ let statusRequested = false;
10
+
11
+ export function useWallet() {
12
+ const wallet = useAppStore((state) => state.wallet);
13
+
14
+ const refreshStatus = useCallback(async () => {
15
+ try {
16
+ const status = await getWalletStatus();
17
+ appStore.setState((state) => ({
18
+ wallet: { ...state.wallet, status },
19
+ }));
20
+ } catch (error) {
21
+ appStore.setState((state) => ({
22
+ wallet: {
23
+ ...state.wallet,
24
+ error: readError(error, "Unable to read wallet status."),
25
+ },
26
+ }));
27
+ }
28
+ }, []);
29
+
30
+ useEffect(() => {
31
+ if (statusRequested) return;
32
+ statusRequested = true;
33
+ void refreshStatus();
34
+ }, [refreshStatus]);
35
+
36
+ const connect = useCallback(async () => {
37
+ appStore.setState((state) => ({
38
+ wallet: { ...state.wallet, busy: true, error: undefined },
39
+ }));
40
+
41
+ try {
42
+ const account = await connectInjectedWallet();
43
+ await switchToArkivBraga();
44
+ const [status, balance] = await Promise.all([
45
+ saveWalletOwner(account),
46
+ getGlmBalance(account),
47
+ ]);
48
+
49
+ appStore.setState((state) => ({
50
+ wallet: { ...state.wallet, status, balance, busy: false },
51
+ }));
52
+ } catch (error) {
53
+ appStore.setState((state) => ({
54
+ wallet: {
55
+ ...state.wallet,
56
+ busy: false,
57
+ error: readError(error, "Wallet connection failed."),
58
+ },
59
+ }));
60
+ }
61
+ }, []);
62
+
63
+ return {
64
+ ...wallet,
65
+ connect,
66
+ refreshStatus,
67
+ balanceLow: wallet.balance !== undefined && Number(wallet.balance) <= 0,
68
+ };
69
+ }
package/src/lib/api.ts ADDED
@@ -0,0 +1,71 @@
1
+ type ApiGetOptions = {
2
+ ttlMs?: number;
3
+ force?: boolean;
4
+ };
5
+
6
+ type CacheEntry = {
7
+ expiresAt: number;
8
+ value: unknown;
9
+ };
10
+
11
+ const getCache = new Map<string, CacheEntry>();
12
+ const inFlightGets = new Map<string, Promise<unknown>>();
13
+
14
+ export async function apiGet<T>(path: string, options: ApiGetOptions = {}): Promise<T> {
15
+ const ttlMs = options.ttlMs ?? 0;
16
+ const now = Date.now();
17
+
18
+ if (!options.force && ttlMs > 0) {
19
+ const cached = getCache.get(path);
20
+ if (cached && cached.expiresAt > now) return cached.value as T;
21
+ }
22
+
23
+ if (!options.force) {
24
+ const inFlight = inFlightGets.get(path);
25
+ if (inFlight) return inFlight as Promise<T>;
26
+ }
27
+
28
+ const request = fetch(path, { cache: "no-store" })
29
+ .then(async (response) => {
30
+ if (!response.ok) throw new Error(await response.text());
31
+ const value = (await response.json()) as T;
32
+ if (ttlMs > 0) getCache.set(path, { value, expiresAt: Date.now() + ttlMs });
33
+ return value;
34
+ })
35
+ .finally(() => {
36
+ inFlightGets.delete(path);
37
+ });
38
+
39
+ inFlightGets.set(path, request);
40
+ return request;
41
+ }
42
+
43
+ export function invalidateApiCache(path?: string): void {
44
+ if (path) {
45
+ getCache.delete(path);
46
+ return;
47
+ }
48
+ getCache.clear();
49
+ }
50
+
51
+ export async function apiPost<T>(path: string, body: unknown): Promise<T> {
52
+ invalidateApiCache();
53
+ const response = await fetch(path, {
54
+ method: "POST",
55
+ headers: { "content-type": "application/json" },
56
+ body: JSON.stringify(body),
57
+ });
58
+ if (!response.ok) throw new Error(await response.text());
59
+ return response.json() as Promise<T>;
60
+ }
61
+
62
+ export async function apiPatch<T>(path: string, body: unknown): Promise<T> {
63
+ invalidateApiCache();
64
+ const response = await fetch(path, {
65
+ method: "PATCH",
66
+ headers: { "content-type": "application/json" },
67
+ body: JSON.stringify(body),
68
+ });
69
+ if (!response.ok) throw new Error(await response.text());
70
+ return response.json() as Promise<T>;
71
+ }
@@ -0,0 +1,88 @@
1
+ "use client";
2
+
3
+ import { createWalletClient } from "@arkiv-network/sdk";
4
+ import { braga } from "@arkiv-network/sdk/chains";
5
+ import { createPublicClient, custom, formatEther, type Chain } from "viem";
6
+
7
+ declare global {
8
+ interface Window {
9
+ ethereum?: {
10
+ request: (args: { method: string; params?: unknown[] | object }) => Promise<unknown>;
11
+ };
12
+ }
13
+ }
14
+
15
+ export const arkivBraga: Chain = {
16
+ id: Number(process.env.NEXT_PUBLIC_ARKIV_CHAIN_ID ?? "60138453102"),
17
+ name: "Arkiv Braga",
18
+ nativeCurrency: {
19
+ name: "GLM",
20
+ symbol: "GLM",
21
+ decimals: 18,
22
+ },
23
+ rpcUrls: {
24
+ default: {
25
+ http: ["https://braga.hoodi.arkiv.network/rpc"],
26
+ },
27
+ },
28
+ blockExplorers: {
29
+ default: {
30
+ name: "Arkiv Braga Explorer",
31
+ url: "https://explorer.braga.hoodi.arkiv.network",
32
+ },
33
+ },
34
+ };
35
+
36
+ export async function connectInjectedWallet(): Promise<`0x${string}`> {
37
+ if (!window.ethereum) throw new Error("No injected browser wallet was found.");
38
+ const accounts = (await window.ethereum.request({ method: "eth_requestAccounts" })) as string[];
39
+ const account = accounts[0];
40
+ if (!account?.startsWith("0x")) throw new Error("No wallet account was returned.");
41
+ return account as `0x${string}`;
42
+ }
43
+
44
+ export async function switchToArkivBraga(): Promise<void> {
45
+ if (!window.ethereum) throw new Error("No injected browser wallet was found.");
46
+
47
+ const chainId = `0x${arkivBraga.id.toString(16)}`;
48
+ try {
49
+ await window.ethereum.request({
50
+ method: "wallet_switchEthereumChain",
51
+ params: [{ chainId }],
52
+ });
53
+ } catch (switchError) {
54
+ console.info("Arkiv Braga chain switch failed, requesting chain add", switchError);
55
+ await window.ethereum.request({
56
+ method: "wallet_addEthereumChain",
57
+ params: [
58
+ {
59
+ chainId,
60
+ chainName: arkivBraga.name,
61
+ nativeCurrency: arkivBraga.nativeCurrency,
62
+ rpcUrls: arkivBraga.rpcUrls.default.http,
63
+ blockExplorerUrls: [arkivBraga.blockExplorers?.default.url],
64
+ },
65
+ ],
66
+ });
67
+ }
68
+ }
69
+
70
+ export async function getGlmBalance(account: `0x${string}`): Promise<string> {
71
+ if (!window.ethereum) throw new Error("No injected browser wallet was found.");
72
+ const publicClient = createPublicClient({
73
+ chain: arkivBraga,
74
+ transport: custom(window.ethereum),
75
+ });
76
+ const balance = await publicClient.getBalance({ address: account });
77
+ return formatEther(balance);
78
+ }
79
+
80
+ export async function createArkivBrowserWalletClient(account: `0x${string}`): Promise<unknown> {
81
+ if (!window.ethereum) throw new Error("No injected browser wallet was found.");
82
+
83
+ return createWalletClient({
84
+ account,
85
+ chain: braga,
86
+ transport: custom(window.ethereum),
87
+ });
88
+ }
@@ -0,0 +1,153 @@
1
+ import {
2
+ createIndexCheckpoint,
3
+ createMemoryCommit,
4
+ createMemoryManifest,
5
+ createMemoryPack,
6
+ } from "@recallkit/core/arkiv";
7
+ import { DEFAULT_EXPIRES_IN } from "@recallkit/core/constants";
8
+ import { encryptJson } from "@recallkit/core/crypto";
9
+ import { hashScope } from "@recallkit/core/keys";
10
+ import { normalizeMemoryScope } from "@recallkit/core/memory";
11
+ import type {
12
+ MemoryCommitPayload,
13
+ MemoryIndexCheckpointPayload,
14
+ MemoryIndexEntry,
15
+ MemoryManifestPayload,
16
+ MemoryPackPayload,
17
+ } from "@recallkit/core";
18
+ import type { MemoryRecord } from "@recallkit/core/schemas";
19
+ import { createArkivBrowserWalletClient } from "@/lib/wallet";
20
+ import { completeCommit } from "./signerApi";
21
+ import type { CommitEntityKeys, CommitPreviewData } from "./types";
22
+
23
+ type CommitMemoriesInput = {
24
+ passphrase: string;
25
+ preview: CommitPreviewData;
26
+ selectedIds: string[];
27
+ };
28
+
29
+ export async function commitSelectedMemories({
30
+ passphrase,
31
+ preview,
32
+ selectedIds,
33
+ }: CommitMemoriesInput): Promise<CommitEntityKeys> {
34
+ if (preview.memories.length === 0) {
35
+ throw new Error("No global or project memories are selected for Arkiv commit.");
36
+ }
37
+
38
+ const walletClient = await createArkivBrowserWalletClient(preview.signerWallet as `0x${string}`);
39
+ const commitId = `commit_${crypto.randomUUID()}`;
40
+ const now = new Date().toISOString();
41
+
42
+ const packPayload: MemoryPackPayload = {
43
+ schemaVersion: 1,
44
+ packId: `pack_${crypto.randomUUID()}`,
45
+ ownerWallet: preview.signerWallet,
46
+ commitId,
47
+ memories: preview.memories,
48
+ createdAt: now,
49
+ };
50
+
51
+ const encryptedPack = await encryptJson(packPayload, passphrase);
52
+ const packWrite = await createMemoryPack({
53
+ walletClient,
54
+ ownerWallet: preview.signerWallet,
55
+ encryptedPayload: encryptedPack,
56
+ scopeHash: await hashScope(scopeLabel(preview.memories[0])),
57
+ expiresIn: DEFAULT_EXPIRES_IN.PROJECT_MEMORY,
58
+ });
59
+
60
+ const newEntries = createIndexEntries(preview.memories, packWrite.entityKey);
61
+
62
+ const indexPayload: MemoryIndexCheckpointPayload = {
63
+ schemaVersion: 1,
64
+ checkpointId: `idx_${crypto.randomUUID()}`,
65
+ ownerWallet: preview.signerWallet,
66
+ entries: [...preview.baseIndexEntries, ...newEntries],
67
+ createdAt: now,
68
+ };
69
+ const encryptedIndex = await encryptJson(indexPayload, passphrase);
70
+ const indexWrite = await createIndexCheckpoint({
71
+ walletClient,
72
+ ownerWallet: preview.signerWallet,
73
+ encryptedPayload: encryptedIndex,
74
+ expiresIn: DEFAULT_EXPIRES_IN.PROJECT_MEMORY,
75
+ });
76
+
77
+ const commitPayload: MemoryCommitPayload = {
78
+ schemaVersion: 1,
79
+ commitId,
80
+ ownerWallet: preview.signerWallet,
81
+ message: `Approved ${preview.memories.length} RecallKit memories`,
82
+ addedMemoryIds: preview.memories.map((memory) => memory.id),
83
+ supersededMemoryIds: preview.conflicts.map((conflict) => conflict.conflictsWith),
84
+ packEntityKeys: [packWrite.entityKey],
85
+ indexCheckpointEntityKey: indexWrite.entityKey,
86
+ createdFrom: "skill",
87
+ approvedByUser: true,
88
+ createdAt: now,
89
+ };
90
+ const encryptedCommit = await encryptJson(commitPayload, passphrase);
91
+ const commitWrite = await createMemoryCommit({
92
+ walletClient,
93
+ ownerWallet: preview.signerWallet,
94
+ encryptedPayload: encryptedCommit,
95
+ expiresIn: DEFAULT_EXPIRES_IN.PROJECT_MEMORY,
96
+ });
97
+
98
+ const manifestPayload: MemoryManifestPayload = {
99
+ schemaVersion: 1,
100
+ ownerWallet: preview.signerWallet,
101
+ latestCommitId: commitId,
102
+ latestIndexCheckpointEntityKey: indexWrite.entityKey,
103
+ activePackEntityKeys: [packWrite.entityKey],
104
+ createdAt: now,
105
+ };
106
+ const encryptedManifest = await encryptJson(manifestPayload, passphrase);
107
+ const manifestWrite = await createMemoryManifest({
108
+ walletClient,
109
+ ownerWallet: preview.signerWallet,
110
+ encryptedPayload: encryptedManifest,
111
+ expiresIn: DEFAULT_EXPIRES_IN.LONG_TERM_MEMORY,
112
+ });
113
+
114
+ const entityKeys: CommitEntityKeys = {
115
+ memory_pack: packWrite.entityKey,
116
+ memory_index_checkpoint: indexWrite.entityKey,
117
+ memory_commit: commitWrite.entityKey,
118
+ memory_manifest: manifestWrite.entityKey,
119
+ };
120
+
121
+ await completeCommit({
122
+ pendingIds: preview.committablePendingIds.length ? preview.committablePendingIds : selectedIds,
123
+ indexCheckpoint: indexPayload,
124
+ packs: [{ ...packPayload, entityKey: packWrite.entityKey }],
125
+ entityKeys,
126
+ });
127
+
128
+ return entityKeys;
129
+ }
130
+
131
+ function createIndexEntries(memories: MemoryRecord[], packEntityKey: string): MemoryIndexEntry[] {
132
+ return memories.map((memory) => ({
133
+ memoryId: memory.id,
134
+ packEntityKey,
135
+ kind: memory.kind,
136
+ scope: normalizeMemoryScope(memory.scope),
137
+ summary: memory.summary,
138
+ keywords: memory.retrieval.keywords,
139
+ topics: memory.retrieval.topics,
140
+ semanticSummary: memory.retrieval.semanticSummary,
141
+ importance: memory.retrieval.importance,
142
+ confidence: memory.evidence.confidence,
143
+ status: memory.lifecycle.status,
144
+ createdAt: memory.lifecycle.createdAt,
145
+ ...(memory.lifecycle.updatedAt ? { updatedAt: memory.lifecycle.updatedAt } : {}),
146
+ }));
147
+ }
148
+
149
+ function scopeLabel(memory: MemoryRecord | undefined): string {
150
+ if (!memory) return "project:recallkit";
151
+ const scope = normalizeMemoryScope(memory.scope);
152
+ return `${scope.level}:${scope.projectId ?? scope.repoPath ?? scope.workspaceId ?? "default"}`;
153
+ }
@@ -0,0 +1,67 @@
1
+ import type { MemoryIndexEntry } from "@recallkit/core";
2
+ import type { PendingMemoryCandidate } from "@recallkit/core/schemas";
3
+ import { apiGet, apiPatch, apiPost } from "@/lib/api";
4
+ import type { CachePack } from "@/stores/slices/memoryCacheSlice";
5
+ import type { WalletStatus } from "@/stores/slices/walletSlice";
6
+ import type { CommitEntityKeys, CommitPreviewData } from "./types";
7
+
8
+ const WALLET_STATUS_TTL_MS = 5_000;
9
+ const PENDING_MEMORY_TTL_MS = 2_000;
10
+ const MEMORY_CACHE_TTL_MS = 30_000;
11
+
12
+ export function getWalletStatus(): Promise<WalletStatus> {
13
+ return apiGet<WalletStatus>("/api/status", { ttlMs: WALLET_STATUS_TTL_MS });
14
+ }
15
+
16
+ export function saveWalletOwner(ownerWallet: string): Promise<WalletStatus> {
17
+ return apiPost<WalletStatus>("/api/status", { ownerWallet });
18
+ }
19
+
20
+ export async function getPendingMemories(options: { force?: boolean } = {}): Promise<PendingMemoryCandidate[]> {
21
+ const response = await apiGet<{ pending: PendingMemoryCandidate[] }>("/api/pending", {
22
+ ...(options.force ? { force: true } : {}),
23
+ ttlMs: PENDING_MEMORY_TTL_MS,
24
+ });
25
+ return response.pending;
26
+ }
27
+
28
+ export function updatePendingMemory(candidate: PendingMemoryCandidate): Promise<unknown> {
29
+ return apiPatch(`/api/pending/${candidate.id}`, {
30
+ text: candidate.text,
31
+ reason: candidate.reason,
32
+ importance: candidate.importance,
33
+ scope: candidate.scope,
34
+ scopeReason: candidate.scopeReason,
35
+ status: candidate.status,
36
+ });
37
+ }
38
+
39
+ export function deletePendingMemory(id: string): Promise<unknown> {
40
+ return apiPost(`/api/pending/${id}/delete`, {});
41
+ }
42
+
43
+ export async function getMemoryCache(): Promise<CachePack[]> {
44
+ const response = await apiGet<{ packs: CachePack[] }>("/api/memory-cache", {
45
+ ttlMs: MEMORY_CACHE_TTL_MS,
46
+ });
47
+ return response.packs;
48
+ }
49
+
50
+ export function buildCommitPreview(ids: string[]): Promise<CommitPreviewData> {
51
+ return apiPost<CommitPreviewData>("/api/commit/preview", { ids });
52
+ }
53
+
54
+ export function completeCommit(input: {
55
+ pendingIds: string[];
56
+ indexCheckpoint: {
57
+ schemaVersion: 1;
58
+ checkpointId: string;
59
+ ownerWallet: string;
60
+ entries: MemoryIndexEntry[];
61
+ createdAt: string;
62
+ };
63
+ packs: unknown[];
64
+ entityKeys: CommitEntityKeys;
65
+ }): Promise<unknown> {
66
+ return apiPost("/api/commit/complete", input);
67
+ }
@@ -0,0 +1,22 @@
1
+ import type { MemoryIndexEntry } from "@recallkit/core";
2
+ import type { MemoryRecord } from "@recallkit/core/schemas";
3
+
4
+ export type CommitPreviewData = {
5
+ ok: true;
6
+ signerWallet: string;
7
+ network: string;
8
+ selectedCount: number;
9
+ committablePendingIds: string[];
10
+ sessionDraftCount: number;
11
+ entities: string[];
12
+ estimatedWrites: number;
13
+ encryption: "enabled";
14
+ memories: MemoryRecord[];
15
+ conflicts: Array<{ memoryId: string; conflictsWith: string; reason: string }>;
16
+ baseIndexEntries: MemoryIndexEntry[];
17
+ };
18
+
19
+ export type CommitEntityKeys = Record<
20
+ "memory_pack" | "memory_index_checkpoint" | "memory_commit" | "memory_manifest",
21
+ string
22
+ >;
@@ -0,0 +1,18 @@
1
+ "use client";
2
+
3
+ import { createStore } from "./createStore";
4
+ import { memoryCacheInitialState, type MemoryCacheSlice } from "./slices/memoryCacheSlice";
5
+ import { pendingMemoryInitialState, type PendingMemorySlice } from "./slices/pendingMemorySlice";
6
+ import { walletInitialState, type WalletSlice } from "./slices/walletSlice";
7
+
8
+ export type AppState = WalletSlice & PendingMemorySlice & MemoryCacheSlice;
9
+
10
+ export const appStore = createStore<AppState>({
11
+ ...walletInitialState,
12
+ ...pendingMemoryInitialState,
13
+ ...memoryCacheInitialState,
14
+ });
15
+
16
+ export function useAppStore<TSelected>(selector: (state: AppState) => TSelected): TSelected {
17
+ return appStore.useStore(selector);
18
+ }
@@ -0,0 +1,41 @@
1
+ "use client";
2
+
3
+ import { useSyncExternalStore } from "react";
4
+
5
+ type Listener = () => void;
6
+ type SetState<TState> = Partial<TState> | ((state: TState) => Partial<TState>);
7
+
8
+ export type StoreApi<TState> = {
9
+ getState: () => TState;
10
+ setState: (next: SetState<TState>) => void;
11
+ subscribe: (listener: Listener) => () => void;
12
+ useStore: <TSelected>(selector: (state: TState) => TSelected) => TSelected;
13
+ };
14
+
15
+ export function createStore<TState>(initialState: TState): StoreApi<TState> {
16
+ let state = initialState;
17
+ const listeners = new Set<Listener>();
18
+
19
+ function getState() {
20
+ return state;
21
+ }
22
+
23
+ function setState(next: SetState<TState>) {
24
+ state = {
25
+ ...state,
26
+ ...(typeof next === "function" ? next(state) : next),
27
+ };
28
+ listeners.forEach((listener) => listener());
29
+ }
30
+
31
+ function subscribe(listener: Listener) {
32
+ listeners.add(listener);
33
+ return () => listeners.delete(listener);
34
+ }
35
+
36
+ function useStore<TSelected>(selector: (current: TState) => TSelected): TSelected {
37
+ return useSyncExternalStore(subscribe, () => selector(state), () => selector(initialState));
38
+ }
39
+
40
+ return { getState, setState, subscribe, useStore };
41
+ }
@@ -0,0 +1,29 @@
1
+ export type CachedMemory = {
2
+ id: string;
3
+ kind: string;
4
+ summary: string;
5
+ retrieval: { importance: number };
6
+ evidence: { confidence: number; source: string };
7
+ };
8
+
9
+ export type CachePack = {
10
+ memories: CachedMemory[];
11
+ };
12
+
13
+ export type MemoryCacheSlice = {
14
+ memoryCache: {
15
+ memories: CachedMemory[];
16
+ loaded: boolean;
17
+ loading: boolean;
18
+ error: string | undefined;
19
+ };
20
+ };
21
+
22
+ export const memoryCacheInitialState: MemoryCacheSlice = {
23
+ memoryCache: {
24
+ memories: [],
25
+ loaded: false,
26
+ loading: false,
27
+ error: undefined,
28
+ },
29
+ };
@@ -0,0 +1,21 @@
1
+ import type { PendingMemoryCandidate } from "@recallkit/core/schemas";
2
+
3
+ export type PendingMemorySlice = {
4
+ pendingMemory: {
5
+ items: PendingMemoryCandidate[];
6
+ selectedIds: string[];
7
+ refreshing: boolean;
8
+ loaded: boolean;
9
+ error: string | undefined;
10
+ };
11
+ };
12
+
13
+ export const pendingMemoryInitialState: PendingMemorySlice = {
14
+ pendingMemory: {
15
+ items: [],
16
+ selectedIds: [],
17
+ refreshing: false,
18
+ loaded: false,
19
+ error: undefined,
20
+ },
21
+ };
@@ -0,0 +1,24 @@
1
+ export type WalletStatus = {
2
+ connected: boolean;
3
+ ownerWallet?: string;
4
+ network?: string;
5
+ approvalAppUrl: string;
6
+ };
7
+
8
+ export type WalletSlice = {
9
+ wallet: {
10
+ status: WalletStatus | undefined;
11
+ balance: string | undefined;
12
+ busy: boolean;
13
+ error: string | undefined;
14
+ };
15
+ };
16
+
17
+ export const walletInitialState: WalletSlice = {
18
+ wallet: {
19
+ status: undefined,
20
+ balance: undefined,
21
+ busy: false,
22
+ error: undefined,
23
+ },
24
+ };
@@ -0,0 +1,61 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ html {
6
+ background: var(--bg);
7
+ }
8
+
9
+ html,
10
+ body {
11
+ margin: 0;
12
+ min-height: 100%;
13
+ color: var(--text);
14
+ font-family: var(--font-sans), Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
15
+ font-feature-settings: "ss01", "cv11";
16
+ }
17
+
18
+ body {
19
+ background: transparent;
20
+ position: relative;
21
+ }
22
+
23
+ body::before {
24
+ content: "";
25
+ position: fixed;
26
+ inset: 0;
27
+ background-image: url("/textures/bg-scene.png");
28
+ background-repeat: no-repeat;
29
+ background-size: cover;
30
+ background-position: center;
31
+ opacity: 0.35;
32
+ pointer-events: none;
33
+ z-index: 0;
34
+ }
35
+
36
+ body::after {
37
+ content: "";
38
+ position: fixed;
39
+ inset: 0;
40
+ background:
41
+ radial-gradient(ellipse at center, rgba(20, 20, 20, 0.0) 0%, rgba(20, 20, 20, 0.25) 100%);
42
+ pointer-events: none;
43
+ z-index: 0;
44
+ }
45
+
46
+ .shell {
47
+ position: relative;
48
+ z-index: 1;
49
+ }
50
+
51
+ a {
52
+ color: inherit;
53
+ text-decoration: none;
54
+ }
55
+
56
+ button,
57
+ input,
58
+ select,
59
+ textarea {
60
+ font: inherit;
61
+ }