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,52 @@
|
|
|
1
|
+
import { Component, type ErrorInfo, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
type Props = {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type State = {
|
|
8
|
+
hasError: boolean;
|
|
9
|
+
error?: Error;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
13
|
+
state: State = { hasError: false };
|
|
14
|
+
|
|
15
|
+
static getDerivedStateFromError(error: Error): State {
|
|
16
|
+
return { hasError: true, error };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
20
|
+
console.error("Uncaught error:", error, errorInfo);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
render() {
|
|
24
|
+
if (this.state.hasError) {
|
|
25
|
+
return (
|
|
26
|
+
<div className="flex flex-col items-center justify-center min-h-screen bg-[var(--wadi-bg)] text-[var(--wadi-alert)] p-8 text-center font-mono-wadi">
|
|
27
|
+
<h1 className="text-4xl font-bold mb-4">
|
|
28
|
+
💥 CRITICAL SYSTEM FAILURE
|
|
29
|
+
</h1>
|
|
30
|
+
<p className="text-xl text-[var(--wadi-text)] mb-8">
|
|
31
|
+
Ni siquiera WADI puede fingir que esto está bien.
|
|
32
|
+
</p>
|
|
33
|
+
<div className="bg-black/50 p-4 rounded border border-[var(--wadi-alert)]/30 text-left max-w-2xl overflow-auto text-xs text-red-300">
|
|
34
|
+
<p className="font-bold mb-2">ERROR TRACE:</p>
|
|
35
|
+
<pre>{this.state.error?.message}</pre>
|
|
36
|
+
<pre className="mt-2 text-[var(--wadi-text-muted)] opacity-50">
|
|
37
|
+
{this.state.error?.stack}
|
|
38
|
+
</pre>
|
|
39
|
+
</div>
|
|
40
|
+
<button
|
|
41
|
+
onClick={() => window.location.reload()}
|
|
42
|
+
className="mt-8 px-6 py-2 bg-[var(--wadi-alert)] text-white rounded hover:bg-red-600 transition-colors uppercase font-bold tracking-widest"
|
|
43
|
+
>
|
|
44
|
+
PURGAR & REINICIAR (F5)
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return this.props.children;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { KeyboardEvent } from "react";
|
|
3
|
+
|
|
4
|
+
interface InputAreaProps {
|
|
5
|
+
onSend: (text: string) => void;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
suggestions?: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function InputArea({
|
|
12
|
+
onSend,
|
|
13
|
+
disabled,
|
|
14
|
+
placeholder,
|
|
15
|
+
suggestions,
|
|
16
|
+
}: InputAreaProps) {
|
|
17
|
+
const [text, setText] = useState("");
|
|
18
|
+
|
|
19
|
+
const handleSend = () => {
|
|
20
|
+
if (!text.trim() || disabled) return;
|
|
21
|
+
onSend(text);
|
|
22
|
+
setText("");
|
|
23
|
+
// Reset height manually if needed, though state change might cause re-render which resets style if we were not careful.
|
|
24
|
+
// Ideally we reset height via ref, but let's stick to the simple onInput for now.
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
28
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
handleSend();
|
|
31
|
+
// Reset height
|
|
32
|
+
(e.target as HTMLTextAreaElement).style.height = "auto";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
style={{
|
|
39
|
+
padding: "1rem 1.5rem 1.5rem",
|
|
40
|
+
borderTop: "1px solid var(--color-border)",
|
|
41
|
+
backgroundColor: "var(--color-bg)",
|
|
42
|
+
display: "flex",
|
|
43
|
+
flexDirection: "column",
|
|
44
|
+
gap: "0.75rem",
|
|
45
|
+
position: "sticky",
|
|
46
|
+
bottom: 0,
|
|
47
|
+
zIndex: 10,
|
|
48
|
+
transition: "background-color 0.2s",
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
{/* Suggestions */}
|
|
52
|
+
{suggestions && suggestions.length > 0 && (
|
|
53
|
+
<div
|
|
54
|
+
style={{
|
|
55
|
+
display: "flex",
|
|
56
|
+
gap: "0.5rem",
|
|
57
|
+
overflowX: "auto",
|
|
58
|
+
paddingBottom: "0.5rem",
|
|
59
|
+
scrollbarWidth: "none", // Hide scrollbar for cleaner look
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
{suggestions.map((s, i) => (
|
|
63
|
+
<button
|
|
64
|
+
key={i}
|
|
65
|
+
onClick={() => onSend(s)}
|
|
66
|
+
disabled={disabled}
|
|
67
|
+
style={{
|
|
68
|
+
fontSize: "var(--text-xs)",
|
|
69
|
+
padding: "0.4rem 1rem",
|
|
70
|
+
backgroundColor: "var(--color-surface)",
|
|
71
|
+
color: "var(--color-text-soft)",
|
|
72
|
+
borderRadius: "999px",
|
|
73
|
+
border: "1px solid var(--color-border)",
|
|
74
|
+
whiteSpace: "nowrap",
|
|
75
|
+
cursor: disabled ? "default" : "pointer",
|
|
76
|
+
transition: "all 0.2s",
|
|
77
|
+
}}
|
|
78
|
+
onMouseEnter={(e) => {
|
|
79
|
+
if (!disabled) {
|
|
80
|
+
e.currentTarget.style.backgroundColor =
|
|
81
|
+
"var(--color-surface-soft)";
|
|
82
|
+
e.currentTarget.style.borderColor = "var(--color-primary)";
|
|
83
|
+
e.currentTarget.style.color = "var(--color-primary)";
|
|
84
|
+
}
|
|
85
|
+
}}
|
|
86
|
+
onMouseLeave={(e) => {
|
|
87
|
+
if (!disabled) {
|
|
88
|
+
e.currentTarget.style.backgroundColor =
|
|
89
|
+
"var(--color-surface)";
|
|
90
|
+
e.currentTarget.style.borderColor = "var(--color-border)";
|
|
91
|
+
e.currentTarget.style.color = "var(--color-text-soft)";
|
|
92
|
+
}
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
{s}
|
|
96
|
+
</button>
|
|
97
|
+
))}
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
|
|
101
|
+
{/* Input Container */}
|
|
102
|
+
<div
|
|
103
|
+
style={{
|
|
104
|
+
display: "flex",
|
|
105
|
+
alignItems: "flex-end",
|
|
106
|
+
gap: "0.75rem",
|
|
107
|
+
backgroundColor: "var(--color-surface)",
|
|
108
|
+
padding: "0.75rem 1rem",
|
|
109
|
+
borderRadius: "1.5rem", // More rounded
|
|
110
|
+
border: "1px solid var(--color-border)",
|
|
111
|
+
transition: "border-color 0.2s, box-shadow 0.2s",
|
|
112
|
+
}}
|
|
113
|
+
onFocus={(e) => {
|
|
114
|
+
e.currentTarget.style.borderColor = "var(--color-primary)";
|
|
115
|
+
e.currentTarget.style.boxShadow = "0 0 0 2px rgba(139, 92, 246, 0.1)";
|
|
116
|
+
}}
|
|
117
|
+
onBlur={(e) => {
|
|
118
|
+
e.currentTarget.style.borderColor = "var(--color-border)";
|
|
119
|
+
e.currentTarget.style.boxShadow = "none";
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
<textarea
|
|
123
|
+
value={text}
|
|
124
|
+
onChange={(e) => setText(e.target.value)}
|
|
125
|
+
onKeyDown={handleKeyDown}
|
|
126
|
+
placeholder={placeholder || "Escribe un mensaje..."}
|
|
127
|
+
disabled={disabled}
|
|
128
|
+
rows={1}
|
|
129
|
+
style={{
|
|
130
|
+
flex: 1,
|
|
131
|
+
resize: "none",
|
|
132
|
+
maxHeight: "120px",
|
|
133
|
+
minHeight: "24px",
|
|
134
|
+
padding: "0.25rem 0",
|
|
135
|
+
background: "transparent",
|
|
136
|
+
border: "none",
|
|
137
|
+
outline: "none",
|
|
138
|
+
fontSize: "var(--text-base)",
|
|
139
|
+
color: "var(--color-text-main)",
|
|
140
|
+
fontFamily: "var(--font-sans)",
|
|
141
|
+
lineHeight: 1.5,
|
|
142
|
+
}}
|
|
143
|
+
onInput={(e) => {
|
|
144
|
+
const target = e.target as HTMLTextAreaElement;
|
|
145
|
+
target.style.height = "auto";
|
|
146
|
+
target.style.height = `${Math.min(target.scrollHeight, 120)}px`;
|
|
147
|
+
}}
|
|
148
|
+
/>
|
|
149
|
+
<button
|
|
150
|
+
onClick={handleSend}
|
|
151
|
+
disabled={!text.trim() || disabled}
|
|
152
|
+
style={{
|
|
153
|
+
padding: "0.5rem",
|
|
154
|
+
borderRadius: "50%",
|
|
155
|
+
backgroundColor:
|
|
156
|
+
text.trim() && !disabled
|
|
157
|
+
? "var(--color-primary)"
|
|
158
|
+
: "var(--color-surface-soft)",
|
|
159
|
+
color:
|
|
160
|
+
text.trim() && !disabled ? "white" : "var(--color-text-soft)",
|
|
161
|
+
display: "flex",
|
|
162
|
+
alignItems: "center",
|
|
163
|
+
justifyContent: "center",
|
|
164
|
+
width: "44px",
|
|
165
|
+
height: "44px",
|
|
166
|
+
transition: "all 0.2s",
|
|
167
|
+
cursor: text.trim() && !disabled ? "pointer" : "default",
|
|
168
|
+
opacity: disabled ? 0.7 : 1,
|
|
169
|
+
}}
|
|
170
|
+
aria-label="Enviar"
|
|
171
|
+
>
|
|
172
|
+
{/* Simple Arrow Icon */}
|
|
173
|
+
<svg
|
|
174
|
+
width="20"
|
|
175
|
+
height="20"
|
|
176
|
+
viewBox="0 0 24 24"
|
|
177
|
+
fill="none"
|
|
178
|
+
stroke="currentColor"
|
|
179
|
+
strokeWidth="2"
|
|
180
|
+
strokeLinecap="round"
|
|
181
|
+
strokeLinejoin="round"
|
|
182
|
+
>
|
|
183
|
+
<line x1="22" y1="2" x2="11" y2="13"></line>
|
|
184
|
+
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
|
185
|
+
</svg>
|
|
186
|
+
</button>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<small
|
|
190
|
+
style={{
|
|
191
|
+
textAlign: "center",
|
|
192
|
+
opacity: 0.6,
|
|
193
|
+
fontSize: "0.75rem",
|
|
194
|
+
color: "var(--color-text-soft)",
|
|
195
|
+
}}
|
|
196
|
+
>
|
|
197
|
+
WADI puede cometer errores. Verifica la información importante.
|
|
198
|
+
</small>
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { Link, useNavigate } from "react-router-dom";
|
|
3
|
+
import { Sidebar } from "./Sidebar";
|
|
4
|
+
import { useChatStore } from "../store/chatStore";
|
|
5
|
+
import { Menu, Plus } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
interface LayoutProps {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Layout({ children }: LayoutProps) {
|
|
12
|
+
const { isSidebarOpen, setSidebarOpen, toggleSidebar, resetChat } =
|
|
13
|
+
useChatStore();
|
|
14
|
+
const navigate = useNavigate();
|
|
15
|
+
|
|
16
|
+
const handleNewChat = () => {
|
|
17
|
+
resetChat();
|
|
18
|
+
navigate("/chat");
|
|
19
|
+
setSidebarOpen(false);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="flex w-full h-screen min-h-screen relative overflow-hidden bg-[var(--wadi-bg)] text-[var(--wadi-text)]">
|
|
24
|
+
{/* Mobile Sidebar Overlay */}
|
|
25
|
+
{isSidebarOpen && (
|
|
26
|
+
<div
|
|
27
|
+
className="lg:hidden fixed inset-0 z-40 bg-black/50 backdrop-blur-sm transition-opacity"
|
|
28
|
+
onClick={() => setSidebarOpen(false)}
|
|
29
|
+
/>
|
|
30
|
+
)}
|
|
31
|
+
|
|
32
|
+
{/* Sidebar - Visible on Desktop, Drawer on Mobile */}
|
|
33
|
+
<div
|
|
34
|
+
className={`fixed lg:relative z-50 h-full transition-transform duration-300 lg:transform-none ${
|
|
35
|
+
isSidebarOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0"
|
|
36
|
+
}`}
|
|
37
|
+
>
|
|
38
|
+
<Sidebar isOpen={isSidebarOpen} onClose={() => setSidebarOpen(false)} />
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div className="flex-1 flex flex-col relative w-full overflow-hidden bg-[var(--wadi-bg)]">
|
|
42
|
+
{/* Mobile Top Bar */}
|
|
43
|
+
<div className="lg:hidden h-[60px] border-b border-[var(--wadi-border)] bg-[var(--wadi-surface)] backdrop-blur-md flex items-center justify-between px-4 shrink-0 z-30 shadow-sm relative">
|
|
44
|
+
<div className="flex items-center gap-3">
|
|
45
|
+
<button
|
|
46
|
+
onClick={toggleSidebar}
|
|
47
|
+
className="text-[var(--wadi-text-dim)] p-2 rounded-lg hover:bg-[var(--wadi-surface-hover)] transition-colors"
|
|
48
|
+
aria-label="Abrir menú"
|
|
49
|
+
>
|
|
50
|
+
<Menu size={20} />
|
|
51
|
+
</button>
|
|
52
|
+
<Link to="/" className="flex items-center gap-2 no-underline">
|
|
53
|
+
<span className="font-bold text-lg text-[var(--wadi-text)] tracking-tight">
|
|
54
|
+
WADI
|
|
55
|
+
</span>
|
|
56
|
+
</Link>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<button
|
|
60
|
+
onClick={handleNewChat}
|
|
61
|
+
className="p-2 bg-[var(--wadi-primary)] text-white rounded-full shadow-md active:scale-95 transition-transform"
|
|
62
|
+
>
|
|
63
|
+
<Plus size={20} />
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<main className="flex-1 overflow-hidden relative flex flex-col">
|
|
68
|
+
{children}
|
|
69
|
+
</main>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Attachment } from "../store/chatStore";
|
|
2
|
+
|
|
3
|
+
interface MessageBubbleProps {
|
|
4
|
+
role: "user" | "assistant";
|
|
5
|
+
content: string;
|
|
6
|
+
timestamp?: string;
|
|
7
|
+
attachments?: Attachment[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function MessageBubble({
|
|
11
|
+
role,
|
|
12
|
+
content,
|
|
13
|
+
timestamp,
|
|
14
|
+
}: MessageBubbleProps) {
|
|
15
|
+
const isUser = role === "user";
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div
|
|
19
|
+
className={`flex flex-col mb-4 w-full ${isUser ? "items-end" : "items-start"}`}
|
|
20
|
+
>
|
|
21
|
+
<div className={isUser ? "bubble-user" : "bubble-wadi"}>
|
|
22
|
+
{isUser ? (
|
|
23
|
+
<div className="whitespace-pre-wrap">{content}</div>
|
|
24
|
+
) : (
|
|
25
|
+
<div>
|
|
26
|
+
{content.split(/(?=^#{1,3}\s)/m).map((block, i) => {
|
|
27
|
+
// Simple markdown header parsing for WADI structure
|
|
28
|
+
const match = block.match(/^(#{1,3})\s+(.+)(\r?\n|$)/);
|
|
29
|
+
if (match) {
|
|
30
|
+
const title = match[2].trim();
|
|
31
|
+
const body = block.replace(match[0], "").trim();
|
|
32
|
+
return (
|
|
33
|
+
<div key={i} className="mb-4 last:mb-0">
|
|
34
|
+
<div className="font-bold text-[1.05em] mb-1 text-[var(--wadi-primary)]">
|
|
35
|
+
{title}
|
|
36
|
+
</div>
|
|
37
|
+
<div className="whitespace-pre-wrap leading-relaxed text-[var(--wadi-text-muted)] font-light">
|
|
38
|
+
{body}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
key={i}
|
|
46
|
+
className="whitespace-pre-wrap leading-relaxed text-[var(--wadi-text-muted)] font-light"
|
|
47
|
+
>
|
|
48
|
+
{block.trim()}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
})}
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
{timestamp && (
|
|
57
|
+
<span className="text-[10px] text-[var(--wadi-text-dim)] px-2 mt-1">
|
|
58
|
+
{new Date(timestamp).toLocaleTimeString([], {
|
|
59
|
+
hour: "2-digit",
|
|
60
|
+
minute: "2-digit",
|
|
61
|
+
})}
|
|
62
|
+
</span>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Button } from "./common/Button";
|
|
2
|
+
|
|
3
|
+
interface OnboardingModalProps {
|
|
4
|
+
onComplete: () => void;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function OnboardingModal({ onComplete }: OnboardingModalProps) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
style={{
|
|
11
|
+
position: "fixed",
|
|
12
|
+
inset: 0,
|
|
13
|
+
backgroundColor: "var(--color-bg)",
|
|
14
|
+
zIndex: 9999,
|
|
15
|
+
display: "flex",
|
|
16
|
+
alignItems: "center",
|
|
17
|
+
justifyContent: "center",
|
|
18
|
+
padding: "1.5rem",
|
|
19
|
+
}}
|
|
20
|
+
>
|
|
21
|
+
<div
|
|
22
|
+
style={{
|
|
23
|
+
maxWidth: "420px",
|
|
24
|
+
width: "100%",
|
|
25
|
+
display: "flex",
|
|
26
|
+
flexDirection: "column",
|
|
27
|
+
gap: "2rem",
|
|
28
|
+
alignItems: "flex-start", // Left align as per "serious" look usually, but centering might be better for intro. Let's try centered for focus.
|
|
29
|
+
textAlign: "left", // Actually, serious text looks good left-aligned often, but let's stick to a clean centered or left layout.
|
|
30
|
+
// User didn't specify alignment, but "Copy textual" suggests reading.
|
|
31
|
+
// Let's center the container but maybe left align text for readability?
|
|
32
|
+
// "Título: WADI no charla..." - Centered is probably safer for a splash screen.
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
|
|
36
|
+
<h1
|
|
37
|
+
style={{
|
|
38
|
+
fontSize: "2rem",
|
|
39
|
+
fontWeight: 700,
|
|
40
|
+
lineHeight: "1.1",
|
|
41
|
+
margin: 0,
|
|
42
|
+
background: "var(--grad-main)",
|
|
43
|
+
WebkitBackgroundClip: "text",
|
|
44
|
+
WebkitTextFillColor: "transparent",
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
WADI no charla.
|
|
48
|
+
<br />
|
|
49
|
+
Piensa con vos.
|
|
50
|
+
</h1>
|
|
51
|
+
|
|
52
|
+
<div
|
|
53
|
+
style={{
|
|
54
|
+
fontSize: "1.1rem",
|
|
55
|
+
color: "var(--color-text-soft)",
|
|
56
|
+
lineHeight: "1.6",
|
|
57
|
+
display: "flex",
|
|
58
|
+
flexDirection: "column",
|
|
59
|
+
gap: "0.25rem",
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<p style={{ margin: 0 }}>No hace magia.</p>
|
|
63
|
+
<p style={{ margin: 0 }}>No valida ideas flojas.</p>
|
|
64
|
+
<p style={{ margin: 0 }}>
|
|
65
|
+
Si algo está desordenado, lo ordena avanzando.
|
|
66
|
+
</p>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div
|
|
71
|
+
style={{ width: "100%", borderTop: "1px solid var(--color-border)" }}
|
|
72
|
+
/>
|
|
73
|
+
|
|
74
|
+
<div
|
|
75
|
+
style={{
|
|
76
|
+
width: "100%",
|
|
77
|
+
display: "flex",
|
|
78
|
+
flexDirection: "column",
|
|
79
|
+
gap: "1.5rem",
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<p
|
|
83
|
+
style={{
|
|
84
|
+
margin: 0,
|
|
85
|
+
color: "var(--color-text-main)",
|
|
86
|
+
fontWeight: 500,
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
Escribí lo primero que tengas. Aunque esté mal.
|
|
90
|
+
</p>
|
|
91
|
+
|
|
92
|
+
<Button
|
|
93
|
+
variant="primary"
|
|
94
|
+
onClick={onComplete}
|
|
95
|
+
style={{
|
|
96
|
+
width: "100%",
|
|
97
|
+
justifyContent: "center",
|
|
98
|
+
padding: "1rem",
|
|
99
|
+
fontSize: "1.1rem",
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
Empezar
|
|
103
|
+
</Button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useChatStore, type ChatMode } from "../store/chatStore";
|
|
3
|
+
|
|
4
|
+
interface SettingsModalProps {
|
|
5
|
+
onClose: () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function SettingsModal({ onClose }: SettingsModalProps) {
|
|
9
|
+
const { settings, updateSettings, exportData, clearAllChats } =
|
|
10
|
+
useChatStore();
|
|
11
|
+
const [activeTab, setActiveTab] = useState<"general" | "persona" | "data">(
|
|
12
|
+
"general"
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="fixed inset-0 z-[200] flex items-center justify-center bg-black/80 backdrop-blur-sm animate-in fade-in duration-200">
|
|
17
|
+
<div className="w-[90%] max-w-[500px] bg-[var(--wadi-bg)] border border-[var(--wadi-primary)] shadow-[0_0_30px_rgba(var(--wadi-primary-rgb),0.2)] rounded-lg overflow-hidden flex flex-col h-[500px]">
|
|
18
|
+
{/* Header */}
|
|
19
|
+
<div className="p-4 border-b border-[var(--wadi-border)] flex justify-between items-center bg-[var(--wadi-surface)]/50">
|
|
20
|
+
<h2 className="font-mono-wadi text-sm font-bold text-[var(--wadi-primary)] uppercase tracking-wider">
|
|
21
|
+
[CONFIGURACIÓN_DEL_SISTEMA]
|
|
22
|
+
</h2>
|
|
23
|
+
<button
|
|
24
|
+
onClick={onClose}
|
|
25
|
+
className="text-[var(--wadi-text-secondary)] hover:text-[var(--wadi-text)] transition-colors text-xs"
|
|
26
|
+
>
|
|
27
|
+
[X]
|
|
28
|
+
</button>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
{/* Tabs */}
|
|
32
|
+
<div className="flex border-b border-[var(--wadi-border)] bg-[var(--wadi-bg)]">
|
|
33
|
+
{(["general", "persona", "data"] as const).map((tab) => (
|
|
34
|
+
<button
|
|
35
|
+
key={tab}
|
|
36
|
+
onClick={() => setActiveTab(tab)}
|
|
37
|
+
className={`flex-1 py-3 text-xs font-mono-wadi uppercase tracking-widest transition-colors ${
|
|
38
|
+
activeTab === tab
|
|
39
|
+
? "bg-[var(--wadi-primary)] text-black font-bold"
|
|
40
|
+
: "text-[var(--wadi-text-muted)] hover:text-[var(--wadi-text)] hover:bg-[var(--wadi-surface)]"
|
|
41
|
+
}`}
|
|
42
|
+
>
|
|
43
|
+
{tab}
|
|
44
|
+
</button>
|
|
45
|
+
))}
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
{/* Content */}
|
|
49
|
+
<div className="flex-1 overflow-y-auto p-6 space-y-8 custom-scrollbar">
|
|
50
|
+
{activeTab === "general" && (
|
|
51
|
+
<div className="space-y-6">
|
|
52
|
+
<div className="space-y-2">
|
|
53
|
+
<label className="text-xs font-mono-wadi text-[var(--wadi-text-secondary)] uppercase">
|
|
54
|
+
Idioma de Interfaz
|
|
55
|
+
</label>
|
|
56
|
+
<div className="flex gap-2">
|
|
57
|
+
<button
|
|
58
|
+
onClick={() => updateSettings({ language: "es" })}
|
|
59
|
+
className={`flex-1 px-4 py-2 border rounded text-xs font-mono-wadi transition-all ${
|
|
60
|
+
settings.language === "es"
|
|
61
|
+
? "border-[var(--wadi-primary)] bg-[var(--wadi-primary)]/10 text-[var(--wadi-primary)]"
|
|
62
|
+
: "border-[var(--wadi-border)] text-[var(--wadi-text-muted)] hover:border-[var(--wadi-text-muted)]"
|
|
63
|
+
}`}
|
|
64
|
+
>
|
|
65
|
+
Español
|
|
66
|
+
</button>
|
|
67
|
+
<button
|
|
68
|
+
onClick={() => updateSettings({ language: "en" })}
|
|
69
|
+
className={`flex-1 px-4 py-2 border rounded text-xs font-mono-wadi transition-all ${
|
|
70
|
+
settings.language === "en"
|
|
71
|
+
? "border-[var(--wadi-primary)] bg-[var(--wadi-primary)]/10 text-[var(--wadi-primary)]"
|
|
72
|
+
: "border-[var(--wadi-border)] text-[var(--wadi-text-muted)] hover:border-[var(--wadi-text-muted)]"
|
|
73
|
+
}`}
|
|
74
|
+
>
|
|
75
|
+
English
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div className="space-y-2">
|
|
81
|
+
<label className="text-xs font-mono-wadi text-[var(--wadi-text-secondary)] uppercase">
|
|
82
|
+
Tema Visual
|
|
83
|
+
</label>
|
|
84
|
+
<div className="grid grid-cols-3 gap-2">
|
|
85
|
+
{(["light", "dark", "system"] as const).map((theme) => (
|
|
86
|
+
<button
|
|
87
|
+
key={theme}
|
|
88
|
+
onClick={() => updateSettings({ theme })}
|
|
89
|
+
className={`px-2 py-2 border rounded text-xs font-mono-wadi transition-all uppercase ${
|
|
90
|
+
settings.theme === theme
|
|
91
|
+
? "border-[var(--wadi-primary)] bg-[var(--wadi-primary)]/10 text-[var(--wadi-primary)]"
|
|
92
|
+
: "border-[var(--wadi-border)] text-[var(--wadi-text-muted)] hover:border-[var(--wadi-text-muted)]"
|
|
93
|
+
}`}
|
|
94
|
+
>
|
|
95
|
+
{theme}
|
|
96
|
+
</button>
|
|
97
|
+
))}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
{activeTab === "persona" && (
|
|
104
|
+
<div className="space-y-6">
|
|
105
|
+
<div className="space-y-2">
|
|
106
|
+
<div className="flex justify-between items-center mb-1">
|
|
107
|
+
<label className="text-xs font-mono-wadi text-[var(--wadi-text-secondary)] uppercase">
|
|
108
|
+
Nivel de Sarcasmo
|
|
109
|
+
</label>
|
|
110
|
+
<span className="text-xs font-mono-wadi text-[var(--wadi-primary)]">
|
|
111
|
+
{settings.sarcasmLevel}%
|
|
112
|
+
</span>
|
|
113
|
+
</div>
|
|
114
|
+
<input
|
|
115
|
+
type="range"
|
|
116
|
+
min="0"
|
|
117
|
+
max="100"
|
|
118
|
+
step="10"
|
|
119
|
+
value={settings.sarcasmLevel}
|
|
120
|
+
onChange={(e) =>
|
|
121
|
+
updateSettings({ sarcasmLevel: parseInt(e.target.value) })
|
|
122
|
+
}
|
|
123
|
+
className="w-full h-1 bg-[var(--wadi-border)] rounded-lg appearance-none cursor-pointer accent-[var(--wadi-primary)]"
|
|
124
|
+
/>
|
|
125
|
+
<div className="flex justify-between text-[10px] text-[var(--wadi-text-muted)] font-mono-wadi">
|
|
126
|
+
<span>Sutil</span>
|
|
127
|
+
<span>Nuclear</span>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div className="space-y-2">
|
|
132
|
+
<label className="text-xs font-mono-wadi text-[var(--wadi-text-secondary)] uppercase">
|
|
133
|
+
Modo por Defecto
|
|
134
|
+
</label>
|
|
135
|
+
<select
|
|
136
|
+
value={settings.defaultMode}
|
|
137
|
+
onChange={(e) =>
|
|
138
|
+
updateSettings({ defaultMode: e.target.value as ChatMode })
|
|
139
|
+
}
|
|
140
|
+
className="w-full bg-[var(--wadi-surface)] border border-[var(--wadi-border)] text-[var(--wadi-text)] text-xs p-3 rounded font-mono-wadi outline-none focus:border-[var(--wadi-primary)]"
|
|
141
|
+
>
|
|
142
|
+
<option value="normal">General (Compañero)</option>
|
|
143
|
+
<option value="tech">Tech (Técnico)</option>
|
|
144
|
+
<option value="biz">Biz (Negocios)</option>
|
|
145
|
+
<option value="tutor">Tutor (Aprendizaje)</option>
|
|
146
|
+
</select>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
)}
|
|
150
|
+
|
|
151
|
+
{activeTab === "data" && (
|
|
152
|
+
<div className="space-y-4">
|
|
153
|
+
<button
|
|
154
|
+
onClick={exportData}
|
|
155
|
+
className="w-full flex items-center justify-between p-4 border border-[var(--wadi-border)] rounded hover:bg-[var(--wadi-surface)] transition-colors group"
|
|
156
|
+
>
|
|
157
|
+
<span className="text-xs font-mono-wadi text-[var(--wadi-text)] uppercase">
|
|
158
|
+
Exportar mis datos (JSON)
|
|
159
|
+
</span>
|
|
160
|
+
<span className="text-[var(--wadi-text-muted)] group-hover:text-[var(--wadi-primary)]">
|
|
161
|
+
↓
|
|
162
|
+
</span>
|
|
163
|
+
</button>
|
|
164
|
+
|
|
165
|
+
<button
|
|
166
|
+
onClick={() => {
|
|
167
|
+
if (
|
|
168
|
+
confirm("ESTO ES IRREVERSIBLE. ¿Borrar TODA la historia?")
|
|
169
|
+
) {
|
|
170
|
+
clearAllChats();
|
|
171
|
+
onClose();
|
|
172
|
+
}
|
|
173
|
+
}}
|
|
174
|
+
className="w-full flex items-center justify-between p-4 border border-[var(--wadi-alert)]/30 rounded hover:bg-[var(--wadi-alert)]/10 transition-colors group text-[var(--wadi-alert)]"
|
|
175
|
+
>
|
|
176
|
+
<span className="text-xs font-mono-wadi uppercase">
|
|
177
|
+
Quemar Archivos (Borrar Todo)
|
|
178
|
+
</span>
|
|
179
|
+
<span className="group-hover:animate-pulse">⚠️</span>
|
|
180
|
+
</button>
|
|
181
|
+
</div>
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|