From ed1f6fc49d7a0c3ad04afd550d9bd6d1ecc686bd Mon Sep 17 00:00:00 2001 From: lusixing <32328454+lusixing@users.noreply.github.com> Date: Sat, 31 Jan 2026 11:11:25 -0800 Subject: [PATCH] multi_prompt_local_storage --- package-lock.json | 42 ++- src/config/index.ts | 40 ++- src/context/AuthContext.tsx | 20 +- src/screens/FlowScreen.tsx | 506 +++++++++++++++++++++++++------- src/screens/MeScreen.tsx | 138 +++++++-- src/services/ai.service.ts | 17 +- src/services/storage.service.ts | 120 ++++++++ 7 files changed, 727 insertions(+), 156 deletions(-) create mode 100644 src/services/storage.service.ts diff --git a/package-lock.json b/package-lock.json index 9e4cc83..889b6d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -80,7 +80,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -484,6 +483,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" @@ -500,6 +500,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -515,6 +516,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -530,6 +532,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", @@ -547,6 +550,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/traverse": "^7.28.6" @@ -647,6 +651,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.9.0" }, @@ -767,6 +772,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -953,6 +959,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -1018,6 +1025,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1064,6 +1072,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" @@ -1132,6 +1141,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1148,6 +1158,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1163,6 +1174,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz", "integrity": "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1179,6 +1191,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1194,6 +1207,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5" @@ -1210,6 +1224,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1289,6 +1304,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1334,6 +1350,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1349,6 +1366,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1381,6 +1399,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", @@ -1399,6 +1418,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -1431,6 +1451,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1495,6 +1516,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1" @@ -1590,6 +1612,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1715,6 +1738,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1731,6 +1755,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1827,6 +1852,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1861,6 +1887,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -1876,6 +1903,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -1908,6 +1936,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" @@ -2026,6 +2055,7 @@ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/types": "^7.4.4", @@ -2643,7 +2673,6 @@ "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-4.0.1.tgz", "integrity": "sha512-CRpbLvdJ1T42S+lrYa1iZp1KfDeBp4oeZOK3hdpiS5n0vR0nhD6sC1gGF0sTboCTp64tLteikz5Y3j53dvgOIw==", "license": "MIT", - "peer": true, "peerDependencies": { "react-native": "*" } @@ -3688,7 +3717,6 @@ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.18.tgz", "integrity": "sha512-mIT9MiL/vMm4eirLcmw2h6h/Nm5FICtnYSdohq4vTLA2FF/6PNhByM7s8ffqoVfE5L0uAa6Xda1B7oddolUiGg==", "license": "MIT", - "peer": true, "dependencies": { "@react-navigation/core": "^6.4.17", "escape-string-regexp": "^4.0.0", @@ -3870,7 +3898,6 @@ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -4482,7 +4509,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5507,6 +5533,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -5631,7 +5658,6 @@ "resolved": "https://registry.npmjs.org/expo/-/expo-52.0.48.tgz", "integrity": "sha512-/HR/vuo57KGEWlvF3GWaquwEAjXuA5hrOCsaLcZ3pMSA8mQ27qKd1jva4GWzpxXYedlzs/7LLP1XpZo6hXTsog==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "0.22.27", @@ -9159,7 +9185,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -9243,7 +9268,6 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.76.9.tgz", "integrity": "sha512-+LRwecWmTDco7OweGsrECIqJu0iyrREd6CTCgC/uLLYipiHvk+MH9nd6drFtCw/6Blz6eoKTcH9YTTJusNtrWg==", "license": "MIT", - "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.6.3", "@react-native/assets-registry": "0.76.9", @@ -9345,7 +9369,6 @@ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.12.0.tgz", "integrity": "sha512-ukk5PxcF4p3yu6qMZcmeiZgowhb5AsKRnil54YFUUAXVIS7PJcMHGGC+q44fCiBg44/1AJk5njGMez1m9H0BVQ==", "license": "MIT", - "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -9356,7 +9379,6 @@ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.4.0.tgz", "integrity": "sha512-c7zc7Zwjty6/pGyuuvh9gK3YBYqHPOxrhXfG1lF4gHlojQSmIx2piNbNaV+Uykj+RDTmFXK0e/hA+fucw/Qozg==", "license": "MIT", - "peer": true, "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" diff --git a/src/config/index.ts b/src/config/index.ts index d892ac0..5fb2bb7 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -27,7 +27,7 @@ export const DEBUG_MODE = true; /** * Base URL for the backend API server */ -export const API_BASE_URL = 'http://localhost:8000'; +export const API_BASE_URL = 'http://192.168.56.103:8000'; /** * API request timeout in milliseconds @@ -137,6 +137,44 @@ export const AI_CONFIG = { * Mock response delay in milliseconds (for NO_BACKEND_MODE) */ MOCK_RESPONSE_DELAY: 500, + + /** + * AI Roles configuration + */ + ROLES: [ + { + id: 'reflective', + name: 'Reflective Assistant', + description: 'Helps you dive deep into your thoughts and feelings through meaningful reflection.', + systemPrompt: 'You are a helpful journal assistant. Help the user reflect on their thoughts and feelings.', + icon: 'journal-outline', + iconFamily: 'Ionicons', + }, + { + id: 'creative', + name: 'Creative Spark', + description: 'A partner for brainstorming, creative writing, and exploring new ideas.', + systemPrompt: 'You are a creative brainstorming partner. Help the user explore new ideas, write stories, or look at things from a fresh perspective.', + icon: 'bulb-outline', + iconFamily: 'Ionicons', + }, + { + id: 'planner', + name: 'Action Planner', + description: 'Focused on turning thoughts into actionable plans and organized goals.', + systemPrompt: 'You are a productivity coach. Help the user break down their thoughts into actionable steps and clear goals.', + icon: 'list-outline', + iconFamily: 'Ionicons', + }, + { + id: 'empathetic', + name: 'Empathetic Guide', + description: 'Provides a safe, non-judgmental space for emotional support and empathy.', + systemPrompt: 'You are a supportive and empathetic friend. Listen to the user\'s concerns and provide emotional support without judgment.', + icon: 'heart-outline', + iconFamily: 'Ionicons', + }, + ], } as const; // ============================================================================= diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index 32d298b..025d324 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -9,6 +9,7 @@ import React, { createContext, useContext, useState, useEffect, ReactNode } from import AsyncStorage from '@react-native-async-storage/async-storage'; import { User, LoginRequest, RegisterRequest } from '../types'; import { authService } from '../services/auth.service'; +import { storageService } from '../services/storage.service'; // ============================================================================= // Type Definitions @@ -137,24 +138,25 @@ export function AuthProvider({ children }: { children: ReactNode }) { }; /** - * Sign out and clear stored auth + * Sign out and clear stored auth and session data */ const signOut = () => { setUser(null); setToken(null); clearAuth(); + //storageService.clearAllData(); }; return ( - {children} diff --git a/src/screens/FlowScreen.tsx b/src/screens/FlowScreen.tsx index dd693bb..2dddebf 100644 --- a/src/screens/FlowScreen.tsx +++ b/src/screens/FlowScreen.tsx @@ -31,6 +31,8 @@ import * as ImagePicker from 'expo-image-picker'; import { colors, typography, spacing, borderRadius, shadows } from '../theme/colors'; import { aiService } from '../services/ai.service'; import { useAuth } from '../context/AuthContext'; +import { AI_CONFIG } from '../config'; +import { storageService } from '../services/storage.service'; // ============================================================================= // Type Definitions @@ -57,16 +59,21 @@ interface ChatSession { // ============================================================================= export default function FlowScreen() { - const { token, signOut } = useAuth(); + const { token, user, signOut } = useAuth(); const scrollViewRef = useRef(null); - + // Current conversation state const [messages, setMessages] = useState([]); const [newContent, setNewContent] = useState(''); const [isSending, setIsSending] = useState(false); const [isRecording, setIsRecording] = useState(false); const [selectedImage, setSelectedImage] = useState(null); - + + // AI Role state + const [selectedRole, setSelectedRole] = useState(AI_CONFIG.ROLES[0]); + const [showRoleModal, setShowRoleModal] = useState(false); + const [expandedRoleId, setExpandedRoleId] = useState(null); + // History modal state const [showHistoryModal, setShowHistoryModal] = useState(false); const modalSlideAnim = useRef(new Animated.Value(0)).current; @@ -95,23 +102,87 @@ export default function FlowScreen() { updatedAt: new Date('2024-01-16T20:30:00'), }, ]); - + // Header date display const today = new Date(); - const dateStr = today.toLocaleDateString('en-US', { - weekday: 'long', - month: 'long', - day: 'numeric' + const dateStr = today.toLocaleDateString('en-US', { + weekday: 'long', + month: 'long', + day: 'numeric' }); - // Auto-scroll to bottom when new messages arrive + // Load history on mount useEffect(() => { + const loadHistory = async () => { + if (!user) return; + try { + console.log('[FlowScreen] Loading chat history...'); + const savedHistory = await storageService.getChatHistory(user.id); + if (savedHistory && savedHistory.length > 0) { + const formattedHistory = savedHistory.map((session: any) => ({ + ...session, + createdAt: new Date(session.createdAt), + updatedAt: new Date(session.updatedAt), + messages: session.messages.map((msg: any) => ({ + ...msg, + createdAt: new Date(msg.createdAt) + })) + })); + setChatHistory(formattedHistory); + console.log('[FlowScreen] Chat history loaded:', formattedHistory.length, 'sessions'); + } else { + console.log('[FlowScreen] No chat history found'); + } + } catch (error) { + console.error('Failed to load history:', error); + } + }; + loadHistory(); + }, [user]); + + // Load messages whenever role changes + useEffect(() => { + const loadRoleMessages = async () => { + if (!user) return; + try { + const savedMessages = await storageService.getCurrentChat(selectedRole.id, user.id); + if (savedMessages) { + const formattedMessages = savedMessages.map((msg: any) => ({ + ...msg, + createdAt: new Date(msg.createdAt) + })); + setMessages(formattedMessages); + } else { + setMessages([]); + } + } catch (error) { + console.error(`Failed to load messages for role ${selectedRole.id}:`, error); + setMessages([]); + } + }; + + loadRoleMessages(); + }, [selectedRole.id, user]); + + // Save current messages for the active role when they change + useEffect(() => { + if (user && messages.length >= 0) { // Save even if empty to allow clearing + storageService.saveCurrentChat(selectedRole.id, messages, user.id); + } + if (messages.length > 0) { setTimeout(() => { scrollViewRef.current?.scrollToEnd({ animated: true }); }, 100); } - }, [messages]); + }, [messages, selectedRole.id, user]); + + // Save history when it changes + useEffect(() => { + if (user) { + storageService.saveChatHistory(chatHistory, user.id); + } + }, [chatHistory, user]); // Modal animation control const openHistoryModal = () => { @@ -143,7 +214,7 @@ export default function FlowScreen() { */ const handleSendMessage = async () => { if (!newContent.trim() || isSending) return; - + // Check authentication if (!token) { Alert.alert( @@ -153,11 +224,11 @@ export default function FlowScreen() { ); return; } - + const userMessage = newContent.trim(); setIsSending(true); setNewContent(''); - + // Add user message immediately const userMsg: ChatMessage = { id: Date.now().toString(), @@ -166,11 +237,11 @@ export default function FlowScreen() { createdAt: new Date(), }; setMessages(prev => [...prev, userMsg]); - + try { - // Call AI proxy - const aiResponse = await aiService.sendMessage(userMessage, token); - + // Call AI proxy with selected role's system prompt + const aiResponse = await aiService.sendMessage(userMessage, token, selectedRole.systemPrompt); + // Add AI response const aiMsg: ChatMessage = { id: (Date.now() + 1).toString(), @@ -179,20 +250,20 @@ export default function FlowScreen() { createdAt: new Date(), }; setMessages(prev => [...prev, aiMsg]); - + } catch (error) { console.error('AI request failed:', error); - + const errorMessage = error instanceof Error ? error.message : String(error); - + // Handle authentication errors (401, credentials, unauthorized) - const isAuthError = - errorMessage.includes('401') || + const isAuthError = + errorMessage.includes('401') || errorMessage.includes('credentials') || errorMessage.includes('Unauthorized') || errorMessage.includes('Not authenticated') || errorMessage.includes('validate'); - + if (isAuthError) { signOut(); Alert.alert( @@ -202,7 +273,7 @@ export default function FlowScreen() { ); return; } - + // Show error as AI message const errorMsg: ChatMessage = { id: (Date.now() + 1).toString(), @@ -246,7 +317,7 @@ export default function FlowScreen() { if (!result.canceled && result.assets[0]) { const imageAsset = result.assets[0]; setSelectedImage(imageAsset.uri); - + // Check authentication if (!token) { Alert.alert( @@ -289,9 +360,9 @@ export default function FlowScreen() { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; // Handle authentication errors - const isAuthError = - errorMessage.includes('401') || - errorMessage.includes('Unauthorized') || + const isAuthError = + errorMessage.includes('401') || + errorMessage.includes('Unauthorized') || errorMessage.includes('credentials') || errorMessage.includes('validate'); @@ -334,9 +405,12 @@ export default function FlowScreen() { }; setChatHistory(prev => [newSession, ...prev]); } - - // Clear current messages + + // Clear current messages and storage for this role setMessages([]); + if (user) { + storageService.saveCurrentChat(selectedRole.id, [], user.id); + } closeHistoryModal(); }; @@ -355,7 +429,7 @@ export default function FlowScreen() { }; setChatHistory(prev => [currentSession, ...prev.filter(s => s.id !== session.id)]); } - + // Load selected conversation setMessages(session.messages); closeHistoryModal(); @@ -370,8 +444,8 @@ export default function FlowScreen() { 'Are you sure you want to delete this conversation?', [ { text: 'Cancel', style: 'cancel' }, - { - text: 'Delete', + { + text: 'Delete', style: 'destructive', onPress: () => setChatHistory(prev => prev.filter(s => s.id !== sessionId)) }, @@ -400,10 +474,10 @@ export default function FlowScreen() { */ const renderMessage = (message: ChatMessage, index: number) => { const isUser = message.role === 'user'; - + return ( - {/* Show image if present */} {message.imageUri && ( - @@ -451,9 +525,9 @@ export default function FlowScreen() { - Start a conversation + Chatting with {selectedRole.name} - Ask me anything or share your thoughts + {selectedRole.description} ); @@ -462,7 +536,7 @@ export default function FlowScreen() { * Render history item in modal */ const renderHistoryItem = ({ item }: { item: ChatSession }) => ( - handleLoadHistory(item)} onLongPress={() => handleDeleteHistory(item.id)} @@ -504,9 +578,26 @@ export default function FlowScreen() { {dateStr} - + + {/* Role Header Dropdown */} + setShowRoleModal(true)} + activeOpacity={0.7} + > + + + {selectedRole.name} + + + + {/* History Button */} - @@ -515,7 +606,7 @@ export default function FlowScreen() { {/* Chat Messages */} - renderMessage(message, index)) )} - + {/* Loading indicator when sending */} {isSending && ( @@ -541,7 +632,7 @@ export default function FlowScreen() { )} - + @@ -549,7 +640,7 @@ export default function FlowScreen() { {/* Image attachment button */} - ) : ( - - )} @@ -607,17 +698,16 @@ export default function FlowScreen() { - {/* History Modal - Background appears instantly, content slides up */} - e.stopPropagation()}> - - - - {/* Modal Header */} - - Chat History - - - New Chat - - - - {/* History List */} - item.id} - style={styles.historyList} - ListEmptyComponent={ - - - No chat history yet + + + {/* Modal Header */} + + Chat History + + + New Chat + - } - /> - - {/* Close Button */} - - Close - + + {/* History List */} + item.id} + style={styles.historyList} + ListEmptyComponent={ + + + No chat history yet + + } + /> + + {/* Close Button */} + + Close + + + {/* Role Selection Modal */} + setShowRoleModal(false)} + > + setShowRoleModal(false)}> + + e.stopPropagation()}> + + + Choose AI Assistant + + + {AI_CONFIG.ROLES.map((role) => ( + + + { + setSelectedRole(role as any); + setShowRoleModal(false); + }} + > + + + + + {role.name} + + + + { + setExpandedRoleId(expandedRoleId === role.id ? null : role.id); + }} + > + + + + + {expandedRoleId === role.id && ( + + {role.description} + + )} + + ))} + + + setShowRoleModal(false)} + > + Cancel + + + + + + ); } @@ -680,16 +853,16 @@ export default function FlowScreen() { const styles = StyleSheet.create({ // Container styles - container: { - flex: 1 + container: { + flex: 1 }, - gradient: { - flex: 1 + gradient: { + flex: 1 }, - safeArea: { - flex: 1 + safeArea: { + flex: 1 }, - + // Header styles header: { flexDirection: 'row', @@ -697,12 +870,33 @@ const styles = StyleSheet.create({ alignItems: 'center', paddingHorizontal: spacing.base, paddingTop: spacing.sm, - paddingBottom: spacing.md, + paddingBottom: spacing.sm, + borderBottomWidth: 1, + borderBottomColor: 'rgba(0,0,0,0.05)', }, headerLeft: { flexDirection: 'row', alignItems: 'center', gap: spacing.sm, + flex: 1, + }, + headerRoleButton: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.flow.cardBackground, + paddingHorizontal: spacing.sm, + paddingVertical: 6, + borderRadius: borderRadius.full, + marginHorizontal: spacing.sm, + borderWidth: 1, + borderColor: colors.flow.cardBorder, + maxWidth: '40%', + }, + headerRoleText: { + fontSize: typography.fontSize.xs, + fontWeight: '600', + color: colors.flow.text, + marginHorizontal: 4, }, iconCircle: { width: 44, @@ -731,20 +925,20 @@ const styles = StyleSheet.create({ alignItems: 'center', ...shadows.soft, }, - + // Messages container styles - messagesContainer: { - flex: 1 + messagesContainer: { + flex: 1 }, - messagesContent: { - padding: spacing.base, - paddingTop: 0 + messagesContent: { + padding: spacing.base, + paddingTop: 0 }, emptyContent: { flex: 1, justifyContent: 'center', }, - + // Empty state styles emptyState: { alignItems: 'center', @@ -772,7 +966,97 @@ const styles = StyleSheet.create({ color: colors.flow.textSecondary, textAlign: 'center', }, - + + // Role selection styles + roleDropdown: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.flow.cardBackground, + paddingHorizontal: spacing.md, + paddingVertical: spacing.sm, + borderRadius: borderRadius.lg, + marginBottom: spacing.md, + ...shadows.soft, + borderWidth: 1, + borderColor: colors.flow.cardBorder, + }, + roleIcon: { + marginRight: spacing.sm, + }, + roleDropdownText: { + fontSize: typography.fontSize.base, + fontWeight: '600', + color: colors.flow.text, + marginRight: spacing.xs, + }, + roleModalContent: { + paddingBottom: spacing.xl, + }, + roleList: { + marginTop: spacing.sm, + maxHeight: 400, + }, + roleItemContainer: { + marginBottom: spacing.sm, + }, + roleItem: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + borderRadius: borderRadius.lg, + backgroundColor: 'transparent', + borderWidth: 1, + borderColor: 'transparent', + overflow: 'hidden', + }, + roleItemActive: { + backgroundColor: colors.nautical.paleAqua, + borderColor: colors.nautical.lightMint, + }, + roleSelectionArea: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + padding: spacing.md, + }, + roleItemIcon: { + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: colors.flow.backgroundGradientStart, + justifyContent: 'center', + alignItems: 'center', + marginRight: spacing.md, + }, + roleItemIconActive: { + backgroundColor: colors.nautical.teal, + }, + roleItemName: { + fontSize: typography.fontSize.base, + fontWeight: '500', + color: colors.flow.text, + }, + roleItemNameActive: { + fontWeight: '700', + color: colors.nautical.teal, + }, + infoButton: { + padding: spacing.md, + justifyContent: 'center', + alignItems: 'center', + }, + roleDescription: { + paddingHorizontal: spacing.md + 36 + spacing.md, // icon width + margins + paddingBottom: spacing.sm, + paddingTop: 0, + }, + roleDescriptionText: { + fontSize: typography.fontSize.sm, + color: colors.flow.textSecondary, + fontStyle: 'italic', + lineHeight: 18, + }, + // Message bubble styles messageBubble: { flexDirection: 'row', @@ -837,7 +1121,7 @@ const styles = StyleSheet.create({ aiMessageTime: { color: colors.flow.textSecondary, }, - + // Input bar styles inputBarContainer: { paddingHorizontal: spacing.base, @@ -893,7 +1177,7 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignItems: 'center', }, - + // Modal styles modalOverlay: { flex: 1, @@ -941,7 +1225,7 @@ const styles = StyleSheet.create({ fontWeight: '600', color: '#fff', }, - + // History list styles historyList: { flex: 1, diff --git a/src/screens/MeScreen.tsx b/src/screens/MeScreen.tsx index 2c0e463..a82f455 100644 --- a/src/screens/MeScreen.tsx +++ b/src/screens/MeScreen.tsx @@ -220,7 +220,8 @@ export default function MeScreen() { const [showHeritageModal, setShowHeritageModal] = useState(false); const [showThemeModal, setShowThemeModal] = useState(false); const [isDarkMode, setIsDarkMode] = useState(false); - + const [showSignOutModal, setShowSignOutModal] = useState(false); + // Heritage / Fleet Legacy states const [heirs, setHeirs] = useState(initialHeirs); const [showAddHeirModal, setShowAddHeirModal] = useState(false); @@ -294,18 +295,14 @@ export default function MeScreen() { }; const handleAbandonIsland = () => { - Alert.alert( - 'Sign Out', - 'Are you sure you want to sign out?', - [ - { text: 'Cancel', style: 'cancel' }, - { - text: 'Sign Out', - style: 'destructive', - onPress: signOut - }, - ] - ); + console.log('[MeScreen] Sign out button clicked'); + setShowSignOutModal(true); + }; + + const handleConfirmSignOut = () => { + console.log('[MeScreen] User confirmed sign out'); + setShowSignOutModal(false); + signOut(); }; return ( @@ -618,10 +615,10 @@ export default function MeScreen() { activeOpacity={0.85} > - Dark Mode @@ -1412,6 +1409,46 @@ export default function MeScreen() { + + {/* Sign Out Confirmation Modal */} + setShowSignOutModal(false)} + > + + + + + + + Sign Out + + Are you sure you want to sign out? You'll need to log in again to access your account. + + + + + setShowSignOutModal(false)} + activeOpacity={0.85} + > + Cancel + + + + Sign Out + + + + + ); } @@ -2333,4 +2370,71 @@ const styles = StyleSheet.create({ justifyContent: 'center', zIndex: 10, }, + // Sign Out Modal Styles + signOutModal: { + backgroundColor: colors.me.cardBackground, + borderRadius: borderRadius.xl, + padding: spacing.xl, + marginHorizontal: spacing.xl, + maxWidth: 400, + width: '100%', + ...shadows.medium, + }, + signOutHeader: { + alignItems: 'center', + marginBottom: spacing.xl, + }, + signOutIcon: { + width: 72, + height: 72, + borderRadius: 36, + backgroundColor: `${colors.nautical.coral}15`, + alignItems: 'center', + justifyContent: 'center', + marginBottom: spacing.base, + }, + signOutTitle: { + fontSize: typography.fontSize.xl, + fontWeight: '700', + color: colors.me.text, + marginBottom: spacing.sm, + }, + signOutMessage: { + fontSize: typography.fontSize.base, + color: colors.me.textSecondary, + textAlign: 'center', + lineHeight: typography.fontSize.base * 1.5, + }, + signOutButtons: { + flexDirection: 'row', + gap: spacing.md, + }, + signOutCancelButton: { + flex: 1, + paddingVertical: spacing.base, + paddingHorizontal: spacing.lg, + borderRadius: borderRadius.lg, + backgroundColor: colors.me.cardBorder, + alignItems: 'center', + justifyContent: 'center', + }, + signOutCancelText: { + fontSize: typography.fontSize.base, + fontWeight: '600', + color: colors.me.text, + }, + signOutConfirmButton: { + flex: 1, + paddingVertical: spacing.base, + paddingHorizontal: spacing.lg, + borderRadius: borderRadius.lg, + backgroundColor: colors.nautical.coral, + alignItems: 'center', + justifyContent: 'center', + }, + signOutConfirmText: { + fontSize: typography.fontSize.base, + fontWeight: '600', + color: '#fff', + }, }); diff --git a/src/services/ai.service.ts b/src/services/ai.service.ts index bdd0636..3a4a6e2 100644 --- a/src/services/ai.service.ts +++ b/src/services/ai.service.ts @@ -95,7 +95,7 @@ export const aiService = { } const url = buildApiUrl(API_ENDPOINTS.AI.PROXY); - + logApiDebug('AI Request', { url, hasToken: !!token, @@ -114,7 +114,7 @@ export const aiService = { if (!response.ok) { const errorText = await response.text(); logApiDebug('AI Error Response', errorText); - + let errorDetail = 'AI request failed'; try { const errorData = JSON.parse(errorText); @@ -131,7 +131,7 @@ export const aiService = { model: data.model, choicesCount: data.choices?.length, }); - + return data; } catch (error) { console.error('AI proxy error:', error); @@ -143,13 +143,14 @@ export const aiService = { * Simple helper for single message chat * @param content - User message content * @param token - JWT token for authentication + * @param systemPrompt - Optional custom system prompt * @returns AI response text */ - async sendMessage(content: string, token?: string): Promise { + async sendMessage(content: string, token?: string, systemPrompt?: string): Promise { const messages: AIMessage[] = [ { role: 'system', - content: AI_CONFIG.DEFAULT_SYSTEM_PROMPT, + content: systemPrompt || AI_CONFIG.DEFAULT_SYSTEM_PROMPT, }, { role: 'user', @@ -179,7 +180,7 @@ export const aiService = { } const url = buildApiUrl(API_ENDPOINTS.AI.PROXY); - + logApiDebug('AI Image Request', { url, hasToken: !!token, @@ -217,7 +218,7 @@ export const aiService = { if (!response.ok) { const errorText = await response.text(); logApiDebug('AI Image Error Response', errorText); - + let errorDetail = 'AI image request failed'; try { const errorData = JSON.parse(errorText); @@ -233,7 +234,7 @@ export const aiService = { id: data.id, model: data.model, }); - + return data.choices[0]?.message?.content || 'No response'; } catch (error) { console.error('AI image proxy error:', error); diff --git a/src/services/storage.service.ts b/src/services/storage.service.ts new file mode 100644 index 0000000..1048ce3 --- /dev/null +++ b/src/services/storage.service.ts @@ -0,0 +1,120 @@ +/** + * Storage Service + * + * Handles local persistence of chat history and active conversations + * using AsyncStorage with user-specific isolation. + */ + +import AsyncStorage from '@react-native-async-storage/async-storage'; + +// ============================================================================= +// Constants +// ============================================================================= + +const STORAGE_KEYS = { + CHAT_HISTORY: '@sentinel:chat_history', + CURRENT_MESSAGES: '@sentinel:current_messages', +} as const; + +// ============================================================================= +// Service Implementation +// ============================================================================= + +export const storageService = { + /** + * Get user-specific storage key + */ + getUserKey(baseKey: string, userId: string | number): string { + return `${baseKey}:user_${userId}`; + }, + + /** + * Save the complete list of chat sessions to local storage for a specific user + */ + async saveChatHistory(history: any[], userId: string | number): Promise { + try { + const jsonValue = JSON.stringify(history); + const key = this.getUserKey(STORAGE_KEYS.CHAT_HISTORY, userId); + await AsyncStorage.setItem(key, jsonValue); + console.log(`[Storage] Saved chat history for user ${userId}:`, history.length, 'sessions'); + } catch (e) { + console.error('Error saving chat history:', e); + } + }, + + /** + * Load the list of chat sessions from local storage for a specific user + */ + async getChatHistory(userId: string | number): Promise { + try { + const key = this.getUserKey(STORAGE_KEYS.CHAT_HISTORY, userId); + const jsonValue = await AsyncStorage.getItem(key); + const result = jsonValue != null ? JSON.parse(jsonValue) : []; + console.log(`[Storage] Loaded chat history for user ${userId}:`, result.length, 'sessions'); + return result; + } catch (e) { + console.error('Error getting chat history:', e); + return []; + } + }, + + /** + * Save the current active conversation messages for a specific role and user + */ + async saveCurrentChat(roleId: string, messages: any[], userId: string | number): Promise { + try { + const jsonValue = JSON.stringify(messages); + const key = `${this.getUserKey(STORAGE_KEYS.CURRENT_MESSAGES, userId)}:${roleId}`; + await AsyncStorage.setItem(key, jsonValue); + console.log(`[Storage] Saved current chat for user ${userId}, role ${roleId}:`, messages.length, 'messages'); + } catch (e) { + console.error(`Error saving current chat for role ${roleId}:`, e); + } + }, + + /** + * Load the current active conversation messages for a specific role and user + */ + async getCurrentChat(roleId: string, userId: string | number): Promise { + try { + const key = `${this.getUserKey(STORAGE_KEYS.CURRENT_MESSAGES, userId)}:${roleId}`; + const jsonValue = await AsyncStorage.getItem(key); + const result = jsonValue != null ? JSON.parse(jsonValue) : []; + console.log(`[Storage] Loaded current chat for user ${userId}, role ${roleId}:`, result.length, 'messages'); + return result; + } catch (e) { + console.error(`Error getting current chat for role ${roleId}:`, e); + return []; + } + }, + + /** + * Clear all stored chat data for a specific user + */ + async clearUserData(userId: string | number): Promise { + try { + const keys = await AsyncStorage.getAllKeys(); + const userPrefix = `:user_${userId}`; + const userKeys = keys.filter(key => key.includes(userPrefix)); + await AsyncStorage.multiRemove(userKeys); + console.log(`[Storage] Cleared all data for user ${userId}:`, userKeys.length, 'keys removed'); + } catch (e) { + console.error('Error clearing user data:', e); + } + }, + + /** + * Clear all stored chat data (all users) + */ + async clearAllData(): Promise { + try { + const keys = await AsyncStorage.getAllKeys(); + const sentinelKeys = keys.filter(key => key.startsWith('@sentinel:')); + await AsyncStorage.multiRemove(sentinelKeys); + console.log('[Storage] Cleared all data:', sentinelKeys.length, 'keys removed'); + } catch (e) { + console.error('Error clearing storage data:', e); + } + } +}; +