wadi 1.0.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.
Files changed (294) hide show
  1. package/.agent/workflows/auto_sync.md +19 -0
  2. package/.agent/workflows/kivo_pipeline.md +27 -0
  3. package/.devcontainer/devcontainer.json +27 -0
  4. package/.github/workflows/kivo-cicd.yml +45 -0
  5. package/.github/workflows/monorepo-ci.yml +51 -0
  6. package/.github/workflows/wadi-ci.yml +38 -0
  7. package/.husky/pre-commit +2 -0
  8. package/.prettierignore +12 -0
  9. package/.prettierrc +9 -0
  10. package/.vscode/settings.json +19 -0
  11. package/CHANGELOG.md +56 -0
  12. package/CODE_OF_CONDUCT.md +43 -0
  13. package/CONTRIBUTING.md +42 -0
  14. package/DEPLOY_GUIDE.md +143 -0
  15. package/GO_LIVE_CHECKLIST.md +41 -0
  16. package/GO_LIVE_COMPLETE_REPORT.md +58 -0
  17. package/GO_LIVE_VALIDATION.md +39 -0
  18. package/LEGAL.md +38 -0
  19. package/MANIFESTO.md +40 -0
  20. package/MANUAL.md +82 -0
  21. package/OPS_PLAN.md +90 -0
  22. package/README.md +126 -0
  23. package/RELEASE_NOTES.md +51 -0
  24. package/ROADMAP.md +51 -0
  25. package/api_listado.txt +2197 -0
  26. package/apps/api/WADI_PROTOCOL.md +52 -0
  27. package/apps/api/debug-brain.js +11 -0
  28. package/apps/api/package.json +21 -0
  29. package/apps/api/src/core/analisis.js +17 -0
  30. package/apps/api/src/core/errors.js +31 -0
  31. package/apps/api/src/core/logger.js +32 -0
  32. package/apps/api/src/core/prompt-kivo.js +62 -0
  33. package/apps/api/src/index.js +212 -0
  34. package/apps/api/src/layers/human_pattern/composeResponse.js +35 -0
  35. package/apps/api/src/layers/human_pattern/detectPattern.js +39 -0
  36. package/apps/api/src/layers/human_pattern/heuristics.js +28 -0
  37. package/apps/api/src/layers/human_pattern/index.js +28 -0
  38. package/apps/api/src/layers/human_pattern/socialMemory.js +35 -0
  39. package/apps/api/src/middleware/errorHandler.js +24 -0
  40. package/apps/api/src/middleware/rateLimiter.js +38 -0
  41. package/apps/api/src/middleware/requestLogger.js +31 -0
  42. package/apps/api/src/middleware/upload.js +21 -0
  43. package/apps/api/src/middleware/validation.js +70 -0
  44. package/apps/api/src/modules/data.js +25 -0
  45. package/apps/api/src/modules/marketing.js +54 -0
  46. package/apps/api/src/modules/projects.js +40 -0
  47. package/apps/api/src/openai.js +16 -0
  48. package/apps/api/src/preferences/index.js +20 -0
  49. package/apps/api/src/register_user.js +22 -0
  50. package/apps/api/src/routes/kivo.js +58 -0
  51. package/apps/api/src/routes/monitoring.js +55 -0
  52. package/apps/api/src/routes.js +656 -0
  53. package/apps/api/src/supabase.js +17 -0
  54. package/apps/api/src/tools/index.js +57 -0
  55. package/apps/api/src/wadi-brain.js +171 -0
  56. package/apps/api/supabase/migrations/20251218_audit_logs.sql +27 -0
  57. package/apps/api/supabase/migrations/v2-chat-persistence.sql +83 -0
  58. package/apps/api/supabase/migrations/v3-cascade-delete.sql +11 -0
  59. package/apps/api/supabase/migrations/v3-security-fix.sql +90 -0
  60. package/apps/api/supabase/migrations/v3-storage.sql +36 -0
  61. package/apps/api/supabase/migrations/v4-gamification.sql +83 -0
  62. package/apps/api/supabase/migrations/v5-smoke-index.sql +6 -0
  63. package/apps/api/supabase/migrations/v6-schema-integrity-fix.sql +98 -0
  64. package/apps/api/supabase/migrations/v7-performance-indexes.sql +5 -0
  65. package/apps/api/supabase/migrations/v7-security-hardening.sql +132 -0
  66. package/apps/api/supabase/migrations/v8-security-hardening.sql +79 -0
  67. package/apps/api/test_human_pattern.js +30 -0
  68. package/apps/api/test_human_pattern_vague.js +30 -0
  69. package/apps/api/test_output.txt +76 -0
  70. package/apps/api/test_vague.js +30 -0
  71. package/apps/api/test_vague_block.js +30 -0
  72. package/apps/api/test_wadi.js +31 -0
  73. package/apps/api/tsconfig.json +13 -0
  74. package/apps/frontend/.env.local +3 -0
  75. package/apps/frontend/README.md +73 -0
  76. package/apps/frontend/eslint.config.js +27 -0
  77. package/apps/frontend/index.html +49 -0
  78. package/apps/frontend/package.json +41 -0
  79. package/apps/frontend/postcss.config.cjs +6 -0
  80. package/apps/frontend/public/cursors/wadi-neutral.svg +1 -0
  81. package/apps/frontend/public/cursors/wadi-select.svg +1 -0
  82. package/apps/frontend/public/icon-192.svg +1 -0
  83. package/apps/frontend/public/icon-512.svg +1 -0
  84. package/apps/frontend/public/manifest.webmanifest +23 -0
  85. package/apps/frontend/public/sw.js +57 -0
  86. package/apps/frontend/public/vite.svg +1 -0
  87. package/apps/frontend/public/wadi.svg +5 -0
  88. package/apps/frontend/src/assets/react.svg +1 -0
  89. package/apps/frontend/src/components/AuthLoader.tsx +50 -0
  90. package/apps/frontend/src/components/ChatInput.tsx +272 -0
  91. package/apps/frontend/src/components/ChatInterface.tsx +202 -0
  92. package/apps/frontend/src/components/ErrorBoundary.tsx +52 -0
  93. package/apps/frontend/src/components/InputArea.tsx +201 -0
  94. package/apps/frontend/src/components/Layout.tsx +73 -0
  95. package/apps/frontend/src/components/MessageBubble.tsx +66 -0
  96. package/apps/frontend/src/components/OnboardingModal.tsx +108 -0
  97. package/apps/frontend/src/components/SettingsModal.tsx +187 -0
  98. package/apps/frontend/src/components/Sidebar.tsx +171 -0
  99. package/apps/frontend/src/components/WadiOnboarding.tsx +71 -0
  100. package/apps/frontend/src/components/auditor/AuditReport.tsx +166 -0
  101. package/apps/frontend/src/components/auditor/AuditorHeader.tsx +34 -0
  102. package/apps/frontend/src/components/auditor/ContextPanel.tsx +138 -0
  103. package/apps/frontend/src/components/auditor/DataDeconstructor.tsx +85 -0
  104. package/apps/frontend/src/components/auditor/DecisionWall.tsx +65 -0
  105. package/apps/frontend/src/components/auditor/Dropzone.tsx +137 -0
  106. package/apps/frontend/src/components/common/Button.tsx +97 -0
  107. package/apps/frontend/src/components/common/Card.tsx +43 -0
  108. package/apps/frontend/src/components/common/Input.tsx +73 -0
  109. package/apps/frontend/src/components/common/Modal.tsx +53 -0
  110. package/apps/frontend/src/components/ui/Button.tsx +68 -0
  111. package/apps/frontend/src/components/ui/Card.tsx +86 -0
  112. package/apps/frontend/src/components/ui/Input.tsx +28 -0
  113. package/apps/frontend/src/components/ui/LogItem.tsx +64 -0
  114. package/apps/frontend/src/components/ui/MondayButton.tsx +40 -0
  115. package/apps/frontend/src/components/ui/MondayCard.tsx +24 -0
  116. package/apps/frontend/src/components/ui/Scouter.tsx +208 -0
  117. package/apps/frontend/src/components/ui/TerminalInput.tsx +202 -0
  118. package/apps/frontend/src/components/ui/Tooltip.tsx +67 -0
  119. package/apps/frontend/src/config/chatShortcuts.ts +20 -0
  120. package/apps/frontend/src/config/supabase.ts +6 -0
  121. package/apps/frontend/src/final_status.txt +3 -0
  122. package/apps/frontend/src/hooks/useScouter.ts +28 -0
  123. package/apps/frontend/src/hooks/useStoreHydration.ts +24 -0
  124. package/apps/frontend/src/improvement_status.txt +5 -0
  125. package/apps/frontend/src/index.css +88 -0
  126. package/apps/frontend/src/main.tsx +62 -0
  127. package/apps/frontend/src/monday_status.txt +7 -0
  128. package/apps/frontend/src/pages/ChatPage.tsx +201 -0
  129. package/apps/frontend/src/pages/DashboardPage.tsx +375 -0
  130. package/apps/frontend/src/pages/IntroWadi.tsx +114 -0
  131. package/apps/frontend/src/pages/LandingPage.tsx +103 -0
  132. package/apps/frontend/src/pages/Login.tsx +190 -0
  133. package/apps/frontend/src/pages/PrivacyPage.tsx +213 -0
  134. package/apps/frontend/src/pages/ProjectDetail.tsx +80 -0
  135. package/apps/frontend/src/pages/Projects.tsx +247 -0
  136. package/apps/frontend/src/pages/TermsPage.tsx +202 -0
  137. package/apps/frontend/src/router.tsx +83 -0
  138. package/apps/frontend/src/store/authStore.ts +152 -0
  139. package/apps/frontend/src/store/chatStore.ts +837 -0
  140. package/apps/frontend/src/store/documentStore.ts +89 -0
  141. package/apps/frontend/src/store/projectsStore.ts +111 -0
  142. package/apps/frontend/src/store/runsStore.ts +98 -0
  143. package/apps/frontend/src/utils/api.ts +34 -0
  144. package/apps/frontend/src/vite-env.d.ts +7 -0
  145. package/apps/frontend/tailwind.config.cjs +32 -0
  146. package/apps/frontend/tsconfig.app.json +27 -0
  147. package/apps/frontend/tsconfig.json +7 -0
  148. package/apps/frontend/tsconfig.node.json +26 -0
  149. package/apps/frontend/vite.config.ts +25 -0
  150. package/apps/kivo/.firebase/hosting.d3d3.cache +11 -0
  151. package/apps/kivo/.firebaserc +5 -0
  152. package/apps/kivo/BRANDING_GUIDE.md +71 -0
  153. package/apps/kivo/DEPLOYMENT_READY.md +46 -0
  154. package/apps/kivo/DEPLOY_URL.md +12 -0
  155. package/apps/kivo/IMPLEMENTATION_REPORT.md +35 -0
  156. package/apps/kivo/IMPLEMENTATION_REPORT_FINAL.md +49 -0
  157. package/apps/kivo/PLAN_MOBILE_2.0.md +44 -0
  158. package/apps/kivo/PWA_VERIFICATION_GUIDE.md +77 -0
  159. package/apps/kivo/README.md +28 -0
  160. package/apps/kivo/REBUILD_REPORT.md +34 -0
  161. package/apps/kivo/UPGRADE_REPORT.md +35 -0
  162. package/apps/kivo/android/app/build.gradle +54 -0
  163. package/apps/kivo/android/app/capacitor.build.gradle +19 -0
  164. package/apps/kivo/android/app/proguard-rules.pro +21 -0
  165. package/apps/kivo/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java +26 -0
  166. package/apps/kivo/android/app/src/main/AndroidManifest.xml +35 -0
  167. package/apps/kivo/android/app/src/main/java/com/kivo/app/MainActivity.java +5 -0
  168. package/apps/kivo/android/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
  169. package/apps/kivo/android/app/src/main/res/drawable/splash.png +0 -0
  170. package/apps/kivo/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
  171. package/apps/kivo/android/app/src/main/res/drawable-land-ldpi/splash.png +0 -0
  172. package/apps/kivo/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
  173. package/apps/kivo/android/app/src/main/res/drawable-land-night-hdpi/splash.png +0 -0
  174. package/apps/kivo/android/app/src/main/res/drawable-land-night-ldpi/splash.png +0 -0
  175. package/apps/kivo/android/app/src/main/res/drawable-land-night-mdpi/splash.png +0 -0
  176. package/apps/kivo/android/app/src/main/res/drawable-land-night-xhdpi/splash.png +0 -0
  177. package/apps/kivo/android/app/src/main/res/drawable-land-night-xxhdpi/splash.png +0 -0
  178. package/apps/kivo/android/app/src/main/res/drawable-land-night-xxxhdpi/splash.png +0 -0
  179. package/apps/kivo/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
  180. package/apps/kivo/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
  181. package/apps/kivo/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
  182. package/apps/kivo/android/app/src/main/res/drawable-night/splash.png +0 -0
  183. package/apps/kivo/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
  184. package/apps/kivo/android/app/src/main/res/drawable-port-ldpi/splash.png +0 -0
  185. package/apps/kivo/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
  186. package/apps/kivo/android/app/src/main/res/drawable-port-night-hdpi/splash.png +0 -0
  187. package/apps/kivo/android/app/src/main/res/drawable-port-night-ldpi/splash.png +0 -0
  188. package/apps/kivo/android/app/src/main/res/drawable-port-night-mdpi/splash.png +0 -0
  189. package/apps/kivo/android/app/src/main/res/drawable-port-night-xhdpi/splash.png +0 -0
  190. package/apps/kivo/android/app/src/main/res/drawable-port-night-xxhdpi/splash.png +0 -0
  191. package/apps/kivo/android/app/src/main/res/drawable-port-night-xxxhdpi/splash.png +0 -0
  192. package/apps/kivo/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
  193. package/apps/kivo/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
  194. package/apps/kivo/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
  195. package/apps/kivo/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +34 -0
  196. package/apps/kivo/android/app/src/main/res/layout/activity_main.xml +12 -0
  197. package/apps/kivo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +9 -0
  198. package/apps/kivo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +9 -0
  199. package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  200. package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png +0 -0
  201. package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  202. package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  203. package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher.png +0 -0
  204. package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher_background.png +0 -0
  205. package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.png +0 -0
  206. package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png +0 -0
  207. package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  208. package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png +0 -0
  209. package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  210. package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  211. package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  212. package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png +0 -0
  213. package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  214. package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  215. package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  216. package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png +0 -0
  217. package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  218. package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  219. package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  220. package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png +0 -0
  221. package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  222. package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  223. package/apps/kivo/android/app/src/main/res/values/ic_launcher_background.xml +4 -0
  224. package/apps/kivo/android/app/src/main/res/values/strings.xml +7 -0
  225. package/apps/kivo/android/app/src/main/res/values/styles.xml +22 -0
  226. package/apps/kivo/android/app/src/main/res/xml/file_paths.xml +5 -0
  227. package/apps/kivo/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java +18 -0
  228. package/apps/kivo/android/build.gradle +29 -0
  229. package/apps/kivo/android/capacitor.settings.gradle +3 -0
  230. package/apps/kivo/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  231. package/apps/kivo/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  232. package/apps/kivo/android/gradle.properties +22 -0
  233. package/apps/kivo/android/gradlew +252 -0
  234. package/apps/kivo/android/gradlew.bat +94 -0
  235. package/apps/kivo/android/settings.gradle +5 -0
  236. package/apps/kivo/android/variables.gradle +16 -0
  237. package/apps/kivo/assets/icon.png +0 -0
  238. package/apps/kivo/assets/splash.png +0 -0
  239. package/apps/kivo/capacitor.config.json +16 -0
  240. package/apps/kivo/firebase.json +6 -0
  241. package/apps/kivo/jest.config.js +4 -0
  242. package/apps/kivo/package.json +26 -0
  243. package/apps/kivo/tests_disabled/logic.test.js +34 -0
  244. package/apps/kivo/www/assets/icon-192.png +0 -0
  245. package/apps/kivo/www/assets/icon-512.png +0 -0
  246. package/apps/kivo/www/assets/kivo-icon.png +0 -0
  247. package/apps/kivo/www/assets/pop.mp3 +0 -0
  248. package/apps/kivo/www/favicon.ico +0 -0
  249. package/apps/kivo/www/firebase-config.js +75 -0
  250. package/apps/kivo/www/index.html +38 -0
  251. package/apps/kivo/www/manifest.json +29 -0
  252. package/apps/kivo/www/script.js +72 -0
  253. package/apps/kivo/www/style.css +82 -0
  254. package/apps/kivo/www/sw.js +46 -0
  255. package/apps/kivo-brain-api/.env.example +2 -0
  256. package/apps/kivo-brain-api/KIVO_BACKEND_SETUP.md +27 -0
  257. package/apps/kivo-brain-api/controllers/kivoController.js +31 -0
  258. package/apps/kivo-brain-api/index.js +24 -0
  259. package/apps/kivo-brain-api/package.json +17 -0
  260. package/apps/kivo-brain-api/routes/message.js +8 -0
  261. package/apps/kivo-brain-api/services/openaiService.js +64 -0
  262. package/apps/tests/wadi-tests.js +155 -0
  263. package/apps/wadi-brain/docs/README_REMOVE_OPTIONS.md +26 -0
  264. package/cli/commands/deploy.js +10 -0
  265. package/cli/commands/docs.js +16 -0
  266. package/cli/commands/explain.js +29 -0
  267. package/cli/commands/lint.js +14 -0
  268. package/cli/index.js +26 -0
  269. package/cli/package.json +12 -0
  270. package/docs/CNAME +1 -0
  271. package/docs/README.md +38 -0
  272. package/docs/USO.md +30 -0
  273. package/docs/index.html +46 -0
  274. package/eslint.config.js +101 -0
  275. package/final_validation.json +27 -0
  276. package/frontend_listado.txt +33387 -0
  277. package/listado.txt +12591 -0
  278. package/package.json +46 -0
  279. package/packages/logger/index.js +43 -0
  280. package/packages/logger/package.json +19 -0
  281. package/packages/logger/test-logger.js +7 -0
  282. package/packages_listado.txt +801 -0
  283. package/pnpm-workspace.yaml +6 -0
  284. package/reseteador_existencial.ps1 +40 -0
  285. package/scripts/bump-version.js +26 -0
  286. package/scripts/env.ps1 +13 -0
  287. package/scripts/setup_android.ps1 +25 -0
  288. package/scripts/setup_virtual_env.ps1 +77 -0
  289. package/scripts/smoke-test.js +84 -0
  290. package/scripts/trigger_render_deploy.ps1 +3 -0
  291. package/scripts/validate-release.js +34 -0
  292. package/temp_check.js +57 -0
  293. package/tsconfig.json +5 -0
  294. package/validation_report.json +27 -0
