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.
- package/.agent/workflows/auto_sync.md +19 -0
- package/.agent/workflows/kivo_pipeline.md +27 -0
- package/.devcontainer/devcontainer.json +27 -0
- package/.github/workflows/kivo-cicd.yml +45 -0
- package/.github/workflows/monorepo-ci.yml +51 -0
- package/.github/workflows/wadi-ci.yml +38 -0
- package/.husky/pre-commit +2 -0
- package/.prettierignore +12 -0
- package/.prettierrc +9 -0
- package/.vscode/settings.json +19 -0
- package/CHANGELOG.md +56 -0
- package/CODE_OF_CONDUCT.md +43 -0
- package/CONTRIBUTING.md +42 -0
- package/DEPLOY_GUIDE.md +143 -0
- package/GO_LIVE_CHECKLIST.md +41 -0
- package/GO_LIVE_COMPLETE_REPORT.md +58 -0
- package/GO_LIVE_VALIDATION.md +39 -0
- package/LEGAL.md +38 -0
- package/MANIFESTO.md +40 -0
- package/MANUAL.md +82 -0
- package/OPS_PLAN.md +90 -0
- package/README.md +126 -0
- package/RELEASE_NOTES.md +51 -0
- package/ROADMAP.md +51 -0
- package/api_listado.txt +2197 -0
- package/apps/api/WADI_PROTOCOL.md +52 -0
- package/apps/api/debug-brain.js +11 -0
- package/apps/api/package.json +21 -0
- package/apps/api/src/core/analisis.js +17 -0
- package/apps/api/src/core/errors.js +31 -0
- package/apps/api/src/core/logger.js +32 -0
- package/apps/api/src/core/prompt-kivo.js +62 -0
- package/apps/api/src/index.js +212 -0
- package/apps/api/src/layers/human_pattern/composeResponse.js +35 -0
- package/apps/api/src/layers/human_pattern/detectPattern.js +39 -0
- package/apps/api/src/layers/human_pattern/heuristics.js +28 -0
- package/apps/api/src/layers/human_pattern/index.js +28 -0
- package/apps/api/src/layers/human_pattern/socialMemory.js +35 -0
- package/apps/api/src/middleware/errorHandler.js +24 -0
- package/apps/api/src/middleware/rateLimiter.js +38 -0
- package/apps/api/src/middleware/requestLogger.js +31 -0
- package/apps/api/src/middleware/upload.js +21 -0
- package/apps/api/src/middleware/validation.js +70 -0
- package/apps/api/src/modules/data.js +25 -0
- package/apps/api/src/modules/marketing.js +54 -0
- package/apps/api/src/modules/projects.js +40 -0
- package/apps/api/src/openai.js +16 -0
- package/apps/api/src/preferences/index.js +20 -0
- package/apps/api/src/register_user.js +22 -0
- package/apps/api/src/routes/kivo.js +58 -0
- package/apps/api/src/routes/monitoring.js +55 -0
- package/apps/api/src/routes.js +656 -0
- package/apps/api/src/supabase.js +17 -0
- package/apps/api/src/tools/index.js +57 -0
- package/apps/api/src/wadi-brain.js +171 -0
- package/apps/api/supabase/migrations/20251218_audit_logs.sql +27 -0
- package/apps/api/supabase/migrations/v2-chat-persistence.sql +83 -0
- package/apps/api/supabase/migrations/v3-cascade-delete.sql +11 -0
- package/apps/api/supabase/migrations/v3-security-fix.sql +90 -0
- package/apps/api/supabase/migrations/v3-storage.sql +36 -0
- package/apps/api/supabase/migrations/v4-gamification.sql +83 -0
- package/apps/api/supabase/migrations/v5-smoke-index.sql +6 -0
- package/apps/api/supabase/migrations/v6-schema-integrity-fix.sql +98 -0
- package/apps/api/supabase/migrations/v7-performance-indexes.sql +5 -0
- package/apps/api/supabase/migrations/v7-security-hardening.sql +132 -0
- package/apps/api/supabase/migrations/v8-security-hardening.sql +79 -0
- package/apps/api/test_human_pattern.js +30 -0
- package/apps/api/test_human_pattern_vague.js +30 -0
- package/apps/api/test_output.txt +76 -0
- package/apps/api/test_vague.js +30 -0
- package/apps/api/test_vague_block.js +30 -0
- package/apps/api/test_wadi.js +31 -0
- package/apps/api/tsconfig.json +13 -0
- package/apps/frontend/.env.local +3 -0
- package/apps/frontend/README.md +73 -0
- package/apps/frontend/eslint.config.js +27 -0
- package/apps/frontend/index.html +49 -0
- package/apps/frontend/package.json +41 -0
- package/apps/frontend/postcss.config.cjs +6 -0
- package/apps/frontend/public/cursors/wadi-neutral.svg +1 -0
- package/apps/frontend/public/cursors/wadi-select.svg +1 -0
- package/apps/frontend/public/icon-192.svg +1 -0
- package/apps/frontend/public/icon-512.svg +1 -0
- package/apps/frontend/public/manifest.webmanifest +23 -0
- package/apps/frontend/public/sw.js +57 -0
- package/apps/frontend/public/vite.svg +1 -0
- package/apps/frontend/public/wadi.svg +5 -0
- package/apps/frontend/src/assets/react.svg +1 -0
- package/apps/frontend/src/components/AuthLoader.tsx +50 -0
- package/apps/frontend/src/components/ChatInput.tsx +272 -0
- package/apps/frontend/src/components/ChatInterface.tsx +202 -0
- package/apps/frontend/src/components/ErrorBoundary.tsx +52 -0
- package/apps/frontend/src/components/InputArea.tsx +201 -0
- package/apps/frontend/src/components/Layout.tsx +73 -0
- package/apps/frontend/src/components/MessageBubble.tsx +66 -0
- package/apps/frontend/src/components/OnboardingModal.tsx +108 -0
- package/apps/frontend/src/components/SettingsModal.tsx +187 -0
- package/apps/frontend/src/components/Sidebar.tsx +171 -0
- package/apps/frontend/src/components/WadiOnboarding.tsx +71 -0
- package/apps/frontend/src/components/auditor/AuditReport.tsx +166 -0
- package/apps/frontend/src/components/auditor/AuditorHeader.tsx +34 -0
- package/apps/frontend/src/components/auditor/ContextPanel.tsx +138 -0
- package/apps/frontend/src/components/auditor/DataDeconstructor.tsx +85 -0
- package/apps/frontend/src/components/auditor/DecisionWall.tsx +65 -0
- package/apps/frontend/src/components/auditor/Dropzone.tsx +137 -0
- package/apps/frontend/src/components/common/Button.tsx +97 -0
- package/apps/frontend/src/components/common/Card.tsx +43 -0
- package/apps/frontend/src/components/common/Input.tsx +73 -0
- package/apps/frontend/src/components/common/Modal.tsx +53 -0
- package/apps/frontend/src/components/ui/Button.tsx +68 -0
- package/apps/frontend/src/components/ui/Card.tsx +86 -0
- package/apps/frontend/src/components/ui/Input.tsx +28 -0
- package/apps/frontend/src/components/ui/LogItem.tsx +64 -0
- package/apps/frontend/src/components/ui/MondayButton.tsx +40 -0
- package/apps/frontend/src/components/ui/MondayCard.tsx +24 -0
- package/apps/frontend/src/components/ui/Scouter.tsx +208 -0
- package/apps/frontend/src/components/ui/TerminalInput.tsx +202 -0
- package/apps/frontend/src/components/ui/Tooltip.tsx +67 -0
- package/apps/frontend/src/config/chatShortcuts.ts +20 -0
- package/apps/frontend/src/config/supabase.ts +6 -0
- package/apps/frontend/src/final_status.txt +3 -0
- package/apps/frontend/src/hooks/useScouter.ts +28 -0
- package/apps/frontend/src/hooks/useStoreHydration.ts +24 -0
- package/apps/frontend/src/improvement_status.txt +5 -0
- package/apps/frontend/src/index.css +88 -0
- package/apps/frontend/src/main.tsx +62 -0
- package/apps/frontend/src/monday_status.txt +7 -0
- package/apps/frontend/src/pages/ChatPage.tsx +201 -0
- package/apps/frontend/src/pages/DashboardPage.tsx +375 -0
- package/apps/frontend/src/pages/IntroWadi.tsx +114 -0
- package/apps/frontend/src/pages/LandingPage.tsx +103 -0
- package/apps/frontend/src/pages/Login.tsx +190 -0
- package/apps/frontend/src/pages/PrivacyPage.tsx +213 -0
- package/apps/frontend/src/pages/ProjectDetail.tsx +80 -0
- package/apps/frontend/src/pages/Projects.tsx +247 -0
- package/apps/frontend/src/pages/TermsPage.tsx +202 -0
- package/apps/frontend/src/router.tsx +83 -0
- package/apps/frontend/src/store/authStore.ts +152 -0
- package/apps/frontend/src/store/chatStore.ts +837 -0
- package/apps/frontend/src/store/documentStore.ts +89 -0
- package/apps/frontend/src/store/projectsStore.ts +111 -0
- package/apps/frontend/src/store/runsStore.ts +98 -0
- package/apps/frontend/src/utils/api.ts +34 -0
- package/apps/frontend/src/vite-env.d.ts +7 -0
- package/apps/frontend/tailwind.config.cjs +32 -0
- package/apps/frontend/tsconfig.app.json +27 -0
- package/apps/frontend/tsconfig.json +7 -0
- package/apps/frontend/tsconfig.node.json +26 -0
- package/apps/frontend/vite.config.ts +25 -0
- package/apps/kivo/.firebase/hosting.d3d3.cache +11 -0
- package/apps/kivo/.firebaserc +5 -0
- package/apps/kivo/BRANDING_GUIDE.md +71 -0
- package/apps/kivo/DEPLOYMENT_READY.md +46 -0
- package/apps/kivo/DEPLOY_URL.md +12 -0
- package/apps/kivo/IMPLEMENTATION_REPORT.md +35 -0
- package/apps/kivo/IMPLEMENTATION_REPORT_FINAL.md +49 -0
- package/apps/kivo/PLAN_MOBILE_2.0.md +44 -0
- package/apps/kivo/PWA_VERIFICATION_GUIDE.md +77 -0
- package/apps/kivo/README.md +28 -0
- package/apps/kivo/REBUILD_REPORT.md +34 -0
- package/apps/kivo/UPGRADE_REPORT.md +35 -0
- package/apps/kivo/android/app/build.gradle +54 -0
- package/apps/kivo/android/app/capacitor.build.gradle +19 -0
- package/apps/kivo/android/app/proguard-rules.pro +21 -0
- package/apps/kivo/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java +26 -0
- package/apps/kivo/android/app/src/main/AndroidManifest.xml +35 -0
- package/apps/kivo/android/app/src/main/java/com/kivo/app/MainActivity.java +5 -0
- package/apps/kivo/android/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
- package/apps/kivo/android/app/src/main/res/drawable/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-land-ldpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-land-night-hdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-land-night-ldpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-land-night-mdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-land-night-xhdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-land-night-xxhdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-land-night-xxxhdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-night/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-port-ldpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-port-night-hdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-port-night-ldpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-port-night-mdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-port-night-xhdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-port-night-xxhdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-port-night-xxxhdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
- package/apps/kivo/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +34 -0
- package/apps/kivo/android/app/src/main/res/layout/activity_main.xml +12 -0
- package/apps/kivo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +9 -0
- package/apps/kivo/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +9 -0
- package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher_background.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
- package/apps/kivo/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/apps/kivo/android/app/src/main/res/values/ic_launcher_background.xml +4 -0
- package/apps/kivo/android/app/src/main/res/values/strings.xml +7 -0
- package/apps/kivo/android/app/src/main/res/values/styles.xml +22 -0
- package/apps/kivo/android/app/src/main/res/xml/file_paths.xml +5 -0
- package/apps/kivo/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java +18 -0
- package/apps/kivo/android/build.gradle +29 -0
- package/apps/kivo/android/capacitor.settings.gradle +3 -0
- package/apps/kivo/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/apps/kivo/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/apps/kivo/android/gradle.properties +22 -0
- package/apps/kivo/android/gradlew +252 -0
- package/apps/kivo/android/gradlew.bat +94 -0
- package/apps/kivo/android/settings.gradle +5 -0
- package/apps/kivo/android/variables.gradle +16 -0
- package/apps/kivo/assets/icon.png +0 -0
- package/apps/kivo/assets/splash.png +0 -0
- package/apps/kivo/capacitor.config.json +16 -0
- package/apps/kivo/firebase.json +6 -0
- package/apps/kivo/jest.config.js +4 -0
- package/apps/kivo/package.json +26 -0
- package/apps/kivo/tests_disabled/logic.test.js +34 -0
- package/apps/kivo/www/assets/icon-192.png +0 -0
- package/apps/kivo/www/assets/icon-512.png +0 -0
- package/apps/kivo/www/assets/kivo-icon.png +0 -0
- package/apps/kivo/www/assets/pop.mp3 +0 -0
- package/apps/kivo/www/favicon.ico +0 -0
- package/apps/kivo/www/firebase-config.js +75 -0
- package/apps/kivo/www/index.html +38 -0
- package/apps/kivo/www/manifest.json +29 -0
- package/apps/kivo/www/script.js +72 -0
- package/apps/kivo/www/style.css +82 -0
- package/apps/kivo/www/sw.js +46 -0
- package/apps/kivo-brain-api/.env.example +2 -0
- package/apps/kivo-brain-api/KIVO_BACKEND_SETUP.md +27 -0
- package/apps/kivo-brain-api/controllers/kivoController.js +31 -0
- package/apps/kivo-brain-api/index.js +24 -0
- package/apps/kivo-brain-api/package.json +17 -0
- package/apps/kivo-brain-api/routes/message.js +8 -0
- package/apps/kivo-brain-api/services/openaiService.js +64 -0
- package/apps/tests/wadi-tests.js +155 -0
- package/apps/wadi-brain/docs/README_REMOVE_OPTIONS.md +26 -0
- package/cli/commands/deploy.js +10 -0
- package/cli/commands/docs.js +16 -0
- package/cli/commands/explain.js +29 -0
- package/cli/commands/lint.js +14 -0
- package/cli/index.js +26 -0
- package/cli/package.json +12 -0
- package/docs/CNAME +1 -0
- package/docs/README.md +38 -0
- package/docs/USO.md +30 -0
- package/docs/index.html +46 -0
- package/eslint.config.js +101 -0
- package/final_validation.json +27 -0
- package/frontend_listado.txt +33387 -0
- package/listado.txt +12591 -0
- package/package.json +46 -0
- package/packages/logger/index.js +43 -0
- package/packages/logger/package.json +19 -0
- package/packages/logger/test-logger.js +7 -0
- package/packages_listado.txt +801 -0
- package/pnpm-workspace.yaml +6 -0
- package/reseteador_existencial.ps1 +40 -0
- package/scripts/bump-version.js +26 -0
- package/scripts/env.ps1 +13 -0
- package/scripts/setup_android.ps1 +25 -0
- package/scripts/setup_virtual_env.ps1 +77 -0
- package/scripts/smoke-test.js +84 -0
- package/scripts/trigger_render_deploy.ps1 +3 -0
- package/scripts/validate-release.js +34 -0
- package/temp_check.js +57 -0
- package/tsconfig.json +5 -0
- package/validation_report.json +27 -0
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import { openai, AI_MODEL } from "./openai.js";
|
|
3
|
+
import { generateSystemPrompt, generateAuditPrompt } from "./wadi-brain.js";
|
|
4
|
+
import { supabase } from "./supabase.js";
|
|
5
|
+
import { AppError, AuthError, ModelError } from "./core/errors.js";
|
|
6
|
+
import {
|
|
7
|
+
validateChatInput,
|
|
8
|
+
validateProjectInput,
|
|
9
|
+
validateRunInput,
|
|
10
|
+
} from "./middleware/validation.js";
|
|
11
|
+
import { upload } from "./middleware/upload.js";
|
|
12
|
+
import { createRequire } from "module";
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const pdf = require("pdf-parse");
|
|
15
|
+
import { wadiPreFlight } from "./layers/human_pattern/index.js";
|
|
16
|
+
|
|
17
|
+
const router = Router();
|
|
18
|
+
|
|
19
|
+
// Helper: Verify Auth Token via Supabase
|
|
20
|
+
const getAuthenticatedUser = async (req) => {
|
|
21
|
+
const authHeader = req.headers.authorization;
|
|
22
|
+
if (!authHeader) return null;
|
|
23
|
+
const token = authHeader.replace("Bearer ", "");
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
data: { user },
|
|
27
|
+
error,
|
|
28
|
+
} = await supabase.auth.getUser(token);
|
|
29
|
+
|
|
30
|
+
if (error || !user) return null;
|
|
31
|
+
return user;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Helper: Async Wrapper
|
|
35
|
+
const asyncHandler = (fn) => (req, res, next) => {
|
|
36
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Helper: Process attachments for OpenAI
|
|
40
|
+
const processAttachments = async (message, attachments) => {
|
|
41
|
+
if (!attachments || attachments.length === 0) return message;
|
|
42
|
+
|
|
43
|
+
// Si hay adjuntos, preparamos el contenido estructurado
|
|
44
|
+
const content = [{ type: "text", text: message }];
|
|
45
|
+
|
|
46
|
+
attachments.forEach((att) => {
|
|
47
|
+
const url = typeof att === "string" ? att : att.url;
|
|
48
|
+
if (url && (url.startsWith("data:image") || url.includes("supabase"))) {
|
|
49
|
+
content.push({ type: "image_url", image_url: { url } });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return content;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Helper: Fetch Past Failures (Long Term Memory)
|
|
57
|
+
const fetchUserCriminalRecord = async (userId) => {
|
|
58
|
+
try {
|
|
59
|
+
const { data: audits } = await supabase
|
|
60
|
+
.from("messages")
|
|
61
|
+
.select("content, created_at")
|
|
62
|
+
.eq("user_id", userId)
|
|
63
|
+
.eq("role", "system")
|
|
64
|
+
.ilike("content", "[AUDIT_LOG_V1]%")
|
|
65
|
+
.order("created_at", { ascending: false })
|
|
66
|
+
.limit(3);
|
|
67
|
+
|
|
68
|
+
if (!audits || audits.length === 0) return [];
|
|
69
|
+
let failures = [];
|
|
70
|
+
|
|
71
|
+
for (const audit of audits) {
|
|
72
|
+
try {
|
|
73
|
+
const jsonPart = audit.content.replace("[AUDIT_LOG_V1]\n", "");
|
|
74
|
+
const parsed = JSON.parse(jsonPart);
|
|
75
|
+
const dateStr = new Date(audit.created_at).toISOString().split("T")[0];
|
|
76
|
+
const highRisk = (parsed.vulnerabilities || [])
|
|
77
|
+
.filter((v) => v.level === "HIGH")
|
|
78
|
+
.map((v) => `${v.title} (${dateStr})`);
|
|
79
|
+
failures = [...failures, ...highRisk];
|
|
80
|
+
} catch (e) {
|
|
81
|
+
console.error("Memory parse error", e);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return [...new Set(failures)].slice(0, 3);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const calculateRank = (points) => {
|
|
91
|
+
if (points >= 801) return "ENTIDAD_DE_ORDEN";
|
|
92
|
+
if (points >= 401) return "ESTRATEGA_JUNIOR";
|
|
93
|
+
if (points >= 101) return "CIVIL_PROMEDIO";
|
|
94
|
+
return "GENERADOR_DE_HUMO";
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// --- ROUTES ---
|
|
98
|
+
|
|
99
|
+
router.get(
|
|
100
|
+
"/user/criminal-summary",
|
|
101
|
+
asyncHandler(async (req, res) => {
|
|
102
|
+
const user = await getAuthenticatedUser(req);
|
|
103
|
+
if (!user) throw new AuthError("Authentication required");
|
|
104
|
+
|
|
105
|
+
const { data: audits } = await supabase
|
|
106
|
+
.from("messages")
|
|
107
|
+
.select("content")
|
|
108
|
+
.eq("user_id", user.id)
|
|
109
|
+
.eq("role", "system")
|
|
110
|
+
.ilike("content", "[AUDIT_LOG_V1]%");
|
|
111
|
+
|
|
112
|
+
let totalHighRisks = 0;
|
|
113
|
+
if (audits) {
|
|
114
|
+
audits.forEach((audit) => {
|
|
115
|
+
try {
|
|
116
|
+
const parsed = JSON.parse(
|
|
117
|
+
audit.content.replace("[AUDIT_LOG_V1]\n", "")
|
|
118
|
+
);
|
|
119
|
+
totalHighRisks += (parsed.vulnerabilities || []).filter(
|
|
120
|
+
(v) => v.level === "HIGH"
|
|
121
|
+
).length;
|
|
122
|
+
} catch (e) {}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
res.json({ totalAudits: audits?.length || 0, totalHighRisks });
|
|
126
|
+
})
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
router.post(
|
|
130
|
+
"/user/admit-failure",
|
|
131
|
+
asyncHandler(async (req, res) => {
|
|
132
|
+
const user = await getAuthenticatedUser(req);
|
|
133
|
+
if (!user) throw new AuthError("Authentication required");
|
|
134
|
+
|
|
135
|
+
const { data: profile } = await supabase
|
|
136
|
+
.from("profiles")
|
|
137
|
+
.select("efficiency_points")
|
|
138
|
+
.eq("id", user.id)
|
|
139
|
+
.maybeSingle();
|
|
140
|
+
const newPoints = profile?.efficiency_points || 0; // No penalty, just reset state
|
|
141
|
+
const newRank = calculateRank(newPoints);
|
|
142
|
+
|
|
143
|
+
await supabase.from("profiles").upsert({
|
|
144
|
+
id: user.id,
|
|
145
|
+
active_focus: null,
|
|
146
|
+
efficiency_points: newPoints,
|
|
147
|
+
efficiency_rank: newRank,
|
|
148
|
+
updated_at: new Date().toISOString(),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
res.json({
|
|
152
|
+
reply:
|
|
153
|
+
"Está bien. A veces el plan se rompe y lo más inteligente es soltarlo antes de que nos hunda a los dos. Perdimos un poco de impulso, pero recuperamos la claridad. Borrón y cuenta nueva. ¿Qué tenemos en la cabeza ahora?",
|
|
154
|
+
efficiencyPoints: newPoints,
|
|
155
|
+
efficiencyRank: newRank,
|
|
156
|
+
});
|
|
157
|
+
})
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// In-memory store for guest sessions (Volatile)
|
|
161
|
+
const guestSessions = new Map();
|
|
162
|
+
|
|
163
|
+
router.post(
|
|
164
|
+
"/chat",
|
|
165
|
+
validateChatInput,
|
|
166
|
+
asyncHandler(async (req, res) => {
|
|
167
|
+
let user = await getAuthenticatedUser(req);
|
|
168
|
+
|
|
169
|
+
// Guest Mode: If no user, user stays null, but we proceed carefully.
|
|
170
|
+
const {
|
|
171
|
+
message,
|
|
172
|
+
conversationId,
|
|
173
|
+
mode,
|
|
174
|
+
explainLevel,
|
|
175
|
+
topic,
|
|
176
|
+
attachments,
|
|
177
|
+
isMobile,
|
|
178
|
+
customSystemPrompt,
|
|
179
|
+
} = req.body;
|
|
180
|
+
|
|
181
|
+
let currentConversationId = conversationId;
|
|
182
|
+
let history = [];
|
|
183
|
+
let profile = {
|
|
184
|
+
efficiency_rank: "VISITANTE",
|
|
185
|
+
efficiency_points: 0,
|
|
186
|
+
active_focus: null,
|
|
187
|
+
};
|
|
188
|
+
let pastFailures = [];
|
|
189
|
+
|
|
190
|
+
// --- CASE A: AUTHENTICATED USER ---
|
|
191
|
+
if (user) {
|
|
192
|
+
if (!currentConversationId) {
|
|
193
|
+
const { data: newConv } = await supabase
|
|
194
|
+
.from("conversations")
|
|
195
|
+
.insert([
|
|
196
|
+
{
|
|
197
|
+
user_id: user.id,
|
|
198
|
+
title: message.substring(0, 60),
|
|
199
|
+
mode: mode || "normal",
|
|
200
|
+
explain_level: explainLevel || "normal",
|
|
201
|
+
},
|
|
202
|
+
])
|
|
203
|
+
.select()
|
|
204
|
+
.single();
|
|
205
|
+
currentConversationId = newConv.id;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
await supabase.from("messages").insert({
|
|
209
|
+
conversation_id: currentConversationId,
|
|
210
|
+
user_id: user.id,
|
|
211
|
+
role: "user",
|
|
212
|
+
content: message,
|
|
213
|
+
attachments: attachments || [],
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const { data: dbHistory } = await supabase
|
|
217
|
+
.from("messages")
|
|
218
|
+
.select("role, content")
|
|
219
|
+
.eq("conversation_id", currentConversationId)
|
|
220
|
+
.order("created_at", { ascending: true });
|
|
221
|
+
history = dbHistory || [];
|
|
222
|
+
|
|
223
|
+
// [SAFETY]: If DB read missed the insert (race condition), manually add current message
|
|
224
|
+
if (history.length === 0) {
|
|
225
|
+
history.push({ role: "user", content: message });
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const { data: dbProfile } = await supabase
|
|
229
|
+
.from("profiles")
|
|
230
|
+
.select("*")
|
|
231
|
+
.eq("id", user.id)
|
|
232
|
+
.maybeSingle();
|
|
233
|
+
if (dbProfile) profile = dbProfile;
|
|
234
|
+
|
|
235
|
+
pastFailures = await fetchUserCriminalRecord(user.id);
|
|
236
|
+
}
|
|
237
|
+
// --- CASE B: GUEST MODE (IN-MEMORY) ---
|
|
238
|
+
else {
|
|
239
|
+
if (!currentConversationId) {
|
|
240
|
+
currentConversationId = `guest-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
241
|
+
guestSessions.set(currentConversationId, []);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!guestSessions.has(currentConversationId)) {
|
|
245
|
+
guestSessions.set(currentConversationId, []);
|
|
246
|
+
}
|
|
247
|
+
history = guestSessions.get(currentConversationId);
|
|
248
|
+
|
|
249
|
+
// Add User Message to Memory
|
|
250
|
+
history.push({ role: "user", content: message });
|
|
251
|
+
|
|
252
|
+
profile = {
|
|
253
|
+
efficiency_rank: "VISITANTE_CURIOSO",
|
|
254
|
+
efficiency_points: 0,
|
|
255
|
+
active_focus: null,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// --- HUMAN PATTERN LAYER (WADI V1) ---
|
|
260
|
+
// WADI responde desde experiencia acumulada, no desde definiciones abstractas.
|
|
261
|
+
// Si puede describir un patrón humano, no explica teoría.
|
|
262
|
+
|
|
263
|
+
// --- HUMAN PATTERN LAYER (WADI V1) ---
|
|
264
|
+
// SKIP IF PANIC MODE
|
|
265
|
+
if (mode !== "panic") {
|
|
266
|
+
const preFlightData = wadiPreFlight(message);
|
|
267
|
+
|
|
268
|
+
if (preFlightData) {
|
|
269
|
+
console.log(`[WADI HUMAN LAYER] Response sent. HALTING.`);
|
|
270
|
+
return res.json({
|
|
271
|
+
reply: preFlightData.reply,
|
|
272
|
+
detectedPattern: preFlightData.pattern,
|
|
273
|
+
conversationId: currentConversationId,
|
|
274
|
+
efficiencyPoints: profile.efficiency_points,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// --- COMMON: GENERATE AI RESPONSE ---
|
|
280
|
+
|
|
281
|
+
// [FIX]: Remove current msg from history for prompt context, and limit to recent history
|
|
282
|
+
const previousHistory = history.slice(0, -1).slice(-20);
|
|
283
|
+
const messageCount = history.length - 1; // Correct count of previous messages
|
|
284
|
+
|
|
285
|
+
const fullSystemPrompt = generateSystemPrompt(
|
|
286
|
+
mode || "normal",
|
|
287
|
+
topic || "general",
|
|
288
|
+
explainLevel || "normal",
|
|
289
|
+
{}, // sessionPrefs
|
|
290
|
+
"hostile",
|
|
291
|
+
isMobile,
|
|
292
|
+
messageCount,
|
|
293
|
+
pastFailures,
|
|
294
|
+
profile.efficiency_rank,
|
|
295
|
+
profile.efficiency_points,
|
|
296
|
+
profile.active_focus,
|
|
297
|
+
req.body.memory || {} // Pass memory
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
// [DEBUG OVERRIDE]
|
|
301
|
+
const finalSystemPrompt = customSystemPrompt || fullSystemPrompt;
|
|
302
|
+
|
|
303
|
+
const userContent = await processAttachments(message, attachments);
|
|
304
|
+
|
|
305
|
+
// Prepare OpenAI Messages: System + Previous History + Current User Message
|
|
306
|
+
const openAIHistory = previousHistory.map((m) => ({
|
|
307
|
+
role: m.role,
|
|
308
|
+
content: m.content,
|
|
309
|
+
}));
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
console.log(
|
|
313
|
+
`[AI START] Calling model for conv ${currentConversationId}...`
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// 3. TIMEOUT SAFETY (25s)
|
|
317
|
+
const timeoutMs = 25000;
|
|
318
|
+
const controller = new AbortController();
|
|
319
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
320
|
+
|
|
321
|
+
const completion = await Promise.race([
|
|
322
|
+
openai.chat.completions.create({
|
|
323
|
+
model: AI_MODEL,
|
|
324
|
+
messages: [
|
|
325
|
+
{ role: "system", content: finalSystemPrompt },
|
|
326
|
+
...openAIHistory,
|
|
327
|
+
{ role: "user", content: userContent },
|
|
328
|
+
],
|
|
329
|
+
}),
|
|
330
|
+
new Promise((_, reject) =>
|
|
331
|
+
setTimeout(() => reject(new Error("AI_TIMEOUT")), timeoutMs)
|
|
332
|
+
),
|
|
333
|
+
]);
|
|
334
|
+
clearTimeout(timeoutId);
|
|
335
|
+
|
|
336
|
+
console.log(`[AI END] Response received.`);
|
|
337
|
+
|
|
338
|
+
const reply = completion.choices[0].message.content;
|
|
339
|
+
|
|
340
|
+
let newPoints = profile.efficiency_points;
|
|
341
|
+
let newRank = profile.efficiency_rank;
|
|
342
|
+
let systemDeath = false;
|
|
343
|
+
|
|
344
|
+
// Persistence Update
|
|
345
|
+
if (user) {
|
|
346
|
+
let pointChange = 0;
|
|
347
|
+
if (reply.includes("[FOCO_LIBERADO]")) {
|
|
348
|
+
pointChange = 20;
|
|
349
|
+
} else if (reply.includes("[FORCE_DECISION]")) {
|
|
350
|
+
pointChange = -10;
|
|
351
|
+
} else if (reply.includes("[DECONSTRUCT_START]")) {
|
|
352
|
+
// Smoke Index: Penalty for excessive noise
|
|
353
|
+
const noiseCount = (reply.match(/"category":\s*"RUIDO"/g) || [])
|
|
354
|
+
.length;
|
|
355
|
+
if (noiseCount > 3) pointChange = -10;
|
|
356
|
+
}
|
|
357
|
+
newPoints += pointChange;
|
|
358
|
+
systemDeath = newPoints <= -50;
|
|
359
|
+
|
|
360
|
+
if (systemDeath) {
|
|
361
|
+
await supabase.from("messages").delete().eq("user_id", user.id);
|
|
362
|
+
await supabase.from("conversations").delete().eq("user_id", user.id);
|
|
363
|
+
newPoints = 0;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
newRank = calculateRank(newPoints);
|
|
367
|
+
|
|
368
|
+
await supabase.from("profiles").upsert({
|
|
369
|
+
id: user.id,
|
|
370
|
+
efficiency_points: newPoints,
|
|
371
|
+
efficiency_rank: newRank,
|
|
372
|
+
active_focus: reply.includes("[FOCO_LIBERADO]")
|
|
373
|
+
? null
|
|
374
|
+
: profile.active_focus,
|
|
375
|
+
updated_at: new Date().toISOString(),
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
if (!systemDeath) {
|
|
379
|
+
await supabase.from("messages").insert({
|
|
380
|
+
conversation_id: currentConversationId,
|
|
381
|
+
user_id: user.id,
|
|
382
|
+
role: "assistant",
|
|
383
|
+
content: reply,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
} else {
|
|
387
|
+
guestSessions
|
|
388
|
+
.get(currentConversationId)
|
|
389
|
+
.push({ role: "assistant", content: reply });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
res.json({
|
|
393
|
+
reply: systemDeath ? "SYSTEM FAILURE" : reply,
|
|
394
|
+
conversationId: currentConversationId,
|
|
395
|
+
efficiencyPoints: newPoints,
|
|
396
|
+
efficiencyRank: newRank,
|
|
397
|
+
systemDeath,
|
|
398
|
+
isGuest: !user,
|
|
399
|
+
});
|
|
400
|
+
} catch (error) {
|
|
401
|
+
console.error("[AI ERROR]:", error);
|
|
402
|
+
let errorReply =
|
|
403
|
+
"El sistema colapsó por su propia complejidad. O OpenAI está caído. Probá de nuevo.";
|
|
404
|
+
if (error.message === "AI_TIMEOUT") {
|
|
405
|
+
errorReply =
|
|
406
|
+
"El modelo se quedó pensando demasiado. Tu pregunta debe ser fascinante o terriblemente aburrida. Simplificala.";
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Return a safe fallback instead of hanging
|
|
410
|
+
res.json({
|
|
411
|
+
reply: errorReply,
|
|
412
|
+
conversationId: currentConversationId,
|
|
413
|
+
efficiencyPoints: profile.efficiency_points,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
})
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
// --- PROYECTOS (Simplificados) ---
|
|
420
|
+
router.get(
|
|
421
|
+
"/projects",
|
|
422
|
+
asyncHandler(async (req, res) => {
|
|
423
|
+
const user = await getAuthenticatedUser(req);
|
|
424
|
+
const { data } = await supabase
|
|
425
|
+
.from("projects")
|
|
426
|
+
.select("*")
|
|
427
|
+
.eq("user_id", user.id)
|
|
428
|
+
.order("created_at", { ascending: false });
|
|
429
|
+
res.json(data);
|
|
430
|
+
})
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
router.post(
|
|
434
|
+
"/projects",
|
|
435
|
+
validateProjectInput,
|
|
436
|
+
asyncHandler(async (req, res) => {
|
|
437
|
+
const user = await getAuthenticatedUser(req);
|
|
438
|
+
const { data } = await supabase
|
|
439
|
+
.from("projects")
|
|
440
|
+
.insert([{ ...req.body, user_id: user.id }])
|
|
441
|
+
.select()
|
|
442
|
+
.single();
|
|
443
|
+
res.json(data);
|
|
444
|
+
})
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
// Helper: Generate Technical Project Name
|
|
448
|
+
const generateProjectName = async (description) => {
|
|
449
|
+
try {
|
|
450
|
+
const completion = await openai.chat.completions.create({
|
|
451
|
+
model: AI_MODEL,
|
|
452
|
+
messages: [
|
|
453
|
+
{
|
|
454
|
+
role: "system",
|
|
455
|
+
content:
|
|
456
|
+
"Generá un NOMBRE TÉCNICO ÚNICO (Max 35 chars) para este proyecto. Uppercase, snake_case. Sin extensión de archivo. EJ: SISTEMA_LOGISTICA_V1, RED_NEURONAL_BASE.",
|
|
457
|
+
},
|
|
458
|
+
{ role: "user", content: description.substring(0, 500) },
|
|
459
|
+
],
|
|
460
|
+
max_tokens: 20,
|
|
461
|
+
});
|
|
462
|
+
let name = completion.choices[0].message.content.trim();
|
|
463
|
+
// Sanitize
|
|
464
|
+
name = name.replace(/[^A-Z0-9_]/g, "_").replace(/_{2,}/g, "_");
|
|
465
|
+
return name;
|
|
466
|
+
} catch (e) {
|
|
467
|
+
return `PROYECTO_${Date.now()}`;
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
router.post(
|
|
472
|
+
"/projects/crystallize",
|
|
473
|
+
asyncHandler(async (req, res) => {
|
|
474
|
+
const user = await getAuthenticatedUser(req);
|
|
475
|
+
if (!user) throw new AuthError("Authentication required");
|
|
476
|
+
|
|
477
|
+
let { name, description } = req.body;
|
|
478
|
+
|
|
479
|
+
if (!description || description.trim().length === 0) {
|
|
480
|
+
throw new AppError(
|
|
481
|
+
"MONDAY_REJECTION",
|
|
482
|
+
"No puedo cristalizar la nada misma. Escribí una descripción."
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Auto-generate name if missing
|
|
487
|
+
if (!name || name.trim().length === 0) {
|
|
488
|
+
name = await generateProjectName(description);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const { data, error } = await supabase
|
|
492
|
+
.from("projects")
|
|
493
|
+
.insert([
|
|
494
|
+
{
|
|
495
|
+
user_id: user.id,
|
|
496
|
+
name: name,
|
|
497
|
+
description: description,
|
|
498
|
+
status: "PLANNING",
|
|
499
|
+
},
|
|
500
|
+
])
|
|
501
|
+
.select()
|
|
502
|
+
.single();
|
|
503
|
+
|
|
504
|
+
if (error) throw new AppError("DB_ERROR", error.message);
|
|
505
|
+
|
|
506
|
+
res.json(data);
|
|
507
|
+
})
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
router.delete(
|
|
511
|
+
"/projects/:id",
|
|
512
|
+
asyncHandler(async (req, res) => {
|
|
513
|
+
const user = await getAuthenticatedUser(req);
|
|
514
|
+
await supabase
|
|
515
|
+
.from("projects")
|
|
516
|
+
.delete()
|
|
517
|
+
.eq("id", req.params.id)
|
|
518
|
+
.eq("user_id", user.id);
|
|
519
|
+
res.json({ success: true });
|
|
520
|
+
})
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
// Conversations list
|
|
524
|
+
router.get(
|
|
525
|
+
"/conversations",
|
|
526
|
+
asyncHandler(async (req, res) => {
|
|
527
|
+
const user = await getAuthenticatedUser(req);
|
|
528
|
+
const { data } = await supabase
|
|
529
|
+
.from("conversations")
|
|
530
|
+
.select("*")
|
|
531
|
+
.eq("user_id", user.id)
|
|
532
|
+
.order("updated_at", { ascending: false });
|
|
533
|
+
res.json(data);
|
|
534
|
+
})
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
router.delete(
|
|
538
|
+
"/conversations/:id",
|
|
539
|
+
asyncHandler(async (req, res) => {
|
|
540
|
+
const user = await getAuthenticatedUser(req);
|
|
541
|
+
await supabase
|
|
542
|
+
.from("conversations")
|
|
543
|
+
.delete()
|
|
544
|
+
.eq("id", req.params.id)
|
|
545
|
+
.eq("user_id", user.id);
|
|
546
|
+
res.json({ success: true });
|
|
547
|
+
})
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
// 1.6 Get Single Conversation (with messages)
|
|
551
|
+
router.get(
|
|
552
|
+
"/conversations/:id",
|
|
553
|
+
asyncHandler(async (req, res) => {
|
|
554
|
+
const user = await getAuthenticatedUser(req);
|
|
555
|
+
if (!user) throw new AuthError("Authentication required");
|
|
556
|
+
const { id } = req.params;
|
|
557
|
+
|
|
558
|
+
// Obtener metadatos de la conversación
|
|
559
|
+
const { data: conversation, error: convError } = await supabase
|
|
560
|
+
.from("conversations")
|
|
561
|
+
.select("*")
|
|
562
|
+
.eq("id", id)
|
|
563
|
+
.eq("user_id", user.id)
|
|
564
|
+
.single();
|
|
565
|
+
|
|
566
|
+
if (convError || !conversation) {
|
|
567
|
+
throw new AppError("NOT_FOUND", "Conversación no encontrada", 404); // Assuming AppError handles this signature or existing error handler does
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Obtener los mensajes vinculados
|
|
571
|
+
const { data: messages, error: msgError } = await supabase
|
|
572
|
+
.from("messages")
|
|
573
|
+
.select("*")
|
|
574
|
+
.eq("conversation_id", id)
|
|
575
|
+
.order("created_at", { ascending: true });
|
|
576
|
+
|
|
577
|
+
if (msgError) throw new AppError("DB_ERROR", msgError.message);
|
|
578
|
+
|
|
579
|
+
res.json({ ...conversation, messages });
|
|
580
|
+
})
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
// ------------------------------------------------------------------
|
|
584
|
+
// DOCUMENT INTAKE (Intake & RAG Phase 1)
|
|
585
|
+
// ------------------------------------------------------------------
|
|
586
|
+
router.post(
|
|
587
|
+
"/documents/upload",
|
|
588
|
+
upload.single("file"),
|
|
589
|
+
asyncHandler(async (req, res) => {
|
|
590
|
+
const user = await getAuthenticatedUser(req);
|
|
591
|
+
// if (!user) throw new AuthError("Identifíquese antes de subir basura."); // Optional strict auth
|
|
592
|
+
|
|
593
|
+
if (!req.file) {
|
|
594
|
+
throw new AppError("No enviaste ningún archivo. ¿Es una broma?", 400);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
let textContent = "";
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
if (req.file.mimetype === "application/pdf") {
|
|
601
|
+
const data = await pdf(req.file.buffer);
|
|
602
|
+
textContent = data.text;
|
|
603
|
+
} else {
|
|
604
|
+
// Text / Markdown
|
|
605
|
+
textContent = req.file.buffer.toString("utf-8");
|
|
606
|
+
}
|
|
607
|
+
} catch (e) {
|
|
608
|
+
console.error("Error parsing document:", e);
|
|
609
|
+
throw new AppError(
|
|
610
|
+
"No pude leer ese archivo. Probablemente esté corrupto como tu moral.",
|
|
611
|
+
422
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Clean text lightly
|
|
616
|
+
textContent = textContent.replace(/\s+/g, " ").trim();
|
|
617
|
+
|
|
618
|
+
// Check token count estimation (rough)
|
|
619
|
+
const estimatedTokens = textContent.length / 4;
|
|
620
|
+
|
|
621
|
+
res.json({
|
|
622
|
+
filename: req.file.originalname,
|
|
623
|
+
content: textContent,
|
|
624
|
+
size: req.file.size,
|
|
625
|
+
tokens: Math.round(estimatedTokens),
|
|
626
|
+
message: "Archivo procesado. Si esperabas un premio, seguí esperando.",
|
|
627
|
+
});
|
|
628
|
+
})
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
// [DEBUG] Get Current System Prompt
|
|
632
|
+
router.post(
|
|
633
|
+
"/debug/system-prompt",
|
|
634
|
+
asyncHandler(async (req, res) => {
|
|
635
|
+
const { mode, topic, explainLevel, isMobile, messageCount } = req.body;
|
|
636
|
+
|
|
637
|
+
// Generate prompt with mock or provided data
|
|
638
|
+
const prompt = generateSystemPrompt(
|
|
639
|
+
mode || "normal",
|
|
640
|
+
topic || "general",
|
|
641
|
+
explainLevel || "normal",
|
|
642
|
+
{},
|
|
643
|
+
"hostile",
|
|
644
|
+
isMobile || false,
|
|
645
|
+
messageCount || 0,
|
|
646
|
+
[], // pastFailures mocked
|
|
647
|
+
"GENERADOR_DE_HUMO", // rank mocked
|
|
648
|
+
0, // points mocked
|
|
649
|
+
null // active_focus mocked
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
res.json({ prompt });
|
|
653
|
+
})
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
export default router;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createClient } from "@supabase/supabase-js";
|
|
2
|
+
import dotenv from "dotenv";
|
|
3
|
+
dotenv.config({ path: "../../.env" });
|
|
4
|
+
|
|
5
|
+
const supabaseUrl = process.env.SUPABASE_URL;
|
|
6
|
+
const supabaseKey = process.env.SUPABASE_KEY;
|
|
7
|
+
|
|
8
|
+
if (!supabaseUrl || !supabaseKey) {
|
|
9
|
+
console.warn(
|
|
10
|
+
"⚠️ Missing Supabase URL or Key. functionality will be limited."
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const supabase = createClient(
|
|
15
|
+
supabaseUrl || "https://placeholder.supabase.co",
|
|
16
|
+
supabaseKey || "placeholder"
|
|
17
|
+
);
|