mindcache 3.5.3 → 3.7.0
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 +29 -4
- package/dist/{CloudAdapter-DK4YecbV.d.mts → CloudAdapter-PLGvGjoA.d.mts} +86 -7
- package/dist/{CloudAdapter-DK4YecbV.d.ts → CloudAdapter-PLGvGjoA.d.ts} +86 -7
- package/dist/cloud/index.d.mts +2 -2
- package/dist/cloud/index.d.ts +2 -2
- package/dist/cloud/index.js +379 -67
- package/dist/cloud/index.js.map +1 -1
- package/dist/cloud/index.mjs +379 -67
- package/dist/cloud/index.mjs.map +1 -1
- package/dist/index.d.mts +417 -19
- package/dist/index.d.ts +417 -19
- package/dist/index.js +1444 -105
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1433 -101
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +379 -67
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +379 -67
- package/dist/server.mjs.map +1 -1
- package/package.json +5 -9
package/dist/index.mjs
CHANGED
|
@@ -4,7 +4,11 @@ import * as decoding from 'lib0/decoding';
|
|
|
4
4
|
import * as Y from 'yjs';
|
|
5
5
|
import { IndexeddbPersistence } from 'y-indexeddb';
|
|
6
6
|
import diff from 'fast-diff';
|
|
7
|
-
import {
|
|
7
|
+
import { tool, streamText, stepCountIs } from 'ai';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import React2, { createContext, useState, useRef, useEffect, useContext, useCallback } from 'react';
|
|
10
|
+
import { createOpenAI } from '@ai-sdk/openai';
|
|
11
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
8
12
|
|
|
9
13
|
var __defProp = Object.defineProperty;
|
|
10
14
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -184,7 +188,6 @@ var init_CloudAdapter = __esm({
|
|
|
184
188
|
if (!config.baseUrl) {
|
|
185
189
|
throw new Error("MindCache Cloud: baseUrl is required. Please provide the cloud API URL in your configuration.");
|
|
186
190
|
}
|
|
187
|
-
this.validateConfig(config);
|
|
188
191
|
this.setupNetworkDetection();
|
|
189
192
|
}
|
|
190
193
|
ws = null;
|
|
@@ -200,43 +203,6 @@ var init_CloudAdapter = __esm({
|
|
|
200
203
|
handleOnline = null;
|
|
201
204
|
handleOffline = null;
|
|
202
205
|
_synced = false;
|
|
203
|
-
/**
|
|
204
|
-
* Validate configuration and warn about common mistakes
|
|
205
|
-
*/
|
|
206
|
-
validateConfig(config) {
|
|
207
|
-
const baseUrl = config.baseUrl;
|
|
208
|
-
if (!baseUrl) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
console.log("\u2601\uFE0F MindCache Cloud Config:", {
|
|
212
|
-
baseUrl,
|
|
213
|
-
instanceId: config.instanceId,
|
|
214
|
-
hasTokenProvider: !!config.tokenProvider,
|
|
215
|
-
hasApiKey: !!config.apiKey
|
|
216
|
-
});
|
|
217
|
-
try {
|
|
218
|
-
const url = new URL(baseUrl);
|
|
219
|
-
if (url.hostname === "mindcache.dev") {
|
|
220
|
-
console.error(
|
|
221
|
-
'\u26A0\uFE0F MindCache Cloud WARNING: baseUrl is set to "mindcache.dev" but the API is at "api.mindcache.dev".\n Current: ' + baseUrl + "\n Expected: https://api.mindcache.dev\n This will cause WebSocket connection failures (404 errors)."
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
if (url.protocol === "ws:" || url.protocol === "wss:") {
|
|
225
|
-
console.warn(
|
|
226
|
-
"\u26A0\uFE0F MindCache Cloud: baseUrl uses WebSocket protocol (" + url.protocol + "). Consider using http:// or https:// - CloudAdapter will handle the WebSocket upgrade automatically."
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
if (url.hostname === "localhost" && url.port !== "8787" && url.port !== "3000") {
|
|
230
|
-
console.warn(
|
|
231
|
-
"\u26A0\uFE0F MindCache Cloud: localhost URL detected with non-standard port " + url.port + ". Default MindCache dev server runs on port 8787."
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
const wsUrl = baseUrl.replace("https://", "wss://").replace("http://", "ws://");
|
|
235
|
-
console.log("\u2601\uFE0F WebSocket will connect to:", wsUrl + "/sync/" + config.instanceId);
|
|
236
|
-
} catch (e) {
|
|
237
|
-
console.error("\u26A0\uFE0F MindCache Cloud: Invalid baseUrl format:", baseUrl);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
206
|
/** Browser network status - instantly updated via navigator.onLine */
|
|
241
207
|
get isOnline() {
|
|
242
208
|
return this._isOnline;
|
|
@@ -489,6 +455,76 @@ var SystemTagHelpers = {
|
|
|
489
455
|
hasTemplateInjection: (attrs) => attrs.systemTags.includes("ApplyTemplate")
|
|
490
456
|
};
|
|
491
457
|
|
|
458
|
+
// src/core/SchemaParser.ts
|
|
459
|
+
var SchemaParser = class {
|
|
460
|
+
/**
|
|
461
|
+
* Parse a markdown schema string into a CustomTypeDefinition
|
|
462
|
+
* @param schema - Markdown schema string
|
|
463
|
+
* @returns Parsed type definition
|
|
464
|
+
* @throws Error if schema format is invalid
|
|
465
|
+
*/
|
|
466
|
+
static parse(schema) {
|
|
467
|
+
const lines = schema.trim().split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
468
|
+
if (lines.length === 0) {
|
|
469
|
+
throw new Error("Schema cannot be empty");
|
|
470
|
+
}
|
|
471
|
+
const typeNameMatch = lines[0].match(/^#\s*(\w+)$/);
|
|
472
|
+
if (!typeNameMatch) {
|
|
473
|
+
throw new Error(`Invalid schema: first line must be "#TypeName", got "${lines[0]}"`);
|
|
474
|
+
}
|
|
475
|
+
const typeName = typeNameMatch[1];
|
|
476
|
+
const fields = [];
|
|
477
|
+
for (let i = 1; i < lines.length; i++) {
|
|
478
|
+
const line = lines[i];
|
|
479
|
+
const fieldMatch = line.match(/^\*\s*([^:]+):\s*(.+)$/);
|
|
480
|
+
if (fieldMatch) {
|
|
481
|
+
fields.push({
|
|
482
|
+
name: fieldMatch[1].trim(),
|
|
483
|
+
description: fieldMatch[2].trim()
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (fields.length === 0) {
|
|
488
|
+
throw new Error(`Schema "${typeName}" must have at least one field`);
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
name: typeName,
|
|
492
|
+
fields,
|
|
493
|
+
rawSchema: schema
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Generate a markdown representation of a type definition
|
|
498
|
+
* Useful for including in LLM prompts
|
|
499
|
+
*/
|
|
500
|
+
static toMarkdown(typeDef) {
|
|
501
|
+
const lines = [`#${typeDef.name}`];
|
|
502
|
+
for (const field of typeDef.fields) {
|
|
503
|
+
lines.push(`* ${field.name}: ${field.description}`);
|
|
504
|
+
}
|
|
505
|
+
return lines.join("\n");
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Generate a prompt-friendly description of the type
|
|
509
|
+
* More verbose than toMarkdown, better for LLM guidance
|
|
510
|
+
*/
|
|
511
|
+
static toPromptDescription(typeDef) {
|
|
512
|
+
const fieldDescs = typeDef.fields.map((f) => ` - ${f.name}: ${f.description}`).join("\n");
|
|
513
|
+
return `Type "${typeDef.name}" with fields:
|
|
514
|
+
${fieldDescs}`;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Generate an example value structure based on the type definition
|
|
518
|
+
*/
|
|
519
|
+
static generateExample(typeDef) {
|
|
520
|
+
const lines = [`#${typeDef.name.toLowerCase()}`];
|
|
521
|
+
for (const field of typeDef.fields) {
|
|
522
|
+
lines.push(`* ${field.name}: [${field.description}]`);
|
|
523
|
+
}
|
|
524
|
+
return lines.join("\n");
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
|
|
492
528
|
// src/core/MarkdownSerializer.ts
|
|
493
529
|
var MarkdownSerializer = class {
|
|
494
530
|
/**
|
|
@@ -835,18 +871,16 @@ var MarkdownSerializer = class {
|
|
|
835
871
|
return result;
|
|
836
872
|
}
|
|
837
873
|
};
|
|
838
|
-
|
|
839
|
-
// src/core/AIToolBuilder.ts
|
|
840
874
|
var AIToolBuilder = class _AIToolBuilder {
|
|
841
875
|
/**
|
|
842
|
-
|
|
843
|
-
|
|
876
|
+
* Sanitize key name for use in tool names
|
|
877
|
+
*/
|
|
844
878
|
static sanitizeKeyForTool(key) {
|
|
845
879
|
return key.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
846
880
|
}
|
|
847
881
|
/**
|
|
848
|
-
|
|
849
|
-
|
|
882
|
+
* Find original key from sanitized tool name
|
|
883
|
+
*/
|
|
850
884
|
static findKeyFromSanitizedTool(mc, sanitizedKey) {
|
|
851
885
|
for (const key of mc.keys()) {
|
|
852
886
|
if (_AIToolBuilder.sanitizeKeyForTool(key) === sanitizedKey) {
|
|
@@ -856,15 +890,174 @@ var AIToolBuilder = class _AIToolBuilder {
|
|
|
856
890
|
return void 0;
|
|
857
891
|
}
|
|
858
892
|
/**
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
893
|
+
* Generate framework-agnostic tools with raw JSON Schema.
|
|
894
|
+
* Works with: OpenAI SDK, Anthropic SDK, LangChain, etc.
|
|
895
|
+
*
|
|
896
|
+
* Tool format:
|
|
897
|
+
* {
|
|
898
|
+
* description: string,
|
|
899
|
+
* parameters: { type: 'object', properties: {...}, required: [...] },
|
|
900
|
+
* execute: async (args) => result
|
|
901
|
+
* }
|
|
902
|
+
*/
|
|
903
|
+
static createTools(mc) {
|
|
904
|
+
return _AIToolBuilder._buildTools(mc);
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Generate Vercel AI SDK v5 compatible tools using Zod schemas.
|
|
908
|
+
* Uses tool() helper with Zod for full AI SDK v5 compatibility.
|
|
909
|
+
*
|
|
910
|
+
* Use this with: generateText(), streamText() from 'ai' package
|
|
911
|
+
*/
|
|
866
912
|
static createVercelAITools(mc) {
|
|
867
913
|
const tools = {};
|
|
914
|
+
const registeredTypes = mc.getRegisteredTypes();
|
|
915
|
+
const typeDesc = registeredTypes.length > 0 ? `Optional type: ${registeredTypes.join(" | ")}` : "No types registered";
|
|
916
|
+
tools["create_key"] = tool({
|
|
917
|
+
description: `Create a new key in MindCache. ${registeredTypes.length > 0 ? `Available types: ${registeredTypes.join(", ")}` : ""}`,
|
|
918
|
+
inputSchema: z.object({
|
|
919
|
+
key: z.string().describe('The key name (e.g., "contact_john_doe")'),
|
|
920
|
+
value: z.string().describe("The value (JSON string for structured data)"),
|
|
921
|
+
type: z.string().optional().describe(typeDesc)
|
|
922
|
+
}),
|
|
923
|
+
execute: async ({ key, value, type }) => {
|
|
924
|
+
if (mc.has(key)) {
|
|
925
|
+
return { result: `Key "${key}" exists. Use write_${_AIToolBuilder.sanitizeKeyForTool(key)}`, error: true };
|
|
926
|
+
}
|
|
927
|
+
if (type && !mc.getTypeSchema(type)) {
|
|
928
|
+
return { result: `Type "${type}" not registered`, error: true };
|
|
929
|
+
}
|
|
930
|
+
mc.set_value(key, value, { systemTags: ["SystemPrompt", "LLMRead", "LLMWrite"] });
|
|
931
|
+
if (type) {
|
|
932
|
+
mc.setType(key, type);
|
|
933
|
+
}
|
|
934
|
+
return { result: `Created "${key}"${type ? ` (${type})` : ""}`, key, value };
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
for (const key of mc.keys()) {
|
|
938
|
+
if (key.startsWith("$") || !mc.keyMatchesContext(key)) {
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
const attrs = mc.get_attributes(key);
|
|
942
|
+
if (!attrs?.systemTags?.includes("LLMWrite")) {
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
const sanitized = _AIToolBuilder.sanitizeKeyForTool(key);
|
|
946
|
+
const customTypeName = mc.getKeyType(key);
|
|
947
|
+
const customType = customTypeName ? mc.getTypeSchema(customTypeName) : void 0;
|
|
948
|
+
let desc = `Write to "${key}"`;
|
|
949
|
+
if (customType) {
|
|
950
|
+
desc = `Write to "${key}" (${customTypeName}). ${SchemaParser.toPromptDescription(customType)}`;
|
|
951
|
+
}
|
|
952
|
+
tools[`write_${sanitized}`] = tool({
|
|
953
|
+
description: desc,
|
|
954
|
+
inputSchema: z.object({
|
|
955
|
+
value: z.string().describe(customType ? `JSON following ${customTypeName} schema` : "Value to write")
|
|
956
|
+
}),
|
|
957
|
+
execute: async ({ value }) => {
|
|
958
|
+
const success = mc.llm_set_key(key, value);
|
|
959
|
+
return success ? { result: `Wrote to ${key}`, key, value } : { result: `Failed to write to ${key}`, error: true };
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
if (attrs?.type === "document") {
|
|
963
|
+
tools[`append_${sanitized}`] = tool({
|
|
964
|
+
description: `Append to "${key}" document`,
|
|
965
|
+
inputSchema: z.object({ text: z.string().describe("Text to append") }),
|
|
966
|
+
execute: async ({ text }) => {
|
|
967
|
+
const yText = mc.get_document(key);
|
|
968
|
+
if (yText) {
|
|
969
|
+
yText.insert(yText.length, text);
|
|
970
|
+
return { result: "Appended", key };
|
|
971
|
+
}
|
|
972
|
+
return { result: "Not found", error: true };
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
tools[`insert_${sanitized}`] = tool({
|
|
976
|
+
description: `Insert text at position in "${key}" document`,
|
|
977
|
+
inputSchema: z.object({
|
|
978
|
+
index: z.number().describe("Position (0 = start)"),
|
|
979
|
+
text: z.string().describe("Text to insert")
|
|
980
|
+
}),
|
|
981
|
+
execute: async ({ index, text }) => {
|
|
982
|
+
mc.insert_text(key, index, text);
|
|
983
|
+
return { result: `Inserted at ${index}`, key };
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
tools[`edit_${sanitized}`] = tool({
|
|
987
|
+
description: `Find and replace in "${key}" document`,
|
|
988
|
+
inputSchema: z.object({
|
|
989
|
+
find: z.string().describe("Text to find"),
|
|
990
|
+
replace: z.string().describe("Replacement")
|
|
991
|
+
}),
|
|
992
|
+
execute: async ({ find, replace }) => {
|
|
993
|
+
const yText = mc.get_document(key);
|
|
994
|
+
if (yText) {
|
|
995
|
+
const text = yText.toString();
|
|
996
|
+
const idx = text.indexOf(find);
|
|
997
|
+
if (idx !== -1) {
|
|
998
|
+
yText.delete(idx, find.length);
|
|
999
|
+
yText.insert(idx, replace);
|
|
1000
|
+
return { result: `Replaced "${find}"`, key };
|
|
1001
|
+
}
|
|
1002
|
+
return { result: `"${find}" not found`, error: true };
|
|
1003
|
+
}
|
|
1004
|
+
return { result: "Document not found", error: true };
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
return tools;
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Internal: Build tools with raw JSON Schema (framework-agnostic).
|
|
1013
|
+
*/
|
|
1014
|
+
static _buildTools(mc) {
|
|
1015
|
+
const tools = {};
|
|
1016
|
+
const registeredTypes = mc.getRegisteredTypes();
|
|
1017
|
+
const typeInfo = registeredTypes.length > 0 ? `Available types: ${registeredTypes.join(", ")}` : "No custom types registered";
|
|
1018
|
+
tools["create_key"] = {
|
|
1019
|
+
description: `Create a new key in MindCache. ${typeInfo}. The new key will be readable and writable by the LLM.`,
|
|
1020
|
+
parameters: {
|
|
1021
|
+
type: "object",
|
|
1022
|
+
properties: {
|
|
1023
|
+
key: { type: "string", description: 'The key name to create (e.g., "contact_john_doe")' },
|
|
1024
|
+
value: { type: "string", description: "The value to store (use JSON string for structured data)" },
|
|
1025
|
+
type: {
|
|
1026
|
+
type: "string",
|
|
1027
|
+
description: registeredTypes.length > 0 ? `Optional: custom type name (${registeredTypes.join(" | ")})` : "Optional: custom type name (none registered)"
|
|
1028
|
+
}
|
|
1029
|
+
},
|
|
1030
|
+
required: ["key", "value"]
|
|
1031
|
+
},
|
|
1032
|
+
execute: async ({ key, value, type }) => {
|
|
1033
|
+
if (mc.has(key)) {
|
|
1034
|
+
return {
|
|
1035
|
+
result: `Key "${key}" already exists. Use write_${_AIToolBuilder.sanitizeKeyForTool(key)} to update it.`,
|
|
1036
|
+
key,
|
|
1037
|
+
error: true
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
if (type && !mc.getTypeSchema(type)) {
|
|
1041
|
+
return {
|
|
1042
|
+
result: `Type "${type}" is not registered. Available types: ${registeredTypes.join(", ") || "none"}`,
|
|
1043
|
+
key,
|
|
1044
|
+
error: true
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
mc.set_value(key, value, {
|
|
1048
|
+
systemTags: ["SystemPrompt", "LLMRead", "LLMWrite"]
|
|
1049
|
+
});
|
|
1050
|
+
if (type) {
|
|
1051
|
+
mc.setType(key, type);
|
|
1052
|
+
}
|
|
1053
|
+
return {
|
|
1054
|
+
result: `Successfully created key "${key}"${type ? ` with type "${type}"` : ""}`,
|
|
1055
|
+
key,
|
|
1056
|
+
value,
|
|
1057
|
+
type
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
868
1061
|
for (const key of mc.keys()) {
|
|
869
1062
|
if (key.startsWith("$")) {
|
|
870
1063
|
continue;
|
|
@@ -879,12 +1072,28 @@ var AIToolBuilder = class _AIToolBuilder {
|
|
|
879
1072
|
}
|
|
880
1073
|
const sanitizedKey = _AIToolBuilder.sanitizeKeyForTool(key);
|
|
881
1074
|
const isDocument = attributes?.type === "document";
|
|
1075
|
+
const customTypeName = mc.getKeyType(key);
|
|
1076
|
+
const customType = customTypeName ? mc.getTypeSchema(customTypeName) : void 0;
|
|
1077
|
+
let writeDescription;
|
|
1078
|
+
if (customType) {
|
|
1079
|
+
const schemaGuidance = SchemaParser.toPromptDescription(customType);
|
|
1080
|
+
const example = SchemaParser.generateExample(customType);
|
|
1081
|
+
writeDescription = `Write a value to "${key}" that must follow this schema:
|
|
1082
|
+
${schemaGuidance}
|
|
1083
|
+
|
|
1084
|
+
Example format:
|
|
1085
|
+
${example}`;
|
|
1086
|
+
} else if (isDocument) {
|
|
1087
|
+
writeDescription = `Rewrite the entire "${key}" document`;
|
|
1088
|
+
} else {
|
|
1089
|
+
writeDescription = `Write a value to the STM key: ${key}`;
|
|
1090
|
+
}
|
|
882
1091
|
tools[`write_${sanitizedKey}`] = {
|
|
883
|
-
description:
|
|
884
|
-
|
|
1092
|
+
description: writeDescription,
|
|
1093
|
+
parameters: {
|
|
885
1094
|
type: "object",
|
|
886
1095
|
properties: {
|
|
887
|
-
value: { type: "string", description: isDocument ? "New document content" : "The value to write" }
|
|
1096
|
+
value: { type: "string", description: customType ? `Value following ${customTypeName} schema` : isDocument ? "New document content" : "The value to write" }
|
|
888
1097
|
},
|
|
889
1098
|
required: ["value"]
|
|
890
1099
|
},
|
|
@@ -907,7 +1116,7 @@ var AIToolBuilder = class _AIToolBuilder {
|
|
|
907
1116
|
if (isDocument) {
|
|
908
1117
|
tools[`append_${sanitizedKey}`] = {
|
|
909
1118
|
description: `Append text to the end of "${key}" document`,
|
|
910
|
-
|
|
1119
|
+
parameters: {
|
|
911
1120
|
type: "object",
|
|
912
1121
|
properties: {
|
|
913
1122
|
text: { type: "string", description: "Text to append" }
|
|
@@ -932,7 +1141,7 @@ var AIToolBuilder = class _AIToolBuilder {
|
|
|
932
1141
|
};
|
|
933
1142
|
tools[`insert_${sanitizedKey}`] = {
|
|
934
1143
|
description: `Insert text at a position in "${key}" document`,
|
|
935
|
-
|
|
1144
|
+
parameters: {
|
|
936
1145
|
type: "object",
|
|
937
1146
|
properties: {
|
|
938
1147
|
index: { type: "number", description: "Position to insert at (0 = start)" },
|
|
@@ -955,7 +1164,7 @@ var AIToolBuilder = class _AIToolBuilder {
|
|
|
955
1164
|
};
|
|
956
1165
|
tools[`edit_${sanitizedKey}`] = {
|
|
957
1166
|
description: `Find and replace text in "${key}" document`,
|
|
958
|
-
|
|
1167
|
+
parameters: {
|
|
959
1168
|
type: "object",
|
|
960
1169
|
properties: {
|
|
961
1170
|
find: { type: "string", description: "Text to find" },
|
|
@@ -992,11 +1201,16 @@ var AIToolBuilder = class _AIToolBuilder {
|
|
|
992
1201
|
return tools;
|
|
993
1202
|
}
|
|
994
1203
|
/**
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1204
|
+
* Generate a system prompt containing all visible STM keys and their values.
|
|
1205
|
+
* Indicates which tools can be used to modify writable keys.
|
|
1206
|
+
*/
|
|
998
1207
|
static getSystemPrompt(mc) {
|
|
999
1208
|
const lines = [];
|
|
1209
|
+
const registeredTypes = mc.getRegisteredTypes();
|
|
1210
|
+
if (registeredTypes.length > 0) {
|
|
1211
|
+
lines.push(`[create_key tool available - registered types: ${registeredTypes.join(", ")}]`);
|
|
1212
|
+
lines.push("");
|
|
1213
|
+
}
|
|
1000
1214
|
for (const key of mc.keys()) {
|
|
1001
1215
|
if (key.startsWith("$")) {
|
|
1002
1216
|
continue;
|
|
@@ -1014,8 +1228,18 @@ var AIToolBuilder = class _AIToolBuilder {
|
|
|
1014
1228
|
const isWritable = attributes?.systemTags?.includes("LLMWrite");
|
|
1015
1229
|
const isDocument = attributes?.type === "document";
|
|
1016
1230
|
const sanitizedKey = _AIToolBuilder.sanitizeKeyForTool(key);
|
|
1231
|
+
const customTypeName = mc.getKeyType(key);
|
|
1232
|
+
const customType = customTypeName ? mc.getTypeSchema(customTypeName) : void 0;
|
|
1017
1233
|
if (isWritable) {
|
|
1018
|
-
if (
|
|
1234
|
+
if (customType) {
|
|
1235
|
+
const schemaInfo = SchemaParser.toMarkdown(customType);
|
|
1236
|
+
lines.push(
|
|
1237
|
+
`${key} (type: ${customTypeName}): ${displayValue}
|
|
1238
|
+
Schema:
|
|
1239
|
+
${schemaInfo}
|
|
1240
|
+
Tool: write_${sanitizedKey}`
|
|
1241
|
+
);
|
|
1242
|
+
} else if (isDocument) {
|
|
1019
1243
|
lines.push(
|
|
1020
1244
|
`${key}: ${displayValue}. Document tools: write_${sanitizedKey}, append_${sanitizedKey}, edit_${sanitizedKey}`
|
|
1021
1245
|
);
|
|
@@ -1026,15 +1250,19 @@ var AIToolBuilder = class _AIToolBuilder {
|
|
|
1026
1250
|
);
|
|
1027
1251
|
}
|
|
1028
1252
|
} else {
|
|
1029
|
-
|
|
1253
|
+
if (customTypeName) {
|
|
1254
|
+
lines.push(`${key} (type: ${customTypeName}): ${displayValue}`);
|
|
1255
|
+
} else {
|
|
1256
|
+
lines.push(`${key}: ${displayValue}`);
|
|
1257
|
+
}
|
|
1030
1258
|
}
|
|
1031
1259
|
}
|
|
1032
1260
|
return lines.join("\n");
|
|
1033
1261
|
}
|
|
1034
1262
|
/**
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1263
|
+
* Execute a tool call by name with the given value.
|
|
1264
|
+
* Returns the result or null if tool not found.
|
|
1265
|
+
*/
|
|
1038
1266
|
static executeToolCall(mc, toolName, value) {
|
|
1039
1267
|
const match = toolName.match(/^(write|append|insert|edit)_(.+)$/);
|
|
1040
1268
|
if (!match) {
|
|
@@ -1361,7 +1589,7 @@ var MindCache = class {
|
|
|
1361
1589
|
listeners = {};
|
|
1362
1590
|
globalListeners = [];
|
|
1363
1591
|
// Metadata
|
|
1364
|
-
version = "3.
|
|
1592
|
+
version = "3.6.0";
|
|
1365
1593
|
// Internal flag to prevent sync loops when receiving remote updates
|
|
1366
1594
|
// (Less critical with Yjs but kept for API compat)
|
|
1367
1595
|
normalizeSystemTags(tags) {
|
|
@@ -1398,6 +1626,8 @@ var MindCache = class {
|
|
|
1398
1626
|
_history = [];
|
|
1399
1627
|
_historyOptions = { maxEntries: 100, snapshotInterval: 10 };
|
|
1400
1628
|
_historyEnabled = false;
|
|
1629
|
+
// Custom type registry
|
|
1630
|
+
_typeRegistry = /* @__PURE__ */ new Map();
|
|
1401
1631
|
constructor(options) {
|
|
1402
1632
|
this.doc = options?.doc || new Y.Doc();
|
|
1403
1633
|
this.rootMap = this.doc.getMap("mindcache");
|
|
@@ -2354,6 +2584,71 @@ var MindCache = class {
|
|
|
2354
2584
|
systemGetKeysByTag(tag) {
|
|
2355
2585
|
return TagManager.systemGetKeysByTag(this, tag);
|
|
2356
2586
|
}
|
|
2587
|
+
// ============================================
|
|
2588
|
+
// Custom Type Methods
|
|
2589
|
+
// ============================================
|
|
2590
|
+
/**
|
|
2591
|
+
* Register a custom type with a markdown schema definition.
|
|
2592
|
+
*
|
|
2593
|
+
* Schema format:
|
|
2594
|
+
* ```
|
|
2595
|
+
* #TypeName
|
|
2596
|
+
* * fieldName: description of the field
|
|
2597
|
+
* * anotherField: description
|
|
2598
|
+
* ```
|
|
2599
|
+
*
|
|
2600
|
+
* @param name - Type name (e.g., 'Contact')
|
|
2601
|
+
* @param schema - Markdown schema definition
|
|
2602
|
+
* @throws Error if schema format is invalid
|
|
2603
|
+
*/
|
|
2604
|
+
registerType(name, schema) {
|
|
2605
|
+
const typeDef = SchemaParser.parse(schema);
|
|
2606
|
+
typeDef.name = name;
|
|
2607
|
+
this._typeRegistry.set(name, typeDef);
|
|
2608
|
+
}
|
|
2609
|
+
/**
|
|
2610
|
+
* Assign a custom type to a key.
|
|
2611
|
+
* The key must exist and the type must be registered.
|
|
2612
|
+
* Also sets the underlying type to 'json' since custom types are structured JSON data.
|
|
2613
|
+
*
|
|
2614
|
+
* @param key - Key to assign type to
|
|
2615
|
+
* @param typeName - Registered type name
|
|
2616
|
+
* @throws Error if key doesn't exist or type is not registered
|
|
2617
|
+
*/
|
|
2618
|
+
setType(key, typeName) {
|
|
2619
|
+
if (!this.rootMap.has(key)) {
|
|
2620
|
+
throw new Error(`Key "${key}" does not exist`);
|
|
2621
|
+
}
|
|
2622
|
+
if (!this._typeRegistry.has(typeName)) {
|
|
2623
|
+
throw new Error(`Type "${typeName}" is not registered. Use registerType() first.`);
|
|
2624
|
+
}
|
|
2625
|
+
this.set_attributes(key, { type: "json", customType: typeName });
|
|
2626
|
+
}
|
|
2627
|
+
/**
|
|
2628
|
+
* Get a registered type schema definition.
|
|
2629
|
+
*
|
|
2630
|
+
* @param typeName - Type name to look up
|
|
2631
|
+
* @returns The type definition or undefined if not registered
|
|
2632
|
+
*/
|
|
2633
|
+
getTypeSchema(typeName) {
|
|
2634
|
+
return this._typeRegistry.get(typeName);
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* Get all registered type names.
|
|
2638
|
+
*/
|
|
2639
|
+
getRegisteredTypes() {
|
|
2640
|
+
return Array.from(this._typeRegistry.keys());
|
|
2641
|
+
}
|
|
2642
|
+
/**
|
|
2643
|
+
* Get the custom type assigned to a key.
|
|
2644
|
+
*
|
|
2645
|
+
* @param key - Key to check
|
|
2646
|
+
* @returns Type name or undefined if no custom type assigned
|
|
2647
|
+
*/
|
|
2648
|
+
getKeyType(key) {
|
|
2649
|
+
const attrs = this.get_attributes(key);
|
|
2650
|
+
return attrs?.customType;
|
|
2651
|
+
}
|
|
2357
2652
|
/**
|
|
2358
2653
|
* Helper to get sorted keys (by zIndex).
|
|
2359
2654
|
* Respects context filtering when set.
|
|
@@ -2615,9 +2910,28 @@ var MindCache = class {
|
|
|
2615
2910
|
findKeyFromSanitizedTool(sanitizedKey) {
|
|
2616
2911
|
return AIToolBuilder.findKeyFromSanitizedTool(this, sanitizedKey);
|
|
2617
2912
|
}
|
|
2913
|
+
/**
|
|
2914
|
+
* Generate framework-agnostic tools with raw JSON Schema.
|
|
2915
|
+
* Works with: OpenAI SDK, Anthropic SDK, LangChain, and other frameworks.
|
|
2916
|
+
*
|
|
2917
|
+
* Tool format:
|
|
2918
|
+
* {
|
|
2919
|
+
* description: string,
|
|
2920
|
+
* parameters: { type: 'object', properties: {...}, required: [...] },
|
|
2921
|
+
* execute: async (args) => result
|
|
2922
|
+
* }
|
|
2923
|
+
*
|
|
2924
|
+
* Security: All tools use llm_set_key internally which:
|
|
2925
|
+
* - Only modifies VALUES, never attributes/systemTags
|
|
2926
|
+
* - Prevents LLMs from escalating privileges
|
|
2927
|
+
*/
|
|
2928
|
+
create_tools() {
|
|
2929
|
+
return AIToolBuilder.createTools(this);
|
|
2930
|
+
}
|
|
2618
2931
|
/**
|
|
2619
2932
|
* Generate Vercel AI SDK compatible tools for writable keys.
|
|
2620
|
-
*
|
|
2933
|
+
* Wraps parameters with jsonSchema() for AI SDK v5 compatibility.
|
|
2934
|
+
* Use this with: generateText(), streamText() from 'ai' package.
|
|
2621
2935
|
*
|
|
2622
2936
|
* Security: All tools use llm_set_key internally which:
|
|
2623
2937
|
* - Only modifies VALUES, never attributes/systemTags
|
|
@@ -2660,8 +2974,6 @@ init_CloudAdapter();
|
|
|
2660
2974
|
init_CloudAdapter();
|
|
2661
2975
|
|
|
2662
2976
|
// src/cloud/OAuthClient.ts
|
|
2663
|
-
var DEFAULT_API_URL = "https://api.mindcache.dev";
|
|
2664
|
-
var DEFAULT_USERINFO_URL = DEFAULT_API_URL + "/oauth/userinfo";
|
|
2665
2977
|
var TOKEN_REFRESH_BUFFER = 5 * 60 * 1e3;
|
|
2666
2978
|
function generateRandomString(length2) {
|
|
2667
2979
|
const array = new Uint8Array(length2);
|
|
@@ -2690,6 +3002,21 @@ var OAuthClient = class {
|
|
|
2690
3002
|
tokens = null;
|
|
2691
3003
|
refreshPromise = null;
|
|
2692
3004
|
constructor(config) {
|
|
3005
|
+
if (!config.baseUrl) {
|
|
3006
|
+
throw new Error(
|
|
3007
|
+
'MindCache OAuth: baseUrl is required!\n For production: baseUrl: "https://api.mindcache.dev"\n For local dev: baseUrl: "http://localhost:8787"'
|
|
3008
|
+
);
|
|
3009
|
+
}
|
|
3010
|
+
try {
|
|
3011
|
+
const url = new URL(config.baseUrl);
|
|
3012
|
+
if (url.hostname === "mindcache.dev") {
|
|
3013
|
+
console.error(
|
|
3014
|
+
'\u274C MindCache OAuth ERROR: baseUrl should be "api.mindcache.dev" not "mindcache.dev"\n Current: ' + config.baseUrl + "\n Correct: https://api.mindcache.dev"
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
3017
|
+
} catch {
|
|
3018
|
+
throw new Error("MindCache OAuth: Invalid baseUrl format: " + config.baseUrl);
|
|
3019
|
+
}
|
|
2693
3020
|
let redirectUri = config.redirectUri;
|
|
2694
3021
|
if (!redirectUri && typeof window !== "undefined") {
|
|
2695
3022
|
const url = new URL(window.location.href);
|
|
@@ -2697,55 +3024,54 @@ var OAuthClient = class {
|
|
|
2697
3024
|
url.hash = "";
|
|
2698
3025
|
redirectUri = url.toString();
|
|
2699
3026
|
}
|
|
2700
|
-
const baseUrl = config.baseUrl
|
|
2701
|
-
const authUrl = config.authUrl || baseUrl + "/oauth/authorize";
|
|
2702
|
-
const tokenUrl = config.tokenUrl || baseUrl + "/oauth/token";
|
|
2703
|
-
const apiUrl = config.apiUrl || baseUrl;
|
|
3027
|
+
const baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
2704
3028
|
this.config = {
|
|
2705
3029
|
clientId: config.clientId,
|
|
3030
|
+
baseUrl,
|
|
2706
3031
|
redirectUri: redirectUri || "",
|
|
2707
3032
|
scopes: config.scopes || ["read", "write"],
|
|
2708
|
-
baseUrl,
|
|
2709
|
-
authUrl,
|
|
2710
|
-
tokenUrl,
|
|
2711
|
-
apiUrl,
|
|
2712
3033
|
usePKCE: config.usePKCE !== false,
|
|
2713
3034
|
// Default true
|
|
2714
3035
|
storagePrefix: config.storagePrefix || "mindcache_oauth"
|
|
2715
3036
|
};
|
|
2716
|
-
console.log("\u{1F510} MindCache OAuth
|
|
3037
|
+
console.log("\u{1F510} MindCache OAuth:", {
|
|
2717
3038
|
baseUrl: this.config.baseUrl,
|
|
2718
|
-
authUrl: this.
|
|
2719
|
-
tokenUrl: this.
|
|
2720
|
-
apiUrl: this.config.apiUrl,
|
|
3039
|
+
authUrl: this.authUrl,
|
|
3040
|
+
tokenUrl: this.tokenUrl,
|
|
2721
3041
|
clientId: this.config.clientId.substring(0, 20) + "..."
|
|
2722
3042
|
});
|
|
2723
3043
|
this.validateApi();
|
|
2724
3044
|
this.loadTokens();
|
|
2725
3045
|
}
|
|
3046
|
+
/** Derived auth URL */
|
|
3047
|
+
get authUrl() {
|
|
3048
|
+
return this.config.baseUrl + "/oauth/authorize";
|
|
3049
|
+
}
|
|
3050
|
+
/** Derived token URL */
|
|
3051
|
+
get tokenUrl() {
|
|
3052
|
+
return this.config.baseUrl + "/oauth/token";
|
|
3053
|
+
}
|
|
3054
|
+
/** Derived userinfo URL */
|
|
3055
|
+
get userinfoUrl() {
|
|
3056
|
+
return this.config.baseUrl + "/oauth/userinfo";
|
|
3057
|
+
}
|
|
2726
3058
|
/**
|
|
2727
|
-
* Validate the API is reachable
|
|
3059
|
+
* Validate the API is reachable
|
|
2728
3060
|
*/
|
|
2729
3061
|
async validateApi() {
|
|
2730
3062
|
try {
|
|
2731
|
-
const response = await fetch(`${this.config.
|
|
3063
|
+
const response = await fetch(`${this.config.baseUrl}/oauth/apps/info`, {
|
|
2732
3064
|
method: "GET",
|
|
2733
3065
|
headers: { "Accept": "application/json" }
|
|
2734
3066
|
});
|
|
2735
3067
|
if (response.status === 404) {
|
|
2736
3068
|
console.error(
|
|
2737
|
-
"\u274C MindCache OAuth ERROR: API not found at " + this.config.
|
|
2738
|
-
);
|
|
2739
|
-
} else if (!response.ok) {
|
|
2740
|
-
console.warn(
|
|
2741
|
-
"\u26A0\uFE0F MindCache OAuth: API responded with status " + response.status + "\n URL: " + this.config.apiUrl
|
|
3069
|
+
"\u274C MindCache OAuth ERROR: API not found at " + this.config.baseUrl + '\n The server returned 404. Common causes:\n - Wrong domain: Use "api.mindcache.dev" not "mindcache.dev"\n - Wrong port: Local dev server is usually on port 8787\n - Server not running: Make sure the MindCache server is started'
|
|
2742
3070
|
);
|
|
2743
|
-
} else {
|
|
2744
|
-
console.log("\u2705 MindCache OAuth: API is reachable at " + this.config.apiUrl);
|
|
2745
3071
|
}
|
|
2746
3072
|
} catch (error) {
|
|
2747
3073
|
console.error(
|
|
2748
|
-
"\u274C MindCache OAuth ERROR: Cannot reach API at " + this.config.
|
|
3074
|
+
"\u274C MindCache OAuth ERROR: Cannot reach API at " + this.config.baseUrl + "\n Error: " + (error instanceof Error ? error.message : String(error))
|
|
2749
3075
|
);
|
|
2750
3076
|
}
|
|
2751
3077
|
}
|
|
@@ -2774,7 +3100,7 @@ var OAuthClient = class {
|
|
|
2774
3100
|
async authorize(options) {
|
|
2775
3101
|
const state = options?.state || generateRandomString(32);
|
|
2776
3102
|
this.setStorage("state", state);
|
|
2777
|
-
const url = new URL(this.
|
|
3103
|
+
const url = new URL(this.authUrl);
|
|
2778
3104
|
url.searchParams.set("response_type", "code");
|
|
2779
3105
|
url.searchParams.set("client_id", this.config.clientId);
|
|
2780
3106
|
url.searchParams.set("redirect_uri", this.config.redirectUri);
|
|
@@ -2837,7 +3163,7 @@ var OAuthClient = class {
|
|
|
2837
3163
|
}
|
|
2838
3164
|
body.code_verifier = codeVerifier;
|
|
2839
3165
|
}
|
|
2840
|
-
const response = await fetch(this.
|
|
3166
|
+
const response = await fetch(this.tokenUrl, {
|
|
2841
3167
|
method: "POST",
|
|
2842
3168
|
headers: {
|
|
2843
3169
|
"Content-Type": "application/json"
|
|
@@ -2889,7 +3215,7 @@ var OAuthClient = class {
|
|
|
2889
3215
|
throw new Error("No refresh token available");
|
|
2890
3216
|
}
|
|
2891
3217
|
try {
|
|
2892
|
-
const response = await fetch(this.
|
|
3218
|
+
const response = await fetch(this.tokenUrl, {
|
|
2893
3219
|
method: "POST",
|
|
2894
3220
|
headers: {
|
|
2895
3221
|
"Content-Type": "application/json"
|
|
@@ -2923,7 +3249,7 @@ var OAuthClient = class {
|
|
|
2923
3249
|
*/
|
|
2924
3250
|
async getUserInfo() {
|
|
2925
3251
|
const token = await this.getAccessToken();
|
|
2926
|
-
const response = await fetch(
|
|
3252
|
+
const response = await fetch(this.userinfoUrl, {
|
|
2927
3253
|
headers: {
|
|
2928
3254
|
Authorization: `Bearer ${token}`
|
|
2929
3255
|
}
|
|
@@ -2945,7 +3271,7 @@ var OAuthClient = class {
|
|
|
2945
3271
|
async logout() {
|
|
2946
3272
|
if (this.tokens?.accessToken) {
|
|
2947
3273
|
try {
|
|
2948
|
-
await fetch(this.
|
|
3274
|
+
await fetch(this.tokenUrl.replace("/token", "/revoke"), {
|
|
2949
3275
|
method: "POST",
|
|
2950
3276
|
headers: {
|
|
2951
3277
|
"Content-Type": "application/json"
|
|
@@ -2968,7 +3294,7 @@ var OAuthClient = class {
|
|
|
2968
3294
|
}
|
|
2969
3295
|
/**
|
|
2970
3296
|
* Token provider for MindCache cloud config
|
|
2971
|
-
* This fetches a WebSocket token
|
|
3297
|
+
* This fetches a WebSocket token using the OAuth access token
|
|
2972
3298
|
* Use this with MindCacheCloudOptions.tokenProvider
|
|
2973
3299
|
*/
|
|
2974
3300
|
tokenProvider = async () => {
|
|
@@ -2977,7 +3303,7 @@ var OAuthClient = class {
|
|
|
2977
3303
|
if (!instanceId) {
|
|
2978
3304
|
throw new Error("No instance ID available. Complete OAuth flow first.");
|
|
2979
3305
|
}
|
|
2980
|
-
const response = await fetch(`${this.config.
|
|
3306
|
+
const response = await fetch(`${this.config.baseUrl}/api/ws-token`, {
|
|
2981
3307
|
method: "POST",
|
|
2982
3308
|
headers: {
|
|
2983
3309
|
"Content-Type": "application/json",
|
|
@@ -2997,7 +3323,6 @@ var OAuthClient = class {
|
|
|
2997
3323
|
};
|
|
2998
3324
|
/**
|
|
2999
3325
|
* Get raw OAuth access token (for API calls, not WebSocket)
|
|
3000
|
-
* Use getAccessToken() for most cases
|
|
3001
3326
|
*/
|
|
3002
3327
|
accessTokenProvider = async () => {
|
|
3003
3328
|
return this.getAccessToken();
|
|
@@ -3082,10 +3407,1017 @@ function useMindCache(options) {
|
|
|
3082
3407
|
error
|
|
3083
3408
|
};
|
|
3084
3409
|
}
|
|
3410
|
+
function createModel(provider, model, apiKey) {
|
|
3411
|
+
switch (provider) {
|
|
3412
|
+
case "openai": {
|
|
3413
|
+
const openai = createOpenAI({ apiKey });
|
|
3414
|
+
return openai(model);
|
|
3415
|
+
}
|
|
3416
|
+
case "anthropic":
|
|
3417
|
+
throw new Error("Anthropic provider not yet implemented. Use modelProvider for custom providers.");
|
|
3418
|
+
default:
|
|
3419
|
+
throw new Error(`Unknown provider: ${provider}. Use modelProvider for custom providers.`);
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
var MindCacheContext = createContext(null);
|
|
3423
|
+
function useMindCacheContext() {
|
|
3424
|
+
const context = useContext(MindCacheContext);
|
|
3425
|
+
if (!context) {
|
|
3426
|
+
throw new Error("useMindCacheContext must be used within a MindCacheProvider");
|
|
3427
|
+
}
|
|
3428
|
+
return context;
|
|
3429
|
+
}
|
|
3430
|
+
function MindCacheProvider({
|
|
3431
|
+
mindcache: mcOptions,
|
|
3432
|
+
sync: syncConfig,
|
|
3433
|
+
ai: aiConfig = {},
|
|
3434
|
+
children
|
|
3435
|
+
}) {
|
|
3436
|
+
const [mindcache2, setMindcache] = useState(null);
|
|
3437
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
3438
|
+
const [error, setError] = useState(null);
|
|
3439
|
+
const [hasApiKey, setHasApiKey] = useState(false);
|
|
3440
|
+
const [lastSyncAt, setLastSyncAt] = useState(null);
|
|
3441
|
+
const [isSyncing, setIsSyncing] = useState(false);
|
|
3442
|
+
const initRef = useRef(false);
|
|
3443
|
+
const resolvedAiConfig = {
|
|
3444
|
+
provider: "openai",
|
|
3445
|
+
model: "gpt-4o",
|
|
3446
|
+
keyStorage: "localStorage",
|
|
3447
|
+
storageKey: "ai_api_key",
|
|
3448
|
+
...aiConfig
|
|
3449
|
+
};
|
|
3450
|
+
useEffect(() => {
|
|
3451
|
+
if (initRef.current) {
|
|
3452
|
+
return;
|
|
3453
|
+
}
|
|
3454
|
+
initRef.current = true;
|
|
3455
|
+
const init = async () => {
|
|
3456
|
+
try {
|
|
3457
|
+
const options = mcOptions || {
|
|
3458
|
+
indexedDB: {
|
|
3459
|
+
dbName: "mindcache_local_first",
|
|
3460
|
+
storeName: "mindcache_store",
|
|
3461
|
+
debounceMs: 1e3
|
|
3462
|
+
}
|
|
3463
|
+
};
|
|
3464
|
+
const mc = new MindCache(options);
|
|
3465
|
+
await mc.waitForSync();
|
|
3466
|
+
setMindcache(mc);
|
|
3467
|
+
setIsLoaded(true);
|
|
3468
|
+
if (resolvedAiConfig.keyStorage === "localStorage" && typeof window !== "undefined") {
|
|
3469
|
+
const stored = localStorage.getItem(resolvedAiConfig.storageKey || "openai_api_key");
|
|
3470
|
+
setHasApiKey(!!stored);
|
|
3471
|
+
} else if (resolvedAiConfig.apiKey) {
|
|
3472
|
+
setHasApiKey(true);
|
|
3473
|
+
}
|
|
3474
|
+
} catch (err) {
|
|
3475
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
3476
|
+
setIsLoaded(true);
|
|
3477
|
+
}
|
|
3478
|
+
};
|
|
3479
|
+
init();
|
|
3480
|
+
return () => {
|
|
3481
|
+
if (mindcache2) {
|
|
3482
|
+
mindcache2.disconnect();
|
|
3483
|
+
}
|
|
3484
|
+
};
|
|
3485
|
+
}, []);
|
|
3486
|
+
const getApiKey = () => {
|
|
3487
|
+
if (resolvedAiConfig.apiKey) {
|
|
3488
|
+
return resolvedAiConfig.apiKey;
|
|
3489
|
+
}
|
|
3490
|
+
if (resolvedAiConfig.keyStorage === "localStorage" && typeof window !== "undefined") {
|
|
3491
|
+
return localStorage.getItem(resolvedAiConfig.storageKey || "openai_api_key");
|
|
3492
|
+
}
|
|
3493
|
+
return null;
|
|
3494
|
+
};
|
|
3495
|
+
const setApiKey = (key) => {
|
|
3496
|
+
if (resolvedAiConfig.keyStorage === "localStorage" && typeof window !== "undefined") {
|
|
3497
|
+
localStorage.setItem(resolvedAiConfig.storageKey || "openai_api_key", key);
|
|
3498
|
+
setHasApiKey(true);
|
|
3499
|
+
}
|
|
3500
|
+
};
|
|
3501
|
+
const getModel = () => {
|
|
3502
|
+
const apiKey = getApiKey();
|
|
3503
|
+
if (!apiKey) {
|
|
3504
|
+
throw new Error("API key not configured. Call setApiKey() first or configure ai.apiKey.");
|
|
3505
|
+
}
|
|
3506
|
+
if (resolvedAiConfig.modelProvider) {
|
|
3507
|
+
return resolvedAiConfig.modelProvider(apiKey);
|
|
3508
|
+
}
|
|
3509
|
+
const provider = resolvedAiConfig.provider || "openai";
|
|
3510
|
+
const model = resolvedAiConfig.model || "gpt-4o";
|
|
3511
|
+
return createModel(provider, model, apiKey);
|
|
3512
|
+
};
|
|
3513
|
+
const syncToGitStore = async () => {
|
|
3514
|
+
if (!mindcache2 || !syncConfig?.gitstore) {
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
setIsSyncing(true);
|
|
3518
|
+
try {
|
|
3519
|
+
let gitStoreModule;
|
|
3520
|
+
try {
|
|
3521
|
+
gitStoreModule = await Function('return import("@mindcache/gitstore")')();
|
|
3522
|
+
} catch {
|
|
3523
|
+
throw new Error("@mindcache/gitstore is not installed. Run: npm install @mindcache/gitstore");
|
|
3524
|
+
}
|
|
3525
|
+
const { GitStore, MindCacheSync } = gitStoreModule;
|
|
3526
|
+
const token = typeof syncConfig.gitstore.token === "function" ? await syncConfig.gitstore.token() : syncConfig.gitstore.token;
|
|
3527
|
+
const gitStore = new GitStore({
|
|
3528
|
+
owner: syncConfig.gitstore.owner,
|
|
3529
|
+
repo: syncConfig.gitstore.repo,
|
|
3530
|
+
tokenProvider: async () => token
|
|
3531
|
+
});
|
|
3532
|
+
const sync = new MindCacheSync(gitStore, mindcache2, {
|
|
3533
|
+
filePath: syncConfig.gitstore.path || "mindcache.md"
|
|
3534
|
+
});
|
|
3535
|
+
await sync.save({ message: "Auto-sync from MindCache" });
|
|
3536
|
+
setLastSyncAt(/* @__PURE__ */ new Date());
|
|
3537
|
+
} catch (err) {
|
|
3538
|
+
console.error("[MindCacheProvider] Sync error:", err);
|
|
3539
|
+
throw err;
|
|
3540
|
+
} finally {
|
|
3541
|
+
setIsSyncing(false);
|
|
3542
|
+
}
|
|
3543
|
+
};
|
|
3544
|
+
const value = {
|
|
3545
|
+
mindcache: mindcache2,
|
|
3546
|
+
isLoaded,
|
|
3547
|
+
error,
|
|
3548
|
+
aiConfig: resolvedAiConfig,
|
|
3549
|
+
syncConfig,
|
|
3550
|
+
getApiKey,
|
|
3551
|
+
setApiKey,
|
|
3552
|
+
hasApiKey,
|
|
3553
|
+
getModel,
|
|
3554
|
+
syncToGitStore,
|
|
3555
|
+
lastSyncAt,
|
|
3556
|
+
isSyncing
|
|
3557
|
+
};
|
|
3558
|
+
return /* @__PURE__ */ jsx(MindCacheContext.Provider, { value, children });
|
|
3559
|
+
}
|
|
3560
|
+
function generateId() {
|
|
3561
|
+
return Math.random().toString(36).substring(2, 15) + Date.now().toString(36);
|
|
3562
|
+
}
|
|
3563
|
+
function useClientChat(options = {}) {
|
|
3564
|
+
const context = useMindCacheContext();
|
|
3565
|
+
const mc = options.mindcache || context.mindcache;
|
|
3566
|
+
const [messages, setMessages] = useState(options.initialMessages || []);
|
|
3567
|
+
const [status, setStatus] = useState("idle");
|
|
3568
|
+
const [error, setError] = useState(null);
|
|
3569
|
+
const [streamingContent, setStreamingContent] = useState("");
|
|
3570
|
+
const abortControllerRef = useRef(null);
|
|
3571
|
+
const {
|
|
3572
|
+
systemPrompt,
|
|
3573
|
+
onMindCacheChange,
|
|
3574
|
+
onFinish,
|
|
3575
|
+
onError,
|
|
3576
|
+
maxToolCalls = 5
|
|
3577
|
+
} = options;
|
|
3578
|
+
const apiKey = context.getApiKey();
|
|
3579
|
+
const addMessage = useCallback((msg) => {
|
|
3580
|
+
const newMessage = {
|
|
3581
|
+
...msg,
|
|
3582
|
+
id: generateId(),
|
|
3583
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
3584
|
+
};
|
|
3585
|
+
setMessages((prev) => [...prev, newMessage]);
|
|
3586
|
+
return newMessage;
|
|
3587
|
+
}, []);
|
|
3588
|
+
const clearMessages = useCallback(() => {
|
|
3589
|
+
setMessages([]);
|
|
3590
|
+
setError(null);
|
|
3591
|
+
setStreamingContent("");
|
|
3592
|
+
}, []);
|
|
3593
|
+
const stop = useCallback(() => {
|
|
3594
|
+
abortControllerRef.current?.abort();
|
|
3595
|
+
setStatus("idle");
|
|
3596
|
+
}, []);
|
|
3597
|
+
const sendMessage = useCallback(async (content) => {
|
|
3598
|
+
if (!mc) {
|
|
3599
|
+
const err = new Error("MindCache not initialized");
|
|
3600
|
+
setError(err);
|
|
3601
|
+
onError?.(err);
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
if (!apiKey) {
|
|
3605
|
+
const err = new Error("API key not configured. Please set your API key.");
|
|
3606
|
+
setError(err);
|
|
3607
|
+
onError?.(err);
|
|
3608
|
+
return;
|
|
3609
|
+
}
|
|
3610
|
+
abortControllerRef.current?.abort();
|
|
3611
|
+
abortControllerRef.current = new AbortController();
|
|
3612
|
+
const userMessage = addMessage({ role: "user", content });
|
|
3613
|
+
setStatus("loading");
|
|
3614
|
+
setError(null);
|
|
3615
|
+
setStreamingContent("");
|
|
3616
|
+
try {
|
|
3617
|
+
const model = context.getModel();
|
|
3618
|
+
const finalSystemPrompt = systemPrompt || mc.get_system_prompt();
|
|
3619
|
+
const tools = mc.create_vercel_ai_tools();
|
|
3620
|
+
const apiMessages = messages.concat(userMessage).map((m) => ({
|
|
3621
|
+
role: m.role,
|
|
3622
|
+
content: m.content
|
|
3623
|
+
}));
|
|
3624
|
+
setStatus("streaming");
|
|
3625
|
+
const parts = [];
|
|
3626
|
+
let accumulatedText = "";
|
|
3627
|
+
const result = await streamText({
|
|
3628
|
+
model,
|
|
3629
|
+
system: finalSystemPrompt,
|
|
3630
|
+
messages: apiMessages,
|
|
3631
|
+
tools,
|
|
3632
|
+
stopWhen: stepCountIs(maxToolCalls),
|
|
3633
|
+
abortSignal: abortControllerRef.current.signal,
|
|
3634
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3635
|
+
onStepFinish: async (step) => {
|
|
3636
|
+
if (step.toolCalls && step.toolCalls.length > 0) {
|
|
3637
|
+
for (const toolCall of step.toolCalls) {
|
|
3638
|
+
const toolName = toolCall.toolName;
|
|
3639
|
+
const args = toolCall.args || toolCall.input || {};
|
|
3640
|
+
parts.push({
|
|
3641
|
+
type: "tool-call",
|
|
3642
|
+
toolCallId: toolCall.toolCallId,
|
|
3643
|
+
toolName,
|
|
3644
|
+
args
|
|
3645
|
+
});
|
|
3646
|
+
if (typeof toolName === "string" && (toolName.startsWith("write_") || toolName === "create_key")) {
|
|
3647
|
+
const value = args.value;
|
|
3648
|
+
const result2 = mc.executeToolCall(toolName, value);
|
|
3649
|
+
parts.push({
|
|
3650
|
+
type: "tool-result",
|
|
3651
|
+
toolCallId: toolCall.toolCallId,
|
|
3652
|
+
toolName,
|
|
3653
|
+
result: result2
|
|
3654
|
+
});
|
|
3655
|
+
onMindCacheChange?.();
|
|
3656
|
+
}
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
if (step.text) {
|
|
3660
|
+
accumulatedText += step.text;
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
});
|
|
3664
|
+
for await (const chunk of result.textStream) {
|
|
3665
|
+
accumulatedText += chunk;
|
|
3666
|
+
setStreamingContent(accumulatedText);
|
|
3667
|
+
}
|
|
3668
|
+
if (accumulatedText) {
|
|
3669
|
+
parts.unshift({ type: "text", text: accumulatedText });
|
|
3670
|
+
}
|
|
3671
|
+
const assistantMessage = {
|
|
3672
|
+
id: generateId(),
|
|
3673
|
+
role: "assistant",
|
|
3674
|
+
content: accumulatedText,
|
|
3675
|
+
parts: parts.length > 0 ? parts : void 0,
|
|
3676
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
3677
|
+
};
|
|
3678
|
+
setMessages((prev) => [...prev, assistantMessage]);
|
|
3679
|
+
setStreamingContent("");
|
|
3680
|
+
setStatus("idle");
|
|
3681
|
+
onFinish?.(assistantMessage);
|
|
3682
|
+
} catch (err) {
|
|
3683
|
+
if (err.name === "AbortError") {
|
|
3684
|
+
if (streamingContent) {
|
|
3685
|
+
const partialMessage = {
|
|
3686
|
+
id: generateId(),
|
|
3687
|
+
role: "assistant",
|
|
3688
|
+
content: streamingContent + " [stopped]",
|
|
3689
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
3690
|
+
};
|
|
3691
|
+
setMessages((prev) => [...prev, partialMessage]);
|
|
3692
|
+
}
|
|
3693
|
+
setStreamingContent("");
|
|
3694
|
+
setStatus("idle");
|
|
3695
|
+
return;
|
|
3696
|
+
}
|
|
3697
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
3698
|
+
setError(error2);
|
|
3699
|
+
setStatus("error");
|
|
3700
|
+
setStreamingContent("");
|
|
3701
|
+
onError?.(error2);
|
|
3702
|
+
}
|
|
3703
|
+
}, [
|
|
3704
|
+
mc,
|
|
3705
|
+
apiKey,
|
|
3706
|
+
context,
|
|
3707
|
+
messages,
|
|
3708
|
+
systemPrompt,
|
|
3709
|
+
maxToolCalls,
|
|
3710
|
+
addMessage,
|
|
3711
|
+
onMindCacheChange,
|
|
3712
|
+
onFinish,
|
|
3713
|
+
onError,
|
|
3714
|
+
streamingContent
|
|
3715
|
+
]);
|
|
3716
|
+
useEffect(() => {
|
|
3717
|
+
return () => {
|
|
3718
|
+
abortControllerRef.current?.abort();
|
|
3719
|
+
};
|
|
3720
|
+
}, []);
|
|
3721
|
+
return {
|
|
3722
|
+
messages,
|
|
3723
|
+
status,
|
|
3724
|
+
error,
|
|
3725
|
+
sendMessage,
|
|
3726
|
+
clearMessages,
|
|
3727
|
+
isLoading: status === "loading" || status === "streaming",
|
|
3728
|
+
addMessage,
|
|
3729
|
+
stop,
|
|
3730
|
+
streamingContent
|
|
3731
|
+
};
|
|
3732
|
+
}
|
|
3733
|
+
var defaultTheme = {
|
|
3734
|
+
background: "#000",
|
|
3735
|
+
userBubble: "#1a1a2e",
|
|
3736
|
+
assistantBubble: "#0d0d0d",
|
|
3737
|
+
textColor: "#22c55e",
|
|
3738
|
+
secondaryTextColor: "#6b7280",
|
|
3739
|
+
borderColor: "#333",
|
|
3740
|
+
primaryColor: "#22c55e",
|
|
3741
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
|
|
3742
|
+
};
|
|
3743
|
+
function DefaultMessage({
|
|
3744
|
+
message,
|
|
3745
|
+
theme
|
|
3746
|
+
}) {
|
|
3747
|
+
const isUser = message.role === "user";
|
|
3748
|
+
return /* @__PURE__ */ jsx(
|
|
3749
|
+
"div",
|
|
3750
|
+
{
|
|
3751
|
+
style: {
|
|
3752
|
+
display: "flex",
|
|
3753
|
+
justifyContent: isUser ? "flex-end" : "flex-start",
|
|
3754
|
+
marginBottom: "12px"
|
|
3755
|
+
},
|
|
3756
|
+
children: /* @__PURE__ */ jsxs(
|
|
3757
|
+
"div",
|
|
3758
|
+
{
|
|
3759
|
+
style: {
|
|
3760
|
+
maxWidth: "80%",
|
|
3761
|
+
padding: "12px 16px",
|
|
3762
|
+
borderRadius: "12px",
|
|
3763
|
+
backgroundColor: isUser ? theme.userBubble : theme.assistantBubble,
|
|
3764
|
+
border: `1px solid ${theme.borderColor}`
|
|
3765
|
+
},
|
|
3766
|
+
children: [
|
|
3767
|
+
/* @__PURE__ */ jsx(
|
|
3768
|
+
"div",
|
|
3769
|
+
{
|
|
3770
|
+
style: {
|
|
3771
|
+
fontSize: "10px",
|
|
3772
|
+
color: theme.secondaryTextColor,
|
|
3773
|
+
marginBottom: "4px",
|
|
3774
|
+
textTransform: "uppercase"
|
|
3775
|
+
},
|
|
3776
|
+
children: isUser ? "You" : "Assistant"
|
|
3777
|
+
}
|
|
3778
|
+
),
|
|
3779
|
+
/* @__PURE__ */ jsx(
|
|
3780
|
+
"div",
|
|
3781
|
+
{
|
|
3782
|
+
style: {
|
|
3783
|
+
color: theme.textColor,
|
|
3784
|
+
whiteSpace: "pre-wrap",
|
|
3785
|
+
wordBreak: "break-word",
|
|
3786
|
+
lineHeight: 1.5
|
|
3787
|
+
},
|
|
3788
|
+
children: message.content
|
|
3789
|
+
}
|
|
3790
|
+
)
|
|
3791
|
+
]
|
|
3792
|
+
}
|
|
3793
|
+
)
|
|
3794
|
+
}
|
|
3795
|
+
);
|
|
3796
|
+
}
|
|
3797
|
+
function ApiKeyInput({
|
|
3798
|
+
theme,
|
|
3799
|
+
onSubmit
|
|
3800
|
+
}) {
|
|
3801
|
+
const [key, setKey] = useState("");
|
|
3802
|
+
const handleSubmit = (e) => {
|
|
3803
|
+
e.preventDefault();
|
|
3804
|
+
if (key.trim()) {
|
|
3805
|
+
onSubmit(key.trim());
|
|
3806
|
+
}
|
|
3807
|
+
};
|
|
3808
|
+
return /* @__PURE__ */ jsxs(
|
|
3809
|
+
"div",
|
|
3810
|
+
{
|
|
3811
|
+
style: {
|
|
3812
|
+
display: "flex",
|
|
3813
|
+
flexDirection: "column",
|
|
3814
|
+
alignItems: "center",
|
|
3815
|
+
justifyContent: "center",
|
|
3816
|
+
height: "100%",
|
|
3817
|
+
padding: "20px"
|
|
3818
|
+
},
|
|
3819
|
+
children: [
|
|
3820
|
+
/* @__PURE__ */ jsx(
|
|
3821
|
+
"div",
|
|
3822
|
+
{
|
|
3823
|
+
style: {
|
|
3824
|
+
fontSize: "14px",
|
|
3825
|
+
color: theme.textColor,
|
|
3826
|
+
marginBottom: "16px",
|
|
3827
|
+
textAlign: "center"
|
|
3828
|
+
},
|
|
3829
|
+
children: "Enter your API key to start chatting"
|
|
3830
|
+
}
|
|
3831
|
+
),
|
|
3832
|
+
/* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, style: { width: "100%", maxWidth: "400px" }, children: [
|
|
3833
|
+
/* @__PURE__ */ jsx(
|
|
3834
|
+
"input",
|
|
3835
|
+
{
|
|
3836
|
+
type: "password",
|
|
3837
|
+
value: key,
|
|
3838
|
+
onChange: (e) => setKey(e.target.value),
|
|
3839
|
+
placeholder: "sk-...",
|
|
3840
|
+
style: {
|
|
3841
|
+
width: "100%",
|
|
3842
|
+
padding: "12px",
|
|
3843
|
+
backgroundColor: theme.assistantBubble,
|
|
3844
|
+
border: `1px solid ${theme.borderColor}`,
|
|
3845
|
+
borderRadius: "8px",
|
|
3846
|
+
color: theme.textColor,
|
|
3847
|
+
fontFamily: theme.fontFamily,
|
|
3848
|
+
fontSize: "14px",
|
|
3849
|
+
marginBottom: "12px"
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
),
|
|
3853
|
+
/* @__PURE__ */ jsx(
|
|
3854
|
+
"button",
|
|
3855
|
+
{
|
|
3856
|
+
type: "submit",
|
|
3857
|
+
disabled: !key.trim(),
|
|
3858
|
+
style: {
|
|
3859
|
+
width: "100%",
|
|
3860
|
+
padding: "12px",
|
|
3861
|
+
backgroundColor: theme.primaryColor,
|
|
3862
|
+
border: "none",
|
|
3863
|
+
borderRadius: "8px",
|
|
3864
|
+
color: "#000",
|
|
3865
|
+
fontFamily: theme.fontFamily,
|
|
3866
|
+
fontSize: "14px",
|
|
3867
|
+
fontWeight: "bold",
|
|
3868
|
+
cursor: key.trim() ? "pointer" : "not-allowed",
|
|
3869
|
+
opacity: key.trim() ? 1 : 0.5
|
|
3870
|
+
},
|
|
3871
|
+
children: "Save & Start"
|
|
3872
|
+
}
|
|
3873
|
+
)
|
|
3874
|
+
] }),
|
|
3875
|
+
/* @__PURE__ */ jsx(
|
|
3876
|
+
"div",
|
|
3877
|
+
{
|
|
3878
|
+
style: {
|
|
3879
|
+
fontSize: "11px",
|
|
3880
|
+
color: theme.secondaryTextColor,
|
|
3881
|
+
marginTop: "16px",
|
|
3882
|
+
textAlign: "center"
|
|
3883
|
+
},
|
|
3884
|
+
children: "Your key is stored locally and never sent to our servers."
|
|
3885
|
+
}
|
|
3886
|
+
)
|
|
3887
|
+
]
|
|
3888
|
+
}
|
|
3889
|
+
);
|
|
3890
|
+
}
|
|
3891
|
+
function MindCacheChat({
|
|
3892
|
+
theme: customTheme,
|
|
3893
|
+
placeholder = "Type a message...",
|
|
3894
|
+
welcomeMessage = "Hello! I'm ready to help you.",
|
|
3895
|
+
showApiKeyInput = true,
|
|
3896
|
+
className,
|
|
3897
|
+
style,
|
|
3898
|
+
renderMessage,
|
|
3899
|
+
header,
|
|
3900
|
+
footer,
|
|
3901
|
+
initialMessages,
|
|
3902
|
+
...chatOptions
|
|
3903
|
+
}) {
|
|
3904
|
+
const context = useMindCacheContext();
|
|
3905
|
+
const theme = { ...defaultTheme, ...customTheme };
|
|
3906
|
+
const messagesEndRef = useRef(null);
|
|
3907
|
+
const inputRef = useRef(null);
|
|
3908
|
+
const [inputValue, setInputValue] = useState("");
|
|
3909
|
+
const defaultInitialMessages = welcomeMessage ? [
|
|
3910
|
+
{
|
|
3911
|
+
id: "welcome",
|
|
3912
|
+
role: "assistant",
|
|
3913
|
+
content: welcomeMessage,
|
|
3914
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
3915
|
+
}
|
|
3916
|
+
] : [];
|
|
3917
|
+
const {
|
|
3918
|
+
messages,
|
|
3919
|
+
sendMessage,
|
|
3920
|
+
isLoading,
|
|
3921
|
+
error,
|
|
3922
|
+
streamingContent,
|
|
3923
|
+
stop
|
|
3924
|
+
} = useClientChat({
|
|
3925
|
+
...chatOptions,
|
|
3926
|
+
initialMessages: initialMessages || defaultInitialMessages,
|
|
3927
|
+
mindcache: context.mindcache || void 0
|
|
3928
|
+
});
|
|
3929
|
+
useEffect(() => {
|
|
3930
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
3931
|
+
}, [messages]);
|
|
3932
|
+
const handleSubmit = async (e) => {
|
|
3933
|
+
e?.preventDefault();
|
|
3934
|
+
if (!inputValue.trim() || isLoading) {
|
|
3935
|
+
return;
|
|
3936
|
+
}
|
|
3937
|
+
const message = inputValue.trim();
|
|
3938
|
+
setInputValue("");
|
|
3939
|
+
await sendMessage(message);
|
|
3940
|
+
};
|
|
3941
|
+
const handleKeyDown = (e) => {
|
|
3942
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
3943
|
+
e.preventDefault();
|
|
3944
|
+
handleSubmit();
|
|
3945
|
+
}
|
|
3946
|
+
};
|
|
3947
|
+
if (showApiKeyInput && !context.hasApiKey && context.aiConfig.keyStorage !== "memory") {
|
|
3948
|
+
return /* @__PURE__ */ jsxs(
|
|
3949
|
+
"div",
|
|
3950
|
+
{
|
|
3951
|
+
className,
|
|
3952
|
+
style: {
|
|
3953
|
+
display: "flex",
|
|
3954
|
+
flexDirection: "column",
|
|
3955
|
+
height: "100%",
|
|
3956
|
+
backgroundColor: theme.background,
|
|
3957
|
+
fontFamily: theme.fontFamily,
|
|
3958
|
+
...style
|
|
3959
|
+
},
|
|
3960
|
+
children: [
|
|
3961
|
+
header,
|
|
3962
|
+
/* @__PURE__ */ jsx(
|
|
3963
|
+
ApiKeyInput,
|
|
3964
|
+
{
|
|
3965
|
+
theme,
|
|
3966
|
+
onSubmit: (key) => context.setApiKey(key)
|
|
3967
|
+
}
|
|
3968
|
+
),
|
|
3969
|
+
footer
|
|
3970
|
+
]
|
|
3971
|
+
}
|
|
3972
|
+
);
|
|
3973
|
+
}
|
|
3974
|
+
if (!context.isLoaded) {
|
|
3975
|
+
return /* @__PURE__ */ jsx(
|
|
3976
|
+
"div",
|
|
3977
|
+
{
|
|
3978
|
+
className,
|
|
3979
|
+
style: {
|
|
3980
|
+
display: "flex",
|
|
3981
|
+
alignItems: "center",
|
|
3982
|
+
justifyContent: "center",
|
|
3983
|
+
height: "100%",
|
|
3984
|
+
backgroundColor: theme.background,
|
|
3985
|
+
color: theme.textColor,
|
|
3986
|
+
fontFamily: theme.fontFamily,
|
|
3987
|
+
...style
|
|
3988
|
+
},
|
|
3989
|
+
children: "Loading..."
|
|
3990
|
+
}
|
|
3991
|
+
);
|
|
3992
|
+
}
|
|
3993
|
+
return /* @__PURE__ */ jsxs(
|
|
3994
|
+
"div",
|
|
3995
|
+
{
|
|
3996
|
+
className,
|
|
3997
|
+
style: {
|
|
3998
|
+
display: "flex",
|
|
3999
|
+
flexDirection: "column",
|
|
4000
|
+
height: "100%",
|
|
4001
|
+
backgroundColor: theme.background,
|
|
4002
|
+
fontFamily: theme.fontFamily,
|
|
4003
|
+
...style
|
|
4004
|
+
},
|
|
4005
|
+
children: [
|
|
4006
|
+
header,
|
|
4007
|
+
/* @__PURE__ */ jsxs(
|
|
4008
|
+
"div",
|
|
4009
|
+
{
|
|
4010
|
+
style: {
|
|
4011
|
+
flex: 1,
|
|
4012
|
+
overflowY: "auto",
|
|
4013
|
+
padding: "16px"
|
|
4014
|
+
},
|
|
4015
|
+
children: [
|
|
4016
|
+
messages.map((message) => renderMessage ? /* @__PURE__ */ jsx(React2.Fragment, { children: renderMessage(message) }, message.id) : /* @__PURE__ */ jsx(DefaultMessage, { message, theme }, message.id)),
|
|
4017
|
+
streamingContent && /* @__PURE__ */ jsx(
|
|
4018
|
+
"div",
|
|
4019
|
+
{
|
|
4020
|
+
style: {
|
|
4021
|
+
display: "flex",
|
|
4022
|
+
justifyContent: "flex-start",
|
|
4023
|
+
marginBottom: "12px"
|
|
4024
|
+
},
|
|
4025
|
+
children: /* @__PURE__ */ jsxs(
|
|
4026
|
+
"div",
|
|
4027
|
+
{
|
|
4028
|
+
style: {
|
|
4029
|
+
maxWidth: "80%",
|
|
4030
|
+
padding: "12px 16px",
|
|
4031
|
+
borderRadius: "12px",
|
|
4032
|
+
backgroundColor: theme.assistantBubble,
|
|
4033
|
+
border: `1px solid ${theme.borderColor}`
|
|
4034
|
+
},
|
|
4035
|
+
children: [
|
|
4036
|
+
/* @__PURE__ */ jsx(
|
|
4037
|
+
"div",
|
|
4038
|
+
{
|
|
4039
|
+
style: {
|
|
4040
|
+
fontSize: "10px",
|
|
4041
|
+
color: theme.secondaryTextColor,
|
|
4042
|
+
marginBottom: "4px",
|
|
4043
|
+
textTransform: "uppercase"
|
|
4044
|
+
},
|
|
4045
|
+
children: "Assistant"
|
|
4046
|
+
}
|
|
4047
|
+
),
|
|
4048
|
+
/* @__PURE__ */ jsxs(
|
|
4049
|
+
"div",
|
|
4050
|
+
{
|
|
4051
|
+
style: {
|
|
4052
|
+
color: theme.textColor,
|
|
4053
|
+
whiteSpace: "pre-wrap",
|
|
4054
|
+
wordBreak: "break-word",
|
|
4055
|
+
lineHeight: 1.5
|
|
4056
|
+
},
|
|
4057
|
+
children: [
|
|
4058
|
+
streamingContent,
|
|
4059
|
+
/* @__PURE__ */ jsx("span", { style: { opacity: 0.5, animation: "blink 1s infinite" }, children: "\u258A" })
|
|
4060
|
+
]
|
|
4061
|
+
}
|
|
4062
|
+
)
|
|
4063
|
+
]
|
|
4064
|
+
}
|
|
4065
|
+
)
|
|
4066
|
+
}
|
|
4067
|
+
),
|
|
4068
|
+
isLoading && !streamingContent && /* @__PURE__ */ jsx(
|
|
4069
|
+
"div",
|
|
4070
|
+
{
|
|
4071
|
+
style: {
|
|
4072
|
+
display: "flex",
|
|
4073
|
+
justifyContent: "flex-start",
|
|
4074
|
+
marginBottom: "12px"
|
|
4075
|
+
},
|
|
4076
|
+
children: /* @__PURE__ */ jsx(
|
|
4077
|
+
"div",
|
|
4078
|
+
{
|
|
4079
|
+
style: {
|
|
4080
|
+
padding: "12px 16px",
|
|
4081
|
+
borderRadius: "12px",
|
|
4082
|
+
backgroundColor: theme.assistantBubble,
|
|
4083
|
+
border: `1px solid ${theme.borderColor}`,
|
|
4084
|
+
color: theme.secondaryTextColor
|
|
4085
|
+
},
|
|
4086
|
+
children: "Thinking..."
|
|
4087
|
+
}
|
|
4088
|
+
)
|
|
4089
|
+
}
|
|
4090
|
+
),
|
|
4091
|
+
error && /* @__PURE__ */ jsxs(
|
|
4092
|
+
"div",
|
|
4093
|
+
{
|
|
4094
|
+
style: {
|
|
4095
|
+
padding: "12px",
|
|
4096
|
+
marginBottom: "12px",
|
|
4097
|
+
borderRadius: "8px",
|
|
4098
|
+
backgroundColor: "rgba(239, 68, 68, 0.1)",
|
|
4099
|
+
border: "1px solid rgba(239, 68, 68, 0.3)",
|
|
4100
|
+
color: "#ef4444",
|
|
4101
|
+
fontSize: "13px"
|
|
4102
|
+
},
|
|
4103
|
+
children: [
|
|
4104
|
+
"Error: ",
|
|
4105
|
+
error.message
|
|
4106
|
+
]
|
|
4107
|
+
}
|
|
4108
|
+
),
|
|
4109
|
+
/* @__PURE__ */ jsx("div", { ref: messagesEndRef })
|
|
4110
|
+
]
|
|
4111
|
+
}
|
|
4112
|
+
),
|
|
4113
|
+
/* @__PURE__ */ jsx(
|
|
4114
|
+
"form",
|
|
4115
|
+
{
|
|
4116
|
+
onSubmit: handleSubmit,
|
|
4117
|
+
style: {
|
|
4118
|
+
padding: "12px 16px",
|
|
4119
|
+
borderTop: `1px solid ${theme.borderColor}`
|
|
4120
|
+
},
|
|
4121
|
+
children: /* @__PURE__ */ jsxs(
|
|
4122
|
+
"div",
|
|
4123
|
+
{
|
|
4124
|
+
style: {
|
|
4125
|
+
display: "flex",
|
|
4126
|
+
gap: "8px",
|
|
4127
|
+
alignItems: "flex-end"
|
|
4128
|
+
},
|
|
4129
|
+
children: [
|
|
4130
|
+
/* @__PURE__ */ jsx(
|
|
4131
|
+
"textarea",
|
|
4132
|
+
{
|
|
4133
|
+
ref: inputRef,
|
|
4134
|
+
value: inputValue,
|
|
4135
|
+
onChange: (e) => {
|
|
4136
|
+
setInputValue(e.target.value);
|
|
4137
|
+
e.target.style.height = "auto";
|
|
4138
|
+
e.target.style.height = Math.min(e.target.scrollHeight, 120) + "px";
|
|
4139
|
+
},
|
|
4140
|
+
onKeyDown: handleKeyDown,
|
|
4141
|
+
placeholder: isLoading ? "Waiting for response..." : placeholder,
|
|
4142
|
+
disabled: isLoading,
|
|
4143
|
+
rows: 1,
|
|
4144
|
+
style: {
|
|
4145
|
+
flex: 1,
|
|
4146
|
+
padding: "12px",
|
|
4147
|
+
backgroundColor: theme.assistantBubble,
|
|
4148
|
+
border: `1px solid ${theme.borderColor}`,
|
|
4149
|
+
borderRadius: "8px",
|
|
4150
|
+
color: theme.textColor,
|
|
4151
|
+
fontFamily: theme.fontFamily,
|
|
4152
|
+
fontSize: "16px",
|
|
4153
|
+
// Prevents iOS zoom on focus
|
|
4154
|
+
resize: "none",
|
|
4155
|
+
minHeight: "44px",
|
|
4156
|
+
// Apple touch target minimum
|
|
4157
|
+
maxHeight: "120px",
|
|
4158
|
+
outline: "none",
|
|
4159
|
+
WebkitAppearance: "none"
|
|
4160
|
+
// Remove iOS styling
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
),
|
|
4164
|
+
isLoading ? /* @__PURE__ */ jsx(
|
|
4165
|
+
"button",
|
|
4166
|
+
{
|
|
4167
|
+
type: "button",
|
|
4168
|
+
onClick: stop,
|
|
4169
|
+
style: {
|
|
4170
|
+
padding: "12px 20px",
|
|
4171
|
+
minWidth: "44px",
|
|
4172
|
+
// Touch target
|
|
4173
|
+
minHeight: "44px",
|
|
4174
|
+
// Touch target
|
|
4175
|
+
backgroundColor: "#ef4444",
|
|
4176
|
+
border: "none",
|
|
4177
|
+
borderRadius: "8px",
|
|
4178
|
+
color: "#fff",
|
|
4179
|
+
fontFamily: theme.fontFamily,
|
|
4180
|
+
fontSize: "16px",
|
|
4181
|
+
fontWeight: "bold",
|
|
4182
|
+
cursor: "pointer",
|
|
4183
|
+
transition: "opacity 0.2s",
|
|
4184
|
+
WebkitTapHighlightColor: "transparent",
|
|
4185
|
+
touchAction: "manipulation"
|
|
4186
|
+
// Faster touch response
|
|
4187
|
+
},
|
|
4188
|
+
children: "Stop"
|
|
4189
|
+
}
|
|
4190
|
+
) : /* @__PURE__ */ jsx(
|
|
4191
|
+
"button",
|
|
4192
|
+
{
|
|
4193
|
+
type: "submit",
|
|
4194
|
+
disabled: !inputValue.trim(),
|
|
4195
|
+
style: {
|
|
4196
|
+
padding: "12px 20px",
|
|
4197
|
+
minWidth: "44px",
|
|
4198
|
+
// Touch target
|
|
4199
|
+
minHeight: "44px",
|
|
4200
|
+
// Touch target
|
|
4201
|
+
backgroundColor: theme.primaryColor,
|
|
4202
|
+
border: "none",
|
|
4203
|
+
borderRadius: "8px",
|
|
4204
|
+
color: "#000",
|
|
4205
|
+
fontFamily: theme.fontFamily,
|
|
4206
|
+
fontSize: "16px",
|
|
4207
|
+
fontWeight: "bold",
|
|
4208
|
+
cursor: !inputValue.trim() ? "not-allowed" : "pointer",
|
|
4209
|
+
opacity: !inputValue.trim() ? 0.5 : 1,
|
|
4210
|
+
transition: "opacity 0.2s",
|
|
4211
|
+
WebkitTapHighlightColor: "transparent",
|
|
4212
|
+
touchAction: "manipulation"
|
|
4213
|
+
// Faster touch response
|
|
4214
|
+
},
|
|
4215
|
+
children: "Send"
|
|
4216
|
+
}
|
|
4217
|
+
)
|
|
4218
|
+
]
|
|
4219
|
+
}
|
|
4220
|
+
)
|
|
4221
|
+
}
|
|
4222
|
+
),
|
|
4223
|
+
footer
|
|
4224
|
+
]
|
|
4225
|
+
}
|
|
4226
|
+
);
|
|
4227
|
+
}
|
|
4228
|
+
function useLocalFirstSync(options) {
|
|
4229
|
+
const {
|
|
4230
|
+
mindcache: mindcache2,
|
|
4231
|
+
gitstore,
|
|
4232
|
+
server,
|
|
4233
|
+
autoSyncInterval = 0,
|
|
4234
|
+
saveDebounceMs = 5e3,
|
|
4235
|
+
loadOnMount = true,
|
|
4236
|
+
mergeOnLoad = true
|
|
4237
|
+
} = options;
|
|
4238
|
+
const [status, setStatus] = useState("idle");
|
|
4239
|
+
const [error, setError] = useState(null);
|
|
4240
|
+
const [lastSyncAt, setLastSyncAt] = useState(null);
|
|
4241
|
+
const [hasLocalChanges, setHasLocalChanges] = useState(false);
|
|
4242
|
+
const saveTimeoutRef = useRef(null);
|
|
4243
|
+
const syncIntervalRef = useRef(null);
|
|
4244
|
+
const mountedRef = useRef(true);
|
|
4245
|
+
const getToken = useCallback(async () => {
|
|
4246
|
+
if (!gitstore) {
|
|
4247
|
+
throw new Error("GitStore not configured");
|
|
4248
|
+
}
|
|
4249
|
+
return typeof gitstore.token === "function" ? await gitstore.token() : gitstore.token;
|
|
4250
|
+
}, [gitstore]);
|
|
4251
|
+
const load = useCallback(async () => {
|
|
4252
|
+
if (!mindcache2) {
|
|
4253
|
+
return;
|
|
4254
|
+
}
|
|
4255
|
+
setStatus("loading");
|
|
4256
|
+
setError(null);
|
|
4257
|
+
try {
|
|
4258
|
+
if (gitstore) {
|
|
4259
|
+
let gitStoreModule;
|
|
4260
|
+
try {
|
|
4261
|
+
gitStoreModule = await Function('return import("@mindcache/gitstore")')();
|
|
4262
|
+
} catch {
|
|
4263
|
+
throw new Error("@mindcache/gitstore is not installed. Run: npm install @mindcache/gitstore");
|
|
4264
|
+
}
|
|
4265
|
+
const { GitStore, MindCacheSync } = gitStoreModule;
|
|
4266
|
+
const token = await getToken();
|
|
4267
|
+
const store = new GitStore({
|
|
4268
|
+
owner: gitstore.owner,
|
|
4269
|
+
repo: gitstore.repo,
|
|
4270
|
+
branch: gitstore.branch,
|
|
4271
|
+
tokenProvider: async () => token
|
|
4272
|
+
});
|
|
4273
|
+
const sync2 = new MindCacheSync(store, mindcache2, {
|
|
4274
|
+
filePath: gitstore.path || "mindcache.md"
|
|
4275
|
+
});
|
|
4276
|
+
await sync2.load({ merge: mergeOnLoad });
|
|
4277
|
+
} else if (server) {
|
|
4278
|
+
const response = await fetch(server.url, {
|
|
4279
|
+
headers: server.authToken ? { Authorization: `Bearer ${server.authToken}` } : {}
|
|
4280
|
+
});
|
|
4281
|
+
if (response.ok) {
|
|
4282
|
+
const markdown = await response.text();
|
|
4283
|
+
mindcache2.fromMarkdown(markdown, mergeOnLoad);
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
if (mountedRef.current) {
|
|
4287
|
+
setLastSyncAt(/* @__PURE__ */ new Date());
|
|
4288
|
+
setStatus("idle");
|
|
4289
|
+
}
|
|
4290
|
+
} catch (err) {
|
|
4291
|
+
if (mountedRef.current) {
|
|
4292
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
4293
|
+
setError(error2);
|
|
4294
|
+
setStatus("error");
|
|
4295
|
+
}
|
|
4296
|
+
throw err;
|
|
4297
|
+
}
|
|
4298
|
+
}, [mindcache2, gitstore, server, getToken, mergeOnLoad]);
|
|
4299
|
+
const save = useCallback(async (message) => {
|
|
4300
|
+
if (!mindcache2) {
|
|
4301
|
+
return;
|
|
4302
|
+
}
|
|
4303
|
+
setStatus("saving");
|
|
4304
|
+
setError(null);
|
|
4305
|
+
try {
|
|
4306
|
+
if (gitstore) {
|
|
4307
|
+
let gitStoreModule;
|
|
4308
|
+
try {
|
|
4309
|
+
gitStoreModule = await Function('return import("@mindcache/gitstore")')();
|
|
4310
|
+
} catch {
|
|
4311
|
+
throw new Error("@mindcache/gitstore is not installed. Run: npm install @mindcache/gitstore");
|
|
4312
|
+
}
|
|
4313
|
+
const { GitStore, MindCacheSync } = gitStoreModule;
|
|
4314
|
+
const token = await getToken();
|
|
4315
|
+
const store = new GitStore({
|
|
4316
|
+
owner: gitstore.owner,
|
|
4317
|
+
repo: gitstore.repo,
|
|
4318
|
+
branch: gitstore.branch,
|
|
4319
|
+
tokenProvider: async () => token
|
|
4320
|
+
});
|
|
4321
|
+
const sync2 = new MindCacheSync(store, mindcache2, {
|
|
4322
|
+
filePath: gitstore.path || "mindcache.md"
|
|
4323
|
+
});
|
|
4324
|
+
await sync2.save({ message: message || "MindCache sync" });
|
|
4325
|
+
} else if (server) {
|
|
4326
|
+
await fetch(server.url, {
|
|
4327
|
+
method: "POST",
|
|
4328
|
+
headers: {
|
|
4329
|
+
"Content-Type": "text/plain",
|
|
4330
|
+
...server.authToken ? { Authorization: `Bearer ${server.authToken}` } : {}
|
|
4331
|
+
},
|
|
4332
|
+
body: mindcache2.toMarkdown()
|
|
4333
|
+
});
|
|
4334
|
+
}
|
|
4335
|
+
if (mountedRef.current) {
|
|
4336
|
+
setLastSyncAt(/* @__PURE__ */ new Date());
|
|
4337
|
+
setHasLocalChanges(false);
|
|
4338
|
+
setStatus("idle");
|
|
4339
|
+
}
|
|
4340
|
+
} catch (err) {
|
|
4341
|
+
if (mountedRef.current) {
|
|
4342
|
+
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
4343
|
+
setError(error2);
|
|
4344
|
+
setStatus("error");
|
|
4345
|
+
}
|
|
4346
|
+
throw err;
|
|
4347
|
+
}
|
|
4348
|
+
}, [mindcache2, gitstore, server, getToken]);
|
|
4349
|
+
const sync = useCallback(async () => {
|
|
4350
|
+
setStatus("syncing");
|
|
4351
|
+
try {
|
|
4352
|
+
await load();
|
|
4353
|
+
if (hasLocalChanges) {
|
|
4354
|
+
await save();
|
|
4355
|
+
}
|
|
4356
|
+
} catch (err) {
|
|
4357
|
+
}
|
|
4358
|
+
}, [load, save, hasLocalChanges]);
|
|
4359
|
+
const markSaved = useCallback(() => {
|
|
4360
|
+
setHasLocalChanges(false);
|
|
4361
|
+
}, []);
|
|
4362
|
+
useEffect(() => {
|
|
4363
|
+
if (!mindcache2 || !gitstore || saveDebounceMs <= 0) {
|
|
4364
|
+
return;
|
|
4365
|
+
}
|
|
4366
|
+
const unsubscribe = mindcache2.subscribeToAll(() => {
|
|
4367
|
+
setHasLocalChanges(true);
|
|
4368
|
+
if (saveTimeoutRef.current) {
|
|
4369
|
+
clearTimeout(saveTimeoutRef.current);
|
|
4370
|
+
}
|
|
4371
|
+
saveTimeoutRef.current = setTimeout(() => {
|
|
4372
|
+
save("Auto-save").catch(console.error);
|
|
4373
|
+
}, saveDebounceMs);
|
|
4374
|
+
});
|
|
4375
|
+
return () => {
|
|
4376
|
+
unsubscribe();
|
|
4377
|
+
if (saveTimeoutRef.current) {
|
|
4378
|
+
clearTimeout(saveTimeoutRef.current);
|
|
4379
|
+
}
|
|
4380
|
+
};
|
|
4381
|
+
}, [mindcache2, gitstore, saveDebounceMs, save]);
|
|
4382
|
+
useEffect(() => {
|
|
4383
|
+
if (!mindcache2 || autoSyncInterval <= 0) {
|
|
4384
|
+
return;
|
|
4385
|
+
}
|
|
4386
|
+
syncIntervalRef.current = setInterval(() => {
|
|
4387
|
+
sync().catch(console.error);
|
|
4388
|
+
}, autoSyncInterval);
|
|
4389
|
+
return () => {
|
|
4390
|
+
if (syncIntervalRef.current) {
|
|
4391
|
+
clearInterval(syncIntervalRef.current);
|
|
4392
|
+
}
|
|
4393
|
+
};
|
|
4394
|
+
}, [mindcache2, autoSyncInterval, sync]);
|
|
4395
|
+
useEffect(() => {
|
|
4396
|
+
if (loadOnMount && mindcache2 && (gitstore || server)) {
|
|
4397
|
+
load().catch(console.error);
|
|
4398
|
+
}
|
|
4399
|
+
}, [mindcache2, gitstore, server, loadOnMount]);
|
|
4400
|
+
useEffect(() => {
|
|
4401
|
+
mountedRef.current = true;
|
|
4402
|
+
return () => {
|
|
4403
|
+
mountedRef.current = false;
|
|
4404
|
+
};
|
|
4405
|
+
}, []);
|
|
4406
|
+
return {
|
|
4407
|
+
status,
|
|
4408
|
+
error,
|
|
4409
|
+
lastSyncAt,
|
|
4410
|
+
hasLocalChanges,
|
|
4411
|
+
load,
|
|
4412
|
+
save,
|
|
4413
|
+
sync,
|
|
4414
|
+
markSaved
|
|
4415
|
+
};
|
|
4416
|
+
}
|
|
3085
4417
|
|
|
3086
4418
|
// src/index.ts
|
|
3087
4419
|
var mindcache = new MindCache();
|
|
3088
4420
|
|
|
3089
|
-
export { CloudAdapter, DEFAULT_KEY_ATTRIBUTES, IndexedDBAdapter, MindCache, OAuthClient, SystemTagHelpers, createOAuthClient, mindcache, useMindCache };
|
|
4421
|
+
export { CloudAdapter, DEFAULT_KEY_ATTRIBUTES, IndexedDBAdapter, MindCache, MindCacheChat, MindCacheProvider, OAuthClient, SchemaParser, SystemTagHelpers, createOAuthClient, mindcache, useClientChat, useLocalFirstSync, useMindCache, useMindCacheContext };
|
|
3090
4422
|
//# sourceMappingURL=index.mjs.map
|
|
3091
4423
|
//# sourceMappingURL=index.mjs.map
|