@@ -0,0 +1,837 @@
1
+ import { create } from "zustand";
2
+ import { persist } from "zustand/middleware";
3
+ import type { WadiMood } from "../components/WadiOnboarding";
4
+ import { supabase } from "../config/supabase";
5
+
6
+ const rawUrl = import.meta.env.VITE_API_URL;
7
+ let apiUrl = rawUrl || "https://wadi-wxg7.onrender.com";
8
+
9
+ // Runtime check: If we are NOT on localhost, we should NOT call localhost.
10
+ if (
11
+ typeof window !== "undefined" &&
12
+ window.location.hostname !== "localhost" &&
13
+ window.location.hostname !== "127.0.0.1" &&
14
+ (apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1"))
15
+ ) {
16
+ console.warn(
17
+ "Detected localhost API URL in production/remote. Switching to relative path."
18
+ );
19
+ apiUrl = "";
20
+ }
21
+
22
+ // Make API_URL exported so components can reuse it
23
+ export const API_URL = apiUrl.replace(/\/api\/?$/, "").replace(/\/$/, "");
24
+
25
+ // New Type Definition
26
+ export interface Attachment {
27
+ url: string;
28
+ name: string;
29
+ type: string;
30
+ }
31
+
32
+ export interface Message {
33
+ id: string;
34
+ role: "user" | "assistant" | "system";
35
+ content: string;
36
+ attachments?: Attachment[]; // Refactored to Object Array
37
+ created_at?: string;
38
+ diagnosis?: string; // WADI visual tag for user patterns
39
+ }
40
+
41
+ export interface Conversation {
42
+ id: string;
43
+ title: string;
44
+ mode: string;
45
+ updated_at: string;
46
+ }
47
+
48
+ export type ChatMode = "normal" | "tech" | "biz" | "tutor";
49
+
50
+ export interface Workspace {
51
+ id: string;
52
+ name: string;
53
+ createdAt: string;
54
+ customPrompt?: string | null;
55
+ aiModel?: "fast" | "deep";
56
+ messages: Message[];
57
+ }
58
+
59
+ interface ChatState {
60
+ // State
61
+ messages: Message[];
62
+ conversations: Conversation[];
63
+ conversationId: string | null;
64
+ conversationTitle: string | null;
65
+ isLoading: boolean;
66
+ error: string | null;
67
+ hasStarted: boolean;
68
+ mood: WadiMood;
69
+ isSidebarOpen: boolean;
70
+ isPanicMode: boolean; // EMERGENCY OVERRIDE
71
+ isUploading: boolean;
72
+ activeFocus: string | null;
73
+
74
+ // Gamification
75
+ rank: string;
76
+ points: number;
77
+ systemDeath: boolean;
78
+
79
+ // Criminal Record (Long Term Memory)
80
+ criminalRecord: {
81
+ auditCount: number;
82
+ riskCount: number;
83
+ };
84
+
85
+ // Memory
86
+ memory: Record<string, string>;
87
+ remember: (key: string, value: string) => void;
88
+ recall: () => Record<string, string>;
89
+ forget: () => void;
90
+
91
+ // Workspaces
92
+ workspaces: Workspace[];
93
+ activeWorkspaceId: string | null;
94
+
95
+ createWorkspace: (name: string) => void;
96
+ switchWorkspace: (name: string) => boolean;
97
+ deleteWorkspace: (name: string) => boolean;
98
+ listWorkspaces: () => Workspace[];
99
+
100
+ // Settings for NEW conversation
101
+ mode: ChatMode;
102
+ topic: string;
103
+ explainLevel: "short" | "normal" | "detailed";
104
+
105
+ // Actions
106
+ setPreset: (
107
+ preset: "tech" | "biz" | "learning" | "productivity" | "reflexivo"
108
+ ) => void;
109
+ setMood: (mood: WadiMood) => void;
110
+ toggleSidebar: () => void;
111
+ setSidebarOpen: (isOpen: boolean) => void;
112
+ togglePanicMode: () => void;
113
+ setPanicMode: (isPanic: boolean) => void;
114
+
115
+ setExplainLevel: (level: "short" | "normal" | "detailed") => void;
116
+
117
+ fetchConversations: () => Promise<void>;
118
+ fetchCriminalSummary: () => Promise<void>;
119
+ startNewConversation: (initialTitle?: string) => Promise<string | null>;
120
+ loadConversations: () => Promise<void>;
121
+ openConversation: (id: string) => Promise<void>;
122
+ loadConversation: (id: string) => Promise<void>;
123
+ uploadFile: (file: File) => Promise<Attachment | null>;
124
+ sendMessage: (
125
+ text: string,
126
+ attachments?: Attachment[]
127
+ ) => Promise<string | null>;
128
+ deleteConversation: (id: string) => Promise<void>;
129
+ resetChat: () => void;
130
+ admitFailure: () => Promise<void>;
131
+ // Settings
132
+ settings: {
133
+ sarcasmLevel: number; // 0 (Soft) to 100 (Nuclear)
134
+ theme: "light" | "dark" | "system";
135
+ language: "es" | "en";
136
+ defaultMode: ChatMode;
137
+ };
138
+ aiModel: "fast" | "deep";
139
+ customSystemPrompt: string | null;
140
+
141
+ updateSettings: (settings: Partial<ChatState["settings"]>) => void;
142
+ setAiModel: (model: "fast" | "deep") => void;
143
+ setCustomSystemPrompt: (prompt: string | null) => void;
144
+ getSystemPrompt: () => Promise<string>;
145
+ exportData: () => Promise<void>;
146
+ clearAllChats: () => Promise<void>;
147
+
148
+ crystallizeProject: (name: string, description: string) => Promise<boolean>;
149
+ // Action to trigger visual alert
150
+ triggerVisualAlert: () => void;
151
+ visualAlertTimestamp: number;
152
+ triggerScorn: () => void;
153
+ scornTimestamp: number;
154
+ }
155
+
156
+ // Helper to get token
157
+ const getToken = async () => {
158
+ const { data } = await supabase.auth.getSession();
159
+ return data.session?.access_token;
160
+ };
161
+
162
+ export const useChatStore = create<ChatState>()(
163
+ persist(
164
+ (set, get) => ({
165
+ // Default State
166
+ messages: [],
167
+ conversations: [],
168
+ conversationId: null,
169
+ conversationTitle: null,
170
+ isLoading: false,
171
+ error: null,
172
+ hasStarted: false,
173
+ mood: "hostile",
174
+
175
+ isSidebarOpen: false,
176
+ isPanicMode: false,
177
+ isUploading: false,
178
+ activeFocus: null,
179
+ rank: "GENERADOR_DE_HUMO",
180
+ points: 0,
181
+ systemDeath: false,
182
+ criminalRecord: { auditCount: 0, riskCount: 0 },
183
+
184
+ // Memory Init
185
+ memory: {},
186
+ remember: (key, value) =>
187
+ set((state) => ({
188
+ memory: { ...state.memory, [key]: value },
189
+ })),
190
+ recall: () => get().memory,
191
+ forget: () => set({ memory: {} }),
192
+
193
+ mode: "normal",
194
+ topic: "general",
195
+ explainLevel: "normal",
196
+ visualAlertTimestamp: 0,
197
+ scornTimestamp: 0,
198
+
199
+ // Settings Defaults
200
+ aiModel: "fast",
201
+ customSystemPrompt: null,
202
+ settings: {
203
+ sarcasmLevel: 50,
204
+ theme: "dark",
205
+ language: "es",
206
+ defaultMode: "normal",
207
+ },
208
+
209
+ // Workspaces Init
210
+ workspaces: [],
211
+ activeWorkspaceId: null,
212
+
213
+ toggleSidebar: () =>
214
+ set((state) => ({ isSidebarOpen: !state.isSidebarOpen })),
215
+ setSidebarOpen: (isOpen) => set({ isSidebarOpen: isOpen }),
216
+
217
+ togglePanicMode: () =>
218
+ set((state) => ({ isPanicMode: !state.isPanicMode })),
219
+ setPanicMode: (isPanic) => set({ isPanicMode: isPanic }),
220
+
221
+ createWorkspace: (name) => {
222
+ const newWorkspace: Workspace = {
223
+ id: crypto.randomUUID(),
224
+ name,
225
+ createdAt: new Date().toISOString(),
226
+ customPrompt: get().customSystemPrompt,
227
+ aiModel: get().aiModel,
228
+ messages: get().messages,
229
+ };
230
+ set((state) => ({
231
+ workspaces: [...state.workspaces, newWorkspace],
232
+ activeWorkspaceId: newWorkspace.id,
233
+ }));
234
+ },
235
+
236
+ switchWorkspace: (name) => {
237
+ const state = get();
238
+ // 1. Save current state to active workspace before switching?
239
+ // Ideally we auto-save on change, but for now let's just find target.
240
+
241
+ const target = state.workspaces.find((w) => w.name === name);
242
+ if (!target) return false;
243
+
244
+ // If there was an active workspace, update it first
245
+ if (state.activeWorkspaceId) {
246
+ const updatedWorkspaces = state.workspaces.map((w) => {
247
+ if (w.id === state.activeWorkspaceId) {
248
+ return {
249
+ ...w,
250
+ messages: state.messages,
251
+ customPrompt: state.customSystemPrompt,
252
+ aiModel: state.aiModel,
253
+ };
254
+ }
255
+ return w;
256
+ });
257
+ set({ workspaces: updatedWorkspaces });
258
+ }
259
+
260
+ // Load target
261
+ set({
262
+ activeWorkspaceId: target.id,
263
+ messages: target.messages,
264
+ customSystemPrompt: target.customPrompt || null,
265
+ aiModel: target.aiModel || "fast",
266
+ // Reset conversation ID to avoid sync conflicts with backend for now
267
+ conversationId: null,
268
+ });
269
+ return true;
270
+ },
271
+
272
+ deleteWorkspace: (name) => {
273
+ const state = get();
274
+ const target = state.workspaces.find((w) => w.name === name);
275
+ if (!target) return false;
276
+
277
+ const remaining = state.workspaces.filter((w) => w.id !== target.id);
278
+
279
+ // If we deleted the active one, revert to default "no workspace" state
280
+ if (state.activeWorkspaceId === target.id) {
281
+ set({
282
+ activeWorkspaceId: null,
283
+ workspaces: remaining,
284
+ // Optional: clear messages or keep them as "detached"
285
+ });
286
+ } else {
287
+ set({ workspaces: remaining });
288
+ }
289
+ return true;
290
+ },
291
+
292
+ listWorkspaces: () => get().workspaces,
293
+
294
+ triggerVisualAlert: () => set({ visualAlertTimestamp: Date.now() }),
295
+ triggerScorn: () => set({ scornTimestamp: Date.now() }),
296
+
297
+ updateSettings: (newSettings) =>
298
+ set((state) => ({ settings: { ...state.settings, ...newSettings } })),
299
+ setAiModel: (model) => set({ aiModel: model }),
300
+ setCustomSystemPrompt: (prompt) => set({ customSystemPrompt: prompt }),
301
+
302
+ getSystemPrompt: async () => {
303
+ const state = get();
304
+ // If we have a custom override, return that
305
+ if (state.customSystemPrompt) return state.customSystemPrompt;
306
+
307
+ try {
308
+ const token = await getToken();
309
+ // We call the debug endpoint
310
+ const res = await fetch(`${API_URL}/api/debug/system-prompt`, {
311
+ method: "POST",
312
+ headers: {
313
+ "Content-Type": "application/json",
314
+ Authorization: token ? `Bearer ${token}` : "",
315
+ },
316
+ body: JSON.stringify({
317
+ mode: state.mode,
318
+ topic: state.topic,
319
+ explainLevel: state.explainLevel,
320
+ isMobile: window.innerWidth < 1024,
321
+ messageCount: state.messages.length,
322
+ }),
323
+ });
324
+ const data = await res.json();
325
+ return data.prompt || "Error fetching prompt";
326
+ } catch (e) {
327
+ console.warn("Failed to fetch system prompt:", e);
328
+ return "Error retrieving system prompt.";
329
+ }
330
+ },
331
+
332
+ exportData: async () => {
333
+ const state = get();
334
+ const data = {
335
+ conversations: state.conversations,
336
+ profile: { rank: state.rank, points: state.points },
337
+ settings: state.settings,
338
+ };
339
+ const blob = new Blob([JSON.stringify(data, null, 2)], {
340
+ type: "application/json",
341
+ });
342
+ const url = URL.createObjectURL(blob);
343
+ const a = document.createElement("a");
344
+ a.href = url;
345
+ a.download = `wadi_export_${new Date().toISOString().split("T")[0]}.json`;
346
+ a.click();
347
+ },
348
+
349
+ clearAllChats: async () => {
350
+ try {
351
+ const token = await getToken();
352
+ if (!token) return;
353
+ // We'll iterate and delete for now, or assume backend has a bulk delete (it doesn't yet, so careful)
354
+ // Ideally we create a bulk delete endpoint, but for this step we will iterate locally or clear local state.
355
+ const convs = get().conversations;
356
+ for (const c of convs) {
357
+ await fetch(`${API_URL}/api/conversations/${c.id}`, {
358
+ method: "DELETE",
359
+ headers: { Authorization: `Bearer ${token}` },
360
+ });
361
+ }
362
+ get().resetChat();
363
+ set({ conversations: [] });
364
+ } catch (e) {
365
+ console.error("Failed to clear chats", e);
366
+ }
367
+ },
368
+
369
+ setPreset: (preset) =>
370
+ set((state) => {
371
+ switch (preset) {
372
+ case "tech":
373
+ return {
374
+ ...state,
375
+ mode: "tech",
376
+ topic: "general",
377
+ explainLevel: "normal",
378
+ };
379
+ case "biz":
380
+ return {
381
+ ...state,
382
+ mode: "biz",
383
+ topic: "negocios",
384
+ explainLevel: "normal",
385
+ };
386
+ case "learning":
387
+ return {
388
+ ...state,
389
+ mode: "tutor",
390
+ topic: "aprendizaje",
391
+ explainLevel: "detailed",
392
+ };
393
+ case "productivity":
394
+ return {
395
+ ...state,
396
+ mode: "normal",
397
+ topic: "productividad",
398
+ explainLevel: "short",
399
+ };
400
+ case "reflexivo":
401
+ return {
402
+ ...state,
403
+ mode: "normal",
404
+ topic: "general",
405
+ explainLevel: "normal",
406
+ };
407
+ default:
408
+ return state;
409
+ }
410
+ }),
411
+
412
+ setMood: (mood) => set({ mood }),
413
+
414
+ setExplainLevel: (level) => set({ explainLevel: level }),
415
+
416
+ resetChat: () =>
417
+ set({
418
+ conversationId: null,
419
+ conversationTitle: null,
420
+ messages: [],
421
+ error: null,
422
+ hasStarted: false,
423
+ }),
424
+
425
+ startNewConversation: async (initialTitle?: string) => {
426
+ set({
427
+ conversationId: null,
428
+ conversationTitle: initialTitle || null,
429
+ messages: [],
430
+ error: null,
431
+ isLoading: false,
432
+ });
433
+ return null;
434
+ },
435
+
436
+ fetchConversations: async () => {
437
+ try {
438
+ const token = await getToken();
439
+ if (!token) return;
440
+
441
+ const res = await fetch(`${API_URL}/api/conversations`, {
442
+ headers: { Authorization: `Bearer ${token}` },
443
+ });
444
+ if (!res.ok) throw new Error("Failed to fetch conversations");
445
+ const data = await res.json();
446
+ set({ conversations: data });
447
+ } catch (err) {
448
+ console.error(err);
449
+ }
450
+ },
451
+
452
+ loadConversations: async () => {
453
+ return get().fetchConversations();
454
+ },
455
+
456
+ fetchCriminalSummary: async () => {
457
+ try {
458
+ const token = await getToken();
459
+ if (!token) return;
460
+ const res = await fetch(`${API_URL}/api/user/criminal-summary`, {
461
+ headers: { Authorization: `Bearer ${token}` },
462
+ });
463
+ if (res.ok) {
464
+ const data = await res.json();
465
+ set({
466
+ criminalRecord: {
467
+ auditCount: data.totalAudits,
468
+ riskCount: data.totalHighRisks,
469
+ },
470
+ });
471
+ }
472
+ } catch (e) {
473
+ console.warn("Failed to fetch criminal record", e);
474
+ }
475
+ },
476
+
477
+ openConversation: async (id: string) => {
478
+ try {
479
+ set({
480
+ isLoading: true,
481
+ error: null,
482
+ conversationId: id,
483
+ messages: [],
484
+ });
485
+ const token = await getToken();
486
+ if (!token) return;
487
+
488
+ const res = await fetch(`${API_URL}/api/conversations/${id}`, {
489
+ headers: { Authorization: `Bearer ${token}` },
490
+ });
491
+
492
+ if (!res.ok) {
493
+ throw new Error("Failed to load conversation");
494
+ }
495
+
496
+ const data = await res.json();
497
+ set({
498
+ messages: data.messages || [],
499
+ conversationTitle: data.title,
500
+ mode: data.mode as ChatMode,
501
+ explainLevel: data.explain_level,
502
+ isLoading: false,
503
+ hasStarted: data.messages && data.messages.length > 0,
504
+ });
505
+ } catch (err: unknown) {
506
+ console.error(err);
507
+ set({ isLoading: false, hasStarted: false });
508
+ }
509
+ },
510
+
511
+ loadConversation: async (id: string) => {
512
+ return get().openConversation(id);
513
+ },
514
+
515
+ uploadFile: async (file: File) => {
516
+ set({ isUploading: true });
517
+ try {
518
+ const fileExt = file.name.split(".").pop();
519
+ const fileName = `${Math.random().toString(36).substring(2)}.${fileExt}`;
520
+ const filePath = `${fileName}`;
521
+
522
+ const { error: uploadError } = await supabase.storage
523
+ .from("wadi-attachments")
524
+ .upload(filePath, file);
525
+
526
+ if (uploadError) throw uploadError;
527
+
528
+ const { data } = supabase.storage
529
+ .from("wadi-attachments")
530
+ .getPublicUrl(filePath);
531
+
532
+ set({ isUploading: false });
533
+
534
+ return {
535
+ url: data.publicUrl,
536
+ name: file.name,
537
+ type: file.type,
538
+ };
539
+ } catch (error) {
540
+ console.error("Error uploading file:", error);
541
+ set({ isUploading: false, error: "Error al subir archivo." });
542
+ return null;
543
+ }
544
+ },
545
+
546
+ crystallizeProject: async (name: string, description: string) => {
547
+ try {
548
+ const token = await getToken();
549
+ if (!token) return false;
550
+
551
+ set({ isLoading: true });
552
+
553
+ const res = await fetch(`${API_URL}/api/projects/crystallize`, {
554
+ method: "POST",
555
+ headers: {
556
+ "Content-Type": "application/json",
557
+ Authorization: `Bearer ${token}`,
558
+ },
559
+ body: JSON.stringify({ name, description }),
560
+ });
561
+
562
+ if (!res.ok) throw new Error("Failed to crystallize");
563
+
564
+ const data = await res.json();
565
+
566
+ // Add System Confirmation
567
+ const sysMsg: Message = {
568
+ id: crypto.randomUUID(),
569
+ role: "assistant",
570
+ content: `[PROYECTO_CRISTALIZADO: ${data.name}]`,
571
+ created_at: new Date().toISOString(),
572
+ };
573
+
574
+ set((state) => ({
575
+ messages: [...state.messages, sysMsg],
576
+ isLoading: false,
577
+ }));
578
+
579
+ return true;
580
+ } catch (e) {
581
+ console.error(e);
582
+ set({ isLoading: false, error: "Fallo al cristalizar proyecto." });
583
+ return false;
584
+ }
585
+ },
586
+
587
+ admitFailure: async () => {
588
+ try {
589
+ const token = await getToken();
590
+ if (!token) return;
591
+
592
+ set({ isLoading: true });
593
+
594
+ const res = await fetch(`${API_URL}/api/user/admit-failure`, {
595
+ method: "POST",
596
+ headers: { Authorization: `Bearer ${token}` },
597
+ });
598
+
599
+ const data = await res.json();
600
+
601
+ // Inject Monday's crushing response
602
+ const aiMsg: Message = {
603
+ id: crypto.randomUUID(),
604
+ role: "assistant",
605
+ content: data.reply,
606
+ created_at: new Date().toISOString(),
607
+ };
608
+
609
+ set((state) => ({
610
+ messages: [...state.messages, aiMsg],
611
+ isLoading: false,
612
+ activeFocus: null, // Cleared
613
+ points: data.efficiencyPoints,
614
+ rank: data.efficiencyRank,
615
+ }));
616
+ } catch (e) {
617
+ console.error(e);
618
+ set({ isLoading: false });
619
+ }
620
+ },
621
+
622
+ sendMessage: async (text: string, attachments: Attachment[] = []) => {
623
+ // Auto-save workspace state if active
624
+ const currentStore = get();
625
+ if (currentStore.activeWorkspaceId) {
626
+ set((state) => ({
627
+ workspaces: state.workspaces.map((w) => {
628
+ if (w.id === state.activeWorkspaceId) {
629
+ return {
630
+ ...w,
631
+ messages: state.messages, // Note: this will be stale immediately, we need to update AFTER send or rely on effect.
632
+ // Actually, let's update it with the CURRENT messages before optimistic update
633
+ };
634
+ }
635
+ return w;
636
+ }),
637
+ }));
638
+ }
639
+
640
+ if (!text.trim() && attachments.length === 0) return null;
641
+
642
+ // OPTIMISTIC UPDATE START
643
+ const tempId = crypto.randomUUID();
644
+ const userMsg: Message = {
645
+ id: tempId,
646
+ role: "user",
647
+ content: text,
648
+ attachments: attachments,
649
+ created_at: new Date().toISOString(),
650
+ };
651
+
652
+ set((state) => ({
653
+ messages: [...state.messages, userMsg],
654
+ isLoading: true,
655
+ error: null,
656
+ hasStarted: true,
657
+ }));
658
+ // OPTIMISTIC UPDATE END
659
+
660
+ try {
661
+ const token = await getToken();
662
+ const {
663
+ conversationId,
664
+ mode,
665
+ topic,
666
+ explainLevel,
667
+ mood,
668
+ rank: oldRank,
669
+ } = get();
670
+
671
+ // 2. Call Unified /api/chat
672
+ const res = await fetch(`${API_URL}/api/chat`, {
673
+ method: "POST",
674
+ headers: {
675
+ "Content-Type": "application/json",
676
+ Authorization: `Bearer ${token}`,
677
+ },
678
+ body: JSON.stringify({
679
+ message: text,
680
+ conversationId,
681
+ mode,
682
+ topic,
683
+ explainLevel,
684
+ mood,
685
+ attachments,
686
+ isMobile: window.innerWidth < 1024,
687
+ customSystemPrompt: get().customSystemPrompt, // Send override
688
+ memory: get().memory, // Send memory context
689
+ }),
690
+ });
691
+
692
+ if (!res.ok) throw new Error("Failed to send message");
693
+
694
+ // Verify we got JSON
695
+ const contentType = res.headers.get("content-type");
696
+ if (contentType && contentType.includes("text/html")) {
697
+ throw new Error("Servidor retornó HTML en lugar de JSON");
698
+ }
699
+
700
+ const data = await res.json();
701
+
702
+ // Handle System Death
703
+ if (data.systemDeath) {
704
+ set({
705
+ systemDeath: true,
706
+ messages: [], // Wiped on client
707
+ points: 0,
708
+ rank: "GENERADOR_DE_HUMO",
709
+ activeFocus: null,
710
+ isLoading: false,
711
+ conversationId: null,
712
+ });
713
+ // Redirect will be handled by UI listening to systemDeath
714
+ return null;
715
+ }
716
+
717
+ // 3. Update State
718
+ let finalReply = data.reply;
719
+ // DETECT SCORN
720
+ if (finalReply.includes("[SCORN_DETECTED]")) {
721
+ get().triggerScorn();
722
+ finalReply = finalReply.replace("[SCORN_DETECTED]", "");
723
+ }
724
+
725
+ const aiMsg: Message = {
726
+ id: crypto.randomUUID(),
727
+ role: "assistant",
728
+ content: finalReply,
729
+ created_at: new Date().toISOString(),
730
+ };
731
+
732
+ const newRank =
733
+ data.efficiencyRank !== undefined ? data.efficiencyRank : oldRank;
734
+
735
+ set((state) => ({
736
+ messages: [...state.messages, aiMsg],
737
+ isLoading: false,
738
+ conversationId: data.conversationId,
739
+ activeFocus: data.activeFocus || null,
740
+ points:
741
+ data.efficiencyPoints !== undefined
742
+ ? data.efficiencyPoints
743
+ : state.points,
744
+ rank: newRank,
745
+ }));
746
+
747
+ // UPDATE WORKSPACE AFTER RECEIVING MESSAGE
748
+ const postState = get();
749
+ if (postState.activeWorkspaceId) {
750
+ set((state) => ({
751
+ workspaces: state.workspaces.map((w) => {
752
+ if (w.id === state.activeWorkspaceId) {
753
+ return {
754
+ ...w,
755
+ messages: state.messages,
756
+ customPrompt: state.customSystemPrompt,
757
+ aiModel: state.aiModel,
758
+ };
759
+ }
760
+ return w;
761
+ }),
762
+ }));
763
+ }
764
+
765
+ // Rank Change Notification
766
+ if (newRank !== oldRank && newRank !== "GENERADOR_DE_HUMO") {
767
+ const rankMsg: Message = {
768
+ id: crypto.randomUUID(),
769
+ role: "assistant",
770
+ content: `[SISTEMA]: ASCENSO A RANGO **${newRank}**. NO TE ACOSTUMBRES.`,
771
+ created_at: new Date().toISOString(),
772
+ };
773
+ set((state) => ({ messages: [...state.messages, rankMsg] }));
774
+ }
775
+
776
+ // Refresh list
777
+ get().loadConversations();
778
+ return data.conversationId;
779
+ } catch (err: unknown) {
780
+ const errorMessage =
781
+ err instanceof Error ? err.message : "An error occurred";
782
+ set({ isLoading: false, error: errorMessage });
783
+ return null;
784
+ }
785
+ },
786
+
787
+ deleteConversation: async (id: string) => {
788
+ try {
789
+ const token = await getToken();
790
+ const res = await fetch(`${API_URL}/api/conversations/${id}`, {
791
+ method: "DELETE",
792
+ headers: { Authorization: `Bearer ${token}` },
793
+ });
794
+
795
+ if (!res.ok) throw new Error("Delete failed");
796
+
797
+ set((state) => {
798
+ const nextConversations = state.conversations.filter(
799
+ (c) => c.id !== id
800
+ );
801
+ // If current open conversation is deleted, reset
802
+ if (state.conversationId === id) {
803
+ return {
804
+ conversations: nextConversations,
805
+ conversationId: null,
806
+ conversationTitle: null,
807
+ messages: [],
808
+ };
809
+ }
810
+ return { conversations: nextConversations };
811
+ });
812
+ } catch (err) {
813
+ console.error(err);
814
+ }
815
+ },
816
+ }),
817
+ {
818
+ name: "wadi-session-v1",
819
+ partialize: (state) => ({
820
+ mood: state.mood,
821
+ conversationId: state.conversationId,
822
+ messages: state.messages,
823
+ hasStarted: state.hasStarted,
824
+ aiModel: state.aiModel,
825
+ customSystemPrompt: state.customSystemPrompt,
826
+ workspaces: state.workspaces,
827
+ activeWorkspaceId: state.activeWorkspaceId,
828
+ memory: state.memory,
829
+ // Don't persist isUploading or blocked states if they are ephemeral
830
+ }),
831
+ onRehydrateStorage: () => () => {
832
+ // [FIX]: Removed automatic "Volviste" greeting on rehydration to prevent repetitive messages.
833
+ // The persistence should just restore state as is.
834
+ },
835
+ }
836
+ )
837
+ );