mindcache 3.6.0 → 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/dist/index.js CHANGED
@@ -8,7 +8,9 @@ var yIndexeddb = require('y-indexeddb');
8
8
  var diff = require('fast-diff');
9
9
  var ai = require('ai');
10
10
  var zod = require('zod');
11
- var react = require('react');
11
+ var React2 = require('react');
12
+ var openai = require('@ai-sdk/openai');
13
+ var jsxRuntime = require('react/jsx-runtime');
12
14
 
13
15
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
16
 
@@ -35,6 +37,7 @@ var encoding__namespace = /*#__PURE__*/_interopNamespace(encoding);
35
37
  var decoding__namespace = /*#__PURE__*/_interopNamespace(decoding);
36
38
  var Y__namespace = /*#__PURE__*/_interopNamespace(Y);
37
39
  var diff__default = /*#__PURE__*/_interopDefault(diff);
40
+ var React2__default = /*#__PURE__*/_interopDefault(React2);
38
41
 
39
42
  var __defProp = Object.defineProperty;
40
43
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -3400,11 +3403,11 @@ function createOAuthClient(config) {
3400
3403
  // src/local/index.ts
3401
3404
  init_IndexedDBAdapter();
3402
3405
  function useMindCache(options) {
3403
- const [isLoaded, setIsLoaded] = react.useState(false);
3404
- const [error, setError] = react.useState(null);
3405
- const mindcacheRef = react.useRef(null);
3406
- const initializingRef = react.useRef(false);
3407
- react.useEffect(() => {
3406
+ const [isLoaded, setIsLoaded] = React2.useState(false);
3407
+ const [error, setError] = React2.useState(null);
3408
+ const mindcacheRef = React2.useRef(null);
3409
+ const initializingRef = React2.useRef(false);
3410
+ React2.useEffect(() => {
3408
3411
  if (initializingRef.current) {
3409
3412
  return;
3410
3413
  }
@@ -3433,17 +3436,1029 @@ function useMindCache(options) {
3433
3436
  error
3434
3437
  };
3435
3438
  }
3439
+ function createModel(provider, model, apiKey) {
3440
+ switch (provider) {
3441
+ case "openai": {
3442
+ const openai$1 = openai.createOpenAI({ apiKey });
3443
+ return openai$1(model);
3444
+ }
3445
+ case "anthropic":
3446
+ throw new Error("Anthropic provider not yet implemented. Use modelProvider for custom providers.");
3447
+ default:
3448
+ throw new Error(`Unknown provider: ${provider}. Use modelProvider for custom providers.`);
3449
+ }
3450
+ }
3451
+ var MindCacheContext = React2.createContext(null);
3452
+ function useMindCacheContext() {
3453
+ const context = React2.useContext(MindCacheContext);
3454
+ if (!context) {
3455
+ throw new Error("useMindCacheContext must be used within a MindCacheProvider");
3456
+ }
3457
+ return context;
3458
+ }
3459
+ function MindCacheProvider({
3460
+ mindcache: mcOptions,
3461
+ sync: syncConfig,
3462
+ ai: aiConfig = {},
3463
+ children
3464
+ }) {
3465
+ const [mindcache2, setMindcache] = React2.useState(null);
3466
+ const [isLoaded, setIsLoaded] = React2.useState(false);
3467
+ const [error, setError] = React2.useState(null);
3468
+ const [hasApiKey, setHasApiKey] = React2.useState(false);
3469
+ const [lastSyncAt, setLastSyncAt] = React2.useState(null);
3470
+ const [isSyncing, setIsSyncing] = React2.useState(false);
3471
+ const initRef = React2.useRef(false);
3472
+ const resolvedAiConfig = {
3473
+ provider: "openai",
3474
+ model: "gpt-4o",
3475
+ keyStorage: "localStorage",
3476
+ storageKey: "ai_api_key",
3477
+ ...aiConfig
3478
+ };
3479
+ React2.useEffect(() => {
3480
+ if (initRef.current) {
3481
+ return;
3482
+ }
3483
+ initRef.current = true;
3484
+ const init = async () => {
3485
+ try {
3486
+ const options = mcOptions || {
3487
+ indexedDB: {
3488
+ dbName: "mindcache_local_first",
3489
+ storeName: "mindcache_store",
3490
+ debounceMs: 1e3
3491
+ }
3492
+ };
3493
+ const mc = new MindCache(options);
3494
+ await mc.waitForSync();
3495
+ setMindcache(mc);
3496
+ setIsLoaded(true);
3497
+ if (resolvedAiConfig.keyStorage === "localStorage" && typeof window !== "undefined") {
3498
+ const stored = localStorage.getItem(resolvedAiConfig.storageKey || "openai_api_key");
3499
+ setHasApiKey(!!stored);
3500
+ } else if (resolvedAiConfig.apiKey) {
3501
+ setHasApiKey(true);
3502
+ }
3503
+ } catch (err) {
3504
+ setError(err instanceof Error ? err : new Error(String(err)));
3505
+ setIsLoaded(true);
3506
+ }
3507
+ };
3508
+ init();
3509
+ return () => {
3510
+ if (mindcache2) {
3511
+ mindcache2.disconnect();
3512
+ }
3513
+ };
3514
+ }, []);
3515
+ const getApiKey = () => {
3516
+ if (resolvedAiConfig.apiKey) {
3517
+ return resolvedAiConfig.apiKey;
3518
+ }
3519
+ if (resolvedAiConfig.keyStorage === "localStorage" && typeof window !== "undefined") {
3520
+ return localStorage.getItem(resolvedAiConfig.storageKey || "openai_api_key");
3521
+ }
3522
+ return null;
3523
+ };
3524
+ const setApiKey = (key) => {
3525
+ if (resolvedAiConfig.keyStorage === "localStorage" && typeof window !== "undefined") {
3526
+ localStorage.setItem(resolvedAiConfig.storageKey || "openai_api_key", key);
3527
+ setHasApiKey(true);
3528
+ }
3529
+ };
3530
+ const getModel = () => {
3531
+ const apiKey = getApiKey();
3532
+ if (!apiKey) {
3533
+ throw new Error("API key not configured. Call setApiKey() first or configure ai.apiKey.");
3534
+ }
3535
+ if (resolvedAiConfig.modelProvider) {
3536
+ return resolvedAiConfig.modelProvider(apiKey);
3537
+ }
3538
+ const provider = resolvedAiConfig.provider || "openai";
3539
+ const model = resolvedAiConfig.model || "gpt-4o";
3540
+ return createModel(provider, model, apiKey);
3541
+ };
3542
+ const syncToGitStore = async () => {
3543
+ if (!mindcache2 || !syncConfig?.gitstore) {
3544
+ return;
3545
+ }
3546
+ setIsSyncing(true);
3547
+ try {
3548
+ let gitStoreModule;
3549
+ try {
3550
+ gitStoreModule = await Function('return import("@mindcache/gitstore")')();
3551
+ } catch {
3552
+ throw new Error("@mindcache/gitstore is not installed. Run: npm install @mindcache/gitstore");
3553
+ }
3554
+ const { GitStore, MindCacheSync } = gitStoreModule;
3555
+ const token = typeof syncConfig.gitstore.token === "function" ? await syncConfig.gitstore.token() : syncConfig.gitstore.token;
3556
+ const gitStore = new GitStore({
3557
+ owner: syncConfig.gitstore.owner,
3558
+ repo: syncConfig.gitstore.repo,
3559
+ tokenProvider: async () => token
3560
+ });
3561
+ const sync = new MindCacheSync(gitStore, mindcache2, {
3562
+ filePath: syncConfig.gitstore.path || "mindcache.md"
3563
+ });
3564
+ await sync.save({ message: "Auto-sync from MindCache" });
3565
+ setLastSyncAt(/* @__PURE__ */ new Date());
3566
+ } catch (err) {
3567
+ console.error("[MindCacheProvider] Sync error:", err);
3568
+ throw err;
3569
+ } finally {
3570
+ setIsSyncing(false);
3571
+ }
3572
+ };
3573
+ const value = {
3574
+ mindcache: mindcache2,
3575
+ isLoaded,
3576
+ error,
3577
+ aiConfig: resolvedAiConfig,
3578
+ syncConfig,
3579
+ getApiKey,
3580
+ setApiKey,
3581
+ hasApiKey,
3582
+ getModel,
3583
+ syncToGitStore,
3584
+ lastSyncAt,
3585
+ isSyncing
3586
+ };
3587
+ return /* @__PURE__ */ jsxRuntime.jsx(MindCacheContext.Provider, { value, children });
3588
+ }
3589
+ function generateId() {
3590
+ return Math.random().toString(36).substring(2, 15) + Date.now().toString(36);
3591
+ }
3592
+ function useClientChat(options = {}) {
3593
+ const context = useMindCacheContext();
3594
+ const mc = options.mindcache || context.mindcache;
3595
+ const [messages, setMessages] = React2.useState(options.initialMessages || []);
3596
+ const [status, setStatus] = React2.useState("idle");
3597
+ const [error, setError] = React2.useState(null);
3598
+ const [streamingContent, setStreamingContent] = React2.useState("");
3599
+ const abortControllerRef = React2.useRef(null);
3600
+ const {
3601
+ systemPrompt,
3602
+ onMindCacheChange,
3603
+ onFinish,
3604
+ onError,
3605
+ maxToolCalls = 5
3606
+ } = options;
3607
+ const apiKey = context.getApiKey();
3608
+ const addMessage = React2.useCallback((msg) => {
3609
+ const newMessage = {
3610
+ ...msg,
3611
+ id: generateId(),
3612
+ createdAt: /* @__PURE__ */ new Date()
3613
+ };
3614
+ setMessages((prev) => [...prev, newMessage]);
3615
+ return newMessage;
3616
+ }, []);
3617
+ const clearMessages = React2.useCallback(() => {
3618
+ setMessages([]);
3619
+ setError(null);
3620
+ setStreamingContent("");
3621
+ }, []);
3622
+ const stop = React2.useCallback(() => {
3623
+ abortControllerRef.current?.abort();
3624
+ setStatus("idle");
3625
+ }, []);
3626
+ const sendMessage = React2.useCallback(async (content) => {
3627
+ if (!mc) {
3628
+ const err = new Error("MindCache not initialized");
3629
+ setError(err);
3630
+ onError?.(err);
3631
+ return;
3632
+ }
3633
+ if (!apiKey) {
3634
+ const err = new Error("API key not configured. Please set your API key.");
3635
+ setError(err);
3636
+ onError?.(err);
3637
+ return;
3638
+ }
3639
+ abortControllerRef.current?.abort();
3640
+ abortControllerRef.current = new AbortController();
3641
+ const userMessage = addMessage({ role: "user", content });
3642
+ setStatus("loading");
3643
+ setError(null);
3644
+ setStreamingContent("");
3645
+ try {
3646
+ const model = context.getModel();
3647
+ const finalSystemPrompt = systemPrompt || mc.get_system_prompt();
3648
+ const tools = mc.create_vercel_ai_tools();
3649
+ const apiMessages = messages.concat(userMessage).map((m) => ({
3650
+ role: m.role,
3651
+ content: m.content
3652
+ }));
3653
+ setStatus("streaming");
3654
+ const parts = [];
3655
+ let accumulatedText = "";
3656
+ const result = await ai.streamText({
3657
+ model,
3658
+ system: finalSystemPrompt,
3659
+ messages: apiMessages,
3660
+ tools,
3661
+ stopWhen: ai.stepCountIs(maxToolCalls),
3662
+ abortSignal: abortControllerRef.current.signal,
3663
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3664
+ onStepFinish: async (step) => {
3665
+ if (step.toolCalls && step.toolCalls.length > 0) {
3666
+ for (const toolCall of step.toolCalls) {
3667
+ const toolName = toolCall.toolName;
3668
+ const args = toolCall.args || toolCall.input || {};
3669
+ parts.push({
3670
+ type: "tool-call",
3671
+ toolCallId: toolCall.toolCallId,
3672
+ toolName,
3673
+ args
3674
+ });
3675
+ if (typeof toolName === "string" && (toolName.startsWith("write_") || toolName === "create_key")) {
3676
+ const value = args.value;
3677
+ const result2 = mc.executeToolCall(toolName, value);
3678
+ parts.push({
3679
+ type: "tool-result",
3680
+ toolCallId: toolCall.toolCallId,
3681
+ toolName,
3682
+ result: result2
3683
+ });
3684
+ onMindCacheChange?.();
3685
+ }
3686
+ }
3687
+ }
3688
+ if (step.text) {
3689
+ accumulatedText += step.text;
3690
+ }
3691
+ }
3692
+ });
3693
+ for await (const chunk of result.textStream) {
3694
+ accumulatedText += chunk;
3695
+ setStreamingContent(accumulatedText);
3696
+ }
3697
+ if (accumulatedText) {
3698
+ parts.unshift({ type: "text", text: accumulatedText });
3699
+ }
3700
+ const assistantMessage = {
3701
+ id: generateId(),
3702
+ role: "assistant",
3703
+ content: accumulatedText,
3704
+ parts: parts.length > 0 ? parts : void 0,
3705
+ createdAt: /* @__PURE__ */ new Date()
3706
+ };
3707
+ setMessages((prev) => [...prev, assistantMessage]);
3708
+ setStreamingContent("");
3709
+ setStatus("idle");
3710
+ onFinish?.(assistantMessage);
3711
+ } catch (err) {
3712
+ if (err.name === "AbortError") {
3713
+ if (streamingContent) {
3714
+ const partialMessage = {
3715
+ id: generateId(),
3716
+ role: "assistant",
3717
+ content: streamingContent + " [stopped]",
3718
+ createdAt: /* @__PURE__ */ new Date()
3719
+ };
3720
+ setMessages((prev) => [...prev, partialMessage]);
3721
+ }
3722
+ setStreamingContent("");
3723
+ setStatus("idle");
3724
+ return;
3725
+ }
3726
+ const error2 = err instanceof Error ? err : new Error(String(err));
3727
+ setError(error2);
3728
+ setStatus("error");
3729
+ setStreamingContent("");
3730
+ onError?.(error2);
3731
+ }
3732
+ }, [
3733
+ mc,
3734
+ apiKey,
3735
+ context,
3736
+ messages,
3737
+ systemPrompt,
3738
+ maxToolCalls,
3739
+ addMessage,
3740
+ onMindCacheChange,
3741
+ onFinish,
3742
+ onError,
3743
+ streamingContent
3744
+ ]);
3745
+ React2.useEffect(() => {
3746
+ return () => {
3747
+ abortControllerRef.current?.abort();
3748
+ };
3749
+ }, []);
3750
+ return {
3751
+ messages,
3752
+ status,
3753
+ error,
3754
+ sendMessage,
3755
+ clearMessages,
3756
+ isLoading: status === "loading" || status === "streaming",
3757
+ addMessage,
3758
+ stop,
3759
+ streamingContent
3760
+ };
3761
+ }
3762
+ var defaultTheme = {
3763
+ background: "#000",
3764
+ userBubble: "#1a1a2e",
3765
+ assistantBubble: "#0d0d0d",
3766
+ textColor: "#22c55e",
3767
+ secondaryTextColor: "#6b7280",
3768
+ borderColor: "#333",
3769
+ primaryColor: "#22c55e",
3770
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
3771
+ };
3772
+ function DefaultMessage({
3773
+ message,
3774
+ theme
3775
+ }) {
3776
+ const isUser = message.role === "user";
3777
+ return /* @__PURE__ */ jsxRuntime.jsx(
3778
+ "div",
3779
+ {
3780
+ style: {
3781
+ display: "flex",
3782
+ justifyContent: isUser ? "flex-end" : "flex-start",
3783
+ marginBottom: "12px"
3784
+ },
3785
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
3786
+ "div",
3787
+ {
3788
+ style: {
3789
+ maxWidth: "80%",
3790
+ padding: "12px 16px",
3791
+ borderRadius: "12px",
3792
+ backgroundColor: isUser ? theme.userBubble : theme.assistantBubble,
3793
+ border: `1px solid ${theme.borderColor}`
3794
+ },
3795
+ children: [
3796
+ /* @__PURE__ */ jsxRuntime.jsx(
3797
+ "div",
3798
+ {
3799
+ style: {
3800
+ fontSize: "10px",
3801
+ color: theme.secondaryTextColor,
3802
+ marginBottom: "4px",
3803
+ textTransform: "uppercase"
3804
+ },
3805
+ children: isUser ? "You" : "Assistant"
3806
+ }
3807
+ ),
3808
+ /* @__PURE__ */ jsxRuntime.jsx(
3809
+ "div",
3810
+ {
3811
+ style: {
3812
+ color: theme.textColor,
3813
+ whiteSpace: "pre-wrap",
3814
+ wordBreak: "break-word",
3815
+ lineHeight: 1.5
3816
+ },
3817
+ children: message.content
3818
+ }
3819
+ )
3820
+ ]
3821
+ }
3822
+ )
3823
+ }
3824
+ );
3825
+ }
3826
+ function ApiKeyInput({
3827
+ theme,
3828
+ onSubmit
3829
+ }) {
3830
+ const [key, setKey] = React2.useState("");
3831
+ const handleSubmit = (e) => {
3832
+ e.preventDefault();
3833
+ if (key.trim()) {
3834
+ onSubmit(key.trim());
3835
+ }
3836
+ };
3837
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3838
+ "div",
3839
+ {
3840
+ style: {
3841
+ display: "flex",
3842
+ flexDirection: "column",
3843
+ alignItems: "center",
3844
+ justifyContent: "center",
3845
+ height: "100%",
3846
+ padding: "20px"
3847
+ },
3848
+ children: [
3849
+ /* @__PURE__ */ jsxRuntime.jsx(
3850
+ "div",
3851
+ {
3852
+ style: {
3853
+ fontSize: "14px",
3854
+ color: theme.textColor,
3855
+ marginBottom: "16px",
3856
+ textAlign: "center"
3857
+ },
3858
+ children: "Enter your API key to start chatting"
3859
+ }
3860
+ ),
3861
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, style: { width: "100%", maxWidth: "400px" }, children: [
3862
+ /* @__PURE__ */ jsxRuntime.jsx(
3863
+ "input",
3864
+ {
3865
+ type: "password",
3866
+ value: key,
3867
+ onChange: (e) => setKey(e.target.value),
3868
+ placeholder: "sk-...",
3869
+ style: {
3870
+ width: "100%",
3871
+ padding: "12px",
3872
+ backgroundColor: theme.assistantBubble,
3873
+ border: `1px solid ${theme.borderColor}`,
3874
+ borderRadius: "8px",
3875
+ color: theme.textColor,
3876
+ fontFamily: theme.fontFamily,
3877
+ fontSize: "14px",
3878
+ marginBottom: "12px"
3879
+ }
3880
+ }
3881
+ ),
3882
+ /* @__PURE__ */ jsxRuntime.jsx(
3883
+ "button",
3884
+ {
3885
+ type: "submit",
3886
+ disabled: !key.trim(),
3887
+ style: {
3888
+ width: "100%",
3889
+ padding: "12px",
3890
+ backgroundColor: theme.primaryColor,
3891
+ border: "none",
3892
+ borderRadius: "8px",
3893
+ color: "#000",
3894
+ fontFamily: theme.fontFamily,
3895
+ fontSize: "14px",
3896
+ fontWeight: "bold",
3897
+ cursor: key.trim() ? "pointer" : "not-allowed",
3898
+ opacity: key.trim() ? 1 : 0.5
3899
+ },
3900
+ children: "Save & Start"
3901
+ }
3902
+ )
3903
+ ] }),
3904
+ /* @__PURE__ */ jsxRuntime.jsx(
3905
+ "div",
3906
+ {
3907
+ style: {
3908
+ fontSize: "11px",
3909
+ color: theme.secondaryTextColor,
3910
+ marginTop: "16px",
3911
+ textAlign: "center"
3912
+ },
3913
+ children: "Your key is stored locally and never sent to our servers."
3914
+ }
3915
+ )
3916
+ ]
3917
+ }
3918
+ );
3919
+ }
3920
+ function MindCacheChat({
3921
+ theme: customTheme,
3922
+ placeholder = "Type a message...",
3923
+ welcomeMessage = "Hello! I'm ready to help you.",
3924
+ showApiKeyInput = true,
3925
+ className,
3926
+ style,
3927
+ renderMessage,
3928
+ header,
3929
+ footer,
3930
+ initialMessages,
3931
+ ...chatOptions
3932
+ }) {
3933
+ const context = useMindCacheContext();
3934
+ const theme = { ...defaultTheme, ...customTheme };
3935
+ const messagesEndRef = React2.useRef(null);
3936
+ const inputRef = React2.useRef(null);
3937
+ const [inputValue, setInputValue] = React2.useState("");
3938
+ const defaultInitialMessages = welcomeMessage ? [
3939
+ {
3940
+ id: "welcome",
3941
+ role: "assistant",
3942
+ content: welcomeMessage,
3943
+ createdAt: /* @__PURE__ */ new Date()
3944
+ }
3945
+ ] : [];
3946
+ const {
3947
+ messages,
3948
+ sendMessage,
3949
+ isLoading,
3950
+ error,
3951
+ streamingContent,
3952
+ stop
3953
+ } = useClientChat({
3954
+ ...chatOptions,
3955
+ initialMessages: initialMessages || defaultInitialMessages,
3956
+ mindcache: context.mindcache || void 0
3957
+ });
3958
+ React2.useEffect(() => {
3959
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
3960
+ }, [messages]);
3961
+ const handleSubmit = async (e) => {
3962
+ e?.preventDefault();
3963
+ if (!inputValue.trim() || isLoading) {
3964
+ return;
3965
+ }
3966
+ const message = inputValue.trim();
3967
+ setInputValue("");
3968
+ await sendMessage(message);
3969
+ };
3970
+ const handleKeyDown = (e) => {
3971
+ if (e.key === "Enter" && !e.shiftKey) {
3972
+ e.preventDefault();
3973
+ handleSubmit();
3974
+ }
3975
+ };
3976
+ if (showApiKeyInput && !context.hasApiKey && context.aiConfig.keyStorage !== "memory") {
3977
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3978
+ "div",
3979
+ {
3980
+ className,
3981
+ style: {
3982
+ display: "flex",
3983
+ flexDirection: "column",
3984
+ height: "100%",
3985
+ backgroundColor: theme.background,
3986
+ fontFamily: theme.fontFamily,
3987
+ ...style
3988
+ },
3989
+ children: [
3990
+ header,
3991
+ /* @__PURE__ */ jsxRuntime.jsx(
3992
+ ApiKeyInput,
3993
+ {
3994
+ theme,
3995
+ onSubmit: (key) => context.setApiKey(key)
3996
+ }
3997
+ ),
3998
+ footer
3999
+ ]
4000
+ }
4001
+ );
4002
+ }
4003
+ if (!context.isLoaded) {
4004
+ return /* @__PURE__ */ jsxRuntime.jsx(
4005
+ "div",
4006
+ {
4007
+ className,
4008
+ style: {
4009
+ display: "flex",
4010
+ alignItems: "center",
4011
+ justifyContent: "center",
4012
+ height: "100%",
4013
+ backgroundColor: theme.background,
4014
+ color: theme.textColor,
4015
+ fontFamily: theme.fontFamily,
4016
+ ...style
4017
+ },
4018
+ children: "Loading..."
4019
+ }
4020
+ );
4021
+ }
4022
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4023
+ "div",
4024
+ {
4025
+ className,
4026
+ style: {
4027
+ display: "flex",
4028
+ flexDirection: "column",
4029
+ height: "100%",
4030
+ backgroundColor: theme.background,
4031
+ fontFamily: theme.fontFamily,
4032
+ ...style
4033
+ },
4034
+ children: [
4035
+ header,
4036
+ /* @__PURE__ */ jsxRuntime.jsxs(
4037
+ "div",
4038
+ {
4039
+ style: {
4040
+ flex: 1,
4041
+ overflowY: "auto",
4042
+ padding: "16px"
4043
+ },
4044
+ children: [
4045
+ messages.map((message) => renderMessage ? /* @__PURE__ */ jsxRuntime.jsx(React2__default.default.Fragment, { children: renderMessage(message) }, message.id) : /* @__PURE__ */ jsxRuntime.jsx(DefaultMessage, { message, theme }, message.id)),
4046
+ streamingContent && /* @__PURE__ */ jsxRuntime.jsx(
4047
+ "div",
4048
+ {
4049
+ style: {
4050
+ display: "flex",
4051
+ justifyContent: "flex-start",
4052
+ marginBottom: "12px"
4053
+ },
4054
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
4055
+ "div",
4056
+ {
4057
+ style: {
4058
+ maxWidth: "80%",
4059
+ padding: "12px 16px",
4060
+ borderRadius: "12px",
4061
+ backgroundColor: theme.assistantBubble,
4062
+ border: `1px solid ${theme.borderColor}`
4063
+ },
4064
+ children: [
4065
+ /* @__PURE__ */ jsxRuntime.jsx(
4066
+ "div",
4067
+ {
4068
+ style: {
4069
+ fontSize: "10px",
4070
+ color: theme.secondaryTextColor,
4071
+ marginBottom: "4px",
4072
+ textTransform: "uppercase"
4073
+ },
4074
+ children: "Assistant"
4075
+ }
4076
+ ),
4077
+ /* @__PURE__ */ jsxRuntime.jsxs(
4078
+ "div",
4079
+ {
4080
+ style: {
4081
+ color: theme.textColor,
4082
+ whiteSpace: "pre-wrap",
4083
+ wordBreak: "break-word",
4084
+ lineHeight: 1.5
4085
+ },
4086
+ children: [
4087
+ streamingContent,
4088
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { opacity: 0.5, animation: "blink 1s infinite" }, children: "\u258A" })
4089
+ ]
4090
+ }
4091
+ )
4092
+ ]
4093
+ }
4094
+ )
4095
+ }
4096
+ ),
4097
+ isLoading && !streamingContent && /* @__PURE__ */ jsxRuntime.jsx(
4098
+ "div",
4099
+ {
4100
+ style: {
4101
+ display: "flex",
4102
+ justifyContent: "flex-start",
4103
+ marginBottom: "12px"
4104
+ },
4105
+ children: /* @__PURE__ */ jsxRuntime.jsx(
4106
+ "div",
4107
+ {
4108
+ style: {
4109
+ padding: "12px 16px",
4110
+ borderRadius: "12px",
4111
+ backgroundColor: theme.assistantBubble,
4112
+ border: `1px solid ${theme.borderColor}`,
4113
+ color: theme.secondaryTextColor
4114
+ },
4115
+ children: "Thinking..."
4116
+ }
4117
+ )
4118
+ }
4119
+ ),
4120
+ error && /* @__PURE__ */ jsxRuntime.jsxs(
4121
+ "div",
4122
+ {
4123
+ style: {
4124
+ padding: "12px",
4125
+ marginBottom: "12px",
4126
+ borderRadius: "8px",
4127
+ backgroundColor: "rgba(239, 68, 68, 0.1)",
4128
+ border: "1px solid rgba(239, 68, 68, 0.3)",
4129
+ color: "#ef4444",
4130
+ fontSize: "13px"
4131
+ },
4132
+ children: [
4133
+ "Error: ",
4134
+ error.message
4135
+ ]
4136
+ }
4137
+ ),
4138
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: messagesEndRef })
4139
+ ]
4140
+ }
4141
+ ),
4142
+ /* @__PURE__ */ jsxRuntime.jsx(
4143
+ "form",
4144
+ {
4145
+ onSubmit: handleSubmit,
4146
+ style: {
4147
+ padding: "12px 16px",
4148
+ borderTop: `1px solid ${theme.borderColor}`
4149
+ },
4150
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
4151
+ "div",
4152
+ {
4153
+ style: {
4154
+ display: "flex",
4155
+ gap: "8px",
4156
+ alignItems: "flex-end"
4157
+ },
4158
+ children: [
4159
+ /* @__PURE__ */ jsxRuntime.jsx(
4160
+ "textarea",
4161
+ {
4162
+ ref: inputRef,
4163
+ value: inputValue,
4164
+ onChange: (e) => {
4165
+ setInputValue(e.target.value);
4166
+ e.target.style.height = "auto";
4167
+ e.target.style.height = Math.min(e.target.scrollHeight, 120) + "px";
4168
+ },
4169
+ onKeyDown: handleKeyDown,
4170
+ placeholder: isLoading ? "Waiting for response..." : placeholder,
4171
+ disabled: isLoading,
4172
+ rows: 1,
4173
+ style: {
4174
+ flex: 1,
4175
+ padding: "12px",
4176
+ backgroundColor: theme.assistantBubble,
4177
+ border: `1px solid ${theme.borderColor}`,
4178
+ borderRadius: "8px",
4179
+ color: theme.textColor,
4180
+ fontFamily: theme.fontFamily,
4181
+ fontSize: "16px",
4182
+ // Prevents iOS zoom on focus
4183
+ resize: "none",
4184
+ minHeight: "44px",
4185
+ // Apple touch target minimum
4186
+ maxHeight: "120px",
4187
+ outline: "none",
4188
+ WebkitAppearance: "none"
4189
+ // Remove iOS styling
4190
+ }
4191
+ }
4192
+ ),
4193
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
4194
+ "button",
4195
+ {
4196
+ type: "button",
4197
+ onClick: stop,
4198
+ style: {
4199
+ padding: "12px 20px",
4200
+ minWidth: "44px",
4201
+ // Touch target
4202
+ minHeight: "44px",
4203
+ // Touch target
4204
+ backgroundColor: "#ef4444",
4205
+ border: "none",
4206
+ borderRadius: "8px",
4207
+ color: "#fff",
4208
+ fontFamily: theme.fontFamily,
4209
+ fontSize: "16px",
4210
+ fontWeight: "bold",
4211
+ cursor: "pointer",
4212
+ transition: "opacity 0.2s",
4213
+ WebkitTapHighlightColor: "transparent",
4214
+ touchAction: "manipulation"
4215
+ // Faster touch response
4216
+ },
4217
+ children: "Stop"
4218
+ }
4219
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
4220
+ "button",
4221
+ {
4222
+ type: "submit",
4223
+ disabled: !inputValue.trim(),
4224
+ style: {
4225
+ padding: "12px 20px",
4226
+ minWidth: "44px",
4227
+ // Touch target
4228
+ minHeight: "44px",
4229
+ // Touch target
4230
+ backgroundColor: theme.primaryColor,
4231
+ border: "none",
4232
+ borderRadius: "8px",
4233
+ color: "#000",
4234
+ fontFamily: theme.fontFamily,
4235
+ fontSize: "16px",
4236
+ fontWeight: "bold",
4237
+ cursor: !inputValue.trim() ? "not-allowed" : "pointer",
4238
+ opacity: !inputValue.trim() ? 0.5 : 1,
4239
+ transition: "opacity 0.2s",
4240
+ WebkitTapHighlightColor: "transparent",
4241
+ touchAction: "manipulation"
4242
+ // Faster touch response
4243
+ },
4244
+ children: "Send"
4245
+ }
4246
+ )
4247
+ ]
4248
+ }
4249
+ )
4250
+ }
4251
+ ),
4252
+ footer
4253
+ ]
4254
+ }
4255
+ );
4256
+ }
4257
+ function useLocalFirstSync(options) {
4258
+ const {
4259
+ mindcache: mindcache2,
4260
+ gitstore,
4261
+ server,
4262
+ autoSyncInterval = 0,
4263
+ saveDebounceMs = 5e3,
4264
+ loadOnMount = true,
4265
+ mergeOnLoad = true
4266
+ } = options;
4267
+ const [status, setStatus] = React2.useState("idle");
4268
+ const [error, setError] = React2.useState(null);
4269
+ const [lastSyncAt, setLastSyncAt] = React2.useState(null);
4270
+ const [hasLocalChanges, setHasLocalChanges] = React2.useState(false);
4271
+ const saveTimeoutRef = React2.useRef(null);
4272
+ const syncIntervalRef = React2.useRef(null);
4273
+ const mountedRef = React2.useRef(true);
4274
+ const getToken = React2.useCallback(async () => {
4275
+ if (!gitstore) {
4276
+ throw new Error("GitStore not configured");
4277
+ }
4278
+ return typeof gitstore.token === "function" ? await gitstore.token() : gitstore.token;
4279
+ }, [gitstore]);
4280
+ const load = React2.useCallback(async () => {
4281
+ if (!mindcache2) {
4282
+ return;
4283
+ }
4284
+ setStatus("loading");
4285
+ setError(null);
4286
+ try {
4287
+ if (gitstore) {
4288
+ let gitStoreModule;
4289
+ try {
4290
+ gitStoreModule = await Function('return import("@mindcache/gitstore")')();
4291
+ } catch {
4292
+ throw new Error("@mindcache/gitstore is not installed. Run: npm install @mindcache/gitstore");
4293
+ }
4294
+ const { GitStore, MindCacheSync } = gitStoreModule;
4295
+ const token = await getToken();
4296
+ const store = new GitStore({
4297
+ owner: gitstore.owner,
4298
+ repo: gitstore.repo,
4299
+ branch: gitstore.branch,
4300
+ tokenProvider: async () => token
4301
+ });
4302
+ const sync2 = new MindCacheSync(store, mindcache2, {
4303
+ filePath: gitstore.path || "mindcache.md"
4304
+ });
4305
+ await sync2.load({ merge: mergeOnLoad });
4306
+ } else if (server) {
4307
+ const response = await fetch(server.url, {
4308
+ headers: server.authToken ? { Authorization: `Bearer ${server.authToken}` } : {}
4309
+ });
4310
+ if (response.ok) {
4311
+ const markdown = await response.text();
4312
+ mindcache2.fromMarkdown(markdown, mergeOnLoad);
4313
+ }
4314
+ }
4315
+ if (mountedRef.current) {
4316
+ setLastSyncAt(/* @__PURE__ */ new Date());
4317
+ setStatus("idle");
4318
+ }
4319
+ } catch (err) {
4320
+ if (mountedRef.current) {
4321
+ const error2 = err instanceof Error ? err : new Error(String(err));
4322
+ setError(error2);
4323
+ setStatus("error");
4324
+ }
4325
+ throw err;
4326
+ }
4327
+ }, [mindcache2, gitstore, server, getToken, mergeOnLoad]);
4328
+ const save = React2.useCallback(async (message) => {
4329
+ if (!mindcache2) {
4330
+ return;
4331
+ }
4332
+ setStatus("saving");
4333
+ setError(null);
4334
+ try {
4335
+ if (gitstore) {
4336
+ let gitStoreModule;
4337
+ try {
4338
+ gitStoreModule = await Function('return import("@mindcache/gitstore")')();
4339
+ } catch {
4340
+ throw new Error("@mindcache/gitstore is not installed. Run: npm install @mindcache/gitstore");
4341
+ }
4342
+ const { GitStore, MindCacheSync } = gitStoreModule;
4343
+ const token = await getToken();
4344
+ const store = new GitStore({
4345
+ owner: gitstore.owner,
4346
+ repo: gitstore.repo,
4347
+ branch: gitstore.branch,
4348
+ tokenProvider: async () => token
4349
+ });
4350
+ const sync2 = new MindCacheSync(store, mindcache2, {
4351
+ filePath: gitstore.path || "mindcache.md"
4352
+ });
4353
+ await sync2.save({ message: message || "MindCache sync" });
4354
+ } else if (server) {
4355
+ await fetch(server.url, {
4356
+ method: "POST",
4357
+ headers: {
4358
+ "Content-Type": "text/plain",
4359
+ ...server.authToken ? { Authorization: `Bearer ${server.authToken}` } : {}
4360
+ },
4361
+ body: mindcache2.toMarkdown()
4362
+ });
4363
+ }
4364
+ if (mountedRef.current) {
4365
+ setLastSyncAt(/* @__PURE__ */ new Date());
4366
+ setHasLocalChanges(false);
4367
+ setStatus("idle");
4368
+ }
4369
+ } catch (err) {
4370
+ if (mountedRef.current) {
4371
+ const error2 = err instanceof Error ? err : new Error(String(err));
4372
+ setError(error2);
4373
+ setStatus("error");
4374
+ }
4375
+ throw err;
4376
+ }
4377
+ }, [mindcache2, gitstore, server, getToken]);
4378
+ const sync = React2.useCallback(async () => {
4379
+ setStatus("syncing");
4380
+ try {
4381
+ await load();
4382
+ if (hasLocalChanges) {
4383
+ await save();
4384
+ }
4385
+ } catch (err) {
4386
+ }
4387
+ }, [load, save, hasLocalChanges]);
4388
+ const markSaved = React2.useCallback(() => {
4389
+ setHasLocalChanges(false);
4390
+ }, []);
4391
+ React2.useEffect(() => {
4392
+ if (!mindcache2 || !gitstore || saveDebounceMs <= 0) {
4393
+ return;
4394
+ }
4395
+ const unsubscribe = mindcache2.subscribeToAll(() => {
4396
+ setHasLocalChanges(true);
4397
+ if (saveTimeoutRef.current) {
4398
+ clearTimeout(saveTimeoutRef.current);
4399
+ }
4400
+ saveTimeoutRef.current = setTimeout(() => {
4401
+ save("Auto-save").catch(console.error);
4402
+ }, saveDebounceMs);
4403
+ });
4404
+ return () => {
4405
+ unsubscribe();
4406
+ if (saveTimeoutRef.current) {
4407
+ clearTimeout(saveTimeoutRef.current);
4408
+ }
4409
+ };
4410
+ }, [mindcache2, gitstore, saveDebounceMs, save]);
4411
+ React2.useEffect(() => {
4412
+ if (!mindcache2 || autoSyncInterval <= 0) {
4413
+ return;
4414
+ }
4415
+ syncIntervalRef.current = setInterval(() => {
4416
+ sync().catch(console.error);
4417
+ }, autoSyncInterval);
4418
+ return () => {
4419
+ if (syncIntervalRef.current) {
4420
+ clearInterval(syncIntervalRef.current);
4421
+ }
4422
+ };
4423
+ }, [mindcache2, autoSyncInterval, sync]);
4424
+ React2.useEffect(() => {
4425
+ if (loadOnMount && mindcache2 && (gitstore || server)) {
4426
+ load().catch(console.error);
4427
+ }
4428
+ }, [mindcache2, gitstore, server, loadOnMount]);
4429
+ React2.useEffect(() => {
4430
+ mountedRef.current = true;
4431
+ return () => {
4432
+ mountedRef.current = false;
4433
+ };
4434
+ }, []);
4435
+ return {
4436
+ status,
4437
+ error,
4438
+ lastSyncAt,
4439
+ hasLocalChanges,
4440
+ load,
4441
+ save,
4442
+ sync,
4443
+ markSaved
4444
+ };
4445
+ }
3436
4446
 
3437
4447
  // src/index.ts
3438
4448
  var mindcache = new MindCache();
3439
4449
 
3440
4450
  exports.DEFAULT_KEY_ATTRIBUTES = DEFAULT_KEY_ATTRIBUTES;
3441
4451
  exports.MindCache = MindCache;
4452
+ exports.MindCacheChat = MindCacheChat;
4453
+ exports.MindCacheProvider = MindCacheProvider;
3442
4454
  exports.OAuthClient = OAuthClient;
3443
4455
  exports.SchemaParser = SchemaParser;
3444
4456
  exports.SystemTagHelpers = SystemTagHelpers;
3445
4457
  exports.createOAuthClient = createOAuthClient;
3446
4458
  exports.mindcache = mindcache;
4459
+ exports.useClientChat = useClientChat;
4460
+ exports.useLocalFirstSync = useLocalFirstSync;
3447
4461
  exports.useMindCache = useMindCache;
4462
+ exports.useMindCacheContext = useMindCacheContext;
3448
4463
  //# sourceMappingURL=index.js.map
3449
4464
  //# sourceMappingURL=index.js.map