@tuturuuu/ai 0.0.10
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.
- package/README.md +76 -0
- package/package.json +106 -0
- package/src/api-key-hash.ts +28 -0
- package/src/calendar/events.ts +34 -0
- package/src/calendar/route.ts +114 -0
- package/src/chat/credit-source.ts +1 -0
- package/src/chat/google/chat-request-schema.ts +150 -0
- package/src/chat/google/default-system-instruction.ts +198 -0
- package/src/chat/google/message-file-processing.ts +212 -0
- package/src/chat/google/mira-step-preparation.ts +221 -0
- package/src/chat/google/new/route.ts +368 -0
- package/src/chat/google/route-auth.ts +81 -0
- package/src/chat/google/route-chat-resolution.ts +98 -0
- package/src/chat/google/route-credits.ts +61 -0
- package/src/chat/google/route-message-preparation.ts +331 -0
- package/src/chat/google/route-mira-runtime.ts +206 -0
- package/src/chat/google/route.ts +632 -0
- package/src/chat/google/stream-finish-persistence.ts +722 -0
- package/src/chat/google/summary/route.ts +153 -0
- package/src/chat/mira-render-ui-policy.ts +540 -0
- package/src/chat/mira-system-instruction.ts +484 -0
- package/src/chat-sdk/adapters.ts +389 -0
- package/src/chat-sdk/registry.ts +197 -0
- package/src/chat-sdk.ts +33 -0
- package/src/core.ts +3 -0
- package/src/credits/cap-output-tokens.ts +90 -0
- package/src/credits/check-credits.ts +232 -0
- package/src/credits/constants.ts +30 -0
- package/src/credits/index.ts +46 -0
- package/src/credits/model-mapping.ts +92 -0
- package/src/credits/reservations.ts +514 -0
- package/src/credits/resolve-plan-model.ts +219 -0
- package/src/credits/sync-gateway-models.ts +351 -0
- package/src/credits/types.ts +109 -0
- package/src/credits/use-ai-credits.ts +3 -0
- package/src/embeddings/metered.ts +283 -0
- package/src/executions/route.ts +137 -0
- package/src/generate/route.ts +411 -0
- package/src/hooks.ts +7 -0
- package/src/meetings/summary/route.ts +7 -0
- package/src/meetings/transcription/route.ts +134 -0
- package/src/memory/client.ts +158 -0
- package/src/memory/config.ts +38 -0
- package/src/memory/index.ts +32 -0
- package/src/memory/ingest.ts +51 -0
- package/src/memory/middleware.ts +35 -0
- package/src/memory/operations.ts +480 -0
- package/src/memory/scope.ts +102 -0
- package/src/memory/settings.ts +121 -0
- package/src/memory/types.ts +101 -0
- package/src/memory/workspace.ts +36 -0
- package/src/memory.ts +1 -0
- package/src/mind/patch.ts +146 -0
- package/src/mind/route.ts +687 -0
- package/src/mind/tools.ts +1500 -0
- package/src/mind/types.ts +20 -0
- package/src/object/core.ts +3 -0
- package/src/object/flashcards/route.ts +140 -0
- package/src/object/quizzes/explanation/route.ts +145 -0
- package/src/object/quizzes/route.ts +142 -0
- package/src/object/types.ts +187 -0
- package/src/object/year-plan/route.ts +196 -0
- package/src/react.ts +1 -0
- package/src/scheduling/algorithm.ts +791 -0
- package/src/scheduling/default.ts +36 -0
- package/src/scheduling/duration-optimizer.ts +689 -0
- package/src/scheduling/index.ts +79 -0
- package/src/scheduling/priority-calculator.ts +187 -0
- package/src/scheduling/recurrence-calculator.ts +621 -0
- package/src/scheduling/templates.ts +892 -0
- package/src/scheduling/types.ts +136 -0
- package/src/scheduling/web-adapter.ts +308 -0
- package/src/scheduling.ts +6 -0
- package/src/supported-actions.ts +1 -0
- package/src/supported-providers.ts +6 -0
- package/src/tools/context-builder.ts +372 -0
- package/src/tools/core.ts +1 -0
- package/src/tools/definitions/calendar.ts +106 -0
- package/src/tools/definitions/finance.ts +197 -0
- package/src/tools/definitions/image.ts +74 -0
- package/src/tools/definitions/memory.ts +83 -0
- package/src/tools/definitions/meta.ts +154 -0
- package/src/tools/definitions/render-ui.ts +81 -0
- package/src/tools/definitions/tasks.ts +343 -0
- package/src/tools/definitions/time-tracking.ts +381 -0
- package/src/tools/definitions/workspace-context.ts +45 -0
- package/src/tools/definitions/workspace-user-chat.ts +111 -0
- package/src/tools/executors/calendar.ts +371 -0
- package/src/tools/executors/chat.ts +15 -0
- package/src/tools/executors/finance.ts +638 -0
- package/src/tools/executors/helpers/encryption.ts +107 -0
- package/src/tools/executors/image.ts +247 -0
- package/src/tools/executors/markitdown.ts +684 -0
- package/src/tools/executors/memory.ts +277 -0
- package/src/tools/executors/parallel-checks.ts +176 -0
- package/src/tools/executors/qr.ts +170 -0
- package/src/tools/executors/scope-helpers.ts +192 -0
- package/src/tools/executors/search.ts +149 -0
- package/src/tools/executors/settings.ts +40 -0
- package/src/tools/executors/tasks.ts +1087 -0
- package/src/tools/executors/theme.ts +23 -0
- package/src/tools/executors/timer/timer-categories-executor.ts +110 -0
- package/src/tools/executors/timer/timer-category-mutations.ts +240 -0
- package/src/tools/executors/timer/timer-goal-mutations.ts +323 -0
- package/src/tools/executors/timer/timer-goals-executor.ts +272 -0
- package/src/tools/executors/timer/timer-helpers.ts +372 -0
- package/src/tools/executors/timer/timer-mutation-schemas.ts +160 -0
- package/src/tools/executors/timer/timer-mutation-types.ts +212 -0
- package/src/tools/executors/timer/timer-mutations.ts +19 -0
- package/src/tools/executors/timer/timer-queries.ts +18 -0
- package/src/tools/executors/timer/timer-session-lifecycle.ts +299 -0
- package/src/tools/executors/timer/timer-session-mutations.ts +10 -0
- package/src/tools/executors/timer/timer-session-queries.ts +153 -0
- package/src/tools/executors/timer/timer-session-updates.ts +200 -0
- package/src/tools/executors/timer/timer-sessions-executor.ts +91 -0
- package/src/tools/executors/timer/timer-stats-executor.ts +157 -0
- package/src/tools/executors/timer.ts +22 -0
- package/src/tools/executors/user.ts +60 -0
- package/src/tools/executors/workspace.ts +135 -0
- package/src/tools/json-render-catalog.ts +875 -0
- package/src/tools/mira-tool-definitions.ts +55 -0
- package/src/tools/mira-tool-dispatcher.ts +265 -0
- package/src/tools/mira-tool-metadata.ts +164 -0
- package/src/tools/mira-tool-names.ts +95 -0
- package/src/tools/mira-tool-render-ui.ts +54 -0
- package/src/tools/mira-tool-types.ts +17 -0
- package/src/tools/mira-tools.ts +167 -0
- package/src/tools/normalize-render-ui-input.ts +321 -0
- package/src/tools/workspace-context.ts +233 -0
- package/src/types.ts +38 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import type {
|
|
3
|
+
AiMemoryMetadata,
|
|
4
|
+
AiMemoryScope,
|
|
5
|
+
AiMemoryScopeInput,
|
|
6
|
+
} from './types';
|
|
7
|
+
|
|
8
|
+
const MAX_SUPERMEMORY_ID_LENGTH = 100;
|
|
9
|
+
const SAFE_ID_PATTERN = /[^a-zA-Z0-9._-]+/g;
|
|
10
|
+
|
|
11
|
+
function safeId(value: string, fallback: string) {
|
|
12
|
+
const normalized = value.replace(SAFE_ID_PATTERN, '_').replace(/_+/g, '_');
|
|
13
|
+
const safe = normalized.replace(/^_+|_+$/g, '');
|
|
14
|
+
return safe || fallback;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function compactId(value: string, fallback: string) {
|
|
18
|
+
const safe = safeId(value, fallback);
|
|
19
|
+
if (safe.length <= MAX_SUPERMEMORY_ID_LENGTH) return safe;
|
|
20
|
+
|
|
21
|
+
const hash = createHash('sha256').update(safe).digest('hex').slice(0, 12);
|
|
22
|
+
const prefix = safe.slice(0, MAX_SUPERMEMORY_ID_LENGTH - hash.length - 1);
|
|
23
|
+
return `${prefix}.${hash}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function metadataValueToSafeValue(value: AiMemoryMetadata[string]) {
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return value
|
|
29
|
+
.filter((entry) => typeof entry === 'string')
|
|
30
|
+
.map((entry) => entry.slice(0, 300));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (typeof value === 'string') return value.slice(0, 1000);
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizeMetadata(metadata?: AiMemoryMetadata): AiMemoryMetadata {
|
|
38
|
+
const normalized: AiMemoryMetadata = {};
|
|
39
|
+
for (const [key, value] of Object.entries(metadata ?? {})) {
|
|
40
|
+
const safeKey = safeId(key, 'metadata').slice(0, 80);
|
|
41
|
+
normalized[safeKey] = metadataValueToSafeValue(value);
|
|
42
|
+
}
|
|
43
|
+
return normalized;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resolveAiMemoryScope(
|
|
47
|
+
input: AiMemoryScopeInput
|
|
48
|
+
): AiMemoryScope | null {
|
|
49
|
+
const userId = input.userId?.trim();
|
|
50
|
+
const wsId = input.wsId?.trim();
|
|
51
|
+
if (!userId || !wsId) return null;
|
|
52
|
+
|
|
53
|
+
const containerTag = compactId(
|
|
54
|
+
`tuturuuu.user.${userId}.workspace.${wsId}`,
|
|
55
|
+
'tuturuuu.user.workspace'
|
|
56
|
+
);
|
|
57
|
+
const customId = compactId(
|
|
58
|
+
[
|
|
59
|
+
'tuturuuu',
|
|
60
|
+
input.product,
|
|
61
|
+
input.surface,
|
|
62
|
+
input.customId?.trim() || input.source?.trim() || 'request',
|
|
63
|
+
].join('.'),
|
|
64
|
+
'tuturuuu.request'
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const metadata = normalizeMetadata({
|
|
68
|
+
...(input.metadata ?? {}),
|
|
69
|
+
product: input.product,
|
|
70
|
+
source: input.source ?? input.surface,
|
|
71
|
+
surface: input.surface,
|
|
72
|
+
userId,
|
|
73
|
+
wsId,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
containerTag,
|
|
78
|
+
customId,
|
|
79
|
+
metadata,
|
|
80
|
+
product: input.product,
|
|
81
|
+
source: input.source,
|
|
82
|
+
surface: input.surface,
|
|
83
|
+
userId,
|
|
84
|
+
wsId,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function buildProductFilter(product: string) {
|
|
89
|
+
return {
|
|
90
|
+
filterType: 'metadata' as const,
|
|
91
|
+
key: 'product',
|
|
92
|
+
value: product,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function buildKeyFilter(key: string) {
|
|
97
|
+
return {
|
|
98
|
+
filterType: 'metadata' as const,
|
|
99
|
+
key: 'memoryKey',
|
|
100
|
+
value: key,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { AiMemoryProduct, AiMemorySettings } from './types';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_SETTINGS: AiMemorySettings = {
|
|
4
|
+
enabled: true,
|
|
5
|
+
productEnabled: true,
|
|
6
|
+
products: {},
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type RpcSettingsRow = {
|
|
10
|
+
enabled?: boolean | null;
|
|
11
|
+
product_enabled?: boolean | null;
|
|
12
|
+
products?: Record<string, boolean> | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type SupabaseRpcClient = {
|
|
16
|
+
schema: (schema: string) => {
|
|
17
|
+
rpc: (
|
|
18
|
+
fn: string,
|
|
19
|
+
args: Record<string, unknown>
|
|
20
|
+
) => Promise<{ data: unknown; error: { message?: string } | null }>;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
async function createDefaultRpcClient() {
|
|
25
|
+
const { createAdminClient } = await import('@tuturuuu/supabase/next/server');
|
|
26
|
+
return (await createAdminClient()) as unknown as SupabaseRpcClient;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeSettings(
|
|
30
|
+
row: RpcSettingsRow | null | undefined,
|
|
31
|
+
product: AiMemoryProduct
|
|
32
|
+
): AiMemorySettings {
|
|
33
|
+
const products = (row?.products ?? {}) as AiMemorySettings['products'];
|
|
34
|
+
const enabled = row?.enabled ?? DEFAULT_SETTINGS.enabled;
|
|
35
|
+
const productEnabled =
|
|
36
|
+
row?.product_enabled ??
|
|
37
|
+
products[product] ??
|
|
38
|
+
DEFAULT_SETTINGS.productEnabled;
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
enabled,
|
|
42
|
+
productEnabled,
|
|
43
|
+
products,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function getAiMemorySettings({
|
|
48
|
+
db,
|
|
49
|
+
product,
|
|
50
|
+
userId,
|
|
51
|
+
wsId,
|
|
52
|
+
}: {
|
|
53
|
+
db?: SupabaseRpcClient;
|
|
54
|
+
product: AiMemoryProduct;
|
|
55
|
+
userId: string;
|
|
56
|
+
wsId: string;
|
|
57
|
+
}): Promise<AiMemorySettings> {
|
|
58
|
+
try {
|
|
59
|
+
const client = db ?? (await createDefaultRpcClient());
|
|
60
|
+
const { data, error } = await client
|
|
61
|
+
.schema('private')
|
|
62
|
+
.rpc('get_ai_memory_settings', {
|
|
63
|
+
p_product: product,
|
|
64
|
+
p_user_id: userId,
|
|
65
|
+
p_ws_id: wsId,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (error) return DEFAULT_SETTINGS;
|
|
69
|
+
|
|
70
|
+
const row = Array.isArray(data) ? data[0] : data;
|
|
71
|
+
return normalizeSettings(row as RpcSettingsRow | null, product);
|
|
72
|
+
} catch {
|
|
73
|
+
return DEFAULT_SETTINGS;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function isAiMemoryEnabledForScope(args: {
|
|
78
|
+
db?: SupabaseRpcClient;
|
|
79
|
+
product: AiMemoryProduct;
|
|
80
|
+
userId: string;
|
|
81
|
+
wsId: string;
|
|
82
|
+
}) {
|
|
83
|
+
const settings = await getAiMemorySettings(args);
|
|
84
|
+
return settings.enabled && settings.productEnabled;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function disableAiMemoryForMeteringFailure({
|
|
88
|
+
db,
|
|
89
|
+
reason,
|
|
90
|
+
userId,
|
|
91
|
+
wsId,
|
|
92
|
+
}: {
|
|
93
|
+
db?: SupabaseRpcClient;
|
|
94
|
+
reason: string;
|
|
95
|
+
userId: string;
|
|
96
|
+
wsId: string;
|
|
97
|
+
}) {
|
|
98
|
+
try {
|
|
99
|
+
const client = db ?? (await createDefaultRpcClient());
|
|
100
|
+
await client.schema('private').rpc('upsert_ai_memory_settings', {
|
|
101
|
+
p_actor_user_id: userId,
|
|
102
|
+
p_enabled: false,
|
|
103
|
+
p_product_settings: {},
|
|
104
|
+
p_user_id: userId,
|
|
105
|
+
p_ws_id: wsId,
|
|
106
|
+
});
|
|
107
|
+
await client.schema('private').rpc('record_ai_memory_audit', {
|
|
108
|
+
p_action: 'settings_update',
|
|
109
|
+
p_actor_user_id: userId,
|
|
110
|
+
p_metadata: {
|
|
111
|
+
disabledBy: 'metered_embedding',
|
|
112
|
+
reason,
|
|
113
|
+
},
|
|
114
|
+
p_product: null,
|
|
115
|
+
p_user_id: userId,
|
|
116
|
+
p_ws_id: wsId,
|
|
117
|
+
});
|
|
118
|
+
} catch {
|
|
119
|
+
// Memory settings are fail-open elsewhere; disabling is best effort.
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { LanguageModel } from 'ai';
|
|
2
|
+
|
|
3
|
+
export const AI_MEMORY_PRODUCTS = [
|
|
4
|
+
'ai_agents',
|
|
5
|
+
'ai_chat',
|
|
6
|
+
'calendar',
|
|
7
|
+
'education',
|
|
8
|
+
'finance',
|
|
9
|
+
'hive',
|
|
10
|
+
'live_assistant',
|
|
11
|
+
'meetings',
|
|
12
|
+
'memories',
|
|
13
|
+
'mind',
|
|
14
|
+
'mira',
|
|
15
|
+
'native_chat',
|
|
16
|
+
'object_generation',
|
|
17
|
+
'playground',
|
|
18
|
+
'rewise',
|
|
19
|
+
'tasks',
|
|
20
|
+
'teach',
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
export type AiMemoryProduct = (typeof AI_MEMORY_PRODUCTS)[number];
|
|
24
|
+
|
|
25
|
+
export type AiMemoryMetadataValue = string | number | boolean | Array<string>;
|
|
26
|
+
|
|
27
|
+
export type AiMemoryMetadata = Record<string, AiMemoryMetadataValue>;
|
|
28
|
+
|
|
29
|
+
export type AiMemoryScopeInput = {
|
|
30
|
+
customId?: string | null;
|
|
31
|
+
metadata?: AiMemoryMetadata;
|
|
32
|
+
product: AiMemoryProduct;
|
|
33
|
+
source?: string | null;
|
|
34
|
+
surface: string;
|
|
35
|
+
userId?: string | null;
|
|
36
|
+
wsId?: string | null;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type AiMemoryScope = {
|
|
40
|
+
containerTag: string;
|
|
41
|
+
customId: string;
|
|
42
|
+
metadata: AiMemoryMetadata;
|
|
43
|
+
product: AiMemoryProduct;
|
|
44
|
+
source?: string | null;
|
|
45
|
+
surface: string;
|
|
46
|
+
userId: string;
|
|
47
|
+
wsId: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type AiMemorySettings = {
|
|
51
|
+
enabled: boolean;
|
|
52
|
+
productEnabled: boolean;
|
|
53
|
+
products: Partial<Record<AiMemoryProduct, boolean>>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type AiMemoryConfig = {
|
|
57
|
+
apiKey: string;
|
|
58
|
+
baseUrl?: string;
|
|
59
|
+
enabled: boolean;
|
|
60
|
+
failOpen: boolean;
|
|
61
|
+
timeoutMs: number;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type AiMemoryResult<T> =
|
|
65
|
+
| { ok: true; skipped?: false; value: T }
|
|
66
|
+
| { ok: true; reason: string; skipped: true; value?: T }
|
|
67
|
+
| { error: string; ok: false; skipped?: false };
|
|
68
|
+
|
|
69
|
+
export type AiMemorySearchResult = {
|
|
70
|
+
id: string;
|
|
71
|
+
key?: string | null;
|
|
72
|
+
metadata: AiMemoryMetadata | null;
|
|
73
|
+
score: number;
|
|
74
|
+
updatedAt: string;
|
|
75
|
+
value: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type AiMemoryDocument = {
|
|
79
|
+
category?: string | null;
|
|
80
|
+
content?: string | null;
|
|
81
|
+
id: string;
|
|
82
|
+
key?: string | null;
|
|
83
|
+
metadata: AiMemoryMetadata | null;
|
|
84
|
+
status: string;
|
|
85
|
+
summary?: string | null;
|
|
86
|
+
title?: string | null;
|
|
87
|
+
updatedAt: string;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type AiMemoryModelOptions<TModel extends LanguageModel = LanguageModel> =
|
|
91
|
+
{
|
|
92
|
+
addMemory?: 'always' | 'never';
|
|
93
|
+
customId?: string | null;
|
|
94
|
+
mode?: 'profile' | 'query' | 'full';
|
|
95
|
+
model: TModel;
|
|
96
|
+
product: AiMemoryProduct;
|
|
97
|
+
source?: string | null;
|
|
98
|
+
surface: string;
|
|
99
|
+
userId?: string | null;
|
|
100
|
+
wsId?: string | null;
|
|
101
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { TypedSupabaseClient } from '@tuturuuu/supabase/types';
|
|
2
|
+
import { ROOT_WORKSPACE_ID } from '@tuturuuu/utils/constants';
|
|
3
|
+
|
|
4
|
+
export async function resolveAiMemoryWorkspaceIdForUser({
|
|
5
|
+
fallbackWsId = ROOT_WORKSPACE_ID,
|
|
6
|
+
supabase,
|
|
7
|
+
userId,
|
|
8
|
+
}: {
|
|
9
|
+
fallbackWsId?: string;
|
|
10
|
+
supabase: TypedSupabaseClient;
|
|
11
|
+
userId: string;
|
|
12
|
+
}) {
|
|
13
|
+
try {
|
|
14
|
+
const { data: userPrivateDetails } = await supabase
|
|
15
|
+
.from('user_private_details')
|
|
16
|
+
.select('default_workspace_id')
|
|
17
|
+
.eq('user_id', userId)
|
|
18
|
+
.maybeSingle();
|
|
19
|
+
|
|
20
|
+
if (userPrivateDetails?.default_workspace_id) {
|
|
21
|
+
return userPrivateDetails.default_workspace_id;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { data: personalWorkspace } = await supabase
|
|
25
|
+
.from('workspaces')
|
|
26
|
+
.select('id, workspace_members!inner(user_id)')
|
|
27
|
+
.eq('personal', true)
|
|
28
|
+
.eq('workspace_members.user_id', userId)
|
|
29
|
+
.limit(1)
|
|
30
|
+
.maybeSingle();
|
|
31
|
+
|
|
32
|
+
return personalWorkspace?.id ?? fallbackWsId;
|
|
33
|
+
} catch {
|
|
34
|
+
return fallbackWsId;
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/memory.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './memory/index';
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MindAiPatch,
|
|
3
|
+
MindBoardSnapshot,
|
|
4
|
+
MindEdge,
|
|
5
|
+
MindNode,
|
|
6
|
+
} from './types';
|
|
7
|
+
|
|
8
|
+
const NOW = '1970-01-01T00:00:00.000Z';
|
|
9
|
+
|
|
10
|
+
function createNode(node: Parameters<typeof normalizeNode>[0]) {
|
|
11
|
+
return normalizeNode(node);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeNode(
|
|
15
|
+
node: Partial<MindNode> &
|
|
16
|
+
Pick<MindNode, 'id' | 'positionX' | 'positionY' | 'title'>
|
|
17
|
+
): MindNode {
|
|
18
|
+
return {
|
|
19
|
+
body: node.body ?? null,
|
|
20
|
+
color: node.color ?? null,
|
|
21
|
+
createdAt: node.createdAt ?? NOW,
|
|
22
|
+
height: node.height ?? 120,
|
|
23
|
+
horizon: node.horizon ?? 'year',
|
|
24
|
+
id: node.id,
|
|
25
|
+
metadata: node.metadata ?? {},
|
|
26
|
+
nodeType: node.nodeType ?? 'idea',
|
|
27
|
+
parentNodeId: node.parentNodeId ?? null,
|
|
28
|
+
positionX: node.positionX,
|
|
29
|
+
positionY: node.positionY,
|
|
30
|
+
status: node.status ?? 'planned',
|
|
31
|
+
title: node.title,
|
|
32
|
+
updatedAt: node.updatedAt ?? NOW,
|
|
33
|
+
width: node.width ?? 240,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizeEdge(
|
|
38
|
+
edge: Partial<MindEdge> &
|
|
39
|
+
Pick<MindEdge, 'id' | 'sourceNodeId' | 'targetNodeId'>
|
|
40
|
+
): MindEdge {
|
|
41
|
+
return {
|
|
42
|
+
color: edge.color ?? null,
|
|
43
|
+
createdAt: edge.createdAt ?? NOW,
|
|
44
|
+
edgeType: edge.edgeType ?? 'relates_to',
|
|
45
|
+
id: edge.id,
|
|
46
|
+
label: edge.label ?? null,
|
|
47
|
+
metadata: edge.metadata ?? {},
|
|
48
|
+
sourceNodeId: edge.sourceNodeId,
|
|
49
|
+
targetNodeId: edge.targetNodeId,
|
|
50
|
+
updatedAt: edge.updatedAt ?? NOW,
|
|
51
|
+
weight: edge.weight ?? 1,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function applyMindPatchToSnapshot(
|
|
56
|
+
snapshot: MindBoardSnapshot,
|
|
57
|
+
patch: MindAiPatch
|
|
58
|
+
): MindBoardSnapshot {
|
|
59
|
+
let nodes: MindNode[] = snapshot.nodes.map((node: MindNode) => ({ ...node }));
|
|
60
|
+
let edges: MindEdge[] = snapshot.edges.map((edge: MindEdge) => ({ ...edge }));
|
|
61
|
+
|
|
62
|
+
for (const operation of patch.operations) {
|
|
63
|
+
if (operation.kind === 'create_node') {
|
|
64
|
+
const nextNode = createNode(operation.node);
|
|
65
|
+
nodes = [
|
|
66
|
+
...nodes.filter((node: MindNode) => node.id !== nextNode.id),
|
|
67
|
+
nextNode,
|
|
68
|
+
];
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (operation.kind === 'update_node') {
|
|
73
|
+
nodes = nodes.map((node: MindNode) =>
|
|
74
|
+
node.id === operation.nodeId
|
|
75
|
+
? {
|
|
76
|
+
...node,
|
|
77
|
+
...Object.fromEntries(
|
|
78
|
+
Object.entries(operation).filter(
|
|
79
|
+
([key, value]) =>
|
|
80
|
+
key !== 'id' &&
|
|
81
|
+
key !== 'kind' &&
|
|
82
|
+
key !== 'nodeId' &&
|
|
83
|
+
value !== undefined
|
|
84
|
+
)
|
|
85
|
+
),
|
|
86
|
+
}
|
|
87
|
+
: node
|
|
88
|
+
);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (operation.kind === 'delete_node') {
|
|
93
|
+
nodes = nodes.filter((node: MindNode) => node.id !== operation.nodeId);
|
|
94
|
+
edges = edges.filter(
|
|
95
|
+
(edge: MindEdge) =>
|
|
96
|
+
edge.sourceNodeId !== operation.nodeId &&
|
|
97
|
+
edge.targetNodeId !== operation.nodeId
|
|
98
|
+
);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (operation.kind === 'create_edge') {
|
|
103
|
+
const nextEdge = normalizeEdge(operation.edge);
|
|
104
|
+
edges = [
|
|
105
|
+
...edges.filter((edge: MindEdge) => edge.id !== nextEdge.id),
|
|
106
|
+
nextEdge,
|
|
107
|
+
];
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (operation.kind === 'update_edge') {
|
|
112
|
+
edges = edges.map((edge: MindEdge) =>
|
|
113
|
+
edge.id === operation.edgeId
|
|
114
|
+
? {
|
|
115
|
+
...edge,
|
|
116
|
+
...Object.fromEntries(
|
|
117
|
+
Object.entries(operation).filter(
|
|
118
|
+
([key, value]) =>
|
|
119
|
+
key !== 'edgeId' &&
|
|
120
|
+
key !== 'id' &&
|
|
121
|
+
key !== 'kind' &&
|
|
122
|
+
value !== undefined
|
|
123
|
+
)
|
|
124
|
+
),
|
|
125
|
+
}
|
|
126
|
+
: edge
|
|
127
|
+
);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (operation.kind === 'delete_edge') {
|
|
132
|
+
edges = edges.filter((edge: MindEdge) => edge.id !== operation.edgeId);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const nodeIds = new Set(nodes.map((node: MindNode) => node.id));
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
...snapshot,
|
|
140
|
+
edges: edges.filter(
|
|
141
|
+
(edge: MindEdge) =>
|
|
142
|
+
nodeIds.has(edge.sourceNodeId) && nodeIds.has(edge.targetNodeId)
|
|
143
|
+
),
|
|
144
|
+
nodes,
|
|
145
|
+
};
|
|
146
|
+
}
|